Pelea Callejera – Unity 3D Tutorial – Beat Em Up Fight Game

Compártelo con otros

Suscribanse!

0
(0)
Hola bienvenidos a este vídeo donde les mostraremos una revisión de éste útil asset pagado en la tienda de Unity. Beat Em Up Figth ?✊. El cual nos sirve de gran ayuda a la hora de implementar un juego totalmente funcional y completo del tipo figther para ejecutarlo en un entorno de producción. Sin previos conocimientos de programación está diseñado tanto para no programadores como para los que si lo son. Espero que lo disfruten y dejen sus comentarios de que tal les parece.

Clase PlayerCombat

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;

[RequireComponent (typeof(Rigidbody))]
[RequireComponent (typeof(UnitState))]
public class PlayerCombat : MonoBehaviour, IDamagable<DamageObject> {

	[Header ("Linked Components")]
	public Transform weaponBone; //the bone were weapon will be parented on
	private UnitAnimator animator; //link to the animator component
	private UnitState playerState; //the state of the player
	private Rigidbody rb;

	[Header("Attack Data & Combos")]
	public float hitZRange = 2f; //the z range of attacks
	private int attackNum = -1; //the current attack combo number
	[Space(5)]

	public DamageObject[] PunchCombo; //a list of punch attacks
	public DamageObject[] KickCombo; //a list of kick Attacks
	public DamageObject JumpKickData; //jump kick Attack
	public DamageObject GroundPunchData; //Ground punch Attack
	public DamageObject GroundKickData; //Ground kick Attack
	public DamageObject RunningPunch; //punch attack during the run animation
	public DamageObject RunningKick; //kick attack during the run animation
	private DamageObject lastAttack; //data from the last attack that has taken place

	[Header("Settings")]
	public bool blockAttacksFromBehind = false; //block enemy attacks coming from behind
	public bool comboContinueOnHit = true; //only continue a combo when the previous attack was a hit
	public bool resetComboChainOnChangeCombo; //restart a combo when switching to a different combo chain
	public bool invulnerableDuringJump = false; //check if the player can be hit during a jump
	public float hitRecoveryTime = .4f; //the time it takes to recover from a hit
	public float hitThreshold = .2f; //the time before we can get hit again
	public float hitKnockBackForce = 1.5f; //the knockback force when we get hit
	public float GroundAttackDistance = 1.5f; //the distance from an enemy at which a ground attack can be preformed
	public int knockdownHitCount = 3; //the number of times the player can be hit before being knocked down
	public float KnockdownTimeout = 0; //the time before we stand up after a knockdown
	public float KnockdownUpForce = 5; //the Up force of a knockDown
	public float KnockbackForce = 4; //the horizontal force of a knockDown
	public float KnockdownStandUpTime = .8f; //the time it takes for the stand up animation to finish

	[Header("Audio")]
	public string knockdownVoiceSFX = "";
	public string hitVoiceSFX = "";
	public string deathVoiceSFX = "";
	public string defenceHitSFX = "";
	public string dropSFX = "";

	[Header ("Stats")]
	public DIRECTION currentDirection; //the current direction
	public GameObject itemInRange; //an item that is currently in interactable range
	private Weapon currentWeapon; //the current weapon the player is holding
	private DIRECTION defendDirection; //the direction while defending
	private bool continuePunchCombo; //true if a punch combo needs to continue
	private bool continueKickCombo; //true if the a kick combo needs to  continue
	private float lastAttackTime = 0; //time of the last attack
	[SerializeField]
	private bool targetHit; //true if the last hit has hit a target
	private int hitKnockDownCount = 0; //the number of times the player is hit in a row
	private int hitKnockDownResetTime = 2; //the time before the hitknockdown counter resets
	private float LastHitTime = 0; // the last time when we were hit 
	private bool isDead = false; //true if this player has died
	private int EnemyLayer; // the enemy layer
	private int DestroyableObjectLayer; // the destroyable object layer
	private int EnvironmentLayer; //the environment layer
	private LayerMask HitLayerMask; // a list of all hittable objects
	private bool isGrounded;
	private Vector3 fixedVelocity;
	private bool updateVelocity;
	private string lastAttackInput;
	private DIRECTION lastAttackDirection;

	//a list of states when the player can attack
	private List<UNITSTATE> AttackStates = new List<UNITSTATE> {
		UNITSTATE.IDLE, 
		UNITSTATE.WALK, 
		UNITSTATE.RUN, 
		UNITSTATE.JUMPING, 
		UNITSTATE.PUNCH,
		UNITSTATE.KICK, 
		UNITSTATE.DEFEND,
	};

	//list of states where the player can be hit
	private List<UNITSTATE> HitableStates = new List<UNITSTATE> {
		UNITSTATE.DEFEND,
		UNITSTATE.HIT,
		UNITSTATE.IDLE,
		UNITSTATE.LAND,
		UNITSTATE.PUNCH,
		UNITSTATE.KICK,
		UNITSTATE.THROW,
		UNITSTATE.WALK,
		UNITSTATE.RUN,
		UNITSTATE.GROUNDKICK,
		UNITSTATE.GROUNDPUNCH,
	};

	//list of states where the player can activate defence
	private List<UNITSTATE> DefendStates = new List<UNITSTATE> {
		UNITSTATE.IDLE,
		UNITSTATE.DEFEND,
		UNITSTATE.WALK,
		UNITSTATE.RUN,
	};

	//a list of states where the player can change direction
	private List<UNITSTATE> MovementStates = new List<UNITSTATE> {
		UNITSTATE.IDLE,
		UNITSTATE.WALK,
		UNITSTATE.RUN,
		UNITSTATE.JUMPING,
		UNITSTATE.JUMPKICK,
		UNITSTATE.LAND,
		UNITSTATE.DEFEND,
	};

	//---

	void OnEnable(){
		InputManager.onInputEvent += OnInputEvent;
		InputManager.onDirectionInputEvent += OnDirectionInputEvent;
	}

	void OnDisable() {
		InputManager.onInputEvent -= OnInputEvent;
		InputManager.onDirectionInputEvent -= OnDirectionInputEvent;
	}

	//awake
	void Start() {
		animator = GetComponentInChildren<UnitAnimator>();
		playerState = GetComponent<UnitState>();
		rb = GetComponent<Rigidbody>();

		//assign layers and layermasks
		EnemyLayer = LayerMask.NameToLayer("Enemy");
		DestroyableObjectLayer = LayerMask.NameToLayer("DestroyableObject");
		EnvironmentLayer = LayerMask.NameToLayer("Environment");
		HitLayerMask = (1 << EnemyLayer) | (1 << DestroyableObjectLayer);

		//display error messages for missing components
		if (!animator) Debug.LogError ("No player animator found inside " + gameObject.name);
		if (!playerState) Debug.LogError ("No playerState component found on " + gameObject.name);
		if (!rb) Debug.LogError ("No rigidbody component found on " + gameObject.name);

		//set invulnerable during jump
		if (!invulnerableDuringJump) {
			HitableStates.Add (UNITSTATE.JUMPING);
			HitableStates.Add (UNITSTATE.JUMPKICK);
		}
	}

	void Update() {
		
		//the player is colliding with the ground
		if(animator) isGrounded = animator.animator.GetBool("isGrounded");

		//update defence state every frame
		Defend(InputManager.defendKeyDown);
	}

	//physics update
	void FixedUpdate(){
		if (updateVelocity){
			rb.velocity = fixedVelocity;
			updateVelocity = false;
		}
	}

	//late Update
	void LateUpdate(){

		//apply any root motion offsets to parent
		if(animator && animator.GetComponent<Animator>().applyRootMotion && animator.transform.localPosition != Vector3.zero) {
			Vector3 offset = animator.transform.localPosition;
			animator.transform.localPosition = Vector3.zero;
			transform.position += offset * -(int)currentDirection;
		}
	}

	//set velocity in next fixed update
	void SetVelocity(Vector3 velocity){
		fixedVelocity = velocity;
		updateVelocity = true;
	}
		
	//movement input event
	void OnDirectionInputEvent(Vector2 inputVector, bool doubleTapActive){
		if(!MovementStates.Contains(playerState.currentState)) return;
		int dir = Mathf.RoundToInt(Mathf.Sign((float)-inputVector.x));
		if(Mathf.Abs(inputVector.x)>0) currentDirection = (DIRECTION)dir;
	}

	#region Combat Input Events
	//combat input event
	private void OnInputEvent(string action, BUTTONSTATE buttonState) {
		if (AttackStates.Contains (playerState.currentState) && !isDead) {

			//running punch
			if(action == "Punch" && buttonState == BUTTONSTATE.PRESS && playerState.currentState == UNITSTATE.RUN && isGrounded){
				animator.SetAnimatorBool("Run", false);
				if(RunningPunch.animTrigger.Length>0) doAttack(RunningPunch, UNITSTATE.ATTACK, "Punch");
				return;
			}

			//running kick
			if(action == "Kick" && buttonState == BUTTONSTATE.PRESS && playerState.currentState == UNITSTATE.RUN && isGrounded){
				animator.SetAnimatorBool("Run", false);
				if(RunningKick.animTrigger.Length>0) doAttack(RunningKick, UNITSTATE.ATTACK, "Kick");
				return;
			}

			//pick up an item
			if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && itemInRange != null && isGrounded && currentWeapon == null) {
				interactWithItem();
				return;
			}

			//use an weapon
			if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && isGrounded && currentWeapon != null) {
				useCurrentWeapon();
				return;
			}

