浏览代码

changes to address PR problems

/fix-mtt-493
eheimburg 3 年前
当前提交
934cfc7e
共有 15 个文件被更改,包括 451 次插入300 次删除
  1. 224
      Assets/BossRoom/Scenes/CharSelect.unity
  2. 6
      Assets/BossRoom/Scripts/Client/Game/Character/ModelAppearanceSetter.cs
  3. 11
      Assets/BossRoom/Scripts/Client/Game/Character/ModelSwap.cs
  4. 14
      Assets/BossRoom/Scripts/Client/Game/State/ClientCharSelectState.cs
  5. 98
      Assets/BossRoom/Scripts/Client/UI/UICharSelectController.cs
  6. 7
      Assets/BossRoom/Scripts/Client/UI/UICharSelectPlayerStateBox.cs
  7. 3
      Assets/BossRoom/Scripts/Client/UI/UICharSelectPlayerStateBoxSettings.cs
  8. 9
      Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs
  9. 38
      Assets/BossRoom/Scripts/Server/Game/LobbyResults.cs
  10. 44
      Assets/BossRoom/Scripts/Server/Game/State/ServerBossRoomState.cs
  11. 66
      Assets/BossRoom/Scripts/Server/Game/State/ServerCharSelectState.cs
  12. 177
      Assets/BossRoom/Scripts/Shared/Game/State/CharSelectData.cs
  13. 4
      Assets/BossRoom/Scripts/Shared/Game/State/GameStateBehaviour.cs
  14. 39
      Assets/BossRoom/Scripts/Server/Game/State/GameStateRelay.cs
  15. 11
      Assets/BossRoom/Scripts/Server/Game/State/GameStateRelay.cs.meta

224
Assets/BossRoom/Scenes/CharSelect.unity


m_Children:
- {fileID: 1720541735}
m_Father: {fileID: 1299736799}
m_RootOrder: 4
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}

- {fileID: 898781742}
- {fileID: 618690587}
- {fileID: 219125661}
- {fileID: 1592710793}
- {fileID: 2043722330}
m_Father: {fileID: 1930582174}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1561728798}
m_CullTransparentMesh: 1
--- !u!1 &1592710792
--- !u!1 &1680881899
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}

m_Component:
- component: {fileID: 1592710793}
- component: {fileID: 1592710795}
- component: {fileID: 1592710794}
- component: {fileID: 1680881900}
- component: {fileID: 1680881902}
- component: {fileID: 1680881901}
m_Name: Caption
m_Name: Player #
m_IsActive: 0
--- !u!224 &1592710793
m_IsActive: 1
--- !u!224 &1680881900
m_GameObject: {fileID: 1592710792}
m_GameObject: {fileID: 1680881899}
m_Father: {fileID: 1299736799}
m_RootOrder: 3
m_Father: {fileID: 219125661}
m_RootOrder: 0
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_SizeDelta: {x: 400, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1592710794
m_SizeDelta: {x: 0, y: 30}
m_Pivot: {x: 0.5, y: 1}
--- !u!114 &1680881901
m_GameObject: {fileID: 1592710792}
m_GameObject: {fileID: 1680881899}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}

m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 24
m_FontSize: 12
m_MinSize: 0
m_MinSize: 1
m_Alignment: 0
m_Alignment: 1
m_Text: CHAR SELECT
--- !u!222 &1592710795
m_Text: Welcome, P2!
--- !u!222 &1680881902
m_GameObject: {fileID: 1592710792}
m_GameObject: {fileID: 1680881899}
--- !u!1 &1680881899
--- !u!1 &1720541734
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}

m_Component:
- component: {fileID: 1680881900}
- component: {fileID: 1680881902}
- component: {fileID: 1680881901}
- component: {fileID: 1720541735}
- component: {fileID: 1720541737}
- component: {fileID: 1720541736}
m_Name: Player #
m_Name: Text
--- !u!224 &1680881900
--- !u!224 &1720541735
m_GameObject: {fileID: 1680881899}
m_GameObject: {fileID: 1720541734}
m_Father: {fileID: 219125661}
m_Father: {fileID: 879295759}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMin: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 30}
m_Pivot: {x: 0.5, y: 1}
--- !u!114 &1680881901
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1720541736
m_GameObject: {fileID: 1680881899}
m_GameObject: {fileID: 1720541734}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}

