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!
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.
¿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?