			//ground punch
			if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && (playerState.currentState != UNITSTATE.PUNCH && NearbyEnemyDown()) && isGrounded) {
				if(GroundPunchData.animTrigger.Length > 0) doAttack(GroundPunchData, UNITSTATE.GROUNDPUNCH, "Punch");
				return;
			}

			//ground kick
			if (action == "Kick" && buttonState == BUTTONSTATE.PRESS && (playerState.currentState != UNITSTATE.KICK && NearbyEnemyDown()) && isGrounded) {
				if(GroundKickData.animTrigger.Length > 0) doAttack(GroundKickData, UNITSTATE.GROUNDKICK, "Kick");
				return;
			}

			//reset combo when switching to another combo chain (user setting)
			if (resetComboChainOnChangeCombo && (action != lastAttackInput)){
				attackNum = -1;
			}

			//default punch
			if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && playerState.currentState != UNITSTATE.PUNCH && playerState.currentState != UNITSTATE.KICK && isGrounded) {

				//continue to the next attack if the time is inside the combo window
				bool insideComboWindow = (lastAttack != null && (Time.time < (lastAttackTime + lastAttack.duration + lastAttack.comboResetTime)));
				if (insideComboWindow && !continuePunchCombo && (attackNum < PunchCombo.Length -1)) {
					attackNum += 1;
				} else {
					attackNum = 0;
				}

				if(PunchCombo[attackNum] != null && PunchCombo[attackNum].animTrigger.Length > 0) doAttack(PunchCombo[attackNum], UNITSTATE.PUNCH, "Punch");
				return;
			}
				
			//advance the punch combo if "punch" was pressed during a punch attack
			if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && (playerState.currentState == UNITSTATE.PUNCH) && !continuePunchCombo && isGrounded) {
				if (attackNum < PunchCombo.Length - 1){
					continuePunchCombo = true;
					continueKickCombo = false;
					return;
				}
			}

			//jump punch
			if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && !isGrounded) {
				if(JumpKickData.animTrigger.Length > 0) {	
					doAttack(JumpKickData, UNITSTATE.JUMPKICK, "Kick");
					StartCoroutine(JumpKickInProgress());
				}
				return;
			}

			//jump kick
			if (action == "Kick" && buttonState == BUTTONSTATE.PRESS && !isGrounded) {
				if(JumpKickData.animTrigger.Length > 0) {
					doAttack(JumpKickData, UNITSTATE.JUMPKICK, "Kick");
					StartCoroutine(JumpKickInProgress());
				}
				return;
			}

			//default kick
			if (action == "Kick" && buttonState == BUTTONSTATE.PRESS && playerState.currentState != UNITSTATE.KICK && playerState.currentState != UNITSTATE.PUNCH && isGrounded) {

				//continue to the next attack if the time is inside the combo window
				bool insideComboWindow = (lastAttack != null && (Time.time < (lastAttackTime + lastAttack.duration + lastAttack.comboResetTime)));
				if (insideComboWindow && !continueKickCombo && (attackNum < KickCombo.Length -1)) {
					attackNum += 1;
				} else {
					attackNum = 0;
				}

				doAttack(KickCombo[attackNum], UNITSTATE.KICK, "Kick");
				return;
			}
				
			//advance the kick combo if "kick" was pressed during a kick attack
			if (action == "Kick" && buttonState == BUTTONSTATE.PRESS && (playerState.currentState == UNITSTATE.KICK) && !continueKickCombo && isGrounded) {
				if (attackNum < KickCombo.Length - 1){
					continueKickCombo = true;
					continuePunchCombo = false;
					return;
				}
			}

		}
	}
	#endregion

	#region Combat functions

	private void doAttack(DamageObject damageObject, UNITSTATE state, string inputAction){
		animator.SetAnimatorTrigger(damageObject.animTrigger);
		playerState.SetState(state);

		//save attack data
		lastAttack = damageObject;
		lastAttack.inflictor = gameObject;
		lastAttackTime = Time.time;
		lastAttackInput = inputAction;
		lastAttackDirection = currentDirection;

		//turn towards current input direction
		TurnToDir(currentDirection);

		if(isGrounded) SetVelocity(Vector3.zero);
		if(damageObject.forwardForce>0) animator.AddForce(damageObject.forwardForce);

		if(state == UNITSTATE.JUMPKICK)	return;
		Invoke ("Ready", damageObject.duration);
	}

	//use the currently equipped weapon
	void useCurrentWeapon(){
		playerState.SetState (UNITSTATE.USEWEAPON);
		TurnToDir(currentDirection);
		SetVelocity(Vector3.zero);
	
		//save attack data
		lastAttackInput = "WeaponAttack";
		lastAttackTime = Time.time;
		lastAttack = currentWeapon.damageObject;
		lastAttack.inflictor = gameObject;
		lastAttackDirection = currentDirection;

		if(!string.IsNullOrEmpty(currentWeapon.damageObject.animTrigger)) animator.SetAnimatorTrigger(currentWeapon.damageObject.animTrigger);
		if(!string.IsNullOrEmpty(currentWeapon.useSound)) GlobalAudioPlayer.PlaySFX(currentWeapon.useSound);
		Invoke ("Ready", currentWeapon.damageObject.duration);

		//weapon degeneration
		if(currentWeapon.degenerateType == DEGENERATETYPE.DEGENERATEONUSE) currentWeapon.useWeapon();
		if(currentWeapon.degenerateType == DEGENERATETYPE.DEGENERATEONUSE && currentWeapon.timesToUse == 0) StartCoroutine(destroyCurrentWeapon(currentWeapon.damageObject.duration));
		if(currentWeapon.degenerateType == DEGENERATETYPE.DEGENERATEONHIT && currentWeapon.timesToUse == 1) StartCoroutine(destroyCurrentWeapon(currentWeapon.damageObject.duration));
	}

	//remove the current weapon
	IEnumerator destroyCurrentWeapon(float delay){
		yield return new WaitForSeconds(delay);
		if(currentWeapon.degenerateType == DEGENERATETYPE.DEGENERATEONUSE) GlobalAudioPlayer.PlaySFX(currentWeapon.breakSound);
		Destroy(currentWeapon.playerHandPrefab);
		currentWeapon.BreakWeapon();
		currentWeapon = null;
	}

	//returns the current weapon
	public Weapon GetCurrentWeapon(){
		return currentWeapon;
	}

	//jump kick in progress
	IEnumerator JumpKickInProgress(){
		animator.SetAnimatorBool ("JumpKickActive", true);

		//a list of enemies that we have hit
		List<GameObject> enemieshit = new List<GameObject>();

		//small delay so the animation has time to play
		yield return new WaitForSeconds(.1f);

		//check for hit
		while (playerState.currentState == UNITSTATE.JUMPKICK) {
			
			//draw a hitbox in front of the character to see which objects it collides with
			Vector3 boxPosition = transform.position + (Vector3.up * lastAttack.collHeight) + Vector3.right * ((int)currentDirection * lastAttack.collDistance);
			Vector3 boxSize = new Vector3 (lastAttack.CollSize/2, lastAttack.CollSize/2, hitZRange/2);
			Collider[] hitColliders = Physics.OverlapBox(boxPosition, boxSize, Quaternion.identity, HitLayerMask); 

			//hit an enemy only once by adding it to a list
			foreach (Collider col in hitColliders) {
				if (!enemieshit.Contains (col.gameObject)) { 
					enemieshit.Add(col.gameObject);

					//hit a damagable object
					IDamagable<DamageObject> damagableObject = col.GetComponent(typeof(IDamagable<DamageObject>)) as IDamagable<DamageObject>;
					if (damagableObject != null) {
						damagableObject.Hit(lastAttack);

						//camera Shake
						CamShake camShake = Camera.main.GetComponent<CamShake> ();
						if (camShake != null) camShake.Shake (.1f);
					}
				}
			}
			yield return null;
		}
	}

	//set defence on/off
	private void Defend(bool defend){
		if(!DefendStates.Contains(playerState.currentState)) return;
		animator.SetAnimatorBool("Defend", defend);
		if (defend) {
			TurnToDir(currentDirection);
			SetVelocity(Vector3.zero);
			playerState.SetState (UNITSTATE.DEFEND);
			animator.SetAnimatorBool("Run", false); //disable running

		} else{
			if(playerState.currentState == UNITSTATE.DEFEND) playerState.SetState(UNITSTATE.IDLE);
		}
	}

	#endregion

	#region Check For Hit

	//check if we have hit something (Animation Event)
	public void CheckForHit() {

		//draw a hitbox in front of the character to see which objects it collides with
		Vector3 boxPosition = transform.position + (Vector3.up * lastAttack.collHeight) + Vector3.right * ((int)lastAttackDirection * lastAttack.collDistance);
		Vector3 boxSize = new Vector3 (lastAttack.CollSize/2, lastAttack.CollSize/2, hitZRange/2);
		Collider[] hitColliders = Physics.OverlapBox(boxPosition, boxSize, Quaternion.identity, HitLayerMask); 

		int i=0;
		while (i < hitColliders.Length) {

			//hit a damagable object
			IDamagable<DamageObject> damagableObject = hitColliders[i].GetComponent(typeof(IDamagable<DamageObject>)) as IDamagable<DamageObject>;
			if (damagableObject != null) {
				damagableObject.Hit(lastAttack);

				//we have hit something
				targetHit = true;
			}
			i++;
		}
			
		//nothing was hit
		if(hitColliders.Length == 0) targetHit = false;

		//on weapon hit
		if(lastAttackInput == "WeaponAttack" && targetHit) currentWeapon.onHitSomething();
	}

	//Display hit box in Unity Editor (Debug)
	#if UNITY_EDITOR 
	void OnDrawGizmos(){
		if (lastAttack != null && (Time.time - lastAttackTime) < lastAttack.duration) {
			Gizmos.color = Color.red;
			Vector3 boxPosition = transform.position + (Vector3.up * lastAttack.collHeight) + Vector3.right * ((int)lastAttackDirection * lastAttack.collDistance);
			Vector3 boxSize = new Vector3 (lastAttack.CollSize, lastAttack.CollSize, hitZRange);
			Gizmos.DrawWireCube (boxPosition, boxSize);
		}
	}
	#endif

	#endregion

	#region We Are Hit

	//we are hit
	public void Hit(DamageObject d) {

		//check if we can get hit again
		if(Time.time < LastHitTime + hitThreshold) return;

		//check if we are in a hittable state
		if (HitableStates.Contains (playerState.currentState)) {
			CancelInvoke();
			 
			//camera Shake
			CamShake camShake = Camera.main.GetComponent<CamShake>();
			if (camShake != null) camShake.Shake(.1f);

			//defend incoming attack
			if(playerState.currentState == UNITSTATE.DEFEND && !d.DefenceOverride && (isFacingTarget(d.inflictor) || blockAttacksFromBehind)) {
				Defend(d);
				return;
			} else {
				animator.SetAnimatorBool("Defend", false);	
			}
				
			//we are hit
			UpdateHitCounter ();
			LastHitTime = Time.time;

			//show hit effect
			animator.ShowHitEffect ();

			//substract health
			HealthSystem healthSystem = GetComponent<HealthSystem>();
			if (healthSystem != null) {
				healthSystem.SubstractHealth (d.damage);
				if (healthSystem.CurrentHp == 0)
					return;
			}

			//check for knockdown
			if ((hitKnockDownCount >= knockdownHitCount || !IsGrounded() || d.knockDown) && playerState.currentState != UNITSTATE.KNOCKDOWN) {
				hitKnockDownCount = 0;
				StopCoroutine ("KnockDownSequence");
				StartCoroutine ("KnockDownSequence", d.inflictor);
				GlobalAudioPlayer.PlaySFXAtPosition (d.hitSFX, transform.position + Vector3.up);
				GlobalAudioPlayer.PlaySFXAtPosition (knockdownVoiceSFX, transform.position + Vector3.up);
				return;
			}

			//default hit
			int i = Random.Range (1, 3);
			animator.SetAnimatorTrigger ("Hit" + i);
			SetVelocity(Vector3.zero);
			playerState.SetState (UNITSTATE.HIT);

			//add a small force from the impact
			if (isFacingTarget(d.inflictor)) { 
				animator.AddForce (-1.5f);
			} else {
				animator.AddForce (1.5f);
			}

			//SFX
			GlobalAudioPlayer.PlaySFXAtPosition (d.hitSFX, transform.position + Vector3.up);
			GlobalAudioPlayer.PlaySFXAtPosition (hitVoiceSFX, transform.position + Vector3.up);

			Invoke("Ready", hitRecoveryTime);
		}
	}

	//update the hit counter
	void UpdateHitCounter() {
		if (Time.time - LastHitTime < hitKnockDownResetTime) { 
			hitKnockDownCount += 1;
		} else {
			hitKnockDownCount = 1;
		}
		LastHitTime = Time.time;
	}

	//defend an incoming attack
	void Defend(DamageObject d){

		//show defend effect
		animator.ShowDefendEffect();

		//play sfx
		GlobalAudioPlayer.PlaySFXAtPosition (defenceHitSFX, transform.position + Vector3.up);

		//add a small force from the impact
		if (isFacingTarget(d.inflictor)) { 
			animator.AddForce (-hitKnockBackForce);
		} else {
			animator.AddForce (hitKnockBackForce);
		}
	}
		
	#endregion

	#region Item interaction

	//item in range
	public void ItemInRange(GameObject item){
		itemInRange = item;
	}

	//item out of range
	public void ItemOutOfRange(GameObject item){
		if(itemInRange == item) itemInRange = null;
	}

	//interact with an item in range
	public void interactWithItem(){
		if (itemInRange != null){
			animator.SetAnimatorTrigger ("Pickup");
			playerState.SetState(UNITSTATE.PICKUPITEM);
			SetVelocity(Vector3.zero);
			Invoke ("Ready", .3f);
			Invoke ("pickupItem", .2f);
		}
	}

	//pick up item
	void pickupItem(){
		if(itemInRange != null)	itemInRange.SendMessage("OnPickup", gameObject, SendMessageOptions.DontRequireReceiver);
	}

	//equip current weapon
	public void equipWeapon(Weapon weapon){
		currentWeapon = weapon;
		currentWeapon.damageObject.inflictor = gameObject;

		//add player hand weapon
		if(weapon.playerHandPrefab != null) {
			GameObject PlayerWeapon = GameObject.Instantiate(weapon.playerHandPrefab, weaponBone) as GameObject;
			currentWeapon.playerHandPrefab = PlayerWeapon;
		}
	}

	#endregion

	#region KnockDown Sequence

	//knockDown sequence
	public IEnumerator KnockDownSequence(GameObject inflictor) {
		playerState.SetState (UNITSTATE.KNOCKDOWN);
		animator.StopAllCoroutines();
		yield return new WaitForFixedUpdate();

		//look towards the direction of the incoming attack
		int dir = inflictor.transform.position.x > transform.position.x ? 1 : -1;
		currentDirection = (DIRECTION)dir;
		TurnToDir(currentDirection);

		//update playermovement
		var pm = GetComponent<PlayerMovement>();
		if(pm != null) {
			pm.CancelJump();
			pm.SetDirection(currentDirection);
		}

		//add knockback force
		animator.SetAnimatorTrigger("KnockDown_Up");
		while(IsGrounded()){
			SetVelocity(new Vector3 (KnockbackForce * -dir , KnockdownUpForce, 0));
			yield return new WaitForFixedUpdate();
		}

		//going up...
		while(rb.velocity.y >= 0) yield return new WaitForFixedUpdate();

		//going down
		animator.SetAnimatorTrigger ("KnockDown_Down");
		while(!IsGrounded()) yield return new WaitForFixedUpdate();

		//hit ground
		animator.SetAnimatorTrigger ("KnockDown_End");
		CamShake camShake = Camera.main.GetComponent<CamShake>();
		if (camShake != null) camShake.Shake(.3f);
		animator.ShowDustEffectLand();

		//sfx
		GlobalAudioPlayer.PlaySFXAtPosition(dropSFX, transform.position);

		//ground slide
		float t = 0;
		float speed = 2;
		Vector3 fromVelocity = rb.velocity;
		while (t<1){
			SetVelocity(Vector3.Lerp (new Vector3(fromVelocity.x, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, fromVelocity.z), new Vector3(0, rb.velocity.y, 0), t));
			t += Time.deltaTime * speed;
			yield return null;
		}

		//knockDown Timeout
		SetVelocity(Vector3.zero);
		yield return new WaitForSeconds(KnockdownTimeout);

		//stand up
		animator.SetAnimatorTrigger ("StandUp");
		playerState.currentState = UNITSTATE.STANDUP;

		yield return new WaitForSeconds (KnockdownStandUpTime);
		playerState.currentState = UNITSTATE.IDLE;
	}

	#endregion

	//returns true if the closest enemy is in a knockdowngrounded state
	bool NearbyEnemyDown(){
		float distance = GroundAttackDistance;
		GameObject closestEnemy = null;
		foreach (GameObject enemy in EnemyManager.activeEnemies) {

			//only check enemies in front of us
			if(isFacingTarget(enemy)){

				//find closest enemy
				float dist2enemy = (enemy.transform.position - transform.position).magnitude;
				if (dist2enemy < distance) {
					distance = dist2enemy;
					closestEnemy = enemy;
				}
			}
		}
		if (closestEnemy != null) {
			EnemyAI AI = closestEnemy.GetComponent<EnemyAI>();
			if (AI != null && AI.enemyState == UNITSTATE.KNOCKDOWNGROUNDED) {
				return true;
			}
		}
		return false;
	}

	//the attack is finished and the player is ready for new actions
	public void Ready() {

		//only continue a combo when we have hit something
		if (comboContinueOnHit && !targetHit) {
			continuePunchCombo = continueKickCombo = false;
			lastAttackTime = 0;
		}

		//continue a punch combo
		if (continuePunchCombo) {
			continuePunchCombo = continueKickCombo = false;

			if (attackNum < PunchCombo.Length-1) {
				attackNum += 1;
			} else {
				attackNum = 0;
			}
			if(PunchCombo[attackNum] != null && PunchCombo[attackNum].animTrigger.Length>0) doAttack(PunchCombo[attackNum], UNITSTATE.PUNCH, "Punch");
			return;
		}

		//continue a kick combo
		if (continueKickCombo) {
			continuePunchCombo = continueKickCombo = false;

			if (attackNum < KickCombo.Length-1) {
				attackNum += 1;
			} else {
				attackNum = 0;
			}
			if(KickCombo[attackNum] != null && KickCombo[attackNum].animTrigger.Length>0) doAttack(KickCombo[attackNum], UNITSTATE.KICK, "Kick");
			return;
		}

		playerState.SetState (UNITSTATE.IDLE);
	}
		
	//returns true is the player is facing a gameobject
	public bool isFacingTarget(GameObject g) {
		return ((g.transform.position.x > transform.position.x && currentDirection == DIRECTION.Left) || (g.transform.position.x < transform.position.x && currentDirection == DIRECTION.Right));
	}

	//returns true if the player is grounded
	public bool IsGrounded(){
		CapsuleCollider c = GetComponent<CapsuleCollider> ();
		float colliderSize = c.bounds.extents.y;
		#if UNITY_EDITOR 
		Debug.DrawRay (transform.position + c.center, Vector3.down * colliderSize, Color.red); 
		#endif
		return Physics.Raycast (transform.position + c.center, Vector3.down, colliderSize + .1f, 1 << EnvironmentLayer );
	}

	//turn towards a direction
	public void TurnToDir(DIRECTION dir) {
		transform.rotation = Quaternion.LookRotation(Vector3.forward * -(int)dir);
	}

	//the player has died
	void Death(){
		if (!isDead){
			isDead = true;
			StopAllCoroutines();
			animator.StopAllCoroutines();
			CancelInvoke();
			SetVelocity(Vector3.zero);
			GlobalAudioPlayer.PlaySFXAtPosition(deathVoiceSFX, transform.position + Vector3.up);
			animator.SetAnimatorBool("Death", true);
			EnemyManager.PlayerHasDied();
			StartCoroutine(ReStartLevel());
		}
	}

	//restart this level
	IEnumerator ReStartLevel(){
		yield return new WaitForSeconds(2);
		float fadeoutTime = 1.3f;

		UIManager UI = GameObject.FindObjectOfType<UIManager>();
		if (UI != null){

			//fade out
			UI.UI_fader.Fade (UIFader.FADE.FadeOut, fadeoutTime, 0);
			yield return new WaitForSeconds (fadeoutTime);

			//show game over screen
			UI.DisableAllScreens();
			UI.ShowMenu("GameOver");
		}
	}
}

