aha
This commit is contained in:
338
Assets/Mirror/Examples/TopDownShooter/Scripts/PlayerTopDown.cs
Normal file
338
Assets/Mirror/Examples/TopDownShooter/Scripts/PlayerTopDown.cs
Normal file
@ -0,0 +1,338 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Mirror;
|
||||
|
||||
namespace Mirror.Examples.TopDownShooter
|
||||
{
|
||||
public class PlayerTopDown : NetworkBehaviour
|
||||
{
|
||||
public readonly static List<PlayerTopDown> playerList = new List<PlayerTopDown>();
|
||||
|
||||
private Camera mainCamera;
|
||||
private CameraTopDown cameraTopDown;
|
||||
private CanvasTopDown canvasTopDown;
|
||||
|
||||
public float moveSpeed = 5f;
|
||||
public CharacterController characterController;
|
||||
|
||||
public GameObject leftFoot, rightFoot;
|
||||
private Vector3 previousPosition;
|
||||
private Quaternion previousRotation;
|
||||
|
||||
[SyncVar(hook = nameof(OnFlashLightChanged))]
|
||||
public bool flashLightStatus = true;
|
||||
public Light flashLight;
|
||||
|
||||
[SyncVar(hook = nameof(OnKillsChanged))]
|
||||
public int kills = 0;
|
||||
|
||||
[SyncVar(hook = nameof(OnPlayerStatusChanged))]
|
||||
public int playerStatus = 0;
|
||||
public GameObject[] objectsToHideOnDeath;
|
||||
|
||||
public float shootDistance = 100f;
|
||||
public LayerMask hitLayers;
|
||||
public GameObject muzzleFlash;
|
||||
public AudioSource soundGunShot, soundDeath, soundFlashLight, soundLeftFoot, soundRightFoot;
|
||||
|
||||
#if !UNITY_SERVER
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
// Grab and setup camera for local player only
|
||||
mainCamera = Camera.main;
|
||||
cameraTopDown = mainCamera.GetComponent<CameraTopDown>();
|
||||
cameraTopDown.playerTransform = this.transform;
|
||||
cameraTopDown.offset.y = 20.0f; // dramatic zoom out once players setup
|
||||
canvasTopDown.playerTopDown = this;
|
||||
|
||||
// We want 3D audio effects to be around the player, not the camera 50 meters in the air
|
||||
// Otherwise it looks weird in-game, trust me
|
||||
mainCamera.GetComponent<AudioListener>().enabled = false;
|
||||
this.gameObject.AddComponent<AudioListener>();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Allow all players to run this, they may need it for reference
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
canvasTopDown = GameObject.FindAnyObjectByType<CanvasTopDown>();
|
||||
#else
|
||||
canvasTopDown = GameObject.FindObjectOfType<CanvasTopDown>();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// If only server needs access to a player list, place the Add and Remove in public override void OnStartServer/OnStopServer
|
||||
playerList.Add(this);
|
||||
print("Player joined, total players: " + playerList.Count);
|
||||
|
||||
#if !UNITY_SERVER
|
||||
if (isClient)
|
||||
{
|
||||
InvokeRepeating("AnimatePlayer", 0.2f, 0.2f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
playerList.Remove(this);
|
||||
print("Player removed, total players: " + playerList.Count);
|
||||
|
||||
if (mainCamera) { mainCamera.GetComponent<AudioListener>().enabled = true; }
|
||||
}
|
||||
|
||||
#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void Update()
|
||||
{
|
||||
if (!Application.isFocused) return;
|
||||
if (isOwned == false) { return; }
|
||||
if (playerStatus != 0) { return; } // make sure we are alive
|
||||
|
||||
// Handle movement
|
||||
float moveHorizontal = Input.GetAxis("Horizontal");
|
||||
float moveVertical = Input.GetAxis("Vertical");
|
||||
|
||||
Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical);
|
||||
if (movement.magnitude > 1f) movement.Normalize(); // Normalize to prevent faster diagonal movement
|
||||
characterController.Move(movement * moveSpeed * Time.deltaTime);
|
||||
|
||||
RotatePlayerToMouse();
|
||||
|
||||
if (Input.GetKeyUp(KeyCode.F))
|
||||
{
|
||||
// We could optionally call this locally too, to avoid minor delay in the command->sync var hook result
|
||||
CmdFlashLight();
|
||||
}
|
||||
|
||||
// We currently have no shoot limiter, ideally thats a feature you would need to add.
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
Shoot();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void RotatePlayerToMouse()
|
||||
{
|
||||
Plane playerPlane = new Plane(Vector3.up, transform.position);
|
||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
if (playerPlane.Raycast(ray, out float hitDist))
|
||||
{
|
||||
Vector3 targetPoint = ray.GetPoint(hitDist);
|
||||
Quaternion targetRotation = Quaternion.LookRotation(targetPoint - transform.position);
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, moveSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
[ClientCallback]
|
||||
void Shoot()
|
||||
{
|
||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
RaycastHit hit;
|
||||
|
||||
if (Physics.Raycast(ray, out hit, shootDistance, hitLayers))
|
||||
{
|
||||
//print("Hit: " + hit.collider.gameObject.name);
|
||||
|
||||
canvasTopDown.shotMarker.transform.position = hit.point;
|
||||
|
||||
if (hit.collider.gameObject.GetComponent<NetworkIdentity>() != null)
|
||||
{
|
||||
CmdShoot(hit.collider.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
CmdShoot(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//print("Missed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_SERVER
|
||||
IEnumerator GunShotEffect()
|
||||
{
|
||||
soundGunShot.Play();
|
||||
muzzleFlash.SetActive(true);
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
canvasTopDown.shotMarker.SetActive(true);
|
||||
}
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
muzzleFlash.SetActive(false);
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
canvasTopDown.shotMarker.SetActive(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[Command]
|
||||
public void CmdFlashLight()
|
||||
{
|
||||
flashLightStatus = !flashLightStatus;
|
||||
}
|
||||
|
||||
// our sync var hook, which sets flashlight status to the same on all clients for this player
|
||||
void OnFlashLightChanged(bool _Old, bool _New)
|
||||
{
|
||||
#if !UNITY_SERVER
|
||||
Debug.Log($"OnFlashLightChanged: {_New}");
|
||||
flashLight.enabled = _New;
|
||||
soundFlashLight.Play();
|
||||
#endif
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void CmdShoot(GameObject target)
|
||||
{
|
||||
RpcShoot();
|
||||
if (target)
|
||||
{
|
||||
// you should check for a tag, not name contains
|
||||
// this is a quick workaround to make sure the example works without custom tags that may not be in your project
|
||||
if (target.name.Contains("Enemy"))
|
||||
{
|
||||
target.GetComponent<EnemyTopDown>().Kill();
|
||||
}
|
||||
else if (CompareTag("Player") == true) // Player tag exists in unity by default, so we should be good to use it here
|
||||
{
|
||||
// Make sure they are alive/dont shoot themself
|
||||
if (target.GetComponent<PlayerTopDown>().playerStatus != 0 || target == this.gameObject) { return; }
|
||||
target.GetComponent<PlayerTopDown>().Kill();
|
||||
}
|
||||
kills += 1; // update user kills sync var
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void RpcShoot()
|
||||
{
|
||||
#if !UNITY_SERVER
|
||||
StartCoroutine(GunShotEffect());
|
||||
#endif
|
||||
}
|
||||
|
||||
// hook for sync var kills
|
||||
void OnKillsChanged(int _Old, int _New)
|
||||
{
|
||||
#if !UNITY_SERVER
|
||||
// all players get your latest kill data, however only local player updates their UI
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
canvasTopDown.UpdateKillsUI(kills);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[ClientCallback]
|
||||
void AnimatePlayer()
|
||||
{
|
||||
#if !UNITY_SERVER
|
||||
// A simple way to change sprite animation, without networking it
|
||||
// If not moving or rotating, show no feet animation or sound, if moving, flick through footstep animations and sound effects.
|
||||
if (this.transform.position == previousPosition && Quaternion.Angle(this.transform.rotation, previousRotation) < 20.0f)
|
||||
{
|
||||
rightFoot.SetActive(false);
|
||||
leftFoot.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rightFoot.activeInHierarchy)
|
||||
{
|
||||
leftFoot.SetActive(true);
|
||||
rightFoot.SetActive(false);
|
||||
soundLeftFoot.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
leftFoot.SetActive(false);
|
||||
rightFoot.SetActive(true);
|
||||
soundRightFoot.Play();
|
||||
}
|
||||
previousPosition = this.transform.position;
|
||||
previousRotation = this.transform.rotation;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void CmdRespawnPlayer()
|
||||
{
|
||||
// We use a number playerStatus here, rather than bool, as you can use it for other things such as delayed respawn, respawn armour, spectating etc
|
||||
if (playerStatus == 0)
|
||||
{
|
||||
playerStatus = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerStatus = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Our sync var hook for death and alive
|
||||
void OnPlayerStatusChanged(int _Old, int _New)
|
||||
{
|
||||
#if !UNITY_SERVER
|
||||
if (playerStatus == 0) // default/show
|
||||
{
|
||||
foreach (var obj in objectsToHideOnDeath)
|
||||
{
|
||||
obj.SetActive(true);
|
||||
}
|
||||
characterController.enabled = true;
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
this.transform.position = NetworkManager.startPositions[Random.Range(0, NetworkManager.startPositions.Count)].position;
|
||||
canvasTopDown.buttonRespawnPlayer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
else if (playerStatus == 1) // death
|
||||
{
|
||||
// have meshes hidden, disable movement and show respawn button
|
||||
foreach (var obj in objectsToHideOnDeath)
|
||||
{
|
||||
obj.SetActive(false);
|
||||
}
|
||||
characterController.enabled = false;
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
canvasTopDown.buttonRespawnPlayer.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
// else if (playerStatus == 2) // can be used for other features, such as spectator, make local camera follow another player
|
||||
#endif
|
||||
}
|
||||
|
||||
[ServerCallback]
|
||||
public void Kill()
|
||||
{
|
||||
//print("Kill Player");
|
||||
playerStatus = 1;
|
||||
RpcKill();
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void RpcKill()
|
||||
{
|
||||
#if !UNITY_SERVER
|
||||
soundDeath.Play();
|
||||
GameObject splatter = Instantiate(canvasTopDown.deathSplatter, this.transform.position, this.transform.rotation);
|
||||
Destroy(splatter, 5.0f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user