m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 12
m_FontSize: 20
m_MinSize: 1
m_MinSize: 2
m_Alignment: 1
m_Alignment: 4
m_Text: Welcome, P2!
--- !u!222 &1680881902
m_Text: READY! GAME IS STARTING!
--- !u!222 &1720541737
m_GameObject: {fileID: 1680881899}
m_GameObject: {fileID: 1720541734}
--- !u!1 &1720541734
--- !u!1 &1871946533
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}

m_Component:
- component: {fileID: 1720541735}
- component: {fileID: 1720541737}
- component: {fileID: 1720541736}
- component: {fileID: 1871946536}
- component: {fileID: 1871946535}
- component: {fileID: 1871946534}
m_Layer: 5
m_Name: Text
m_TagString: Untagged

m_IsActive: 1
--- !u!224 &1720541735
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1720541734}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 879295759}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1720541736
--- !u!114 &1871946534
m_GameObject: {fileID: 1720541734}
m_GameObject: {fileID: 1871946533}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}

m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: READY! GAME IS STARTING!
--- !u!222 &1720541737
m_Text: Error text goes here. Lorem ipsum blah blah blah that's about long enough.
--- !u!222 &1871946535
m_GameObject: {fileID: 1720541734}
m_GameObject: {fileID: 1871946533}
--- !u!224 &1871946536
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1871946533}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2043722330}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1924630032
GameObject:
m_ObjectHideFlags: 0

- {fileID: 219125660}
m_ShowTheseUIElementsWhenLobbyIsLockedIn:
- {fileID: 879295756}
m_HideTheseUIElementsOnFatalLobbyError:
- {fileID: 219125660}
m_ShowTheseUIElementsOnFatalLobbyError:
- {fileID: 2043722329}
m_FatalLobbyErrorText: {fileID: 1871946534}
m_FatalErrorLobbyFullMsg: 'Error: lobby is full! You cannot play.'
m_WelcomeMsg: Welcome, P{0}!
--- !u!1 &1953834081
GameObject:

m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1989587315}
m_CullTransparentMesh: 1
--- !u!1 &2043722329
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2043722330}
- component: {fileID: 2043722332}
- component: {fileID: 2043722331}
m_Layer: 5
m_Name: FatalErrorBox
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &2043722330
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2043722329}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 1871946536}
m_Father: {fileID: 1299736799}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 100}
m_SizeDelta: {x: 400, y: 200}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &2043722331
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2043722329}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &2043722332
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2043722329}
m_CullTransparentMesh: 1
--- !u!1 &2092792601
GameObject:

6
Assets/BossRoom/Scripts/Client/Game/Character/ModelAppearanceSetter.cs


public void SetModel(CharacterTypeEnum Class, bool IsMale)
{
int newModelIndex = MapToModelIdx(Class, IsMale);
for (int x = 0; x < m_BodyParts.Length; ++x)
for (int i = 0; i < m_BodyParts.Length; ++i)
if (m_BodyParts[ x ])
m_BodyParts[ x ].SetModel(newModelIndex);
if (m_BodyParts[ i ])
m_BodyParts[ i ].SetModel(newModelIndex);
}
}

11
Assets/BossRoom/Scripts/Client/Game/Character/ModelSwap.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
namespace BossRoom.Visual
{

{
index = System.Math.Min(index, modelArray.Length - 1);
m_ModelIndex = index;
for (int x = 0; x < modelArray.Length; x++)
for (int i = 0; i < modelArray.Length; i++)
modelArray[x].SetActive(x == m_ModelIndex);
modelArray[ i ].SetActive(i == m_ModelIndex);
}
}