Clase PlayerMovement

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(UnitState))]
[RequireComponent(typeof(CapsuleCollider))]
public class PlayerMovement : MonoBehaviour {

	[Header("Linked Components")]
	private UnitAnimator animator;
	private Rigidbody rb;
	private UnitState playerState;
	private CapsuleCollider capsule;

	[Header("Settings")]
	public float walkSpeed = 3f;
	public float runSpeed = 6f;
	public float ZSpeed = 1.5f;
	public float JumpForce = 8f;
	public bool AllowDepthJumping;
	public float AirAcceleration = 3f;
	public float AirMaxSpeed = 3f;
	public float rotationSpeed = 15f;
	public float jumpRotationSpeed = 30f;
	public float lookAheadDistance = .2f;
	public float landRecoveryTime = .1f;
	public float landTime = 0;
	public LayerMask CollisionLayer;

	[Header("Audio")]
	public string jumpUpVoice = "";
	public string jumpLandVoice = "";

	[Header("Stats")]
	public DIRECTION currentDirection;
	public Vector2 inputDirection;
	public bool jumpInProgress;

	private bool isDead = false;
	private bool JumpNextFixedUpdate;
	private float jumpDownwardsForce = .3f;

	//a list of states where movement can take place
	private List<UNITSTATE> MovementStates = new List<UNITSTATE> {
		UNITSTATE.IDLE,
		UNITSTATE.WALK,
		UNITSTATE.RUN,
		UNITSTATE.JUMPING,
		UNITSTATE.JUMPKICK,
		UNITSTATE.LAND,
		UNITSTATE.DEFEND,
	};

