|
|
|
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;
|
|
|
|
|
|
|
|
PlayerMovement _playerMovement;
|
|
|
|
|
|
|
|
//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>();
|
|
|
|
_playerMovement = GetComponent<PlayerMovement>();
|
|
|
|
|
|
|
|
//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, int playerNumber)
|
|
|
|
{
|
|
|
|
if (playerNumber != _playerMovement.playerNumber)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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, int playerNumber) {
|
|
|
|
if (playerNumber != _playerMovement.playerNumber)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//special attack (both)
|
|
|
|
if (action == "Punch" && buttonState == BUTTONSTATE.PRESS) {
|
|
|
|
|
|
|
|
|
|
|
|
if (_playerMovement != null && (_playerMovement.JumpNextFixedUpdate || (_playerMovement.jumpInProgress && _playerMovement.JumpIsRecent())))
|
|
|
|
{
|
|
|
|
DoSpecialAttack(_playerMovement);
|
|
|
|
Debug.Log("did special attack");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//jump kick (using punch button)
|
|
|
|
if (action == "Punch" && buttonState == BUTTONSTATE.PRESS && !isGrounded) {
|
|
|
|
if(JumpKickData.animTrigger.Length > 0) {
|
|
|
|
Debug.Log("did jump kick");
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void DoSpecialAttack(PlayerMovement pm)
|
|
|
|
{
|
|
|
|
pm.CancelJump();
|
|
|
|
pm.JumpNextFixedUpdate = false;
|
|
|
|
pm.SetDirection(currentDirection);
|
|
|
|
doAttack(KickCombo[0], UNITSTATE.KICK, "Kick");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Combat functions
|
|
|
|
|
|
|
|
public bool IsPunching()
|
|
|
|
{
|
|
|
|
return playerState.currentState == UNITSTATE.PUNCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
public const float SIMULTANEOUS_BUTTON_SPACING = .03f;
|
|
|
|
public bool AttackIsRecent()
|
|
|
|
{
|
|
|
|
return lastAttackTime > Time.time - PlayerCombat.SIMULTANEOUS_BUTTON_SPACING;
|
|
|
|
}
|
|
|
|
|
|
|
|
//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
|
|
|
|
if(_playerMovement != null) {
|
|
|
|
_playerMovement.CancelJump();
|
|
|
|
_playerMovement.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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|