浏览代码

Shifting the target sequence tracking to the host. Adding in scores and UI for them. These both warranted shifting some of the input handling to stay on the host, so that anything that involves validating input and awarding points is serverside.

/main/staging/ngo_minigame_structure
nathaniel.buck@unity3d.com 3 年前
当前提交
e429e5b7
共有 8 个文件被更改,包括 403 次插入40 次删除
  1. 287
      Assets/Prefabs/InGame/InGameLogic.prefab
  2. 12
      Assets/Scripts/Game/InGame/IInGameInputHandler.cs
  3. 15
      Assets/Scripts/Game/InGame/InGameRunner.cs
  4. 2
      Assets/Scripts/Game/InGame/PlayerCursor.cs
  5. 46
      Assets/Scripts/Game/InGame/SequenceSelector.cs
  6. 18
      Assets/Scripts/Game/InGame/SymbolObject.cs
  7. 52
      Assets/Scripts/Game/InGame/Scorer.cs
  8. 11
      Assets/Scripts/Game/InGame/Scorer.cs.meta

287
Assets/Prefabs/InGame/InGameLogic.prefab


- component: {fileID: 8928417086886337167}
- component: {fileID: 6673480979101889538}
- component: {fileID: 6829526275642584874}
- component: {fileID: 2250928641321586401}
m_Layer: 0
m_Name: InGameRunner
m_TagString: Untagged

m_symbolContainerPrefab: {fileID: 3984715711634906321, guid: f42ed38d10b57ec48870f76a7a63389e, type: 3}
m_symbolObjectPrefab: {fileID: 1734492152380024498, guid: e371ca3112f9e244ab574b472387b64b, type: 3}
m_sequenceSelector: {fileID: 6829526275642584874}
m_scorer: {fileID: 2250928641321586401}
--- !u!114 &6829526275642584874
MonoBehaviour:
m_ObjectHideFlags: 0

- {fileID: 5510123497471373079}
- {fileID: 7719423909162636986}
- {fileID: 1757541467328778976}
--- !u!114 &2250928641321586401
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1306704497370578788}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d90dac0e0ae417b4ea7f60baf966db37, type: 3}
m_Name:
m_EditorClassIdentifier:
m_scoreOutputText: {fileID: 4345563381811920084}
--- !u!1 &4526823870396177388
GameObject:
m_ObjectHideFlags: 0

y: 0
width: 1
height: 1
--- !u!1 &7244084094049670647
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1522243555523151353}
- component: {fileID: 8176548497988931772}
- component: {fileID: 3644590409001504268}
m_Layer: 5
m_Name: ScoreLabel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1522243555523151353
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7244084094049670647}
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: 8063659512749448051}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.6, y: 0}
m_AnchorMax: {x: 0.6, y: 0}
m_AnchoredPosition: {x: 0, y: 5}
m_SizeDelta: {x: 250, y: 50}
m_Pivot: {x: 0, y: 0}
--- !u!222 &8176548497988931772
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7244084094049670647}
m_CullTransparentMesh: 1
--- !u!114 &3644590409001504268
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7244084094049670647}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, 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_text: 'Score:'
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 008daa1b26adf2b4ba0fbd11b7f79b6e, type: 2}
m_sharedMaterial: {fileID: 7878303141229832600, guid: 008daa1b26adf2b4ba0fbd11b7f79b6e, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 48
m_fontSizeBase: 48
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &7460461347722702836
GameObject:
m_ObjectHideFlags: 0

m_LocalScale: {x: 0, y: 0, z: 0}
m_Children:
- {fileID: 6730446115375840747}
- {fileID: 1522243555523151353}
- {fileID: 6399302101402788512}
m_Father: {fileID: 485451675458297819}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 0
m_AdditionalShaderChannelsFlag: 25
m_SortingLayerID: 0
m_SortingOrder: 0
m_TargetDisplay: 0

ConnectionData:
Address: 127.0.0.1
Port: 7777
--- !u!1 &9108024644068474088
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6399302101402788512}
- component: {fileID: 8903220003800967113}
- component: {fileID: 4345563381811920084}
m_Layer: 5
m_Name: ScoreText
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6399302101402788512
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9108024644068474088}
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: 8063659512749448051}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.6, y: 0}
m_AnchorMax: {x: 0.6, y: 0}
m_AnchoredPosition: {x: 0, y: 5}
m_SizeDelta: {x: 250, y: 50}
m_Pivot: {x: 0, y: 0}
--- !u!222 &8903220003800967113
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9108024644068474088}
m_CullTransparentMesh: 1
--- !u!114 &4345563381811920084
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9108024644068474088}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, 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_text: 00
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 008daa1b26adf2b4ba0fbd11b7f79b6e, type: 2}
m_sharedMaterial: {fileID: 7878303141229832600, guid: 008daa1b26adf2b4ba0fbd11b7f79b6e, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 54
m_fontSizeBase: 54
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 4
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}