//DEBUG: This allows you to change models at runtime by tweaking the ModelIndex in the editor.
if( m_lastModelIndex != m_ModelIndex )
//DEBUG: This allows you to change models at runtime by tweaking the ModelIndex in the editor.
if (m_lastModelIndex != m_ModelIndex)
{
m_lastModelIndex = m_ModelIndex;
SetModel(m_ModelIndex);

14
Assets/BossRoom/Scripts/Client/Game/State/ClientCharSelectState.cs


using System.Collections;
using System.Collections.Generic;
using MLAPI;
using BossRoom;
using MLAPI;
namespace BossRoom.Client
{

/// <summary>
/// Reference to the scene's state object so that UI can access state
/// </summary>
public static ClientCharSelectState Instance;
public static ClientCharSelectState Instance { get; private set; }
public override GameState ActiveState { get { return GameState.CHARSELECT; } }
public CharSelectData CharSelectData { get; private set; }

CharSelectData = GetComponent<CharSelectData>();
}
private void OnDestroy()
protected override void OnDestroy()
base.OnDestroy();
CharSelectData.OnAssignedLobbyIndex -= OnAssignedCharIndex;
if (Instance == this)
Instance = null;

{
CharSelectData.InvokeServerRpc(CharSelectData.RpcChangeSlot,
NetworkingManager.Singleton.LocalClientId,
newClass, newIsMale, newState);
}
new CharSelectData.CharSelectSlot(newClass, newIsMale, newState));
}
private void OnAssignedCharIndex(int index)
{

98
Assets/BossRoom/Scripts/Client/UI/UICharSelectController.cs


using System.Collections;
using MLAPI.NetworkedVar.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UICharSelectController : MonoBehaviour
{
#region Variables set in editor
[Tooltip("Connect to dummy character model in the char-select screen")]
[Tooltip("Reference to dummy character model in the char-select screen")]
private BossRoom.Visual.ModelAppearanceSetter m_InSceneCharacter;
[SerializeField]

private List<GameObject> m_ShowTheseUIElementsWhenLobbyIsLockedIn;
[SerializeField]
private List<GameObject> m_HideTheseUIElementsOnFatalLobbyError;
[SerializeField]
private List<GameObject> m_ShowTheseUIElementsOnFatalLobbyError;
[SerializeField]
private Text m_FatalLobbyErrorText;
[SerializeField]
[Tooltip("Error shown when lobby is full")]
private string m_FatalErrorLobbyFullMsg = "Error: lobby is full! You cannot play.";
[SerializeField]
#endregion
private Dictionary<CharacterTypeEnum, Button> m_ClassButtons;
private CharacterTypeEnum m_Class;

if (ClientCharSelectState.Instance == null)
Debug.LogError("No CharSelectData in scene!");
ClientCharSelectState.Instance.CharSelectData.OnAssignedLobbyIndex += OnAssignedLobbyIndex;
ClientCharSelectState.Instance.CharSelectData.OnCharSelectSlotChanged += OnCharSelectSlotChanged;
ClientCharSelectState.Instance.CharSelectData.OnLobbyLockedIn += OnLobbyLockedIn;
ClientCharSelectState.Instance.CharSelectData.CharacterSlots.OnListChanged += OnCharSelectSlotChanged;
ClientCharSelectState.Instance.CharSelectData.IsLobbyLocked.OnValueChanged += OnLobbyLockedInChanged;
ClientCharSelectState.Instance.CharSelectData.OnFatalLobbyError += OnFatalLobbyError;
m_InSceneCharacter.SetModel(m_Class, m_IsMale);
SetButtonInteractibleness();

if (ClientCharSelectState.Instance)
{
ClientCharSelectState.Instance.CharSelectData.OnAssignedLobbyIndex -= OnAssignedLobbyIndex;
ClientCharSelectState.Instance.CharSelectData.OnCharSelectSlotChanged -= OnCharSelectSlotChanged;
ClientCharSelectState.Instance.CharSelectData.OnLobbyLockedIn -= OnLobbyLockedIn;
ClientCharSelectState.Instance.CharSelectData.CharacterSlots.OnListChanged -= OnCharSelectSlotChanged;
ClientCharSelectState.Instance.CharSelectData.IsLobbyLocked.OnValueChanged -= OnLobbyLockedInChanged;
ClientCharSelectState.Instance.CharSelectData.OnFatalLobbyError -= OnFatalLobbyError;
for (int x = 0; x < m_PlayerStateBoxes.Count; ++x)
for (int i = 0; i < m_PlayerStateBoxes.Count && i < CharSelectData.k_MaxLobbyPlayers; ++i)
m_PlayerStateBoxes[ x ].SetPlayerSlotIndex(x);
var slotData = ClientCharSelectState.Instance.CharSelectData.GetCharSelectSlot(x);
m_PlayerStateBoxes[ x ].SetClassAndState(slotData.Class, slotData.State);
m_PlayerStateBoxes[ i ].SetPlayerSlotIndex(i);
var slotData = ClientCharSelectState.Instance.CharSelectData.CharacterSlots[ i ];
m_PlayerStateBoxes[ i ].SetClassAndState(slotData.Class, slotData.State);
}
}

m_GenderButtonFemale.interactable = m_IsMale;
}
#region UI Event callbacks (these are directly called by the UI elements in CharSelect scene)
// UI Event callbacks (these are directly called by the UI elements in CharSelect scene)
public void OnClickClassButtonTank()
{
SetClass(CharacterTypeEnum.TANK);

{
SetGender(false);
}
#endregion
#region networking callbacks
// networking callbacks
private void OnAssignedLobbyIndex(int index)
{
m_PlayerNumberText.text = string.Format(m_WelcomeMsg, (index + 1));

private void OnCharSelectSlotChanged(int slotIdx, CharSelectData.CharSelectSlot slot)
private void OnCharSelectSlotChanged(NetworkedListEvent<CharSelectData.CharSelectSlot> changeEvent)
m_PlayerStateBoxes[ slotIdx ].SetClassAndState(slot.Class, slot.State);
m_PlayerStateBoxes[ changeEvent.index ].SetClassAndState(changeEvent.value.Class, changeEvent.value.State);
private void OnLobbyLockedIn()
private void OnLobbyLockedInChanged(bool wasLockedIn, bool isLockedIn)
go.SetActive(false);
go.SetActive(!isLockedIn);
go.SetActive(isLockedIn);
}
}
private void OnLobbyLockedIn()
{
}
private void OnFatalLobbyError(CharSelectData.FatalLobbyError error)
{
foreach (var go in m_HideTheseUIElementsOnFatalLobbyError)
{
go.SetActive(false);
}
foreach (var go in m_ShowTheseUIElementsOnFatalLobbyError)
{
switch (error)
{
case CharSelectData.FatalLobbyError.LOBBY_FULL:
m_FatalLobbyErrorText.text = m_FatalErrorLobbyFullMsg;
break;
default:
Debug.LogError("Unknown fatal error " + error);
break;
}
#endregion
#if UNITY_EDITOR
private void OnValidate()
{
if (m_PlayerStateBoxes.Count != CharSelectData.k_MaxLobbyPlayers)
{
Debug.LogError("There should be exactly " + CharSelectData.k_MaxLobbyPlayers + " entries in the Player State Boxes list");
}
for (int i = 0; i < m_PlayerStateBoxes.Count; ++i)
{
if (m_PlayerStateBoxes[ i ] == null)
{
Debug.LogError("Entry index " + i + " Player State Boxes list is null");
}
}
if (!m_InSceneCharacter)
{
Debug.LogError("In Scene Character not set!");
}
}
#endif
}

7
Assets/BossRoom/Scripts/Client/UI/UICharSelectPlayerStateBox.cs


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
#region Variables set in editor
[SerializeField]
private UICharSelectPlayerStateBoxSettings m_StateInactive;
[SerializeField]

private UICharSelectPlayerStateBoxSettings m_StateArcherActive;
[SerializeField]
private UICharSelectPlayerStateBoxSettings m_StateArcherLockedIn;
#endregion
private int m_PlayerIndex; // 0-based; e.g. this is 0 for Player 1, 1 for Player 2, etc.
private CharacterTypeEnum m_CharacterClass;

3
Assets/BossRoom/Scripts/Client/UI/UICharSelectPlayerStateBoxSettings.cs


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

9
Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs


NetState.NetworkLifeState.OnValueChanged += OnLifeStateChanged;
// store our character-creation choices in NetworkedVars so clients can figure out how to visualize us
var lobbyChoices = LobbyResults.GetInstance().GetCharSelectChoiceForClient(OwnerClientId);
NetState.CharacterClass.Value = lobbyChoices.Class;
NetState.IsMale.Value = lobbyChoices.IsMale;
if (!IsNPC)
{
var lobbyChoices = ServerBossRoomState.Instance.GetLobbyResultsForClient(OwnerClientId);
NetState.CharacterClass.Value = lobbyChoices.Class;
NetState.IsMale.Value = lobbyChoices.IsMale;
}
}
}