	//--

	void OnEnable() {
		InputManager.onInputEvent += OnInputEvent;
		InputManager.onDirectionInputEvent += OnDirectionInputEvent;
	}

	void OnDisable() {
		InputManager.onInputEvent -= OnInputEvent;
		InputManager.onDirectionInputEvent -= OnDirectionInputEvent;
	}

	void Start(){

		//find components
		if(!animator) animator = GetComponentInChildren<UnitAnimator>();
		if(!rb) rb = GetComponent<Rigidbody>();
		if(!playerState) playerState = GetComponent<UnitState>();
		if(!capsule) capsule = GetComponent<CapsuleCollider>();

		//error messages for missing components
		if(!animator) Debug.LogError("No animator found inside " + gameObject.name);
		if(!rb) Debug.LogError("No Rigidbody component found on " + gameObject.name);
		if(!playerState) Debug.LogError("No UnitState component found on " + gameObject.name);
		if(!capsule) Debug.LogError("No Capsule Collider found on " + gameObject.name);

	}

	void FixedUpdate() {
		if(!MovementStates.Contains(playerState.currentState) || isDead) return;

		//defend
		if(playerState.currentState == UNITSTATE.DEFEND){
			TurnToCurrentDirection();
			return;
		}

		//start a jump
		if(JumpNextFixedUpdate){
			Jump();
			return;
		}

		//land after a jump
		if(jumpInProgress && IsGrounded()){
			HasLanded();
			return;
		}

		//A short recovery time after landing
		if(playerState.currentState == UNITSTATE.LAND && Time.time - landTime > landRecoveryTime) playerState.SetState(UNITSTATE.IDLE);

		//air and ground Movement
		bool isGrounded = IsGrounded();
		animator.SetAnimatorBool("isGrounded", isGrounded);
		if(isGrounded) animator.SetAnimatorBool("Falling", false);

		if(isGrounded){
			MoveGrounded();
		} else {
			MoveAirborne();
		}

		//always turn towards the current direction
		TurnToCurrentDirection();
	}

	//movement on the ground
	void MoveGrounded(){

		//do nothing when landing
		if(playerState.currentState == UNITSTATE.LAND) return;

		//move when there is no wall in front of us and input is detected
		if(rb != null && (inputDirection.sqrMagnitude>0 && !WallInFront())) {

			//set movement speed to run speed or walk speed depending on the current state
			float movementSpeed = playerState.currentState == UNITSTATE.RUN? runSpeed : walkSpeed;

			rb.velocity = new Vector3( inputDirection.x * -movementSpeed, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, inputDirection.y * -ZSpeed);
			if(animator) animator.SetAnimatorFloat("MovementSpeed", rb.velocity.magnitude);

		} else {

			//stop moving, but still apply gravity
			rb.velocity = new Vector3(0, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, 0);

			if(animator) animator.SetAnimatorFloat("MovementSpeed", 0);
			playerState.SetState(UNITSTATE.IDLE);
		}

		//sets the run state in the animator to true or false
		animator.SetAnimatorBool("Run", playerState.currentState == UNITSTATE.RUN);
	}

	//movement in the air
	void MoveAirborne(){

		//falling down
		if(rb.velocity.y < 0.1f && playerState.currentState != UNITSTATE.KNOCKDOWN)	animator.SetAnimatorBool("Falling", true);

		if(!WallInFront()) {

			//movement direction based on current input
			int dir = Mathf.Clamp(Mathf.RoundToInt(-inputDirection.x), -1, 1);
			float xpeed = Mathf.Clamp(rb.velocity.x + AirMaxSpeed * dir * Time.fixedDeltaTime * AirAcceleration, -AirMaxSpeed, AirMaxSpeed);
			float downForce = rb.velocity.y>0? 0 : jumpDownwardsForce; //adds a small downwards force when going down 

			//apply movement
			if(AllowDepthJumping) {
				rb.velocity = new Vector3(xpeed, rb.velocity.y - downForce, -inputDirection.y * ZSpeed);
			} else {
				rb.velocity = new Vector3(xpeed, rb.velocity.y - downForce, 0);
			}
		}
	}