12
Assets/Scripts/Game/InGame/IInGameInputHandler.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LobbyRelaySample.inGame
namespace LobbyRelaySample.inGame
void OnPlayerInput(SymbolObject selectedSymbol);
void OnPlayerInput(ulong id, SymbolObject selectedSymbol);
public void OnPlayerInput(SymbolObject selectedSymbol) { }
public void OnPlayerInput(ulong id, SymbolObject selectedSymbol) { }
public void OnReProvided(IInGameInputHandler previousProvider) { }
}
}

15
Assets/Scripts/Game/InGame/InGameRunner.cs


private Transform m_symbolContainerInstance;
[SerializeField] private NetworkObject m_symbolObjectPrefab = default;
[SerializeField] private SequenceSelector m_sequenceSelector = default;
[SerializeField] private Scorer m_scorer = default;
private ulong m_localId; // This is not necessarily the same as the OwnerClientId, since all clients will see all spawned objects regardless of ownership.

}
}
public void OnPlayerInput(SymbolObject selectedSymbol)
/// <summary>
/// Called while on the host to determine if incoming input has scored or not.
/// </summary>
public void OnPlayerInput(ulong id, SymbolObject selectedSymbol)
if (m_sequenceSelector.ConfirmSymbolCorrect(selectedSymbol.symbolIndex.Value))
selectedSymbol.OnSelectConfirmed();
if (m_sequenceSelector.ConfirmSymbolCorrect(id, selectedSymbol.symbolIndex.Value))
{ selectedSymbol.OnSelectConfirmed_ClientRpc();
selectedSymbol.Destroy_ServerRpc();
m_scorer.ScoreSuccess(id);
}
else
m_scorer.ScoreFailure(id);
}
public void OnReProvided(IInGameInputHandler previousProvider) { /*No-op*/ }

2
Assets/Scripts/Game/InGame/PlayerCursor.cs


{
SymbolObject symbol = m_currentlyCollidingSymbols[0];
m_currentlyCollidingSymbols.RemoveAt(0);
symbol.OnSelect_ClientRpc(id);
Locator.Get.InGameInputHandler.OnPlayerInput(id, symbol);
}
}

46
Assets/Scripts/Game/InGame/SequenceSelector.cs


[SerializeField] private SymbolData m_symbolData = default;
[SerializeField] private RawImage[] m_targetSequenceOutput = default;
public const int k_symbolCount = 100;
private bool m_hasReceivedTargetSequence = false; // TODO: Perhaps split up members by client vs. host?
private ulong m_localId;
private int m_targetSequenceCurrentIndex = -1;
private Dictionary<ulong, int> m_targetSequenceIndexPerPlayer = new Dictionary<ulong, int>(); // Also owned by the host, indexed by client ID.
public void Awake()
{

}
}
}
m_localId = NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_localId);
}
[ServerRpc(RequireOwnership = false)]
private void AddClient_ServerRpc(ulong id)
{
m_targetSequenceIndexPerPlayer.Add(id, 0);
}
// Very simple random selection. Duplicates are allowed.

public void Update()
{
// We can't guarantee timing with the host's selection of the target sequence, so retrieve it once it's available.
if (m_targetSequenceCurrentIndex < 0 && m_targetSequence.Count > 0)
if (!m_hasReceivedTargetSequence && m_targetSequence.Count > 0)
m_targetSequenceCurrentIndex = 0;
ScaleTargetUi();
m_hasReceivedTargetSequence = true;
ScaleTargetUi(m_localId, 0);
}
}

