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

140 行
5.5 KiB

using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
namespace LobbyRelaySample.ngo
{
/// <summary>
/// This cursor object will follow the owning player's mouse cursor and be visible to the other players.
/// The host will use this object's movement for detecting collision with symbol objects.
/// </summary>
[RequireComponent(typeof(Collider))]
public class PlayerCursor : NetworkBehaviour
{
[SerializeField]
SpriteRenderer m_renderer = default;
[SerializeField]
ParticleSystem m_onClickParticles = default;
[SerializeField]
TMPro.TMP_Text m_nameOutput = default;
Camera m_mainCamera;
NetworkVariable<Vector3> m_position = new NetworkVariable<Vector3>(Vector3.zero);
ulong m_localId;
// If the local player cursor spawns before this cursor's owner, the owner's data won't be available yet. This is used to retrieve the data later.
Action<ulong, Action<PlayerData>> m_retrieveName;
// The host is responsible for determining if a player has successfully selected a symbol object, since collisions should be handled serverside.
List<SymbolObject> m_currentlyCollidingSymbols;
/// <summary>
/// This cursor is spawned in dynamically but needs references to some scene objects. Pushing full object references over RPC calls
/// is an option if we create custom data for each and ensure they're all spawned on the host correctly, but it's simpler to do
/// some one-time retrieval here instead.
/// This also sets up the visuals to make remote player cursors appear distinct from the local player's cursor.
/// </summary>
public override void OnNetworkSpawn()
{
m_retrieveName = NetworkedDataStore.Instance.GetPlayerData;
m_mainCamera = GameObject.Find("InGameCamera").GetComponent<Camera>();
InGameRunner.Instance.onGameBeginning += OnGameBegan;
if (IsHost)
m_currentlyCollidingSymbols = new List<SymbolObject>();
m_localId = NetworkManager.Singleton.LocalClientId;
// Other players' cursors should be less prominent than the local player's cursor.
if (OwnerClientId != m_localId)
{
m_renderer.transform.localScale *= 0.75f;
m_renderer.color = new Color(1, 1, 1, 0.5f);
var trails = m_onClickParticles.trails;
trails.colorOverLifetime = new ParticleSystem.MinMaxGradient(Color.grey);
}
else
{
m_renderer.enabled =
false; // The local player should see their cursor instead of the simulated cursor object, since the object will appear laggy.
}
}
[ClientRpc]
private void SetName_ClientRpc(PlayerData data)
{
if (!IsOwner)
m_nameOutput.text = data.name;
}
// It'd be better to have a separate input handler, but we don't need the mouse input anywhere else, so keep it simple.
private bool IsSelectInputHit()
{
return Input.GetMouseButtonDown(0);
}
public void Update()
{
transform.position = m_position.Value;
if (m_mainCamera == null || !IsOwner)
return;
Vector3 targetPos = (Vector2)m_mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,
Input.mousePosition.y, -m_mainCamera.transform.position.z));
SetPosition_ServerRpc(targetPos); // Client can't set a network variable value.
if (IsSelectInputHit())
SendInput_ServerRpc(m_localId);
}
[ServerRpc] // Leave (RequireOwnership = true) for these so that only the player whose cursor this is can make updates.
private void SetPosition_ServerRpc(Vector3 position)
{
m_position.Value = position;
}
[ServerRpc]
private void SendInput_ServerRpc(ulong id)
{
if (m_currentlyCollidingSymbols.Count > 0)
{
SymbolObject symbol = m_currentlyCollidingSymbols[0];
InGameRunner.Instance.OnPlayerInput(id, symbol);
}
OnInputVisuals_ClientRpc();
}
[ClientRpc]
private void OnInputVisuals_ClientRpc()
{
m_onClickParticles.Stop(false, ParticleSystemStopBehavior.StopEmitting);
m_onClickParticles.Play();
}
public void OnTriggerEnter(Collider other)
{
if (!IsHost)
return;
SymbolObject symbol = other.GetComponent<SymbolObject>();
if (symbol == null)
return;
if (!m_currentlyCollidingSymbols.Contains(symbol))
m_currentlyCollidingSymbols.Add(symbol);
}
public void OnTriggerExit(Collider other)
{
if (!IsHost)
return;
SymbolObject symbol = other.GetComponent<SymbolObject>();
if (symbol == null)
return;
if (m_currentlyCollidingSymbols.Contains(symbol))
m_currentlyCollidingSymbols.Remove(symbol);
}
public void OnGameBegan()
{
m_retrieveName.Invoke(OwnerClientId, SetName_ClientRpc);
InGameRunner.Instance.onGameBeginning -= OnGameBegan;
}
}
}