	//perform a jump
	void Jump(){
		playerState.SetState(UNITSTATE.JUMPING);
		JumpNextFixedUpdate = false;
		jumpInProgress = true;
		rb.velocity = Vector3.up * JumpForce;

		//play animation
		animator.SetAnimatorBool("JumpInProgress", true);
		animator.SetAnimatorBool("Run", false);
		animator.SetAnimatorTrigger("JumpUp");
		animator.ShowDustEffectJump();

		//play sfx
		if(jumpUpVoice != "") GlobalAudioPlayer.PlaySFXAtPosition(jumpUpVoice, transform.position);
	}

	//player has landed after a jump
	void HasLanded(){
		jumpInProgress = false;
		playerState.SetState(UNITSTATE.LAND);
		rb.velocity = Vector2.zero;
		landTime = Time.time;

		//set animator properties
		animator.SetAnimatorFloat("MovementSpeed", 0f);
		animator.SetAnimatorBool("JumpInProgress", false);
		animator.SetAnimatorBool("JumpKickActive", false);
		animator.SetAnimatorBool("Falling", false);
		animator.ShowDustEffectLand();

		//sfx
		GlobalAudioPlayer.PlaySFX("FootStep");
		if(jumpLandVoice != "") GlobalAudioPlayer.PlaySFXAtPosition(jumpLandVoice, transform.position);
	}

	#region controller input

	//set current direction to input direction
	void OnDirectionInputEvent(Vector2 dir, bool doubleTapActive) {

		//ignore input when we are dead or when this state is not active
		if(!MovementStates.Contains(playerState.currentState) || isDead) return;

		//set current direction based on the input vector. Mathf.sign is used because we want the player to stay in the left or right direction when moving up/down)
		int dir2 = Mathf.RoundToInt(Mathf.Sign((float)-inputDirection.x));
		if(Mathf.Abs(inputDirection.x) > 0) SetDirection((DIRECTION)dir2);
		inputDirection = dir;

		//start running on double tap
		if(doubleTapActive && IsGrounded() && Mathf.Abs(dir.x)>0) playerState.SetState(UNITSTATE.RUN);
	}

	//input actions
	void OnInputEvent(string action, BUTTONSTATE buttonState) {

		//ignore input when we are dead or when this state is not active
		if(!MovementStates.Contains(playerState.currentState) || isDead) return;

		//start a jump
		if(action == "Jump" && buttonState == BUTTONSTATE.PRESS && IsGrounded() && playerState.currentState != UNITSTATE.JUMPING) JumpNextFixedUpdate = true;

		//start running when a run button is pressed (e.g. Joypad controls)
		if(action == "Run") playerState.SetState(UNITSTATE.RUN);
	}

	#endregion
		
	//interrups an ongoing jump
	public void CancelJump(){
		jumpInProgress = false;
	}
		
	//set current direction
	public void SetDirection(DIRECTION dir) {
		currentDirection = dir;
		if(animator) animator.currentDirection = currentDirection;
	}

	//returns the current direction
	public DIRECTION getCurrentDirection() {
		return currentDirection;
	}

	//returns true if the player is grounded
	public bool IsGrounded() {

		//check for capsule collisions with a 0.1 downwards offset from the capsule collider
		Vector3 bottomCapsulePos = transform.position + (Vector3.up) * (capsule.radius - 0.1f);
		return Physics.CheckCapsule(transform.position + capsule.center, bottomCapsulePos, capsule.radius, CollisionLayer);
	}
		
	//look (and turns) towards a direction
	public void TurnToCurrentDirection() {
		if(currentDirection == DIRECTION.Right || currentDirection == DIRECTION.Left) {
			float turnSpeed = jumpInProgress? jumpRotationSpeed : rotationSpeed;
			Vector3 newDir = Vector3.RotateTowards(transform.forward, Vector3.forward * -(int)currentDirection, turnSpeed * Time.fixedDeltaTime, 0.0f);
			transform.rotation = Quaternion.LookRotation(newDir);
		}
	}

	//update the direction based on the current input
	public void updateDirection() {
		TurnToCurrentDirection();
	}

	//the player has died
	void Death() {
		isDead = true;
		rb.velocity = Vector3.zero;
	}

	//returns true if there is a environment collider in front of us
	bool WallInFront() {
		var MovementOffset = new Vector3(inputDirection.x, 0, inputDirection.y) * lookAheadDistance;
		var c = GetComponent<CapsuleCollider>();
		Collider[] hitColliders = Physics.OverlapSphere(transform.position + Vector3.up * (c.radius + .1f) + -MovementOffset, c.radius, CollisionLayer);

		int i = 0;
		bool hasHitwall = false;
		while(i < hitColliders.Length) {
			if(CollisionLayer == (CollisionLayer | 1 << hitColliders[i].gameObject.layer)) hasHitwall = true;
			i++;
		}
		return hasHitwall;
	}

	//draw a lookahead sphere in the unity editor
	#if UNITY_EDITOR
	void OnDrawGizmos() {
		var c = GetComponent<CapsuleCollider>();
		Gizmos.color = Color.yellow;
		Vector3 MovementOffset = new Vector3(inputDirection.x, 0, inputDirection.y) * lookAheadDistance;
		Gizmos.DrawWireSphere(transform.position + Vector3.up * (c.radius + .1f) + -MovementOffset, c.radius);
	}
	#endif
}

public enum DIRECTION {
	Right = -1,
	Left = 1,
	Up = 2,
	Down = -2,
};

Clase EnemyAI

using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    private PhotonView PV;
    private CharacterController myCC;
    public float movementSpeed;
    public float rotationSpeed;
    // Start is called before the first frame update
    void Start()
    {
        PV = GetComponent<PhotonView>();
        myCC = GetComponent<CharacterController>();
    }

    // Update is called once per frame
    void Update()
    {
        if(PV.IsMine && PhotonNetwork.IsConnected)
        {
            BasicMovement();
            BasicRotation();
        }
    }

    void BasicMovement()
    {
        if(Input.GetKey(KeyCode.W))
        {
            myCC.Move(transform.forward * Time.deltaTime * movementSpeed);
        }
        if (Input.GetKey(KeyCode.A))
        {
            myCC.Move(-transform.right * Time.deltaTime * movementSpeed);
        }
        if (Input.GetKey(KeyCode.S))
        {
            myCC.Move(-transform.forward * Time.deltaTime * movementSpeed);
        }
        if (Input.GetKey(KeyCode.D))
        {
            myCC.Move(transform.right * Time.deltaTime * movementSpeed);
        }
    }

    void BasicRotation()
    {
        float mouseX = Input.GetAxis("Mouse X") * Time.deltaTime * rotationSpeed;
        transform.Rotate(new Vector3(0, mouseX, 0));
    }
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAI : EnemyActions, IDamagable<DamageObject>{

	[Space(10)]
	public bool enableAI;

	//a list of states where the AI is executed
	private List<UNITSTATE> ActiveAIStates = new List<UNITSTATE> { 
		UNITSTATE.IDLE, 
		UNITSTATE.WALK 
	};

	void Start(){

		//add this enemy to the enemylist
		EnemyManager.enemyList.Add(gameObject);

		//set z spread (zspread is used to keep space between the enemies)
		ZSpread = (EnemyManager.enemyList.Count-1);
		Invoke ("SetZSpread", .1f);	

		//randomize values to avoid synchronous movement
		if(randomizeValues) SetRandomValues();

		OnStart();
	}

	void FixedUpdate(){
		OnFixedUpdate();
	}

	void LateUpdate(){
		OnLateUpdate();
	}

	void Update(){

		//do nothing when there is no target or when AI is disabled
		if (target == null || !enableAI) {
			Ready ();
			return;

		} else {

			//get range to target
			range = GetDistanceToTarget ();
		}
			
		if(!isDead && enableAI){
			if(ActiveAIStates.Contains(enemyState) && targetSpotted) {

				//AI active 
				AI();

			} else {

				//try to spot the player
				if(distanceToTarget.magnitude < sightDistance) targetSpotted = true;
			}
		}
	}

	void AI(){
		LookAtTarget(target.transform);
		if (range == RANGE.ATTACKRANGE){

			//attack the target
			if (!cliffSpotted){
				if (Time.time - lastAttackTime > attackInterval) {
					ATTACK();
				} else {
					Ready();
				}
				return;
			}

			//actions for ATTACKRANGE distance
			if (enemyTactic == ENEMYTACTIC.KEEPCLOSEDISTANCE) WalkTo(closeRangeDistance, 0f);
			if (enemyTactic == ENEMYTACTIC.KEEPMEDIUMDISTANCE) WalkTo(midRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.KEEPFARDISTANCE) WalkTo(farRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.STANDSTILL) Ready ();

		} else {

			//actions for CLOSERANGE, MIDRANGE & FARRANGE distances
			if (enemyTactic == ENEMYTACTIC.ENGAGE) WalkTo (attackRangeDistance, 0f);
			if (enemyTactic == ENEMYTACTIC.KEEPCLOSEDISTANCE) WalkTo(closeRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.KEEPMEDIUMDISTANCE) WalkTo(midRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.KEEPFARDISTANCE) WalkTo(farRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.STANDSTILL) Ready();
		}
	}

	//update the current range
	private RANGE GetDistanceToTarget(){
		if (target != null) {

			//get distance from the target
			distanceToTarget = target.transform.position - transform.position;
			distance = Vector3.Distance (target.transform.position, transform.position);

			float distX = Mathf.Abs(distanceToTarget.x);
			float distZ = Mathf.Abs(distanceToTarget.z);

			//AttackRange
			if(distX <= attackRangeDistance){
				if(distZ < (hitZRange/2f)) 
					return RANGE.ATTACKRANGE;
				else
					return RANGE.CLOSERANGE;
			}

			//Close Range
			if (distX > attackRangeDistance && distX < midRangeDistance) return RANGE.CLOSERANGE;

			//Mid range
			if(distX > closeRangeDistance && distance < farRangeDistance) return RANGE.MIDRANGE;

			//Far range
			if(distX > farRangeDistance) return RANGE.FARRANGE;

		}
		return RANGE.FARRANGE;
	}
		
	//set an enemy tactic
	public void SetEnemyTactic(ENEMYTACTIC tactic){
		enemyTactic = tactic;
	}

	//spread enemies out in z distance
	void SetZSpread(){
		ZSpread = (ZSpread - (float)(EnemyManager.enemyList.Count - 1) / 2f) * (capsule.radius*2) * zSpreadMultiplier;
		if (ZSpread > attackRangeDistance) ZSpread = attackRangeDistance - 0.1f;
	}

	//Unit has died
	void Death(){
		StopAllCoroutines();
		CancelInvoke();

		enableAI = false;
		isDead = true;
		animator.SetAnimatorBool("isDead", true);
		Move(Vector3.zero, 0);
		EnemyManager.RemoveEnemyFromList(gameObject);
		gameObject.layer = LayerMask.NameToLayer ("Default");

		//ground death
		if(enemyState == UNITSTATE.KNOCKDOWNGROUNDED) {
			StartCoroutine(GroundHit());
		} else {

			//normal death
			animator.SetAnimatorTrigger("Death");
		}

		GlobalAudioPlayer.PlaySFXAtPosition("EnemyDeath", transform.position);
		StartCoroutine (animator.FlickerCoroutine(2));
		enemyState = UNITSTATE.DEATH;
		DestroyUnit();
	}
}