38
Assets/BossRoom/Scripts/Server/Game/LobbyResults.cs


namespace BossRoom.Server
{
/// <summary>
/// Records the results of the lobby screen: all the players' choices.
/// This is used when setting up the in-game characters.
/// (It's a singleton so that it persists beyond the char-select scene.)
/// Simple data-storage of the choices made in the lobby screen for all players
/// in the lobby. This object is passed from the lobby scene to the gameplay
/// scene, so that the game knows how to set up the players' characters.
class LobbyResults
public class LobbyResults
private static LobbyResults s_Instance;
public static LobbyResults GetInstance()
{
if (s_Instance == null)
s_Instance = new LobbyResults();
return s_Instance;
}
public struct CharSelectChoice
{
public CharacterTypeEnum Class;

this.IsMale = IsMale;
}
}
private Dictionary<ulong, CharSelectChoice> m_Choices = new Dictionary<ulong, CharSelectChoice>();
public CharSelectChoice GetCharSelectChoiceForClient(ulong clientId)
{
CharSelectChoice returnValue;
if (!m_Choices.TryGetValue(clientId, out returnValue))
{
// We don't know about this client ID! That probably means they joined the game late!
// We don't yet handle this scenario (e.g. showing them a "wait for next game" screen, maybe?),
// so for now we just let them join. We'll give them some generic char-gen choices.
returnValue = new CharSelectChoice(CharacterTypeEnum.TANK, true);
m_Choices.Add(clientId, returnValue);
}
return returnValue;
}
public void SetCharSelectChoiceForClient(ulong clientId, CharSelectChoice choices)
{
m_Choices[ clientId ] = choices;
}
public readonly Dictionary<ulong, CharSelectChoice> Choices = new Dictionary<ulong, CharSelectChoice>();
}
}

