|
|
|
|
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; // we'll create/destroy this at runtime
|
|
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
// Ensure this launcher persists across loads
|
|
|
|
|
if (FindObjectsOfType<FusionLauncher>().Length > 1)
|
|
|
|
|
{
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
|
|
|
|
// Kick off our dummy runner purely in client/browse mode:
|
|
|
|
|
StartDummyRunner();
|
|
|
|
|
|
|
|
|
|
// UI hookups
|
|
|
|
|
if (lobbyNameInput != null) lobbyNameInput.onValueChanged.AddListener(OnLobbyNameChanged);
|
|
|
|
|
OnLobbyNameChanged(lobbyNameInput.text);
|
|
|
|
|
waitingText.gameObject.SetActive(false);
|
|
|
|
|
|
|
|
|
|
// Start periodic lobby refresh once connected
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// —————— DUMMY RUNNER ——————
|
|
|
|
|
// Starts a runner in CLIENT mode so we can list sessions.
|
|
|
|
|
async void StartDummyRunner()
|
|
|
|
|
{
|
|
|
|
|
// If already up & running, just bail.
|
|
|
|
|
if (runner != null && runner.IsRunning)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Ensure any old runner is cleared
|
|
|
|
|
await EnsureFreshRunner();
|
|
|
|
|
|
|
|
|
|
// Start as CLIENT
|
|
|
|
|
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}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// —————— RESET / CREATE FRESH RUNNER ——————
|
|
|
|
|
private async Task EnsureFreshRunner()
|
|
|
|
|
{
|
|
|
|
|
// — Teardown any existing runner —
|
|
|
|
|
playerCount = 0;
|
|
|
|
|
gameplayLoaded = false;
|
|
|
|
|
connectedToServer = false;
|
|
|
|
|
|
|
|
|
|
if (runner != null && runner.IsRunning)
|
|
|
|
|
{
|
|
|
|
|
await runner.Shutdown();
|
|
|
|
|
Destroy(runner.gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (refreshCoroutine != null)
|
|
|
|
|
{
|
|
|
|
|
StopCoroutine(refreshCoroutine);
|
|
|
|
|
refreshCoroutine = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// — Build a new runner GameObject —
|
|
|
|
|
var go = new GameObject("NetworkRunnerGO");
|
|
|
|
|
DontDestroyOnLoad(go);
|
|
|
|
|
|
|
|
|
|
// 1) Add the NetworkRunner
|
|
|
|
|
runner = go.AddComponent<NetworkRunner>();
|
|
|
|
|
|
|
|
|
|
// 2) Enable input collection BEFORE StartGame
|
|
|
|
|
runner.ProvideInput = true;
|
|
|
|
|
|
|
|
|
|
// 3) Register this launcher’s callbacks
|
|
|
|
|
runner.AddCallbacks(this);
|
|
|
|
|
|
|
|
|
|
// 4) Attach & register your FusionInputProvider
|
|
|
|
|
var inputProv = go.AddComponent<FusionInputProvider>();
|
|
|
|
|
runner.AddCallbacks(inputProv);
|
|
|
|
|
|
|
|
|
|
// 5) Log out what we’ve done
|
|
|
|
|
Debug.Log($"[Launcher] Spawned {go.name} → ProvideInput={runner.ProvideInput}; " +
|
|
|
|
|
$"FusionInputProvider attached");
|
|
|
|
|
|
|
|
|
|
// Note: we don’t call StartGame here. That happens in CreateLobby() / JoinLobby().
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//private async Task EnsureFreshRunner()
|
|
|
|
|
//{
|
|
|
|
|
// // Reset flags
|
|
|
|
|
// playerCount = 0;
|
|
|
|
|
// gameplayLoaded = false;
|
|
|
|
|
// connectedToServer = false;
|
|
|
|
|
|
|
|
|
|
// // If an old runner exists & is running, shut it down & destroy its GO
|
|
|
|
|
// if (runner != null && runner.IsRunning)
|
|
|
|
|
// {
|
|
|
|
|
// await runner.Shutdown();
|
|
|
|
|
// Destroy(runner.gameObject);
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Stop auto-refresh while we rebuild
|
|
|
|
|
// if (refreshCoroutine != null)
|
|
|
|
|
// {
|
|
|
|
|
// StopCoroutine(refreshCoroutine);
|
|
|
|
|
// refreshCoroutine = null;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Make brand-new runner GameObject
|
|
|
|
|
// var go = new GameObject("NetworkRunnerGO");
|
|
|
|
|
// DontDestroyOnLoad(go); // keep it alive across scenes
|
|
|
|
|
|
|
|
|
|
// runner = go.AddComponent<NetworkRunner>();
|
|
|
|
|
// var inputProv = go.AddComponent<FusionInputProvider>();
|
|
|
|
|
// runner.AddCallbacks(inputProv);
|
|
|
|
|
// Debug.Log("✔️ Added FusionInputProvider AND registered it on " + go.name);
|
|
|
|
|
|
|
|
|
|
// // 3) Tell Fusion we’ll be providing input
|
|
|
|
|
// runner.ProvideInput = true;
|
|
|
|
|
|
|
|
|
|
// // 4) Register your launcher callbacks
|
|
|
|
|
// runner.AddCallbacks(this);
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
// —————— CREATE LOBBY (Host) ——————
|
|
|
|
|
public async void CreateLobby()
|
|
|
|
|
{
|
|
|
|
|
var name = lobbyNameInput.text.Trim();
|
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("Empty lobby name!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tear down browse runner & spawn a fresh one
|
|
|
|
|
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}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// —————— JOIN LOBBY (Client) ——————
|
|
|
|
|
public async void JoinLobby(SessionInfo info)
|
|
|
|
|
{
|
|
|
|
|
// Tear down browse runner & spawn a fresh one
|
|
|
|
|
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}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// —————— REFRESH LOBBY LIST ——————
|
|
|
|
|
public void RefreshLobbyList()
|
|
|
|
|
{
|
|
|
|
|
if (!connectedToServer) return;
|
|
|
|
|
//sessionListUIHandler.ClearList();
|
|
|
|
|
runner.JoinSessionLobby(SessionLobby.ClientServer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// —————— FUSION CALLBACKS ——————
|
|
|
|
|
public void OnConnectedToServer(NetworkRunner r)
|
|
|
|
|
{
|
|
|
|
|
connectedToServer = true;
|
|
|
|
|
Debug.Log("✔ Connected to server; can refresh lobbies.");
|
|
|
|
|
RefreshLobbyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
|
|
|
|
|
{
|
|
|
|
|
// First thing, wipe out the old items every time
|
|
|
|
|
sessionListUIHandler.ClearList();
|
|
|
|
|
|
|
|
|
|
// If there really are no sessions, show your “no sessions” message
|
|
|
|
|
if (sessionList.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
sessionListUIHandler.ShowNoSessionsMessage();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise re-populate
|
|
|
|
|
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++;
|
|
|
|
|
// Only the host in Gameplay scene spawns cars
|
|
|
|
|
//if (r.IsServer && SceneManager.GetActiveScene().name == "Gameplay")
|
|
|
|
|
//{
|
|
|
|
|
// var pos = new Vector3(Random.Range(-5, 5), 0, Random.Range(-5, 5));
|
|
|
|
|
// r.Spawn(playerPrefab, pos, Quaternion.identity, p);
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only the host triggers scene‐load when 2 players are present
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// only run once per game start
|
|
|
|
|
if (gameplayLoaded)
|
|
|
|
|
{
|
|
|
|
|
gameplayLoaded = false;
|
|
|
|
|
|
|
|
|
|
// grab your provider
|
|
|
|
|
var provider = FindObjectOfType<SpawnPointProvider>();
|
|
|
|
|
if (provider == null || provider.spawnPoints == null || provider.spawnPoints.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("No SpawnPointProvider or no spawnPoints assigned!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert the IEnumerable to an array
|
|
|
|
|
PlayerRef[] players = runner.ActivePlayers.ToArray();
|
|
|
|
|
// pick the smaller of (number of players) vs (number of spawnPoints)
|
|
|
|
|
int count = Mathf.Min(players.Length, provider.spawnPoints.Length);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
PlayerRef p = players[i];
|
|
|
|
|
Transform sp = provider.spawnPoints[i];
|
|
|
|
|
runner.Spawn(playerPrefab, sp.position, sp.rotation, p);
|
|
|
|
|
Debug.Log("SpawnPoints done: "+i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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 OnSceneLoadDone(NetworkRunner r) { }
|
|
|
|
|
public void OnSceneLoadStart(NetworkRunner r) { }
|
|
|
|
|
public void OnHostMigration(NetworkRunner r, HostMigrationToken t) { }
|
|
|
|
|
public void OnCustomAuthenticationResponse(NetworkRunner r, Dictionary<string, object> data) { }
|
|
|
|
|
}
|