您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

1075 行
38 KiB

//#define USE_UNET
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using UnityEngine.Profiling;
using UnityEngine.Ucg.Matchmaking;
public class ClientGameWorld
{
public bool PredictionEnabled = true;
public float frameTimeScale = 1.0f;
public GameTime PredictedTime
{
get { return m_PredictedTime; }
}
public GameTime RenderTime
{
get { return m_RenderTime; }
}
public ClientGameWorld(GameWorld world, NetworkClient networkClient, NetworkStatisticsClient networkStatistics, BundledResourceManager resourceSystem)
{
m_NetworkClient = networkClient;
m_NetworkStatistics = networkStatistics;
m_GameWorld = world;
m_CharacterModule = new CharacterModuleClient(m_GameWorld, resourceSystem);
m_ProjectileModule = new ProjectileModuleClient(m_GameWorld, resourceSystem);
m_HitCollisionModule = new HitCollisionModule(m_GameWorld,1, 1);
m_PlayerModule = new PlayerModuleClient(m_GameWorld);
m_DebugPrimitiveModule = new DebugPrimitiveModule(m_GameWorld, 1.0f, 0);
m_SpectatorCamModule = new SpectatorCamModuleClient(m_GameWorld);
m_EffectModule = new EffectModuleClient(m_GameWorld, resourceSystem);
m_ReplicatedEntityModule = new ReplicatedEntityModuleClient(m_GameWorld, resourceSystem);
m_ItemModule = new ItemModule(m_GameWorld);
m_ragdollSystem = new RagdollModule(m_GameWorld);
m_GameModeSystem = m_GameWorld.GetECSWorld().CreateManager<GameModeSystemClient>(m_GameWorld, Game.game.clientFrontend.scoreboardPanel.uiBinding, Game.game.clientFrontend.gameScorePanel);
m_ClientFrontendUpdate = m_GameWorld.GetECSWorld().CreateManager<ClientFrontendUpdate>(m_GameWorld);
m_DestructiblePropSystemClient = m_GameWorld.GetECSWorld().CreateManager<DestructiblePropSystemClient>(m_GameWorld);
m_InterpolateGrenadeSystem = m_GameWorld.GetECSWorld().CreateManager<InterpolateGrenadePresentation>(m_GameWorld);
m_ApplyGrenadePresentation = m_GameWorld.GetECSWorld().CreateManager<ApplyGrenadePresentation>(m_GameWorld);
m_moverUpdate = m_GameWorld.GetECSWorld().CreateManager<MoverUpdate>(m_GameWorld);
m_TeleporterSystemClient = m_GameWorld.GetECSWorld().CreateManager<TeleporterSystemClient>(m_GameWorld);
m_SpinSystem = m_GameWorld.GetECSWorld().CreateManager<SpinSystem>(m_GameWorld);
m_HandleNamePlateOwnerSpawn = m_GameWorld.GetECSWorld().CreateManager<HandleNamePlateSpawn>(m_GameWorld);
m_HandleNamePlateOwnerDespawn = m_GameWorld.GetECSWorld().CreateManager<HandleNamePlateDespawn>(m_GameWorld);
m_UpdateNamePlates = m_GameWorld.GetECSWorld().CreateManager<UpdateNamePlates>(m_GameWorld);
m_GameModeSystem.SetLocalPlayerId(m_NetworkClient.clientId);
m_TwistSystem = new TwistSystem(m_GameWorld);
m_FanSystem = new FanSystem(m_GameWorld);
m_TranslateScaleSystem = new TranslateScaleSystem(m_GameWorld);
}
public void Shutdown()
{
m_CharacterModule.Shutdown();
m_ProjectileModule.Shutdown();
m_HitCollisionModule.Shutdown();
m_PlayerModule.Shutdown();
m_DebugPrimitiveModule.Shutdown();
m_SpectatorCamModule.Shutdown();
m_EffectModule.Shutdown();
m_ReplicatedEntityModule.Shutdown();
m_ItemModule.Shutdown();
m_GameWorld.GetECSWorld().DestroyManager(m_GameModeSystem);
m_GameWorld.GetECSWorld().DestroyManager(m_DestructiblePropSystemClient);
m_GameWorld.GetECSWorld().DestroyManager(m_InterpolateGrenadeSystem);
m_GameWorld.GetECSWorld().DestroyManager(m_ApplyGrenadePresentation);
m_GameWorld.GetECSWorld().DestroyManager(m_moverUpdate);
m_GameWorld.GetECSWorld().DestroyManager(m_TeleporterSystemClient);
m_GameWorld.GetECSWorld().DestroyManager(m_SpinSystem);
m_GameWorld.GetECSWorld().DestroyManager(m_HandleNamePlateOwnerSpawn);
m_GameWorld.GetECSWorld().DestroyManager(m_HandleNamePlateOwnerDespawn);
m_GameWorld.GetECSWorld().DestroyManager(m_UpdateNamePlates);
m_ragdollSystem.Shutdown();
m_TwistSystem.ShutDown();
m_FanSystem.ShutDown();
m_TranslateScaleSystem.ShutDown();
}
// This is called at the actual client frame rate, so may be faster or slower than tickrate.
public void Update(float frameDuration)
{
// Advances time and accumulate input into the UserCommand being generated
HandleTime(frameDuration);
m_GameWorld.worldTime = m_RenderTime;
m_GameWorld.frameDuration = frameDuration;
m_GameWorld.lastServerTick = m_NetworkClient.serverTime;
m_PlayerModule.ResolveReferenceFromLocalPlayerToPlayer();
m_PlayerModule.HandleCommandReset();
// Handle spawn requests
m_ProjectileModule.HandleProjectileRequests();
// Handle controlled entity changed
m_PlayerModule.HandleControlledEntityChanged();
m_CharacterModule.HandleControlledEntityChanged();
// Handle spawning
m_ProjectileModule.HandleProjectileSpawn();
m_CharacterModule.HandleSpawns();
m_HitCollisionModule.HandleSpawning();
m_HandleNamePlateOwnerSpawn.Update();
m_ragdollSystem.HandleSpawning();
m_TwistSystem.HandleSpawning();
m_FanSystem.HandleSpawning();
m_TranslateScaleSystem.HandleSpawning();
m_PlayerModule.HandleSpawn();
// Update movement of scene objects. Projectiles and grenades can also start update as they use collision data from last frame
m_SpinSystem.Update();
m_moverUpdate.Update();
m_CharacterModule.Interpolate();
m_InterpolateGrenadeSystem.Update();
// Prediction
m_GameWorld.worldTime = m_PredictedTime;
m_ProjectileModule.StartPredictedMovement();
if (IsPredictionAllowed())
{
// ROLLBACK. All predicted entities (with the ServerEntity component) are rolled back to last server state
m_GameWorld.worldTime.SetTime(m_NetworkClient.serverTime, m_PredictedTime.tickInterval);
PredictionRollback();
// PREDICT PREVIOUS TICKS. Replay every tick *after* the last tick we have from server up to the last stored command we have
for (var tick = m_NetworkClient.serverTime + 1; tick < m_PredictedTime.tick; tick++)
{
m_GameWorld.worldTime.SetTime(tick, m_PredictedTime.tickInterval);
m_PlayerModule.RetrieveCommand(m_GameWorld.worldTime.tick);
PredictionUpdate();
}
// PREDICT CURRENT TICK. Update current tick using duration of current tick
m_GameWorld.worldTime = m_PredictedTime;
m_PlayerModule.RetrieveCommand(m_GameWorld.worldTime.tick);
// Dont update systems with close to zero time.
if (m_GameWorld.worldTime.tickDuration > 0.008f)
{
PredictionUpdate();
}
}
m_ProjectileModule.FinalizePredictedMovement();
m_GameModeSystem.Update();
// Update Presentation
m_GameWorld.worldTime = m_PredictedTime;
m_CharacterModule.UpdatePresentation();
m_DestructiblePropSystemClient.Update();
m_TeleporterSystemClient.Update();
m_GameWorld.worldTime = m_RenderTime;
m_DebugPrimitiveModule.HandleRequests();
// Handle despawns
m_ProjectileModule.HandleProjectileDespawn();
m_HandleNamePlateOwnerDespawn.Update();
m_TwistSystem.HandleDespawning();
m_FanSystem.HandleDespawning();
m_ragdollSystem.HandleDespawning();
m_HitCollisionModule.HandleDespawn();
m_CharacterModule.HandleDepawns();
m_TranslateScaleSystem.HandleDepawning();
m_GameWorld.ProcessDespawns();
}
public void LateUpdate(ChatSystemClient chatSystem, float frameDuration)
{
m_GameWorld.worldTime = m_RenderTime;
m_HitCollisionModule.StoreColliderState();
m_ragdollSystem.Update();
m_TranslateScaleSystem.Schedule();
var twistSystemHandle = m_TwistSystem.Schedule();
m_FanSystem.Schedule(twistSystemHandle);
var teamId = -1;
bool showScorePanel = false;
if (m_localPlayer != null && m_localPlayer.playerState != null && m_localPlayer.playerState.controlledEntity != Entity.Null)
{
teamId = m_localPlayer.playerState.teamIndex;
if (m_GameWorld.GetEntityManager().HasComponent<CharacterPredictedState>(m_localPlayer.playerState.controlledEntity))
{
var character = m_GameWorld.GetEntityManager()
.GetComponentObject<Character>(m_localPlayer.playerState.controlledEntity);
// Only show score board when alive
showScorePanel = character.healthState.health <= 0;
}
}
// TODO (petera) fix this hack
chatSystem.UpdateLocalTeamIndex(teamId);
m_ItemModule.Update();
m_CharacterModule.CameraUpdate();
m_PlayerModule.CameraUpdate();
m_CharacterModule.LateUpdate();
m_GameWorld.worldTime = m_RenderTime;
m_ProjectileModule.UpdateClientProjectilesNonPredicted();
m_GameWorld.worldTime = m_PredictedTime;
m_ProjectileModule.UpdateClientProjectilesPredicted();
m_ApplyGrenadePresentation.Update();
m_EffectModule.ClientUpdate();
m_UpdateNamePlates.Update();
m_ClientFrontendUpdate.Update();
Game.game.clientFrontend.SetShowScorePanel(showScorePanel);
m_DebugPrimitiveModule.DrawPrimitives();
m_TranslateScaleSystem.Complete();
m_FanSystem.Complete();
}
bool IsPredictionAllowed()
{
if (!m_PlayerModule.PlayerStateReady)
{
GameDebug.Log("No predict! No player state.");
return false;
}
if(!m_PlayerModule.IsControllingEntity)
{
GameDebug.Log("No predict! No controlled entity.");
return false;
}
if (m_PredictedTime.tick <= m_NetworkClient.serverTime)
{
GameDebug.Log("No predict! Predict time not ahead of server tick! " + GetFramePredictInfo());
return false;
}
if (!m_PlayerModule.HasCommands(m_NetworkClient.serverTime + 1, m_PredictedTime.tick))
{
GameDebug.Log("No predict! No commands available. " + GetFramePredictInfo());
return false;
}
return true;
}
string GetFramePredictInfo()
{
int firstCommandTick;
int lastCommandTick;
m_PlayerModule.GetBufferedCommandsTick(out firstCommandTick, out lastCommandTick);
return string.Format("Last server:{0} predicted:{1} buffer:{2}->{3} time since snap:{4} rtt avr:{5}",
m_NetworkClient.serverTime, m_PredictedTime.tick,
firstCommandTick, lastCommandTick,
m_NetworkClient.timeSinceSnapshot,m_NetworkStatistics.rtt.average);
}
public LocalPlayer RegisterLocalPlayer(int playerId)
{
m_localPlayer = m_PlayerModule.RegisterLocalPlayer(playerId, m_NetworkClient);
return m_localPlayer;
}
public void ProcessSnapshot(int serverTick)
{
Profiler.BeginSample("ClientGameWorld.ProcessNetworkData");
m_ReplicatedEntityModule.HandleEntityDespawns(); // Handle entity depawns from last frame here so they are marked as deleted in gameworld this frame
m_NetworkClient.ProcessSnapshot(m_ReplicatedEntityModule);
Profiler.EndSample();
}
void PredictionRollback()
{
m_CharacterModule.Rollback();
}
void PredictionUpdate()
{
m_SpectatorCamModule.Update();
m_CharacterModule.MovementStart();
m_CharacterModule.MovementResolve();
m_CharacterModule.AbilityStart();
m_CharacterModule.AbilityResolve();
}
void HandleTime(float frameDuration)
{
// Update tick rate (this will only change runtime in test scenarios)
// TODO (petera) consider use ConfigVars with Server flag for this
if (m_NetworkClient.serverTickRate != m_PredictedTime.tickRate)
{
m_PredictedTime.tickRate = m_NetworkClient.serverTickRate;
m_RenderTime.tickRate = m_NetworkClient.serverTickRate;
}
// Sample input into current command
// The time passed in here is used to calculate the amount of rotation from stick position
// The command stores final view direction
bool userInputEnabled = Game.GetMousePointerLock() && !Game.game.clientFrontend.chatPanel.isOpen;
m_PlayerModule.SampleInput(userInputEnabled, Time.deltaTime, m_RenderTime.tick);
int prevTick = m_PredictedTime.tick;
// Increment time
var deltaPredictedTime = frameDuration * frameTimeScale;
m_PredictedTime.AddDuration(deltaPredictedTime);
// Adjust time to be synchronized with server
int preferredBufferedCommandCount = 2;
int preferredTick = m_NetworkClient.serverTime + (int)(((m_NetworkClient.timeSinceSnapshot + m_NetworkStatistics.rtt.average) / 1000.0f) * m_GameWorld.worldTime.tickRate) + preferredBufferedCommandCount;
bool resetTime = false;
if (!resetTime && m_PredictedTime.tick < preferredTick - 3)
{
GameDebug.Log(string.Format("Client hard catchup ... "));
resetTime = true;
}
if (!resetTime && m_PredictedTime.tick > preferredTick + 6)
{
GameDebug.Log(string.Format("Client hard slowdown ... "));
resetTime = true;
}
frameTimeScale = 1.0f;
if (resetTime)
{
GameDebug.Log(string.Format("CATCHUP ({0} -> {1})", m_PredictedTime.tick, preferredTick));
m_NetworkStatistics.notifyHardCatchup = true;
m_GameWorld.nextTickTime = Game.frameTime;
m_PredictedTime.tick = preferredTick;
m_PredictedTime.SetTime(preferredTick, 0);
}
else
{
int bufferedCommands = m_NetworkClient.lastAcknowlegdedCommandTime - m_NetworkClient.serverTime;
if (bufferedCommands < preferredBufferedCommandCount)
frameTimeScale = 1.01f;
if (bufferedCommands > preferredBufferedCommandCount)
frameTimeScale = 0.99f;
}
// Increment interpolation time
m_RenderTime.AddDuration(frameDuration * frameTimeScale);
// Force interp time to not exeede server time
if (m_RenderTime.tick >= m_NetworkClient.serverTime)
{
m_RenderTime.SetTime(m_NetworkClient.serverTime, 0);
}
// hard catchup
if (m_RenderTime.tick < m_NetworkClient.serverTime - 10)
{
m_RenderTime.SetTime(m_NetworkClient.serverTime - 8, 0);
}
// Throttle up to catch up
if (m_RenderTime.tick < m_NetworkClient.serverTime - 1)
{
m_RenderTime.AddDuration(frameDuration * 0.01f);
}
// If predicted time has entered a new tick the stored commands should be sent to server
if (m_PredictedTime.tick > prevTick)
{
for (int tick = prevTick; tick < m_PredictedTime.tick; tick++)
{
m_PlayerModule.StoreCommand(tick);
m_PlayerModule.SendCommand(tick);
}
m_PlayerModule.ResetInput(userInputEnabled);
m_PlayerModule.StoreCommand(m_PredictedTime.tick);
}
// Store command
m_PlayerModule.StoreCommand(m_PredictedTime.tick);
}
GameWorld m_GameWorld;
GameTime m_PredictedTime = new GameTime(60);
GameTime m_RenderTime = new GameTime(60);
// External systems
NetworkClient m_NetworkClient;
NetworkStatisticsClient m_NetworkStatistics;
ClientFrontendUpdate m_ClientFrontendUpdate;
// Internal systems
readonly CharacterModuleClient m_CharacterModule;
readonly ProjectileModuleClient m_ProjectileModule;
readonly HitCollisionModule m_HitCollisionModule;
readonly PlayerModuleClient m_PlayerModule;
readonly DebugPrimitiveModule m_DebugPrimitiveModule;
readonly SpectatorCamModuleClient m_SpectatorCamModule;
readonly EffectModuleClient m_EffectModule;
readonly ReplicatedEntityModuleClient m_ReplicatedEntityModule;
readonly ItemModule m_ItemModule;
readonly RagdollModule m_ragdollSystem;
readonly GameModeSystemClient m_GameModeSystem;
readonly InterpolateGrenadePresentation m_InterpolateGrenadeSystem;
readonly ApplyGrenadePresentation m_ApplyGrenadePresentation;
readonly TwistSystem m_TwistSystem;
readonly FanSystem m_FanSystem;
readonly TranslateScaleSystem m_TranslateScaleSystem;
readonly MoverUpdate m_moverUpdate;
readonly DestructiblePropSystemClient m_DestructiblePropSystemClient;
readonly HandleNamePlateSpawn m_HandleNamePlateOwnerSpawn;
readonly HandleNamePlateDespawn m_HandleNamePlateOwnerDespawn;
readonly UpdateNamePlates m_UpdateNamePlates;
readonly SpinSystem m_SpinSystem;
readonly TeleporterSystemClient m_TeleporterSystemClient;
LocalPlayer m_localPlayer;
}
public class ClientGameLoop : Game.IGameLoop, INetworkCallbacks, INetworkClientCallbacks
{
// Client vars
[ConfigVar(Name ="client.updaterate", DefaultValue = "30000", Description = "Max bytes/sec client wants to receive", Flags = ConfigVar.Flags.ClientInfo)]
public static ConfigVar clientUpdateRate;
[ConfigVar(Name ="client.updatesendrate", DefaultValue = "20", Description = "Snapshot sendrate requested by client", Flags = ConfigVar.Flags.ClientInfo)]
public static ConfigVar clientUpdateSendRate;
[ConfigVar(Name ="client.playername", DefaultValue = "Noname", Description = "Name of player", Flags = ConfigVar.Flags.ClientInfo | ConfigVar.Flags.Save)]
public static ConfigVar clientPlayerName;
[ConfigVar(Name = "client.matchmaker", DefaultValue = "0.0.0.0:80", Description = "Address of matchmaker", Flags = ConfigVar.Flags.None)]
public static ConfigVar clientMatchmaker;
public bool Init(string[] args)
{
m_StateMachine = new StateMachine<ClientState>();
m_StateMachine.Add(ClientState.Browsing, EnterBrowsingState, UpdateBrowsingState, LeaveBrowsingState);
m_StateMachine.Add(ClientState.Connecting, EnterConnectingState, UpdateConnectingState, null);
m_StateMachine.Add(ClientState.Loading, EnterLoadingState, UpdateLoadingState, null);
m_StateMachine.Add(ClientState.Playing, EnterPlayingState, UpdatePlayingState, LeavePlayingState);
#if UNITY_EDITOR
Game.game.levelManager.UnloadLevel();
World.DisposeAllWorlds();
#endif
m_GameWorld = new GameWorld("ClientWorld");
#if USE_UNET
m_NetworkTransport = new UNETTransport();
if (!m_NetworkTransport.Init())
return false;
m_NetworkClient = new NetworkClient(m_NetworkTransport);
#else
m_NetworkTransport = new SocketTransport();
m_NetworkClient = new NetworkClient(m_NetworkTransport);
#endif
if (Application.isEditor || Game.game.buildId == "AutoBuild")
NetworkClient.clientVerifyProtocol.Value = "0";
m_NetworkClient.UpdateClientConfig();
m_NetworkStatistics = new NetworkStatisticsClient(m_NetworkClient);
m_ChatSystem = new ChatSystemClient(m_NetworkClient);
#if USE_UNET
//m_BroadcastListener = new UNETBroadcastListener();
//m_BroadcastListener.config = new UNETBroadcastConfig();
//m_BroadcastListener.Init();
m_ServerListClient = new ServerListClient(ServerListConfig.BasicConfig("da76f801-609f-47c8-b711-a54234e2b7be"));
#endif
GameDebug.Log("Network client initialized");
m_requestedPlayerSettings.playerName = clientPlayerName.Value;
m_requestedPlayerSettings.teamId = -1;
Console.AddCommand("disconnect", CmdDisconnect, "Disconnect from server if connected", this.GetHashCode());
Console.AddCommand("prediction", CmdTogglePrediction, "Toggle prediction", this.GetHashCode());
Console.AddCommand("runatserver", CmdRunAtServer, "Run command at server", this.GetHashCode());
Console.AddCommand("respawn", CmdRespawn, "Force a respawn", this.GetHashCode());
Console.AddCommand("nextchar", CmdNextChar, "Select next character", this.GetHashCode());
Console.AddCommand("nextteam", CmdNextTeam, "Select next character", this.GetHashCode());
Console.AddCommand("spectator", CmdSpectator, "Select spectator cam", this.GetHashCode());
Console.AddCommand("matchmake", CmdMatchmake, "matchmake <ip:port>: Find and join a server", this.GetHashCode());
if (args.Length > 0)
{
targetServer = args[0];
m_StateMachine.SwitchTo(ClientState.Connecting);
}
else
m_StateMachine.SwitchTo(ClientState.Browsing);
GameDebug.Log("Client initialized");
return true;
}
public void Shutdown()
{
GameDebug.Log("ClientGameLoop shutdown");
Console.RemoveCommandsWithTag(this.GetHashCode());
m_StateMachine.Shutdown();
#if USE_UNET
//m_BroadcastListener.Shutdown();
#endif
m_NetworkClient.Shutdown();
m_NetworkTransport.Shutdown();
m_GameWorld.Shutdown();
}
public void OnConnect(int clientId) { }
public void OnDisconnect(int clientId) { }
public void OnEvent(int clientId, NetworkEvent info)
{
Profiler.BeginSample("-ProcessEvent");
switch ((GameNetworkEvents.EventType)info.type.typeId)
{
case GameNetworkEvents.EventType.Chat:
var data = new NetworkReader(info.data, info.type.schema);
m_ChatSystem.ReceiveMessage(data.ReadString(256));
break;
}
Profiler.EndSample();
}
public void OnMapUpdate(ref NetworkReader data)
{
m_LevelName = data.ReadString();
if(m_StateMachine.CurrentState() != ClientState.Loading)
m_StateMachine.SwitchTo(ClientState.Loading);
}
public void Update()
{
Profiler.BeginSample("ClientGameLoop.Update");
Profiler.BeginSample("-NetworkClientUpdate");
m_NetworkClient.Update(this);
Profiler.EndSample();
Profiler.BeginSample("-StateMachine update");
m_StateMachine.Update();
Profiler.EndSample();
// TODO (petera) change if we have a lobby like setup one day
if(m_StateMachine.CurrentState() == ClientState.Playing)
Game.game.clientFrontend.UpdateChat(m_ChatSystem);
m_NetworkClient.SendData();
// TODO (petera) merge with clientinfo
if (m_requestedPlayerSettings.playerName != clientPlayerName.Value)
{
// Cap name length
clientPlayerName.Value = clientPlayerName.Value.Substring(0, Mathf.Min(clientPlayerName.Value.Length, 16));
m_requestedPlayerSettings.playerName = clientPlayerName.Value;
m_playerSettingsUpdated = true;
}
if(m_NetworkClient.isConnected && m_playerSettingsUpdated)
{
m_playerSettingsUpdated = false;
SendPlayerSettings();
}
if(m_clientWorld != null)
m_NetworkStatistics.Update(m_clientWorld.frameTimeScale, GameTime.GetDuration(m_clientWorld.RenderTime, m_clientWorld.PredictedTime));
Profiler.EndSample();
}
void EnterBrowsingState()
{
GameDebug.Assert(m_clientWorld == null && Game.game.levelManager.currentLevel == null);
m_ClientState = ClientState.Browsing;
}
void UpdateBrowsingState()
{
if (m_useMatchmaking)
{
m_matchmaker?.UpdateMatchmaking();
}
UpdateIdleScreen();
}
void LeaveBrowsingState()
{
}
string targetServer = "";
int connectRetryCount;
void EnterConnectingState()
{
GameDebug.Assert(m_ClientState == ClientState.Browsing);
GameDebug.Assert(m_clientWorld == null);
GameDebug.Assert(m_NetworkClient.connectionState == NetworkClient.ConnectionState.Disconnected);
m_ClientState = ClientState.Connecting;
connectRetryCount = 0;
}
void UpdateConnectingState()
{
UpdateIdleScreen();
switch (m_NetworkClient.connectionState)
{
case NetworkClient.ConnectionState.Connected:
m_GameMessage = "Waiting for map info";
break;
case NetworkClient.ConnectionState.Disconnected:
if(connectRetryCount < 2)
{
connectRetryCount++;
m_GameMessage = string.Format("Trying to connect to {0} (attempt #{1})...", targetServer, connectRetryCount);
GameDebug.Log(m_GameMessage);
m_NetworkClient.Connect(targetServer);
}
else
{
m_GameMessage = "Failed to connect to server";
GameDebug.Log(m_GameMessage);
m_NetworkClient.Disconnect();
m_StateMachine.SwitchTo(ClientState.Browsing);
}
break;
}
}
void EnterLoadingState()
{
Game.game.clientFrontend.ShowMenu(ClientFrontend.MenuShowing.None);
Console.SetOpen(false);
GameDebug.Assert(m_clientWorld == null);
GameDebug.Assert(m_NetworkClient.isConnected);
m_requestedPlayerSettings.playerName = clientPlayerName.Value;
m_requestedPlayerSettings.characterType = (short)Game.characterType.IntValue;
m_playerSettingsUpdated = true;
m_ClientState = ClientState.Loading;
}
void UpdateLoadingState()
{
// Handle disconnects
if (!m_NetworkClient.isConnected)
{
m_GameMessage = m_DisconnectReason != null ? string.Format("Disconnected from server ({0})", m_DisconnectReason) : "Disconnected from server (lost connection)";
m_DisconnectReason = null;
m_StateMachine.SwitchTo(ClientState.Browsing);
}
// Wait until we got level info
if (m_LevelName == null)
return;
// Load if we are not already loading
var level = Game.game.levelManager.currentLevel;
if (level == null || level.name != m_LevelName)
{
if (!Game.game.levelManager.LoadLevel(m_LevelName))
{
m_DisconnectReason = string.Format("could not load requested level '{0}'", m_LevelName);
m_NetworkClient.Disconnect();
return;
}
level = Game.game.levelManager.currentLevel;
}
// Wait for level to be loaded
if (level.state == LevelState.Loaded)
m_StateMachine.SwitchTo(ClientState.Playing);
}
void EnterPlayingState()
{
GameDebug.Assert(m_clientWorld == null && Game.game.levelManager.IsCurrentLevelLoaded());
m_GameWorld.RegisterSceneEntities();
m_resourceSystem = new BundledResourceManager("BundledResources/Client");
m_clientWorld = new ClientGameWorld(m_GameWorld, m_NetworkClient, m_NetworkStatistics, m_resourceSystem);
m_clientWorld.PredictionEnabled = m_predictionEnabled;
m_LocalPlayer = m_clientWorld.RegisterLocalPlayer(m_NetworkClient.clientId);
m_NetworkClient.QueueEvent((ushort)GameNetworkEvents.EventType.PlayerReady, true, (ref NetworkWriter data) => {});
m_ClientState = ClientState.Playing;
}
void LeavePlayingState()
{
m_resourceSystem.Shutdown();
Game.game.clientFrontend.Clear();
m_LocalPlayer = null;
m_clientWorld.Shutdown();
m_clientWorld = null;
Game.game.levelManager.UnloadLevel();
m_resourceSystem.Shutdown();
}
void UpdatePlayingState()
{
// Handle disconnects
if (!m_NetworkClient.isConnected)
{
m_GameMessage = m_DisconnectReason != null ? string.Format("Disconnected from server ({0})", m_DisconnectReason) : "Disconnected from server (lost connection)";
m_StateMachine.SwitchTo(ClientState.Browsing);
return;
}
// (re)send client info if any of the configvars that contain clientinfo has changed
if ((ConfigVar.DirtyFlags & ConfigVar.Flags.ClientInfo) == ConfigVar.Flags.ClientInfo)
{
m_NetworkClient.UpdateClientConfig();
ConfigVar.DirtyFlags &= ~ConfigVar.Flags.ClientInfo;
}
if (Game.Input.GetKeyUp(KeyCode.H))
{
RemoteConsoleCommand("nextchar");
}
if (Game.Input.GetKeyUp(KeyCode.T))
CmdNextTeam(null);
float frameDuration = m_lastFrameTime != 0 ? (float)(Game.frameTime - m_lastFrameTime) : 0;
m_lastFrameTime = Game.frameTime;
m_clientWorld.Update(frameDuration);
m_performGameWorldLateUpdate = true;
}
public void FixedUpdate()
{
}
public void LateUpdate()
{
if (m_clientWorld != null && m_performGameWorldLateUpdate)
{
m_performGameWorldLateUpdate = false;
m_clientWorld.LateUpdate(m_ChatSystem, Time.deltaTime);
}
ShowInfoOverlay(0, 1);
}
public void RemoteConsoleCommand(string command)
{
m_NetworkClient.QueueEvent((ushort)GameNetworkEvents.EventType.RemoteConsoleCmd, true, (ref NetworkWriter writer) =>
{
writer.WriteString("args", command);
});
}
void UpdateIdleScreen()
{
#if USE_UNET
//List<ServerInfo> servers = m_BroadcastListener.GetKnownServers();
List<ServerInfo> servers = m_ServerListClient.KnownServers;
#else
List<ServerInfo> servers = new List<ServerInfo>();
#endif
Game.game.clientFrontend.UpdateMenu(m_requestedPlayerSettings.playerName, servers, m_GameMessage);
}
public void CmdConnect(string[] args)
{
targetServer = args.Length > 0 ? args[0] : "127.0.0.1";
m_StateMachine.SwitchTo(ClientState.Connecting);
}
void CmdDisconnect(string[] args)
{
m_DisconnectReason = "user manually disconnected";
m_NetworkClient.Disconnect();
}
void CmdTogglePrediction(string[] args)
{
m_predictionEnabled = !m_predictionEnabled;
Console.Write("Prediction:" + m_predictionEnabled);
if (m_clientWorld != null)
m_clientWorld.PredictionEnabled = m_predictionEnabled;
}
void CmdRunAtServer(string[] args)
{
RemoteConsoleCommand(string.Join(" ", args));
}
void CmdRespawn(string[] args)
{
if (m_LocalPlayer == null || m_LocalPlayer.playerState == null || m_LocalPlayer.playerState.controlledEntity == Entity.Null)
return;
// Request new char type
if (args.Length == 1)
{
m_requestedPlayerSettings.characterType = short.Parse(args[0]);
m_playerSettingsUpdated = true;
}
// Tell server who to respawn
RemoteConsoleCommand(string.Format("respawn {0}",m_LocalPlayer.playerState.playerId));
}
void CmdNextChar(string[] args)
{
if (m_LocalPlayer == null || m_LocalPlayer.playerState == null || m_LocalPlayer.playerState.controlledEntity == Entity.Null)
return;
if (Game.allowCharChange.IntValue != 1)
return;
if (!m_GameWorld.GetEntityManager()
.HasComponent<CharacterPredictedState>(m_LocalPlayer.playerState.controlledEntity))
return;
var charSetupRegistry = m_resourceSystem.GetResourceRegistry<HeroTypeRegistry>();
var charSetupCount = charSetupRegistry.entries.Length;
m_requestedPlayerSettings.characterType = m_requestedPlayerSettings.characterType + 1;
if (m_requestedPlayerSettings.characterType >= charSetupCount)
m_requestedPlayerSettings.characterType = 0;
m_playerSettingsUpdated = true;
}
void CmdSpectator(string[] args)
{
if (m_LocalPlayer == null || m_LocalPlayer.playerState == null || m_LocalPlayer.playerState.controlledEntity == Entity.Null)
return;
if (Game.allowCharChange.IntValue != 1)
return;
var isControllingSpectatorCam = m_GameWorld.GetEntityManager()
.HasComponent<SpectatorCam>(m_LocalPlayer.playerState.controlledEntity);
// TODO find better way to identity spectatorcam
m_requestedPlayerSettings.characterType = isControllingSpectatorCam ? 0 : 1000;
m_playerSettingsUpdated = true;
}
void CmdNextTeam(string[] args)
{
if (m_LocalPlayer == null || m_LocalPlayer.playerState == null)
return;
if (Game.allowCharChange.IntValue != 1)
return;
m_requestedPlayerSettings.teamId = (short)(m_LocalPlayer.playerState.teamIndex + 1);
if (m_requestedPlayerSettings.teamId > 1)
m_requestedPlayerSettings.teamId = 0;
m_playerSettingsUpdated = true;
}
/// <summary>
/// Start matchmaking by issuing a request to the provided endpoint. Use client.matchmaker value
/// as endpoint if none given.
/// </summary>
void CmdMatchmake(string[] args)
{
if (m_matchmaker != null)
{
GameDebug.Log("matchmake: Already in a matchmaking session. Wait for completion before matchmaking again.");
return;
}
string endpoint = clientMatchmaker.Value;
if (args.Length > 0)
endpoint = args[0];
if (string.IsNullOrEmpty(endpoint))
{
GameDebug.LogError("matchmake: command requires an endpoint <ip:port>");
return;
}
if (string.IsNullOrEmpty(clientPlayerName.Value))
{
GameDebug.LogError("matchmake: Player name must be set before matchmaking can be started");
return;
}
if (m_StateMachine.CurrentState() != ClientState.Browsing)
{
GameDebug.LogError("matchmake: matchmaking can only be started in Browsing state. Current state is " + m_StateMachine.CurrentState().ToString());
return;
}
GameDebug.Log($"matchmake: Starting the matchmaker. Requesting match from {endpoint} for player {clientPlayerName.Value}.");
m_useMatchmaking = true;
m_matchmaker = new Matchmaker(endpoint);
MatchmakingPlayerProperties playerProps = new MatchmakingPlayerProperties() {hats = 5};
MatchmakingGroupProperties groupProps = new MatchmakingGroupProperties() {mode = 0};
MatchmakingRequest request = Matchmaker.CreateMatchmakingRequest(clientPlayerName.Value, playerProps, groupProps);
m_matchmaker.RequestMatch(request, OnMatchmakingSuccess, OnMatchmakingError);
}
void OnMatchmakingSuccess(string connectionInfo)
{
GameDebug.Log($"Matchmaking has found a game! The server is at: {connectionInfo}");
// TODO: Uncomment following line when matchmaking service returns an endpoint instead of the roster
//Console.EnqueueCommand($"connect {connectionInfo}");
m_matchmaker = null;
}
void OnMatchmakingError(string errorInfo)
{
GameDebug.LogError($"Matchmaking failed! Error is: {errorInfo}");
m_matchmaker = null;
}
void ShowInfoOverlay(float x, float y)
{
if(m_showTickInfo.IntValue == 1)
DebugOverlay.Write(x, y++, "Tick:{0} Last server:{1} Predicted:{2}", m_clientWorld.PredictedTime.tick, m_NetworkClient.serverTime, m_clientWorld.PredictedTime.tick - m_NetworkClient.serverTime - 1);
if(m_showCommandInfo.IntValue == 1)
{
UserCommand command = UserCommand.defaultCommand;
bool valid = m_LocalPlayer.commandBuffer.TryGetValue(m_clientWorld.PredictedTime.tick + 1, ref command);
if(valid)
DebugOverlay.Write(x, y++, "Next cmd: PrimaryFire:{0}", command.primaryFire ? 1:0);
valid = m_LocalPlayer.commandBuffer.TryGetValue(m_clientWorld.PredictedTime.tick, ref command);
if (valid)
DebugOverlay.Write(x, y++, "Tick cmd: PrimaryFire:{0}", command.primaryFire ? 1:0);
}
}
void SendPlayerSettings()
{
m_NetworkClient.QueueEvent((ushort)GameNetworkEvents.EventType.PlayerSetup, true, (ref NetworkWriter writer) =>
{
m_requestedPlayerSettings.Serialize(ref writer);
});
}
public void ProcessSnapshot(int serverTime)
{
m_clientWorld.ProcessSnapshot(serverTime);
}
enum ClientState
{
Browsing,
Connecting,
Loading,
Playing,
}
StateMachine<ClientState> m_StateMachine;
ClientState m_ClientState;
GameWorld m_GameWorld;
#if USE_UNET
UNETTransport m_NetworkTransport;
//UNETBroadcastListener m_BroadcastListener;
ServerListClient m_ServerListClient;
#else
SocketTransport m_NetworkTransport;
#endif
NetworkClient m_NetworkClient;
LocalPlayer m_LocalPlayer;
PlayerSettings m_requestedPlayerSettings = new PlayerSettings();
bool m_playerSettingsUpdated;
NetworkStatisticsClient m_NetworkStatistics;
ChatSystemClient m_ChatSystem;
ClientGameWorld m_clientWorld;
BundledResourceManager m_resourceSystem;
string m_LevelName;
string m_DisconnectReason = null;
string m_GameMessage = "Welcome to the sample game!";
double m_lastFrameTime;
bool m_predictionEnabled = true;
bool m_performGameWorldLateUpdate;
bool m_useMatchmaking = false;
Matchmaker m_matchmaker;
[ConfigVar(Name ="client.showtickinfo", DefaultValue = "0", Description = "Show tick info")]
static ConfigVar m_showTickInfo;
[ConfigVar(Name ="client.showcommandinfo", DefaultValue = "0", Description = "Show command info")]
static ConfigVar m_showCommandInfo;
}