Upload files to "/"
This commit is contained in:
commit
d071106868
|
@ -0,0 +1,71 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class CharacterBase : MonoBehaviour, IDamageable {
|
||||
|
||||
public int Health = 5;
|
||||
public float Speed = 5;
|
||||
public Display Display;
|
||||
|
||||
[SerializeField] protected List<AudioClip> Footsteps = new List<AudioClip>();
|
||||
|
||||
protected float _footstepDelay = 0.5f;
|
||||
protected float _footstepTimer = 0;
|
||||
|
||||
protected CharacterController _cc;
|
||||
protected AudioSource _audioSource;
|
||||
|
||||
public bool IsAlive() { return Health > 0; }
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_cc = GetComponent<CharacterController>();
|
||||
_audioSource = GetComponent<AudioSource>();
|
||||
}
|
||||
|
||||
protected virtual void Start () {
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Update () {
|
||||
|
||||
}
|
||||
|
||||
protected void PlayFootstep()
|
||||
{
|
||||
if (Footsteps.Count == 0) return;
|
||||
if (!_audioSource.isPlaying)
|
||||
{
|
||||
if (_footstepTimer > 0)
|
||||
{
|
||||
_footstepTimer -= Time.deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
_audioSource.clip = Footsteps[Random.Range(0, Footsteps.Count)];
|
||||
_audioSource.Play();
|
||||
_footstepTimer = _footstepDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void TakeDamage(int dmg)
|
||||
{
|
||||
if(Health > 0)
|
||||
{
|
||||
Health -= dmg;
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
OnDeath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDeath()
|
||||
{
|
||||
//GameManager.Instance.PlaySound(DeathSound);
|
||||
//Destroy(this.gameObject);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using UnityEngine;
|
||||
|
||||
public class Display : MonoBehaviour
|
||||
{
|
||||
public bool Standalone = false;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Standalone)
|
||||
{
|
||||
ManualUpdate(transform.forward);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Init() { }
|
||||
|
||||
public virtual void ManualUpdate(Vector3 direction) { }
|
||||
|
||||
public virtual void PlayState(string stateName, int layer = 0, float time = -1) { }
|
||||
|
||||
public virtual void SetBool(string name, bool value) { }
|
||||
|
||||
public virtual void SetFloat(string name, float value) { }
|
||||
|
||||
public virtual void SetEvent(string name, bool value) { }
|
||||
|
||||
public virtual void SetLayerWeight(int layer, float weight) { }
|
||||
|
||||
public virtual Sprite GetSprite() { return null; }
|
||||
|
||||
public virtual void ShowAfterImage(float timeOut) { }
|
||||
|
||||
public virtual int GetAnimatorLayersCount() { return 0; }
|
||||
public virtual AnimatorStateInfo GetCurrentAnimatorStateInfo(int layer = 0) { return new AnimatorStateInfo(); }
|
||||
public virtual AnimatorStateInfo GetNextAnimatorStateInfo(int layer = 0) { return new AnimatorStateInfo(); }
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RequireComponent(typeof(NavMeshAgent), typeof(CharacterController))]
|
||||
public class EnemyController : CharacterBase
|
||||
{
|
||||
public enum AIState
|
||||
{
|
||||
Patrolling,
|
||||
Chasing
|
||||
}
|
||||
|
||||
public AIState State;
|
||||
public List<Collider> PatrolPoints;
|
||||
private NavMeshAgent _agent;
|
||||
private Coroutine _attack;
|
||||
private Coroutine _currentAction;
|
||||
public LayerMask PlayerLayer;
|
||||
public Image PlayerDetectIndicator;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
_agent = GetComponent<NavMeshAgent>();
|
||||
_agent.speed = Speed;
|
||||
}
|
||||
|
||||
public bool IsAttacking()
|
||||
{
|
||||
return Display.GetCurrentAnimatorStateInfo(1).IsName("Attack");
|
||||
}
|
||||
|
||||
public bool IsRecoil()
|
||||
{
|
||||
return Display.GetCurrentAnimatorStateInfo(0).IsName("Hit");
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
if (!IsAlive()) return;
|
||||
if (GameManager.Instance == null) return;
|
||||
|
||||
CharacterBase target = GameManager.Instance.Player;
|
||||
if (_attack != null) return;
|
||||
if (target == null) return;
|
||||
|
||||
if (State == AIState.Patrolling)
|
||||
{
|
||||
if (_currentAction == null)
|
||||
{
|
||||
Debug.Log("Patrollin");
|
||||
_currentAction = StartCoroutine(SetNewPatrolPoint());
|
||||
}
|
||||
else
|
||||
{
|
||||
var detectDistance = 5;
|
||||
var detectAngle = 45;
|
||||
var playerDir = target.transform.position - transform.position;
|
||||
|
||||
if(Vector3.Angle(transform.forward, playerDir) <= detectAngle
|
||||
&& Physics.Raycast(transform.position + Vector3.up * 0.5f, playerDir, detectDistance, PlayerLayer.value))
|
||||
{
|
||||
PlayerDetectIndicator.fillAmount += Time.deltaTime;
|
||||
if(PlayerDetectIndicator.fillAmount >= 1)
|
||||
{
|
||||
State = AIState.Chasing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerDetectIndicator.fillAmount -= Time.deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (State == AIState.Chasing)
|
||||
{
|
||||
_agent.SetDestination(target.transform.position);
|
||||
var dist = Vector3.Distance(transform.position, target.transform.position);
|
||||
|
||||
if (dist < 2.5f)
|
||||
{
|
||||
_attack = StartCoroutine(Attack((target.transform.position - transform.position).normalized));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator Attack(Vector3 moveDirection)
|
||||
{
|
||||
_agent.enabled = false;
|
||||
_cc.enabled = true;
|
||||
|
||||
Display.PlayState("Attack");
|
||||
float attackTime = 0;
|
||||
|
||||
while(attackTime < 2)
|
||||
{
|
||||
_cc.SimpleMove(moveDirection * 5);
|
||||
attackTime += Time.deltaTime;
|
||||
yield return 0;
|
||||
}
|
||||
|
||||
_cc.enabled = false;
|
||||
_agent.enabled = true;
|
||||
_attack = null;
|
||||
}
|
||||
|
||||
private IEnumerator SetNewPatrolPoint()
|
||||
{
|
||||
var patrolPoint = PatrolPoints[Random.Range(0, PatrolPoints.Count)];
|
||||
var bounds = patrolPoint.bounds;
|
||||
var point = new Vector3(
|
||||
Random.Range(bounds.min.x, bounds.max.x),
|
||||
Random.Range(bounds.min.y, bounds.max.y),
|
||||
Random.Range(bounds.min.z, bounds.max.z)
|
||||
);
|
||||
_agent.SetDestination(point);
|
||||
float timeout = 10f;
|
||||
while (!_agent.isStopped && timeout > 0)
|
||||
{
|
||||
timeout -= Time.deltaTime;
|
||||
yield return 0;
|
||||
}
|
||||
yield return new WaitForSeconds(Random.Range(5f, 10f));
|
||||
_currentAction = null;
|
||||
}
|
||||
|
||||
public override void TakeDamage(int dmg)
|
||||
{
|
||||
base.TakeDamage(dmg);
|
||||
if (IsAlive())
|
||||
{
|
||||
Display.PlayState("Hit");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDeath()
|
||||
{
|
||||
Display.PlayState("Die");
|
||||
_cc.enabled = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
public interface IDamageable
|
||||
{
|
||||
void TakeDamage(int damage);
|
||||
bool IsAlive();
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
Sample character + enemy code extracted from a recent WIP 3rd person adventure game project.
|
||||
|
||||
Contains:
|
||||
- base class used by player, enemies and NPCs
|
||||
- player character controller with tank controls, dynamic camera and environment interaction
|
||||
- sample enemy with patrolling/attacking states
|
Loading…
Reference in New Issue