44
Assets/BossRoom/Scripts/Server/Game/State/ServerBossRoomState.cs


public override GameState ActiveState { get { return GameState.BOSSROOM; } }
/// <summary>
/// Reference to the scene's state object so that newly-spawned players can access state
/// </summary>
public static ServerBossRoomState Instance { get; private set; }
private LobbyResults m_LobbyResults;
public LobbyResults.CharSelectChoice GetLobbyResultsForClient(ulong clientId)
{
LobbyResults.CharSelectChoice returnValue;
if (!m_LobbyResults.Choices.TryGetValue(clientId, out returnValue))
{
// We don't know about this client ID! That probably means they joined the game late, after the lobby was closed.
// We don't yet handle this scenario well (e.g. showing them a "wait for next game" screen, maybe?),
// so for now we just let them join. We'll pretend that they made them some generic character choices.
returnValue = new LobbyResults.CharSelectChoice(CharacterTypeEnum.TANK, true);
m_LobbyResults.Choices[ clientId ] = returnValue;
}
return returnValue;
}
private void Awake()
{
Instance = this;
}
protected override void OnDestroy()
{
base.OnDestroy();
if (Instance == this)
Instance = null;
}
public override void NetworkStart()
{

}
else
{
// retrieve the lobby state info so that the players we're about to spawn can query it
System.Object o = GameStateRelay.GetRelayObject();
if (o != null && o.GetType() != typeof(LobbyResults))
throw new System.Exception("No LobbyResults found!");
m_LobbyResults = (LobbyResults)o;
// the client is officially allowed to be here.
// the client is officially allowed to be here. (And they are joining the game post-lobby...
// should we do something special here?)
// if any other players are already connected to us (i.e. they connected while we were
// in the login screen), give them player characters
// Now create player characters for all the players
foreach (var connection in NetworkingManager.Singleton.ConnectedClientsList)
{
SpawnPlayer(connection.ClientId);

66
Assets/BossRoom/Scripts/Server/Game/State/ServerCharSelectState.cs


using MLAPI;
using BossRoom;
using MLAPI;
using MLAPI.SceneManagement;
namespace BossRoom.Server
{

m_CharSlotClientIDs = new List<ulong>();
}
private void OnClientChangedSlot(ulong clientId, CharacterTypeEnum newClass, bool newIsMale, CharSelectData.SlotState newState)
private void OnClientChangedSlot(ulong clientId, CharSelectData.CharSelectSlot newSlot)
if (CharSelectData.IsLobbyLocked())
if (CharSelectData.IsLobbyLocked.Value)
return;
return;
}
int idx = FindClientIdx(clientId);

CharSelectData.SetCharSelectSlot(idx, new CharSelectData.CharSelectSlot(newClass, newIsMale, newState));
if (newState == CharSelectData.SlotState.LOCKEDIN)
CharSelectData.CharacterSlots[ idx ] = newSlot;
if (newSlot.State == CharSelectData.SlotState.LOCKEDIN)
{
// it's possible that this is the last person we were waiting for. See if we're fully locked in!
LockLobbyIfReady();

for (int x = 0; x < m_CharSlotClientIDs.Count; ++x)
{
if (MLAPI.NetworkingManager.Singleton.ConnectedClients.ContainsKey(m_CharSlotClientIDs[ x ]) &&
CharSelectData.GetCharSelectSlot(x).State != CharSelectData.SlotState.LOCKEDIN)
CharSelectData.CharacterSlots[ x ].State != CharSelectData.SlotState.LOCKEDIN)
{
return; // this is a real player, and they are not ready to start, so we're done
}

CharSelectData.SetLobbyLocked();
CharSelectData.IsLobbyLocked.Value = true;
for (int x = 0; x < m_CharSlotClientIDs.Count; ++x)
LobbyResults lobbyResults = new LobbyResults();
for (int i = 0; i < m_CharSlotClientIDs.Count; ++i)
if (MLAPI.NetworkingManager.Singleton.ConnectedClients.ContainsKey(m_CharSlotClientIDs[ x ]))
if (MLAPI.NetworkingManager.Singleton.ConnectedClients.ContainsKey(m_CharSlotClientIDs[ i ]))
var charSelectChoices = CharSelectData.GetCharSelectSlot(x);
LobbyResults.GetInstance().SetCharSelectChoiceForClient(m_CharSlotClientIDs[ x ],
new LobbyResults.CharSelectChoice(charSelectChoices.Class, charSelectChoices.IsMale));
var charSelectChoices = CharSelectData.CharacterSlots[ i ];
lobbyResults.Choices[ m_CharSlotClientIDs[ i ] ] = new LobbyResults.CharSelectChoice(charSelectChoices.Class, charSelectChoices.IsMale);
GameStateRelay.SetRelayObject(lobbyResults);
// Delay a few seconds to give the UI time to react, then switch scenes
StartCoroutine(CoroEndLobby());

MLAPI.SceneManagement.NetworkSceneManager.SwitchScene("DungeonTest");
}
private void OnDestroy()
protected override void OnDestroy()
base.OnDestroy();
if (NetworkingManager.Singleton)
{
NetworkingManager.Singleton.OnClientConnectedCallback -= OnClientConnected;

private int FindClientIdx(ulong clientId)
{
for (int x = 0; x < m_CharSlotClientIDs.Count; ++x)
for (int i = 0; i < m_CharSlotClientIDs.Count; ++i)
if (m_CharSlotClientIDs[ x ] == clientId)
return x;
if (m_CharSlotClientIDs[ i ] == clientId)
return i;
}
return -1;
}

// Note that we may find this new clientId is already in our list, if
// "reuse client IDs" is enabled... and it is! This means that somebody
// else was in the lobby, but then quit, and a new client got their ID.
for (int x = 0; x < m_CharSlotClientIDs.Count; ++x)
for (int i = 0; i < m_CharSlotClientIDs.Count; ++i)
if (m_CharSlotClientIDs[ x ] == clientId ||
!MLAPI.NetworkingManager.Singleton.ConnectedClients.ContainsKey(m_CharSlotClientIDs[x]))
if (m_CharSlotClientIDs[ i ] == clientId ||
!MLAPI.NetworkingManager.Singleton.ConnectedClients.ContainsKey(m_CharSlotClientIDs[ i ]))
m_CharSlotClientIDs[ x ] = clientId;
newClientIdx = x;
m_CharSlotClientIDs[ i ] = clientId;
newClientIdx = i;
if (newClientIdx == -1)
if (newClientIdx == -1 && m_CharSlotClientIDs.Count < CharSelectData.k_MaxLobbyPlayers)
// all existing slots are in use; get a new one
// all existing slots are in use; get a new one, if there's room...
CharSelectData.InvokeClientRpcOnClient(CharSelectData.RpcAssignLobbyIndex, clientId, newClientIdx, "MLAPI_INTERNAL");
if (newClientIdx == -1)
{
// there was no room!
CharSelectData.InvokeClientRpcOnClient(CharSelectData.RpcFatalLobbyError, clientId, CharSelectData.FatalLobbyError.LOBBY_FULL, "MLAPI_INTERNAL");
}
else
{
CharSelectData.InvokeClientRpcOnClient(CharSelectData.RpcAssignLobbyIndex, clientId, newClientIdx, "MLAPI_INTERNAL");
}
}
private void OnClientDisconnectCallback(ulong clientId)