public bool ConfirmSymbolCorrect(int symbolIndex)
public bool ConfirmSymbolCorrect(ulong id, int symbolIndex)
if (symbolIndex != m_targetSequence[m_targetSequenceCurrentIndex])
int index = m_targetSequenceIndexPerPlayer[id];
if (symbolIndex != m_targetSequence[index])
if (++m_targetSequenceCurrentIndex >= m_targetSequence.Count)
m_targetSequenceCurrentIndex = 0;
ScaleTargetUi();
if (++index >= m_targetSequence.Count)
index = 0;
m_targetSequenceIndexPerPlayer[id] = index;
ScaleTargetUi_ClientRpc(id, index);
private void ScaleTargetUi()
[ClientRpc]
private void ScaleTargetUi_ClientRpc(ulong id, int sequenceIndex)
{
ScaleTargetUi(id, sequenceIndex);
}
private void ScaleTargetUi(ulong id, int sequenceIndex)
for (int i = 0; i < m_targetSequenceOutput.Length; i++)
m_targetSequenceOutput[i].transform.localScale = Vector3.one * (m_targetSequenceCurrentIndex == i ? 1 : 0.7f);
if (NetworkManager.Singleton.LocalClientId == id)
for (int i = 0; i < m_targetSequenceOutput.Length; i++)
m_targetSequenceOutput[i].transform.localScale = Vector3.one * (sequenceIndex == i ? 1 : 0.7f);
}
public int GetNextSymbol(int symbolObjectIndex)

18
Assets/Scripts/Game/InGame/SymbolObject.cs


}
/// <summary>
/// The host has detected a player clicking on this symbol, but it also needs to check if this symbol is next in that player's target sequence.
/// The host has confirmed this symbol as a valid selection (this player's cursor collides with it and it's also next in their target sequence), so handle any visual feedback.
public void OnSelect_ClientRpc(ulong id)
{
if (m_localId == id)
Locator.Get.InGameInputHandler.OnPlayerInput(this);
}
/// <summary>
/// The host has confirmed this symbol as a valid selection, so handle any visual feedback.
/// </summary>
public void OnSelectConfirmed()
public void OnSelectConfirmed_ClientRpc()
Destroy_ServerRpc();
// TODO: Visual effects here.
[ServerRpc(RequireOwnership = false)]
private void Destroy_ServerRpc()
[ServerRpc]
public void Destroy_ServerRpc()
{
// Actually destroying the symbol objects can cause garbage collection and other delays that might lead to desyncs.
// Instead, just deactivate the object, and it will be cleaned up once the NetworkManager is destroyed.

52
Assets/Scripts/Game/InGame/Scorer.cs


using System.Collections.Generic;
using TMPro;
using Unity.Netcode;
using UnityEngine;
namespace LobbyRelaySample.inGame
{
// TODO: I'm using host and server interchangeably...
/// <summary>
/// Used by the host to actually track scores for all players, and by each client to monitor for updates to their own score.
/// </summary>
public class Scorer : NetworkBehaviour
{
// TODO: Most of the ints could be bytes?
private Dictionary<ulong, int> m_scoresByClientId = new Dictionary<ulong, int>();
private ulong m_localId;
[SerializeField] private TMP_Text m_scoreOutputText = default;
public override void OnNetworkSpawn()
{
m_localId = NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_localId);
}
[ServerRpc(RequireOwnership = false)]
private void AddClient_ServerRpc(ulong id)
{
if (!m_scoresByClientId.ContainsKey(id))
m_scoresByClientId.Add(id, 0);
}
public void ScoreSuccess(ulong id)
{
m_scoresByClientId[id] += 5;
UpdateScoreOutput_ClientRpc(id, m_scoresByClientId[id]);
}
public void ScoreFailure(ulong id)
{
m_scoresByClientId[id] -= 1;
UpdateScoreOutput_ClientRpc(id, m_scoresByClientId[id]);
}
[ClientRpc]
private void UpdateScoreOutput_ClientRpc(ulong id, int score)
{
if (m_localId == id)
m_scoreOutputText.text = score.ToString("00");
}
}
}

11
Assets/Scripts/Game/InGame/Scorer.cs.meta


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