public enum ENEMYTACTIC {
	ENGAGE = 0,
	KEEPCLOSEDISTANCE = 1,
	KEEPMEDIUMDISTANCE = 2,
	KEEPFARDISTANCE = 3,
	STANDSTILL = 4,
}

public enum RANGE {
	ATTACKRANGE,
	CLOSERANGE,
	MIDRANGE,
	FARRANGE,
}

Clase EnemyAction

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class EnemyActions : MonoBehaviour {

	[Space(10)]
	[Header ("Linked components")]
	public GameObject target; //current target
	public UnitAnimator animator; //animator component
	public GameObject GFX; //GFX of this unit
	public Rigidbody rb; //rigidbody component
	public CapsuleCollider capsule; //capsule collider

	[Header("Attack Data")]
	public DamageObject[] AttackList; //a list of attacks
	public bool PickRandomAttack; //choose a random attack from the list
	public float hitZRange = 2; //the z range of all attacks
	public float defendChance = 0; //the chance that an incoming attack is defended %
	public float hitRecoveryTime = .4f; //the timeout after a hit before the enemy can do an action
	public float standUpTime = 1.1f; //the time it takes for this enemy to stand up
	public bool canDefendDuringAttack; //true if the enemy is able to defend an incoming attack while he is doing his own attack
	public bool AttackPlayerAirborne; //attack a player while he is in the air
	private DamageObject lastAttack; //data from the last attack that has taken place
	private int AttackCounter = 0; //current attack number
	public bool canHitEnemies; //true is this enemy can hit other enemies
	public bool canHitDestroyableObjects; //true is this enemy can hit destroyable objects like crates, barrels.
	[HideInInspector]
	public float lastAttackTime; //time of the last attack

	[Header ("Settings")]
	public bool pickARandomName; //assign a random name
	public TextAsset enemyNamesList; //the list of enemy names
	public string enemyName = ""; //the name of this enemy
	public float attackRangeDistance = 1.4f; //the distance from the target where the enemy is able to attack
	public float closeRangeDistance = 2f; //the distance from the target at close range
	public float midRangeDistance = 3f; //the distance from the target at mid range
	public float farRangeDistance = 4.5f; //the distance from the target at far range
	public float RangeMarging = 1f; //the amount of space that is allowed between the player and enemy before we reposition ourselves
	public float walkSpeed = 1.95f; //the speed of a walk
	public float walkBackwardSpeed = 1.2f; //the speed of walking backwards
	public float sightDistance = 10f; //the distance when we can see the target
	public float attackInterval = 1.2f; //the time inbetween attacking
	public float rotationSpeed = 15f; //the rotation speed when switching directions
	public float lookaheadDistance; //the distance at which we check for obstacles in from of us
	public bool ignoreCliffs; //ignore cliff detection
	public float KnockdownTimeout = 0f; //the time before we stand up after a knockdown
	public float KnockdownUpForce = 5f; //the up force of a knockDown
	public float KnockbackForce = 4; //the horizontal force of a knockDown
	private LayerMask HitLayerMask; //the layermask for damagable objects
	public LayerMask CollisionLayer; //the layers we check collisions with
	public bool randomizeValues = true; //randomize values to avoid enemy synchronization
	[HideInInspector]
	public float zSpreadMultiplier = 2f; //multiplyer for the z distance between enemies

	[Header ("Stats")]
	public RANGE range;
	public ENEMYTACTIC enemyTactic;
	public UNITSTATE enemyState;
	public DIRECTION currentDirection; 
	public bool targetSpotted;
	public bool cliffSpotted;
	public bool wallspotted;
	public bool isGrounded;
	public bool isDead;
	private Vector3 moveDirection;
	public float distance;
	private Vector3 fixedVelocity;
	private bool updateVelocity;

	//list of states where the enemy cannot move
	private List<UNITSTATE> NoMovementStates = new List<UNITSTATE> {
		UNITSTATE.DEATH,
		UNITSTATE.ATTACK,
		UNITSTATE.DEFEND,
		UNITSTATE.GROUNDHIT,
		UNITSTATE.HIT,
		UNITSTATE.IDLE,
		UNITSTATE.KNOCKDOWNGROUNDED,
		UNITSTATE.STANDUP,
	};

	//list of states where the player can be hit
	private List<UNITSTATE> HitableStates = new List<UNITSTATE> {
		UNITSTATE.ATTACK,
		UNITSTATE.DEFEND,
		UNITSTATE.HIT,
		UNITSTATE.IDLE,
		UNITSTATE.KICK,
		UNITSTATE.PUNCH,
		UNITSTATE.STANDUP,
		UNITSTATE.WALK,
		UNITSTATE.KNOCKDOWNGROUNDED,
	};

	[HideInInspector]
	public float ZSpread; //the distance between enemies on the z-axis

	//[HideInInspector]
	public Vector3 distanceToTarget;

	private List<UNITSTATE> defendableStates = new List<UNITSTATE> { UNITSTATE.IDLE, UNITSTATE.WALK, UNITSTATE.DEFEND }; //a list of states where the enemy is able to defend an incoming attack

	//global event handler for enemies
	public delegate void UnitEventHandler(GameObject Unit);

	//global event Handler for destroying units
	public static event UnitEventHandler OnUnitDestroy;

	//---

	public void OnStart(){

		//assign a name to this enemy
		if(pickARandomName) enemyName = GetRandomName();

		//set player as target
		if(target == null) target = GameObject.FindGameObjectWithTag("Player");

		//tell enemymanager to update the list of active enemies
		EnemyManager.getActiveEnemies();

		//enable defending during an attack
		if (canDefendDuringAttack) defendableStates.Add (UNITSTATE.ATTACK);

		//set up HitLayerMask
		HitLayerMask = 1 << LayerMask.NameToLayer("Player");
		if(canHitEnemies)HitLayerMask |= (1 << LayerMask.NameToLayer("Enemy"));
		if(canHitDestroyableObjects)HitLayerMask |= (1 << LayerMask.NameToLayer("DestroyableObject"));
	}

	#region Update

	//late Update
	public void OnLateUpdate(){

		//apply any root motion offsets to parent
		if(animator && animator.GetComponent<Animator>().applyRootMotion && animator.transform.localPosition != Vector3.zero) {
			Vector3 offset = animator.transform.localPosition;
			animator.transform.localPosition = Vector3.zero;
			transform.position += offset * (int)currentDirection;
		}
	}

	//physics update
	public void OnFixedUpdate() {
		if(updateVelocity) {
			rb.velocity = fixedVelocity;
			updateVelocity = false;
		}
	}

	//set velocity on next fixed update
	void SetVelocity(Vector3 velocity) {
		fixedVelocity = velocity;
		updateVelocity = true;
	}
	#endregion

	#region Attack

	//Attack
	public void ATTACK() {

		//don't attack when player is jumping
		var playerMovement = target.GetComponent<PlayerMovement>();
		if (!AttackPlayerAirborne && playerMovement != null && playerMovement.jumpInProgress) {
			return;

		} else {

			//init
			enemyState = UNITSTATE.ATTACK;
			Move(Vector3.zero, 0f);
			LookAtTarget(target.transform);
			TurnToDir(currentDirection);

			//pick random attack
			if (PickRandomAttack) AttackCounter = Random.Range (0, AttackList.Length);

			//play animation
			animator.SetAnimatorTrigger (AttackList[AttackCounter].animTrigger);

			//go to the next attack in the list
			if (!PickRandomAttack) {
				AttackCounter += 1;
				if (AttackCounter >= AttackList.Length) AttackCounter = 0;
			}

			lastAttackTime = Time.time;
			lastAttack = AttackList [AttackCounter];
			lastAttack.inflictor = gameObject;

			//resume
			Invoke ("Ready", AttackList [AttackCounter].duration);
		}
	}

	#endregion

	#region We are Hit

	//Unit was hit
	public void Hit(DamageObject d){
		if(HitableStates.Contains(enemyState)) {

			//only allow ground attacks to hit us when we are knocked down
			if(enemyState == UNITSTATE.KNOCKDOWNGROUNDED && !d.isGroundAttack) return;

			CancelInvoke();
			StopAllCoroutines();
			animator.StopAllCoroutines();
			Move(Vector3.zero, 0f);

			//add attack time out so this enemy cannot attack instantly after a hit
			lastAttackTime = Time.time;

			//don't hit this unit when it's allready down
			if((enemyState == UNITSTATE.KNOCKDOWNGROUNDED || enemyState == UNITSTATE.GROUNDHIT) && !d.isGroundAttack)
				return;

			//defend an incoming attack
			if(!d.DefenceOverride && defendableStates.Contains(enemyState)) {
				int rand = Random.Range(0, 100);
				if(rand < defendChance) {
					Defend();
					return;
				}
			}

			//hit sfx
			GlobalAudioPlayer.PlaySFXAtPosition(d.hitSFX, transform.position);

			//hit particle effect
			ShowHitEffectAtPosition(new Vector3(transform.position.x, d.inflictor.transform.position.y + d.collHeight, transform.position.z));

			//camera Shake
			CamShake camShake = Camera.main.GetComponent<CamShake>();
			if(camShake != null)
				camShake.Shake(.1f);

			//activate slow motion camera
			if(d.slowMotionEffect) {
				CamSlowMotionDelay cmd = Camera.main.GetComponent<CamSlowMotionDelay>();
				if(cmd != null)
					cmd.StartSlowMotionDelay(.2f);
			}

			//substract health
			HealthSystem healthSystem = GetComponent<HealthSystem>();
			if(healthSystem != null) {
				healthSystem.SubstractHealth(d.damage);
				if(healthSystem.CurrentHp == 0)
					return;
			}

			//ground attack
			if(enemyState == UNITSTATE.KNOCKDOWNGROUNDED) {
				StopAllCoroutines();
				enemyState = UNITSTATE.GROUNDHIT;
				StartCoroutine(GroundHit());
				return;
			}
				
			//turn towards the direction of the incoming attack
			int dir = d.inflictor.transform.position.x > transform.position.x? 1 : -1;
			TurnToDir((DIRECTION)dir);

			//check for a knockdown
			if(d.knockDown) {
				StartCoroutine(KnockDownSequence(d.inflictor));
				return;

			} else {

				//default hit
				int rand = Random.Range(1, 3);
				animator.SetAnimatorTrigger("Hit" + rand);
				enemyState = UNITSTATE.HIT;

				//add small force from the impact
				LookAtTarget(d.inflictor.transform);
				animator.AddForce(-KnockbackForce);

				//switch  enemy state from passive to aggressive when attacked
				if(enemyTactic != ENEMYTACTIC.ENGAGE) {
					EnemyManager.setAgressive(gameObject);
				}

				Invoke("Ready", hitRecoveryTime);
				return;
			}
		}
	}

	//Defend
	void Defend(){
		enemyState = UNITSTATE.DEFEND;
		animator.ShowDefendEffect();
		animator.SetAnimatorTrigger ("Defend");
		GlobalAudioPlayer.PlaySFX ("DefendHit");
		animator.SetDirection (currentDirection);
	}

	#endregion

	#region Check for hit

	//checks if we have hit something (Animation Event)
	public void CheckForHit() {

		//draws a hitbox in front of the character to see which objects are overlapping it
		Vector3 boxPosition = transform.position + (Vector3.up * lastAttack.collHeight) + Vector3.right * ((int)currentDirection * lastAttack.collDistance);
		Vector3 boxSize = new Vector3 (lastAttack.CollSize/2, lastAttack.CollSize/2, hitZRange/2);
		Collider[] hitColliders = Physics.OverlapBox(boxPosition, boxSize, Quaternion.identity, HitLayerMask); 

		int i=0;
		while (i < hitColliders.Length) {

			//hit a damagable object
			IDamagable<DamageObject> damagableObject = hitColliders[i].GetComponent(typeof(IDamagable<DamageObject>)) as IDamagable<DamageObject>;
			if (damagableObject != null && damagableObject != (IDamagable<DamageObject>)this) {
				damagableObject.Hit(lastAttack);
			}
			i++;
		}
	}

	//Display hit box + lookahead sphere in Unity Editor (Debug)
	#if UNITY_EDITOR 
	void OnDrawGizmos(){

		//visualize hitbox
		if (lastAttack != null && (Time.time - lastAttackTime) < lastAttack.duration) {
			Gizmos.color = Color.red;
			Vector3 boxPosition = transform.position + (Vector3.up * lastAttack.collHeight) + Vector3.right * ((int)currentDirection * lastAttack.collDistance);
			Vector3 boxSize = new Vector3 (lastAttack.CollSize, lastAttack.CollSize, hitZRange);
			Gizmos.DrawWireCube (boxPosition, boxSize);
		}

		//visualize lookahead sphere
		Gizmos.color = Color.yellow;
		Vector3 offset = -moveDirection.normalized * lookaheadDistance;
		Gizmos.DrawWireSphere (transform.position + capsule.center - offset, capsule.radius); 
	}

	#endif

	#endregion

	#region KnockDown Sequence

	//knockDown sequence
	IEnumerator KnockDownSequence(GameObject inflictor) {
		enemyState = UNITSTATE.KNOCKDOWN;
		yield return new WaitForFixedUpdate();

		//look towards the direction of the incoming attack
		int dir = 1;
		if(inflictor != null) dir = inflictor.transform.position.x > transform.position.x? 1 : -1;
		currentDirection = (DIRECTION)dir;
		animator.SetDirection(currentDirection);
		TurnToDir(currentDirection);

		//add knockback force
		animator.SetAnimatorTrigger("KnockDown_Up");
		while(IsGrounded()){
			SetVelocity(new Vector3(KnockbackForce * -dir, KnockdownUpForce, 0));
			yield return new WaitForFixedUpdate();
		}

		//going up...
		while(rb.velocity.y >= 0) yield return new WaitForFixedUpdate();

		//going down
		animator.SetAnimatorTrigger ("KnockDown_Down");
		while(!IsGrounded()) yield return new WaitForFixedUpdate();

		//hit ground
		animator.SetAnimatorTrigger ("KnockDown_End");
		GlobalAudioPlayer.PlaySFXAtPosition("Drop", transform.position);
		animator.SetAnimatorFloat ("MovementSpeed", 0f);
		animator.ShowDustEffectLand();
		enemyState = UNITSTATE.KNOCKDOWNGROUNDED;
		Move(Vector3.zero, 0f);

		//cam shake
		CamShake camShake = Camera.main.GetComponent<CamShake>();
		if (camShake != null) camShake.Shake(.3f);

		//dust effect
		animator.ShowDustEffectLand();

		//stop sliding
		float t = 0;
		float speed = 2;
		Vector3 fromVelocity = rb.velocity;
		while (t<1){
			SetVelocity(Vector3.Lerp (new Vector3(fromVelocity.x, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, fromVelocity.z), new Vector3(0, rb.velocity.y, 0), t));
			t += Time.deltaTime * speed;
			yield return new WaitForFixedUpdate();
		}

		//knockDown Timeout
		Move(Vector3.zero, 0f);
		yield return new WaitForSeconds(KnockdownTimeout);

		//stand up
		enemyState = UNITSTATE.STANDUP;
		animator.SetAnimatorTrigger ("StandUp");
		Invoke("Ready", standUpTime);
	}

	//ground hit
	public IEnumerator GroundHit(){
		CancelInvoke();
		GlobalAudioPlayer.PlaySFXAtPosition ("EnemyGroundPunchHit", transform.position);
		animator.SetAnimatorTrigger ("GroundHit");
		yield return new WaitForSeconds(KnockdownTimeout);
		if(!isDead)	animator.SetAnimatorTrigger ("StandUp");
		Invoke("Ready", standUpTime);
	}

	#endregion

	#region Movement

	//walk to target
	public void WalkTo(float proximityRange, float movementMargin){
		Vector3 dirToTarget;
		LookAtTarget(target.transform);
		enemyState = UNITSTATE.WALK;

		//clamp zspread to attackDistance when ENGAGED, otherwise we might not be able to reach the player at all
		if (enemyTactic == ENEMYTACTIC.ENGAGE) {
			dirToTarget = target.transform.position - (transform.position + new Vector3 (0, 0, Mathf.Clamp(ZSpread, 0, attackRangeDistance)));
		} else {
			dirToTarget = target.transform.position - (transform.position + new Vector3 (0, 0, ZSpread));
		}

		//we are too far away, move closer
		if (distance >= proximityRange ) {
			moveDirection = new Vector3(dirToTarget.x,0,dirToTarget.z);
			if (IsGrounded() && !WallSpotted() && !PitfallSpotted()) {
				Move(moveDirection.normalized, walkSpeed);
				animator.SetAnimatorFloat ("MovementSpeed", rb.velocity.sqrMagnitude);
				return;
			}
		}

		//we are too close, move away
		if (distance <= proximityRange - movementMargin) {
			moveDirection = new Vector3(-dirToTarget.x,0,0);
			if (IsGrounded() && !WallSpotted() && !PitfallSpotted()) {
				Move(moveDirection.normalized, walkBackwardSpeed);
				animator.SetAnimatorFloat ("MovementSpeed", -rb.velocity.sqrMagnitude);
				return;
			}
		}

		//otherwise do nothing
		Move(Vector3.zero, 0f);
		animator.SetAnimatorFloat ("MovementSpeed", 0);
	}

	//move towards a vector
	public void Move(Vector3 vector, float speed){
		if(!NoMovementStates.Contains(enemyState)) {
			SetVelocity(new Vector3(vector.x * speed, rb.velocity.y + Physics.gravity.y * Time.fixedDeltaTime, vector.z * speed));
		} else {
			SetVelocity(Vector3.zero);
		}
	}

	//returns true if there is an environment collider in front of us
	bool WallSpotted(){
		Vector3 Offset =  moveDirection.normalized * lookaheadDistance;
		Collider[] hitColliders = Physics.OverlapSphere (transform.position + capsule.center + Offset, capsule.radius, CollisionLayer);

		int i = 0;
		bool hasHitwall = false;
		while (i < hitColliders.Length) {
			if(CollisionLayer == (CollisionLayer | 1 << hitColliders[i].gameObject.layer)) {
				hasHitwall = true;
			}
			i++;
		}
		wallspotted = hasHitwall;
		return hasHitwall;
	}

	//returns true if there is a cliff in front of us
	bool PitfallSpotted(){
		if (!ignoreCliffs) {
			float lookDownDistance = 1f;
			Vector3 StartPoint = transform.position + (Vector3.up * .3f) + (Vector3.right * (capsule.radius + lookaheadDistance) * moveDirection.normalized.x);
			RaycastHit hit;

			#if UNITY_EDITOR 
			Debug.DrawRay (StartPoint, Vector3.down * lookDownDistance, Color.red);
			#endif

			if (!Physics.Raycast (StartPoint, Vector3.down, out hit, lookDownDistance, CollisionLayer)) {
				cliffSpotted = true;
				return true;
			}
		}
		cliffSpotted = false;
		return false;
	}

	//returns true if this unit is grounded
	public bool IsGrounded(){
		float colliderSize = capsule.bounds.extents.y - .1f;
		if (Physics.CheckCapsule (capsule.bounds.center, capsule.bounds.center + Vector3.down*colliderSize, capsule.radius, CollisionLayer)) {
			isGrounded = true;
			return true;
		} else {
			isGrounded = false;
			return false;
		}
	}

	//turn towards a direction
	public void TurnToDir(DIRECTION dir) {
		transform.rotation = Quaternion.LookRotation(Vector3.forward * (int)dir);
	}

	#endregion

	//show hit effect
	public void ShowHitEffectAtPosition(Vector3 pos) {
		GameObject.Instantiate (Resources.Load ("HitEffect"), pos, Quaternion.identity);
	}

	//unit is ready for new actions
	public void Ready()	{
		enemyState = UNITSTATE.IDLE;
		animator.SetAnimatorTrigger("Idle");
		animator.SetAnimatorFloat ("MovementSpeed", 0f);
		Move(Vector3.zero, 0f);
	}

	//look at the current target
	public void LookAtTarget(Transform _target){
		if(_target != null){
			Vector3 newDir = Vector3.zero;
			int dir = _target.transform.position.x >= transform.position.x ? 1 : -1;
			currentDirection = (DIRECTION)dir;
			if (animator != null) animator.currentDirection = currentDirection;
			newDir = Vector3.RotateTowards(transform.forward, Vector3.forward * dir, rotationSpeed * Time.deltaTime, 0.0f);	
			transform.rotation = Quaternion.LookRotation(newDir);
		}
	}

	//randomizes values
	public void SetRandomValues(){
		walkSpeed *= Random.Range(.8f, 1.2f);
		walkBackwardSpeed *= Random.Range(.8f, 1.2f);
		attackInterval *= Random.Range(.7f, 1.5f);
		KnockdownTimeout *= Random.Range(.7f, 1.5f);
		KnockdownUpForce *= Random.Range(.8f, 1.2f);
		KnockbackForce *= Random.Range(.7f, 1.5f);
	}

	//destroy event
	public void DestroyUnit(){
		if(OnUnitDestroy != null) OnUnitDestroy(gameObject);
	}

	//returns a random name
	string GetRandomName(){
		if(enemyNamesList == null) {
			Debug.Log("no list of names was found, please create 'EnemyNames.txt' that contains a list of enemy names and put it in the 'Resources' folder.");
			return "";
		}

		//convert the lines of the txt file to an array
		string data = enemyNamesList.ToString();
		string cReturns = System.Environment.NewLine + "\n" + "\r"; 
		string[] lines = data.Split(cReturns.ToCharArray());

		//pick a random name from the list
		string name = "";
		int cnt = 0;
		while(name.Length == 0 && cnt < 100) {
			int rand = Random.Range(0, lines.Length);
			name = lines[rand];
			cnt += 1;
		}
		return name;
	}
}