if (idx != -1)
{
CharSelectData.SetCharSelectSlot(idx, new CharSelectData.CharSelectSlot(CharacterTypeEnum.TANK, true, CharSelectData.SlotState.INACTIVE));
{
CharSelectData.CharacterSlots[ idx ] = new CharSelectData.CharSelectSlot(CharSelectData.SlotState.INACTIVE);
}
}
}

177
Assets/BossRoom/Scripts/Shared/Game/State/CharSelectData.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAPI.Messaging;
using MLAPI.Messaging;
using System.Collections.Generic;
using System.IO;
namespace BossRoom
{

public class CharSelectData : NetworkedBehaviour
{
#region public interface
[Serializable]
public enum SlotState
{
INACTIVE,

public const int MAX_LOBBY_PLAYERS = 8;
public enum FatalLobbyError
{
LOBBY_FULL,
}
public const int k_MaxLobbyPlayers = 8;
[Serializable]
public struct CharSelectSlot
public struct CharSelectSlot : MLAPI.Serialization.IBitWritable
{
public CharacterTypeEnum Class;
public bool IsMale;

this.IsMale = IsMale;
this.Class = Class;
}
public CharSelectSlot(string encoded)
public CharSelectSlot(SlotState State)
{
this.State = State;
this.IsMale = true;
this.Class = CharacterTypeEnum.TANK;
}
public void Read(Stream stream)
string[] parts = encoded.Split(',');
State = (SlotState)Enum.Parse(typeof(SlotState), parts[ 0 ]);
IsMale = parts[ 1 ] == "1";
Class = (CharacterTypeEnum)Enum.Parse(typeof(CharacterTypeEnum), parts[ 2 ]);
using (var reader = MLAPI.Serialization.Pooled.PooledBitReader.Get(stream))
{
Class = (CharacterTypeEnum)reader.ReadInt16();
IsMale = reader.ReadBool();
State = (SlotState)reader.ReadByte();
}
public string EncodeForMLAPI()
public void Write(Stream stream)
return State + "," + (IsMale ? "1" : "0") + "," + Class;
using (var writer = MLAPI.Serialization.Pooled.PooledBitWriter.Get(stream))
{
writer.WriteInt16((short)Class);
writer.WriteBool(IsMale);
writer.WriteByte((byte)State);
}
/// Retrieves info about one of the slots in the character-select UI
/// Current state of each of the seats in the lobby.
public CharSelectSlot GetCharSelectSlot(int idx)
{
if (m_CharSlots.Count <= idx)
return new CharSelectSlot(CharacterTypeEnum.TANK, true, SlotState.INACTIVE);
return new CharSelectSlot(m_CharSlots[ idx ]);
}
public MLAPI.NetworkedVar.Collections.NetworkedList<CharSelectSlot> CharacterSlots { get; private set; }
/// Set the networked-var info for a character slot. (Only the server can call this!)
/// When this becomes true, the lobby is closed and in process of terminating (switching to gameplay).
public void SetCharSelectSlot(int idx, CharSelectSlot slot)
{
while (m_CharSlots.Count <= idx)
m_CharSlots.Add(new CharSelectSlot(CharacterTypeEnum.TANK, true, SlotState.INACTIVE).EncodeForMLAPI());
m_CharSlots[ idx ] = slot.EncodeForMLAPI();
}
public MLAPI.NetworkedVar.NetworkedVarBool IsLobbyLocked { get; } = new MLAPI.NetworkedVar.NetworkedVarBool(false);
/// Notify clients that the lobby is all done and we'll be transitioning to gameplay soon.
/// (Only the server can call this!)
/// Client notification when the server has assigned this client a player Index (from 0 to 7);
/// UI uses this tell whether we are "P1", "P2", etc. in the char-select UI
public void SetLobbyLocked()
{
m_Ready.Value = true;
}
public event Action<int> OnAssignedLobbyIndex;
/// Indicates whether the lobby is closed to further changes.
/// RPC to tell a client which slot in the char-gen screen they will be using.
public bool IsLobbyLocked()
/// <param name="idx">Index on the UI screen, starting at 0 for the first slot</param>
[ClientRPC]
public void RpcAssignLobbyIndex(int idx)
return m_Ready.Value;
OnAssignedLobbyIndex?.Invoke(idx);
/// Client code can register for this notification to be informed of changes to chargen slots
/// Client notification when the server has told us that we cannot participate.
/// (Client should display an appropriate error and terminate)
public event Action<int, CharSelectSlot> OnCharSelectSlotChanged;
public event Action<FatalLobbyError> OnFatalLobbyError;
/// Client code can register for this notification to know when all lobby participants are ready.
/// The scene will be switched automatically a few seconds later, so this is just for UI visualization
/// </summary>
public event Action OnLobbyLockedIn;
/// <summary>
/// Client notification when the server has assigned this client a player Index (from 0 to 7);
/// UI uses this tell whether we are "P1", "P2", etc. in the char-select UI
/// </summary>
public event Action<int> OnAssignedLobbyIndex;
/// <summary>
/// Server notification when a new client is available
/// RPC to tell a client that they cannot participate in the game due to a fatal error.
public event Action<ulong> OnRequestLobbyIndex;
/// <summary>
/// Server notification when a client changes their char-gen state
/// </summary>
public event Action<ulong, CharacterTypeEnum, bool, SlotState> OnClientChangedSlot;
#endregion
#region Implementation details
// Because MLAPI's NetworkedList cannot serialize arbitrary serializable
// structures, we encode all the relevant info about a lobby-slot into a single
// string, then reformat it into a struct whenever we want to work with it.
// We hide this implementation detail from callers.
private MLAPI.NetworkedVar.Collections.NetworkedList<string> m_CharSlots;
private MLAPI.NetworkedVar.NetworkedVarBool m_Ready;
private void Awake()
{
m_CharSlots = new MLAPI.NetworkedVar.Collections.NetworkedList<string>();
m_Ready = new MLAPI.NetworkedVar.NetworkedVarBool(false);
}
public override void NetworkStart()
{
base.NetworkStart();
m_CharSlots.OnListChanged += OnCharSlotsListChanged;
m_Ready.OnValueChanged += OnReadyValueChanged;
}
private void OnCharSlotsListChanged(MLAPI.NetworkedVar.Collections.NetworkedListEvent<string> changeEvent)
/// <param name="movementTarget">Index on the UI screen, starting at 0 for the first slot</param>
[ClientRPC]
public void RpcFatalLobbyError(FatalLobbyError error)
OnCharSelectSlotChanged?.Invoke(changeEvent.index, new CharSelectSlot(changeEvent.value));
}
private void OnReadyValueChanged(bool oldValue, bool newValue)
{
if (newValue)
{
OnLobbyLockedIn?.Invoke();
}
OnFatalLobbyError?.Invoke(error);
/// RPC to notify the server that a new client is in the lobby and is ready for a seat in the lobby.
/// Server notification when a client requests changes to their char-gen state
[ServerRPC(RequireOwnership = false)]
public void RpcRequestLobbyIndex(ulong clientId)
{
OnRequestLobbyIndex?.Invoke(clientId);
}
public event Action<ulong, CharSelectSlot> OnClientChangedSlot;
public void RpcChangeSlot(ulong clientId, CharacterTypeEnum newClass, bool isMale, SlotState newState)
public void RpcChangeSlot(ulong clientId, CharSelectSlot newSlot)
OnClientChangedSlot?.Invoke(clientId, newClass, isMale, newState);
OnClientChangedSlot?.Invoke(clientId, newSlot);
/// <summary>
/// RPC to tell a client which slot in the char-gen screen they will be using.
/// </summary>
/// <param name="movementTarget">Index on the UI screen, starting at 0 for the first slot</param>
[ClientRPC]
public void RpcAssignLobbyIndex(int idx)
private void Awake()
OnAssignedLobbyIndex?.Invoke(idx);
List<CharSelectSlot> initialList = new List<CharSelectSlot>();
for (int i = 0; i < k_MaxLobbyPlayers; ++i)
{
initialList.Add(new CharSelectSlot(SlotState.INACTIVE));
}
// initialize the char-slots list with all the slots it will ever have
CharacterSlots = new MLAPI.NetworkedVar.Collections.NetworkedList<CharSelectSlot>(initialList);
#endregion

4
Assets/BossRoom/Scripts/Shared/Game/State/GameStateBehaviour.cs


private static GameObject s_activeStateGO;
// Start is called before the first frame update
void Start()
protected virtual void Start()
{
if( s_activeStateGO != null )
{

if( Persists ) { Object.DontDestroyOnLoad(this.gameObject); }
}
private void OnDestroy()
protected virtual void OnDestroy()
{
if( !Persists )
{

39
Assets/BossRoom/Scripts/Server/Game/State/GameStateRelay.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BossRoom.Server
{
/// <summary>
/// Stores game-state information when switching between scenes. The ending
/// scene calls SetRelayObject() on an arbitrary information object, and the
/// new scene calls GetRelayObject() to retrieve it.
/// </summary>
public class GameStateRelay
{
private static System.Object k_RelayObject = null;
/// <summary>
/// Retrieves the last-set relay object and clears its reference to it.
/// (Calling this again without setting a new relay object will return null!)
/// </summary>
public static System.Object GetRelayObject()
{
System.Object ret = k_RelayObject;
k_RelayObject = null;
return ret;
}
/// <summary>
/// Stores a relay object to be retrieved in a later scene.
/// </summary>
/// <param name="o"></param>
public static void SetRelayObject(System.Object o)
{
k_RelayObject = o;
}
}
}

11
Assets/BossRoom/Scripts/Server/Game/State/GameStateRelay.cs.meta


fileFormatVersion: 2
guid: 52fd4f61df4653d44ba8820e0b51104e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存