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.
¿Estás listo para un combate? Los juegos Beat Em Up Fight son entretenimiento de lucha para todos los gustos y edades. ¡Explora esta clase de juegos en este artículo para descubrir la diversión que traen!
Introducción
Los juegos de lucha o «beat ‘em up» han estado presentes desde los primeros días de los videojuegos. Aunque el género ha tenido altibajos, siempre ha logrado mantener un cierto nivel de popularidad. Los juegos de lucha se caracterizan por su combate en tiempo real, en el que los jugadores controlan a un personaje y lo mueven por un escenario para enfrentarse a otros personajes controlled por la CPU o por otros jugadores.
El objetivo principal de los juegos de lucha es derrotar al oponente mediante el uso de puñetazos y patadas, aunque algunos juegos incorporan armas blancas o de fuego. A menudo, los juegos cuentan con un sistema de combate basado en combos, en el que los jugadores pueden realizar una serie de movimientos seguidos para infligir más daño.
Qué es un juego de lucha?
Un juego de lucha, también conocido como «beat ‘em up», es un videojuego en el que el jugador controla a un personaje y lo mueve por un escenario mientras ataca a los enemigos que aparecen. Los juegos de lucha suelen ser de acción y combates cuerpo a cuerpo, aunque algunos incluyen elementos de juegos de plataformas o tiroteos. El objetivo del juego suele ser derrotar a todos los enemigos del nivel, o bien completar una serie de objetivos específicos.
Géneros de juegos de lucha
En los últimos años, los juegos de lucha se han popularizado en todo el mundo. Estos juegos suelen incluir una gran variedad de géneros, desde simuladores realistas hasta juegos de cartas coleccionables. A continuación se presentan algunos de los géneros más populares de los juegos de lucha.
Los simuladores de lucha son probablemente el género más popular entre los aficionados a los juegos de lucha. Estos juegos suelen intentar imitar la experiencia real del combate, ya sea mediante el uso de gráficos realistas o mecánicas complicadas. Los jugadores pueden elegir entre una variedad de ataques y técnicas para derrotar a sus oponentes, lo que hace que estos juegos sean extremadamente competitivos. Algunos ejemplos populares de este género incluyen la serie Tekken y Virtua Fighter.
Los juegossubpunto 2 son un tipo relativamente nuevo de l
Características de los juegos de lucha
Los juegos de lucha son un subgénero de los videojuegos que se caracterizan por el combate cuerpo a cuerpo entre dos o más jugadores. A menudo, los juegos de lucha incluyen una variedad de movimientos especiales y ataques especiales que pueden ser utilizados por los personajes para derrotar a sus oponentes. Los juegos de lucha también suelen incluir un sistema de combate en el que los jugadores deben usar sus habilidades para vencer a sus adversarios.
Cómo se juega un juego de lucha?
Los juegos de lucha, también conocidos como beat em up, son un género de videojuegos en el que el jugador controla a un personaje que tiene que luchar contra un grupo de adversarios. A menudo, los juegos de lucha se caracterizan por su ritmo rápido y sus gráficos llamativos.
Los juegos de lucha suelen incluir una variedad de golpes y movimientos especiales que pueden usar el jugador para derrotar a sus adversarios. Algunos juegos también permiten a los jugadores usar objetos del escenario para golpear a sus enemigos o interactuar con ellos de otras maneras.
A menudo, los juegos de lucha se centran en la historia y los personajes, y muchos incluyen elementos de RPG para permitir al jugador mejorar las habilidades y el equipo del personaje principal a medida que avanza la historia.
Consejos para mejorar en los juegos de lucha
En los juegos de lucha, hay muchas cosas que puedes hacer para mejorar tu rendimiento. Estos son algunos consejos para ayudarte a sacar el máximo provecho de tus sesiones de juego:
-Practica: la mejor manera de mejorar en los juegos de lucha es practicar. Asegúrate de estar jugando con frecuencia y trata de aprender todo lo que puedas acerca del juego. Observa las técnicas de otros jugadores y trata de imitarlas. También puedes encontrar tutoriales online o en videos que te ayuden a mejorar tus habilidades.
-Analiza tus partidas: después de cada partida, analiza cuidadosamente lo que hiciste bien y lo que hiciste mal. Trata de identificar qué fue lo que te hizo ganar o perder cada round. De esta manera, podrás concentrarte en mejorar tus debilidades y reforzar tus puntos fuertes.
-Estudia a tus oponent
Los mejores juegos de lucha
Los mejores juegos de lucha se caracterizan por su intensidad y el desafío que presentan. A menudo, estos juegos involucran a personajes fuertemente animados y ofrecen una experiencia de combate única e intensa. La mayoría de los juegos de lucha se centran en el combate cuerpo a cuerpo, pero también hay juegos que incorporan armas y otros elementos. Aquí están algunos de los mejores juegos de lucha disponibles:
-Injustice: Gods Among Us: Este es uno de los últimos lanzamientos en la categoría de los mejores juegos de lucha. El juego cuenta con personajes populares del Universo DC, como Superman, Batman y Wonder Woman. Ofrece una amplia variedad de modos para mantenerte ocupado, incluyendo batallas multijugador en línea.
-Mortal Kombat X: Mortal Kombat X es el último lanzamiento en la serie Mortal Kombat. El juego se caracteriza por su violencia gráfica y sus
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");
}
}
}
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,
};
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,
}
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.
Únete a nuestro Discord
¿Quieres publicar tus propios proyectos?. ¡Pues que esperas!

ZoeGeop Technologies
MarketPlace
Crea tu cuenta



