|
|
|
|
using Fusion;
|
|
|
|
|
using Fusion.Sockets;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.SceneManagement;
|
|
|
|
|
using TMPro;
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
public class FusionLauncher : MonoBehaviour, INetworkRunnerCallbacks
|
|
|
|
|
{
|
|
|
|
|
[Header("Network Runner (Drag NOTHING here)")]
|
|
|
|
|
public NetworkRunner runner;
|
|
|
|
|
|
|
|
|
|
[Header("Lobby UI")]
|
|
|
|
|
public GameObject lobbyUI;
|
|
|
|
|
public SessionListUIHandler sessionListUIHandler;
|
|
|
|
|
public TMP_InputField lobbyNameInput;
|
|
|
|
|
public Button createLobbyButton;
|
|
|
|
|
public TextMeshProUGUI waitingText;
|
|
|
|
|
|
|
|
|
|
[Header("Player Prefab")]
|
|
|
|
|
public NetworkPrefabRef playerPrefab;
|
|
|
|
|
|
|
|
|
|
private Coroutine refreshCoroutine;
|
|
|
|
|
private int playerCount = 0;
|
|
|
|
|
private const int maxPlayers = 2;
|
|
|
|
|
private bool gameplayLoaded = false;
|
|
|
|
|
private bool connectedToServer = false;
|
|
|
|
|
|
|
|
|
|
private List<PlayerRef> playersToSpawn = new List<PlayerRef>();
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
if (FindObjectsOfType<FusionLauncher>().Length > 1)
|
|
|
|
|
{
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
|
|
|
|
StartDummyRunner();
|
|
|
|
|
if (lobbyNameInput != null) lobbyNameInput.onValueChanged.AddListener(OnLobbyNameChanged);
|
|
|
|
|
OnLobbyNameChanged(lobbyNameInput.text);
|
|
|
|
|
waitingText.gameObject.SetActive(false);
|
|
|
|
|
refreshCoroutine = StartCoroutine(AutoRefreshLobbyList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
if (refreshCoroutine != null)
|
|
|
|
|
StopCoroutine(refreshCoroutine);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IEnumerator AutoRefreshLobbyList()
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
RefreshLobbyList();
|
|
|
|
|
yield return new WaitForSeconds(5f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnLobbyNameChanged(string s)
|
|
|
|
|
{
|
|
|
|
|
createLobbyButton.interactable = !string.IsNullOrWhiteSpace(s) && s.Length <= 15;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async void StartDummyRunner()
|
|
|
|
|
{
|
|
|
|
|
if (runner != null && runner.IsRunning) return;
|
|
|
|
|
|
|
|
|
|
await EnsureFreshRunner();
|
|
|
|
|
|
|
|
|
|
var result = await runner.StartGame(new StartGameArgs()
|
|
|
|
|
{
|
|
|
|
|
GameMode = GameMode.Client,
|
|
|
|
|
SceneManager = runner.gameObject.AddComponent<NetworkSceneManagerDefault>()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.Ok)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("✅ Browser runner connected (Client mode).");
|
|
|
|
|
connectedToServer = true;
|
|
|
|
|
}
|
|
|
|
|
else if (result.ShutdownReason == ShutdownReason.GameNotFound)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("ℹ️ No lobbies yet—but browser runner is live.");
|
|
|
|
|
connectedToServer = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"❌ Browser runner failed: {result.ShutdownReason}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task EnsureFreshRunner()
|
|
|
|
|
{
|
|
|
|
|
playerCount = 0;
|
|
|
|
|
gameplayLoaded = false;
|
|
|
|
|
connectedToServer = false;
|
|
|
|
|
playersToSpawn.Clear();
|
|
|
|
|
|
|
|
|
|
if (runner != null && runner.IsRunning)
|
|
|
|
|
{
|
|
|
|
|
await runner.Shutdown();
|
|
|
|
|
Destroy(runner.gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (refreshCoroutine != null)
|
|
|
|
|
{
|
|
|
|
|
StopCoroutine(refreshCoroutine);
|
|
|
|
|
refreshCoroutine = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var go = new GameObject("NetworkRunnerGO");
|
|
|
|
|
DontDestroyOnLoad(go);
|
|
|
|
|
|
|
|
|
|
runner = go.AddComponent<NetworkRunner>();
|
|
|
|
|
runner.ProvideInput = true;
|
|
|
|
|
runner.AddCallbacks(this);
|
|
|
|
|
|
|
|
|
|
var inputProv = go.AddComponent<FusionInputProvider>();
|
|
|
|
|
runner.AddCallbacks(inputProv);
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[Launcher] Spawned {go.name} → ProvideInput={runner.ProvideInput}; FusionInputProvider attached");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async void CreateLobby()
|
|
|
|
|
{
|
|
|
|
|
var name = lobbyNameInput.text.Trim();
|
|
|
|
|
if (string.IsNullOrEmpty(name)) return;
|
|
|
|
|
|
|
|
|
|
await EnsureFreshRunner();
|
|
|
|
|
|
|
|
|
|
var sceneRef = SceneRef.FromIndex(SceneManager.GetActiveScene().buildIndex);
|
|
|
|
|
var res = await runner.StartGame(new StartGameArgs()
|
|
|
|
|
{
|
|
|
|
|
GameMode = GameMode.Host,
|
|
|
|
|
SessionName = name,
|
|
|
|
|
PlayerCount = maxPlayers,
|
|
|
|
|
Scene = sceneRef,
|
|
|
|
|
SceneManager = runner.gameObject.AddComponent<NetworkSceneManagerDefault>()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.Ok)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log($"✔ Hosted Lobby '{name}'");
|
|
|
|
|
lobbyUI.SetActive(false);
|
|
|
|
|
waitingText.text = "Waiting for player 2…";
|
|
|
|
|
waitingText.gameObject.SetActive(true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"✖ Host failed: {res.ShutdownReason}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async void JoinLobby(SessionInfo info)
|
|
|
|
|
{
|
|
|
|
|
await EnsureFreshRunner();
|
|
|
|
|
|
|
|
|
|
var res = await runner.StartGame(new StartGameArgs()
|
|
|
|
|
{
|
|
|
|
|
GameMode = GameMode.Client,
|
|
|
|
|
SessionName = info.Name,
|
|
|
|
|
Scene = SceneRef.FromIndex(SceneManager.GetActiveScene().buildIndex),
|
|
|
|
|
SceneManager = runner.gameObject.AddComponent<NetworkSceneManagerDefault>()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.Ok)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log($"✔ Joined Lobby '{info.Name}'");
|
|
|
|
|
lobbyUI.SetActive(false);
|
|
|
|
|
waitingText.text = "Waiting for host…";
|
|
|
|
|
waitingText.gameObject.SetActive(true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"✖ Join failed: {res.ShutdownReason}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RefreshLobbyList()
|
|
|
|
|
{
|
|
|
|
|
if (!connectedToServer) return;
|
|
|
|
|
runner.JoinSessionLobby(SessionLobby.ClientServer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnConnectedToServer(NetworkRunner r)
|
|
|
|
|
{
|
|
|
|
|
connectedToServer = true;
|
|
|
|
|
Debug.Log("✔ Connected to server; can refresh lobbies.");
|
|
|
|
|
RefreshLobbyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
|
|
|
|
|
{
|
|
|
|
|
sessionListUIHandler.ClearList();
|
|
|
|
|
|
|
|
|
|
if (sessionList.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
sessionListUIHandler.ShowNoSessionsMessage();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var session in sessionList)
|
|
|
|
|
{
|
|
|
|
|
if (session.IsOpen && session.IsVisible && session.PlayerCount < session.MaxPlayers)
|
|
|
|
|
{
|
|
|
|
|
sessionListUIHandler.AddToList(session, JoinLobby);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnPlayerJoined(NetworkRunner r, PlayerRef p)
|
|
|
|
|
{
|
|
|
|
|
playerCount++;
|
|
|
|
|
playersToSpawn.Add(p);
|
|
|
|
|
Debug.Log($"➡ Player joined: {p}");
|
|
|
|
|
|
|
|
|
|
if (r.IsServer && playerCount == maxPlayers && !gameplayLoaded)
|
|
|
|
|
{
|
|
|
|
|
gameplayLoaded = true;
|
|
|
|
|
var scene = SceneRef.FromIndex(SceneUtility.GetBuildIndexByScenePath("Assets/Scenes/Gameplay.unity"));
|
|
|
|
|
r.LoadScene(scene, LoadSceneMode.Single);
|
|
|
|
|
waitingText.gameObject.SetActive(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnSceneLoadDone(NetworkRunner runner)
|
|
|
|
|
{
|
|
|
|
|
if (!runner.IsServer) return;
|
|
|
|
|
|
|
|
|
|
var provider = FindObjectOfType<SpawnPointProvider>();
|
|
|
|
|
if (provider == null || provider.spawnPoints == null || provider.spawnPoints.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("❌ No SpawnPointProvider or no spawnPoints assigned!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < playersToSpawn.Count && i < provider.spawnPoints.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
PlayerRef player = playersToSpawn[i];
|
|
|
|
|
Transform spawnPoint = provider.spawnPoints[i];
|
|
|
|
|
|
|
|
|
|
runner.Spawn(playerPrefab, spawnPoint.position, spawnPoint.rotation, inputAuthority: player);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"✔ Spawned player with authority: {player}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
playersToSpawn.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stub callbacks
|
|
|
|
|
public void OnDisconnectedFromServer(NetworkRunner r, NetDisconnectReason reason) { }
|
|
|
|
|
public void OnConnectRequest(NetworkRunner r, NetworkRunnerCallbackArgs.ConnectRequest req, byte[] tok) { }
|
|
|
|
|
public void OnConnectFailed(NetworkRunner r, NetAddress addr, NetConnectFailedReason reason) { }
|
|
|
|
|
public void OnUserSimulationMessage(NetworkRunner r, SimulationMessagePtr msg) { }
|
|
|
|
|
public void OnShutdown(NetworkRunner r, ShutdownReason reason) { }
|
|
|
|
|
public void OnPlayerLeft(NetworkRunner r, PlayerRef p) { }
|
|
|
|
|
public void OnInput(NetworkRunner r, NetworkInput input) { }
|
|
|
|
|
public void OnInputMissing(NetworkRunner r, PlayerRef p, NetworkInput input) { }
|
|
|
|
|
public void OnObjectEnterAOI(NetworkRunner r, NetworkObject obj, PlayerRef p) { }
|
|
|
|
|
public void OnObjectExitAOI(NetworkRunner r, NetworkObject obj, PlayerRef p) { }
|
|
|
|
|
public void OnReliableDataReceived(NetworkRunner r, PlayerRef p, ReliableKey k, System.ArraySegment<byte> data) { }
|
|
|
|
|
public void OnReliableDataProgress(NetworkRunner r, PlayerRef p, ReliableKey k, float prog) { }
|
|
|
|
|
public void OnSceneLoadStart(NetworkRunner r) { }
|
|
|
|
|
public void OnHostMigration(NetworkRunner r, HostMigrationToken t) { }
|
|
|
|
|
public void OnCustomAuthenticationResponse(NetworkRunner r, Dictionary<string, object> data) { }
|
|
|
|
|
}
|