¿Quieres acceder al proyecto completo?

¡Registrate y hazte Premium!

  • Si quieres avanzar a un nuevo nivel en tus proyectos tecnológicos con este plan de suscripción no solamente tendrás acceso a los vídeos e información detallada de tutoriales y temas de intereses dentro de ZoeGeop, sino que también dispondrás de acceso exclusivo a materiales, soporte y recursos de nuestros proyectos en curso los cuales iremos actualizando constantemente para que puedas utilizarlos libremente e implementarlos en tus propios proyectos. Contarás además con 3 sesiones de asesoría a tu proyecto por parte del equipo de ZoeGeop al mes. Nota: Por cada sesión adicional tendrá un costo adicional.
    Si quieres avanzar a un nuevo nivel en tus proyectos tecnológicos con este plan de suscripción no solamente tendrás acceso a los vídeos e información detallada de tutoriales y temas de intereses dentro de ZoeGeop, sino que también dispondrás de acceso exclusivo a materiales, soporte y recursos de nuestros proyectos en curso los cuales iremos actualizando constantemente para que puedas utilizarlos libremente e implementarlos en tus propios proyectos. Contarás además con 1 sesión de asesoría a tu proyecto por parte del equipo de ZoeGeop al mes. Nota: Por cada sesión adicional tendrá un costo adicional.
    Si quieres disfrutar y difundir tus proyectos tecnológicos con este plan de suscripción no solamente tendrás acceso a los vídeos e información detallada de tutoriales y temas de interés dentro de ZoeGeop, sino que también podrás dar a conocer tus proyectos en nuestro marketplace global para que cualquier comprador o donante pueden contribuir al crecimiento de tu proyecto.

Suscribete a nuestro canal de Youtube

Siguenos en nuestro canal de Youtube Síguenos en nuestro canal de Youtube dedicado a Tecnología, MarketPlace de Proyectos Tecnológicos, Cursos Online y Tutoriales de Desarrollo de Videojuegos. Ofrecemos consultoría en Desarrollo de Software, Marketing Online, Servicios de TI, Hosting Web, dominios web entre otros.

Suscribete

Siguenos en Patreon

Si quieres contribuir con cualquier aporte o donación hacia nuestros proyectos y el canal puedes hacerlo a través de nuestra cuenta en Patreon.

¿Quieres publicar tus propios proyectos?. ¡Pues que esperas!

¿Qué tan útil fue esta publicación?

¡Haz clic en una estrella para calificarla!

Puntuación media 0 / 5. Recuento de votos: 0

No hay votos hasta ahora! Sé el primero en calificar esta publicación.

¡Lamentamos que esta publicación no te haya sido útil!

¡Permítanos mejorar esta publicación!

¿Cuéntanos cómo podemos mejorar esta publicación?

Written by 

Tecnología, MarketPlace, Videojuegos, Marketing Online, Web Hosting y más..

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *