Character-code-sample/Characters/PlayerController.cs

279 lines
7.3 KiB
C#

using System.Collections.Generic;
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
public class PlayerController : CharacterBase {
public Display WildDisplay;
public Display3DFriend FriendDisplay;
[SerializeField] protected MeleeAttackCollider AttackCollider;
[Header("Camera")]
public Camera Camera;
public Transform CameraTarget;
public float CameraDistanceMax;
public float CameraDistance;
public LayerMask CameraCollisionMask;
[Header("Effects")]
[SerializeField] private ParticleSystem SandstarParticles;
[SerializeField] private ParticleSystem DeathParticles;
private List<Interactable> _interactablesInRange = new List<Interactable>();
private NavMeshAgent _agent;
private DialogueController _currentDialogue;
private bool _lastAttackState = false;
public bool IsAttacking()
{
if (Display.GetAnimatorLayersCount() < 1) return false;
return Display.GetCurrentAnimatorStateInfo(1).IsName("Attack");
}
public bool IsRecoil()
{
return Display.GetCurrentAnimatorStateInfo(0).IsName("Hit");
}
protected override void Awake()
{
base.Awake();
_agent = GetComponent<NavMeshAgent>();
}
protected override void Start()
{
if(GameManager.Instance != null)
{
GameManager.Instance.Player = this;
}
}
protected override void Update()
{
#if UNITY_EDITOR
if (Input.GetKeyDown(Controls.Debug_K))
{
TakeDamage(9999);
}
#endif
if (!IsAlive() || !_cc.enabled || _currentDialogue != null) return;
Vector3 input = new Vector3(Input.GetAxis(Controls.XAxis), 0, Input.GetAxis(Controls.YAxis));
bool isAttacking = IsAttacking();
bool isRecoil = IsRecoil();
if (_lastAttackState != isAttacking && !isAttacking)
{
AttackEnd();
}
if (isRecoil)
{
if (isAttacking)
{
Display.SetEvent("InterruptAttack", true);
}
input = Vector3.zero;
}
if (Input.GetKeyDown(Controls.Interact))
{
if (TryInteract())
{
return;
}
else if (!isAttacking)
{
AttackStart();
}
}
if (isAttacking)
{
input /= 2;
}
_cc.SimpleMove(transform.forward * input.z * Speed);
Display.SetFloat("Input", input.z);
if (input != Vector3.zero)
{
_cc.transform.Rotate(Vector3.up, input.x * 120 * Time.deltaTime);
}
if (input.z != 0)
{
PlayFootstep();
}
_lastAttackState = isAttacking;
}
void LateUpdate()
{
RaycastHit hit;
if (Physics.Raycast(CameraTarget.position, -CameraTarget.forward, out hit, CameraDistanceMax, CameraCollisionMask.value))
{
CameraDistance = hit.distance;
}
else
{
CameraDistance = Mathf.Lerp(CameraDistance, CameraDistanceMax, 0.5f);
}
Camera.transform.position = CameraTarget.position - CameraTarget.forward * CameraDistance;
}
private bool TryInteract()
{
for (int i = _interactablesInRange.Count - 1; i>=0; i--)
{
if(_interactablesInRange[i] == null)
{
_interactablesInRange.RemoveAt(i);
}
}
if (_interactablesInRange.Count == 0)
return false;
if (!_interactablesInRange[0].Interact(this)) return false;
Display.PlayState("Interact");
return true;
}
private void AttackStart()
{
Display.SetEvent("InterruptAttack", false);
AttackCollider.Toggle(true);
AttackCollider.OnHit.AddListener((target) =>
{
if ((PlayerController)target == this) return;
target.TakeDamage(1);
});
Display.SetLayerWeight(1, 1);
Display.PlayState("Attack", 1);
}
private void AttackEnd()
{
if (AttackCollider.gameObject.activeSelf)
{
AttackCollider.Toggle(false);
AttackCollider.OnHit.RemoveAllListeners();
}
Display.SetLayerWeight(1, 0);
}
public void ToggleCharacterController(bool enabled)
{
_cc.enabled = enabled;
_interactablesInRange.Clear();
}
public void ToggleSandstarEmission(bool value)
{
var emission = SandstarParticles.emission;
emission.enabled = value;
}
public override void TakeDamage(int dmg)
{
base.TakeDamage(dmg);
if (IsAlive())
{
Display.PlayState("Hit");
}
}
protected override void OnDeath()
{
ToggleCharacterController(false);
Display.PlayState("Die");
StartCoroutine(PlayDeathSequence(2));
}
private IEnumerator PlayDeathSequence(float delay)
{
yield return new WaitForSeconds(delay);
FriendDisplay.gameObject.SetActive(false);
DeathParticles.gameObject.SetActive(true);
yield return new WaitForSeconds(0.5f);
transform.SetParent(null);
WildDisplay.gameObject.SetActive(true);
Display = WildDisplay;
yield return PathfindToEntrance();
}
private IEnumerator PathfindToEntrance()
{
var entrance = GameManager.Instance.CurrentExit;
if(entrance == null) { entrance = GameManager.Instance.Exits[0]; }
ToggleCharacterController(false);
float timeOut = 2; //seconds
_agent.enabled = true;
_agent.SetDestination(entrance.GetComponent<ILocationChange>().GetExitLocator().position);
Display.SetFloat("Input", 1);
do
{
timeOut -= Time.deltaTime;
yield return 0;
}
while ((_agent.remainingDistance > _agent.stoppingDistance) && timeOut > 0);
Display.SetFloat("Input", 0);
Debug.Log("Pathfound");
if(string.IsNullOrEmpty(GameManager.Instance.PreviousExit.TargetScene))
{
Debug.LogError("Previous scene not found! Using default");
GameManager.ChangeSceneAsync(entrance.GetComponent<ILocationChange>().GetDefaultExit());
}
else
{
GameManager.ChangeSceneAsync(GameManager.Instance.PreviousExit);
}
}
public void SetDialogue(DialogueController controller)
{
Display.SetFloat("Input", 0);
_currentDialogue = controller;
}
void OnControllerColliderHit(ControllerColliderHit hit)
{
var triggers = hit.gameObject.GetComponent<CollisionTriggers>();
if (triggers != null && triggers.CollisionTags.Contains(this.tag))
{
triggers.OnCollisionEnterAction.Invoke();
}
}
void OnTriggerEnter(Collider other)
{
var interactable = other.GetComponent<Interactable>();
if (interactable != null)
{
_interactablesInRange.Add(interactable);
}
}
void OnTriggerExit(Collider other)
{
var interactable = other.GetComponent<Interactable>();
if (interactable != null && _interactablesInRange.Contains(interactable))
{
_interactablesInRange.Remove(interactable);
}
}
}