├── README.md ├── 03 - Melee Attack (2D) ├── IAttackable.cs ├── Enemy.cs └── AdventurerController.cs ├── 01 - Movement & Jump (2D) └── AdventurerController.cs ├── 02 - Animations (2D) └── AdventurerController.cs └── 04 - Double Jump (2D) └── AdventurerController.cs /README.md: -------------------------------------------------------------------------------- 1 | # Aprende-a-programar-con-Melenitas 2 | 3 | ¡Muy buenas, familia! 4 | Yo soy Melenitas y he creado este repositorio para que puedas descargar el contenido de mi serie de tutoriales "Aprende a Programar con Melenitas". 5 | Aquí encontrarás todos los scripts explicados en los vídeos comentados línea por línea para facilitarte el aprendizaje. 6 | 7 | ¡Espero que te sea de ayuda! 8 | 9 | Sígueme en [TikTok](https://www.tiktok.com/@melenitasdev), [Twitch](https://www.twitch.tv/melenitasdev), [YouTube](https://www.youtube.com/@MelenitasDev) e [Instagram](https://www.instagram.com/melenitasdev/) como: 10 | @Melenitas Dev 11 | 12 | ¡Muchas gracias! 13 | -------------------------------------------------------------------------------- /03 - Melee Attack (2D)/IAttackable.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace MelenitasDev 3 | { 4 | // Esta es la interfaz que hace que cualquier clase sea Atacable. Las funciones que declaremos aquí dentro, 5 | // tendrán que estar declaradas obligatoriamente en las clases que implementen esta interfaz. 6 | // Así nos aseguramos de que cualquier clase atacable tenga la funcionalidad "Herir" en común. 7 | 8 | // Nosotros la implementaremos en el enemigo, así cuando ejecutemos el ataque, si detectamos algo atacable, 9 | // llamaremos a su función "Hurt", que controlará lo que pasa cuando se le hiere. 10 | public interface IAttackable 11 | { 12 | // Declaramos la función que deberán llevar todas las clases atacables. En este caso, Herir. 13 | // No tenemos que meter código en su interior, solo decirle el nombre de la función. 14 | // El código que controlará lo que pasa al ser herido lo controlará la clase donde implementemos esta interfaz. 15 | void Hurt (); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /03 - Melee Attack (2D)/Enemy.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace MelenitasDev 4 | { 5 | // Esta clase "Enemigo" implementa la interfaz "IAttackable", lo que le obliga a implementar 6 | // la función "Herir" y nos permite detectar desde fuera si se le puede atacar. 7 | public class Enemy : MonoBehaviour, IAttackable // Después de MonoBehaviour, implementamos la interfaz. 8 | { 9 | // ----- Fields 10 | private Animator anim; 11 | 12 | // ----- Unity Callbacks 13 | void Awake () 14 | { 15 | // Referenciamos el animator del enemigo. 16 | anim = GetComponent(); 17 | } 18 | 19 | // ----- Interface Methods 20 | 21 | // Esta función es la que nos obliga a implementar la interfaz "IAttackable". Todas las clases atacables 22 | // deberán llevarla, y cada una controlará que pasa cuando se le llama. 23 | public void Hurt () 24 | { 25 | // Ejecutamos la animación de muerte del enemigo. 26 | anim.SetTrigger("Die"); 27 | } 28 | 29 | // ----- Private Methods 30 | 31 | // Esta función se ejecuta en el último frame de la animación de muerte. 32 | private void Hide () 33 | { 34 | // Desactiva y oculta al enemigo. 35 | gameObject.SetActive(false); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /01 - Movement & Jump (2D)/AdventurerController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace MelenitasDev 4 | { 5 | public class AdventurerController : MonoBehaviour 6 | { 7 | // ----- Serialized Fields 8 | [Header("References")] 9 | [SerializeField] private Transform groundCheck; 10 | [SerializeField] private LayerMask whatIsGround; 11 | 12 | [Header("Settings")] 13 | [SerializeField] private float speed; 14 | [SerializeField] private float jumpForce; 15 | 16 | // ----- Fields 17 | private Rigidbody2D rb; 18 | private float xInput; 19 | 20 | // ----- Unity Callbacks 21 | void Awake () 22 | { 23 | // Referenciamos el RigidBody2D adjunto al personaje. 24 | rb = GetComponent(); 25 | } 26 | 27 | void Update () 28 | { 29 | // Llamamos a la función que gira al personaje. 30 | Flip(); 31 | 32 | // Detectamos si se presiona la tecla espacio y si el personaje está tocando el suelo. 33 | if (Input.GetKeyDown(KeyCode.Space) && IsGrounded()) 34 | { 35 | // Ejecutamos el salto. 36 | Jump(); 37 | } 38 | } 39 | 40 | void FixedUpdate () 41 | { 42 | // Llamamos al control de movimiento 43 | HandleMovement(); 44 | } 45 | 46 | // ----- Private Methods 47 | private void HandleMovement () 48 | { 49 | // Capturamos las teclas ("A" y "D") y ("←" y "→") para conocer la dirección. 50 | xInput = Input.GetAxisRaw("Horizontal"); 51 | 52 | // Creamos un vector para el movimiento horizontal multiplicando la dirección por la velocidad. 53 | // En el eje vertical mantenemos la velocidad del Rigidbody. 54 | Vector2 move = new Vector2(xInput * speed, rb.velocity.y); 55 | 56 | // Le pasamos el movimiento a la velocidad del Rigidbody. 57 | rb.velocity = move; 58 | } 59 | 60 | private void Flip () 61 | { 62 | // Si el personaje se mueve a la derecha... 63 | if (xInput > 0 && transform.localScale.x < 0) 64 | { 65 | // Ponemos la escala en X en positivo (Recuerda cambiar los unos por la escala de tu personaje). 66 | transform.localScale = new Vector3(1, 1, 1); 67 | } 68 | // Si el personaje se mueve a la izquierda... 69 | else if (xInput < 0 && transform.localScale.x > 0) 70 | { 71 | // Ponemos la escala en X en negativo (Recuerda cambiar los unos por la escala de tu personaje). 72 | transform.localScale = new Vector3(-1, 1, 1); 73 | } 74 | } 75 | 76 | private void Jump () 77 | { 78 | // Frenamos cualquier fuerza vertical (como la gravedad) para que no interfiera con el salto. 79 | rb.velocity = new Vector2(rb.velocity.x, 0); 80 | 81 | // Aplicamos una fuerza hacia arriba con la fuerza del salto que elijamos en el inspector. 82 | rb.AddForce(Vector2.up * (jumpForce * Time.fixedDeltaTime), ForceMode2D.Impulse); 83 | } 84 | 85 | private bool IsGrounded () 86 | { 87 | // Creamos un rayo desde el objeto colocado en los pies del personaje. 88 | Ray2D ray = new Ray2D(groundCheck.position, Vector2.down); 89 | 90 | // Lanzamos el rayo y almacenamos la colisión del rayo con los objetos en la capa "Suelo". 91 | RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, 0.05f, whatIsGround); 92 | 93 | // Devolvemos "true" si el rayo ha detectado suelo y "false" si no. 94 | return hit.collider is not null; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /02 - Animations (2D)/ AdventurerController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace MelenitasDev 4 | { 5 | public class AdventurerController : MonoBehaviour 6 | { 7 | // ----- Serialized Fields 8 | [Header("References")] 9 | [SerializeField] private Transform groundCheck; 10 | [SerializeField] private LayerMask whatIsGround; 11 | 12 | [Header("Settings")] 13 | [SerializeField] private float speed; 14 | [SerializeField] private float jumpForce; 15 | 16 | // ----- Fields 17 | private Rigidbody2D rb; 18 | private Animator anim; 19 | 20 | private float xInput; 21 | private AdventurerState currentState; 22 | 23 | // ----- Enums 24 | private enum AdventurerState 25 | { 26 | Idle, 27 | Running, 28 | Jumping, 29 | Falling 30 | } 31 | 32 | // ----- Unity Callbacks 33 | void Awake () 34 | { 35 | // Referenciamos los componentes adjuntos al personaje. 36 | rb = GetComponent(); 37 | anim = GetComponent(); 38 | } 39 | 40 | void Update () 41 | { 42 | // Llamamos a la función que gira al personaje. 43 | Flip(); 44 | 45 | // Detectamos si se presiona la tecla espacio y si el personaje está tocando el suelo. 46 | if (Input.GetKeyDown(KeyCode.Space) && IsGrounded()) 47 | { 48 | // Ejecutamos el salto. 49 | Jump(); 50 | } 51 | 52 | // Si el jugador no está tocando el suelo, ni saltando, ni tocando el suelo... 53 | if (xInput == 0 && currentState != AdventurerState.Jumping && IsGrounded()) 54 | { 55 | // Cambiamos el estado a Idle. 56 | ChangeState(AdventurerState.Idle); 57 | } 58 | // Si está tocando el teclado y tocando el suelo... 59 | else if (Mathf.Abs(xInput) > 0 && IsGrounded()) 60 | { 61 | // Cambiamos el estado a Corriendo. 62 | ChangeState(AdventurerState.Running); 63 | } 64 | // Si no está tocando el suelo y su velocidad en Y es negativa... 65 | else if (!IsGrounded() && rb.velocity.y < -0.5f) 66 | { 67 | // Cambiamos el estado a Cayendo. 68 | ChangeState(AdventurerState.Falling); 69 | } 70 | } 71 | 72 | void FixedUpdate () 73 | { 74 | // Llamamos al control de movimiento. 75 | HandleMovement(); 76 | } 77 | 78 | // ----- Private Methods 79 | private void HandleMovement () 80 | { 81 | // Capturamos las teclas ("A" y "D") y ("←" y "→") para conocer la dirección. 82 | xInput = Input.GetAxisRaw("Horizontal"); 83 | 84 | // Creamos un vector para el movimiento horizontal multiplicando la dirección por la velocidad. 85 | // En el eje vertical mantenemos la velocidad del Rigidbody. 86 | Vector2 move = new Vector2(xInput * speed, rb.velocity.y); 87 | 88 | // Le pasamos el movimiento a la velocidad del Rigidbody. 89 | rb.velocity = move; 90 | } 91 | 92 | private void Flip () 93 | { 94 | // Si el personaje se mueve a la derecha... 95 | if (xInput > 0 && transform.localScale.x < 0) 96 | { 97 | // Ponemos la escala en X en positivo (Recuerda cambiar los unos por la escala de tu personaje). 98 | transform.localScale = new Vector3(1, 1, 1); 99 | } 100 | // Si el personaje se mueve a la izquierda... 101 | else if (xInput < 0 && transform.localScale.x > 0) 102 | { 103 | // Ponemos la escala en X en negativo (Recuerda cambiar los unos por la escala de tu personaje). 104 | transform.localScale = new Vector3(-1, 1, 1); 105 | } 106 | } 107 | 108 | private void Jump () 109 | { 110 | // Frenamos cualquier fuerza vertical (como la gravedad) para que no interfiera con el salto. 111 | rb.velocity = new Vector2(rb.velocity.x, 0); 112 | 113 | // Aplicamos una fuerza hacia arriba con la fuerza del salto que elijamos en el inspector. 114 | rb.AddForce(Vector2.up * (jumpForce * Time.fixedDeltaTime), ForceMode2D.Impulse); 115 | 116 | // Cambiamos el estado a Saltando. 117 | ChangeState(AdventurerState.Jumping); 118 | } 119 | 120 | private bool IsGrounded () 121 | { 122 | // Creamos un rayo desde el objeto colocado en los pies del personaje. 123 | Ray2D ray = new Ray2D(groundCheck.position, Vector2.down); 124 | 125 | // Lanzamos el rayo y almacenamos la colisión del rayo con los objetos en la capa "Suelo". 126 | RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, 0.05f, whatIsGround); 127 | 128 | // Devolvemos "true" si el rayo ha detectado suelo y "false" si no. 129 | return hit.collider is not null; 130 | } 131 | 132 | private void ChangeState (AdventurerState newState) 133 | { 134 | // Si le pasamos el estado en el que ya está el personaje, paramos la función. 135 | if (newState == currentState) return; 136 | 137 | // Almacenamos el nuevo estado. 138 | currentState = newState; 139 | 140 | // Dependiendo del nuevo estado, notificaremos al Trigger del Animator. 141 | switch (newState) 142 | { 143 | case AdventurerState.Idle: 144 | anim.SetTrigger("Idle"); 145 | break; 146 | case AdventurerState.Running: 147 | anim.SetTrigger("Run"); 148 | break; 149 | case AdventurerState.Jumping: 150 | anim.SetTrigger("Jump"); 151 | break; 152 | case AdventurerState.Falling: 153 | anim.SetTrigger("Fall"); 154 | break; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /03 - Melee Attack (2D)/AdventurerController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace MelenitasDev 4 | { 5 | public class AdventurerController : MonoBehaviour 6 | { 7 | // ----- Serialized Fields 8 | [Header("References")] 9 | [SerializeField] private Transform groundCheck; 10 | [SerializeField] private LayerMask whatIsGround; 11 | [SerializeField] private Transform attackArea; 12 | 13 | [Header("Settings")] 14 | [SerializeField] private float speed; 15 | [SerializeField] private float jumpForce; 16 | 17 | // ----- Fields 18 | private Rigidbody2D rb; 19 | private Animator anim; 20 | 21 | private float xInput; 22 | private AdventurerState currentState; 23 | 24 | // ----- Enums 25 | private enum AdventurerState 26 | { 27 | Idle, 28 | Running, 29 | Jumping, 30 | Falling, 31 | Attacking 32 | } 33 | 34 | // ----- Unity Callbacks 35 | void Awake () 36 | { 37 | // Referenciamos los componentes adjuntos al personaje. 38 | rb = GetComponent(); 39 | anim = GetComponent(); 40 | } 41 | 42 | void Update () 43 | { 44 | // Llamamos a la función que gira al personaje. 45 | Flip(); 46 | 47 | // Detectamos si se presiona la tecla espacio y si el personaje está tocando el suelo. 48 | if (Input.GetKeyDown(KeyCode.Space) && IsGrounded()) 49 | { 50 | // Ejecutamos el salto. 51 | Jump(); 52 | } 53 | 54 | // Detectamos el click izquierdo del ratón. 55 | if (Input.GetMouseButtonDown(0)) 56 | { 57 | // Empezamos el ataque. 58 | StartAttack(); 59 | } 60 | 61 | // Si estamos atacando... 62 | if (currentState == AdventurerState.Attacking) 63 | { 64 | // Detenemos el movimiento. 65 | rb.velocity = Vector2.zero; 66 | return; 67 | } 68 | 69 | // Si el jugador no está tocando el suelo, ni saltando, ni tocando el suelo... 70 | if (xInput == 0 && currentState != AdventurerState.Jumping && IsGrounded()) 71 | { 72 | // Cambiamos el estado a Idle. 73 | ChangeState(AdventurerState.Idle); 74 | } 75 | // Si está tocando el teclado y tocando el suelo... 76 | else if (Mathf.Abs(xInput) > 0 && IsGrounded()) 77 | { 78 | // Cambiamos el estado a Corriendo. 79 | ChangeState(AdventurerState.Running); 80 | } 81 | // Si no está tocando el suelo y su velocidad en Y es negativa... 82 | else if (!IsGrounded() && rb.velocity.y < -0.5f) 83 | { 84 | // Cambiamos el estado a Cayendo. 85 | ChangeState(AdventurerState.Falling); 86 | } 87 | } 88 | 89 | void FixedUpdate () 90 | { 91 | // Llamamos al control de movimiento. 92 | HandleMovement(); 93 | } 94 | 95 | // Este evento se ejecuta cuando se dibujan los gizmos en el editor. 96 | // Lo usaremos para crear nuestro propio gizmo y poder ver el área de ataque. 97 | void OnDrawGizmos () 98 | { 99 | // Cambiamos el color del gizmo, en este caso, al azul. 100 | Gizmos.color = new Color(0, 0.35f, 1, 0.3f); 101 | // Dibujamos la caja que representa el área de ataque para poder ajustarla mejor. 102 | Gizmos.DrawCube(attackArea.position, attackArea.localScale); 103 | } 104 | 105 | // ----- Private Methods 106 | private void HandleMovement () 107 | { 108 | // Capturamos las teclas ("A" y "D") y ("←" y "→") para conocer la dirección. 109 | xInput = Input.GetAxisRaw("Horizontal"); 110 | 111 | // Si estamos atacando, detenemos la función para que el personaje no se mueva. 112 | if (currentState == AdventurerState.Attacking) return; 113 | 114 | // Creamos un vector para el movimiento horizontal multiplicando la dirección por la velocidad. 115 | // En el eje vertical mantenemos la velocidad del Rigidbody. 116 | Vector2 move = new Vector2(xInput * speed, rb.velocity.y); 117 | 118 | // Le pasamos el movimiento a la velocidad del Rigidbody. 119 | rb.velocity = move; 120 | } 121 | 122 | private void Flip () 123 | { 124 | // Si el personaje se mueve a la derecha... 125 | if (xInput > 0 && transform.localScale.x < 0) 126 | { 127 | // Ponemos la escala en X en positivo (Recuerda cambiar los unos por la escala de tu personaje). 128 | transform.localScale = new Vector3(1, 1, 1); 129 | } 130 | // Si el personaje se mueve a la izquierda... 131 | else if (xInput < 0 && transform.localScale.x > 0) 132 | { 133 | // Ponemos la escala en X en negativo (Recuerda cambiar los unos por la escala de tu personaje). 134 | transform.localScale = new Vector3(-1, 1, 1); 135 | } 136 | } 137 | 138 | private void Jump () 139 | { 140 | // Frenamos cualquier fuerza vertical (como la gravedad) para que no interfiera con el salto. 141 | rb.velocity = new Vector2(rb.velocity.x, 0); 142 | 143 | // Aplicamos una fuerza hacia arriba con la fuerza del salto que elijamos en el inspector. 144 | rb.AddForce(Vector2.up * (jumpForce * Time.fixedDeltaTime), ForceMode2D.Impulse); 145 | 146 | // Cambiamos el estado a Saltando. 147 | ChangeState(AdventurerState.Jumping); 148 | } 149 | 150 | private void StartAttack () 151 | { 152 | // Si no estamos tocando el suelo, cancelamos el ataque. 153 | if (!IsGrounded()) return; 154 | 155 | // Cambiamos el estado a Atacando. 156 | ChangeState(AdventurerState.Attacking); 157 | } 158 | 159 | // Esta función se ejecuta desde la animación de ataque. 160 | private void Attack () 161 | { 162 | // Creamos una caja en la zona del área de ataque y guardamos todos los colisionadores que 163 | // detectemos en su interior en el array "colliders". 164 | Collider2D[] colliders = Physics2D.OverlapBoxAll(attackArea.position, 165 | attackArea.localScale, 0); 166 | 167 | // Comprobamos todos los colisionadores que hemos guardado en la línea anterior... 168 | foreach (Collider2D collider in colliders) 169 | { 170 | // Y si alguno de ellos es "Atacable"... 171 | if (collider.TryGetComponent(out IAttackable attackable)) 172 | { 173 | // Llamamos a su función Herir. 174 | attackable.Hurt(); 175 | } 176 | } 177 | } 178 | 179 | // Esta función se ejecuta desde el último frame de la animación de ataque. 180 | private void FinishAttack () 181 | { 182 | // Cambiamos el estado a Idle. 183 | ChangeState(AdventurerState.Idle); 184 | } 185 | 186 | private bool IsGrounded () 187 | { 188 | // Creamos un rayo desde el objeto colocado en los pies del personaje. 189 | Ray2D ray = new Ray2D(groundCheck.position, Vector2.down); 190 | 191 | // Lanzamos el rayo y almacenamos la colisión del rayo con los objetos en la capa "Suelo". 192 | RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, 0.05f, whatIsGround); 193 | 194 | // Devolvemos "true" si el rayo ha detectado suelo y "false" si no. 195 | return hit.collider is not null; 196 | } 197 | 198 | private void ChangeState (AdventurerState newState) 199 | { 200 | // Si le pasamos el estado en el que ya está el personaje, paramos la función. 201 | if (newState == currentState) return; 202 | 203 | // Almacenamos el nuevo estado. 204 | currentState = newState; 205 | 206 | // Dependiendo del nuevo estado, notificaremos al Trigger del Animator. 207 | switch (newState) 208 | { 209 | case AdventurerState.Idle: 210 | anim.SetTrigger("Idle"); 211 | break; 212 | case AdventurerState.Running: 213 | anim.SetTrigger("Run"); 214 | break; 215 | case AdventurerState.Jumping: 216 | anim.SetTrigger("Jump"); 217 | break; 218 | case AdventurerState.Falling: 219 | anim.SetTrigger("Fall"); 220 | break; 221 | case AdventurerState.Attacking: 222 | anim.SetTrigger("Attack"); 223 | break; 224 | } 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /04 - Double Jump (2D)/AdventurerController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace MelenitasDev 4 | { 5 | public class AdventurerController : MonoBehaviour 6 | { 7 | // ----- Serialized Fields 8 | [Header("References")] 9 | [SerializeField] private Transform groundCheck; 10 | [SerializeField] private LayerMask whatIsGround; 11 | [SerializeField] private Transform attackArea; 12 | 13 | [Header("Settings")] 14 | [SerializeField] private float speed; 15 | [SerializeField] private float jumpForce; 16 | [SerializeField] private float doubleJumpForce; 17 | 18 | // ----- Fields 19 | private Rigidbody2D rb; 20 | private Animator anim; 21 | 22 | private float xInput; 23 | private AdventurerState currentState; 24 | private bool doubleJumpUsed; 25 | 26 | // ----- Enums 27 | private enum AdventurerState 28 | { 29 | Idle, 30 | Running, 31 | Jumping, 32 | DoubleJumping, 33 | Falling, 34 | Attacking 35 | } 36 | 37 | // ----- Unity Callbacks 38 | void Awake () 39 | { 40 | // Referenciamos los componentes adjuntos al personaje. 41 | rb = GetComponent(); 42 | anim = GetComponent(); 43 | } 44 | 45 | void Update () 46 | { 47 | // Llamamos a la función que gira al personaje. 48 | Flip(); 49 | 50 | // Detectamos si se presiona la tecla espacio... 51 | if (Input.GetKeyDown(KeyCode.Space)) 52 | { 53 | // Si está tocando el suelo... 54 | if (IsGrounded()) 55 | { 56 | // Ejecutamos el salto. 57 | Jump(); 58 | } 59 | // Si no, comprobamos si el doble salto no ha sido usado... 60 | else if (!doubleJumpUsed) 61 | { 62 | // Ejecutamos el doble salto. 63 | DoubleJump(); 64 | } 65 | } 66 | 67 | // Cuando el doble salto ha sido usado y tocamos de nuevo el suelo... 68 | if (doubleJumpUsed && IsGrounded()) 69 | { 70 | // Volvemos a permitir el uso del doble salto. 71 | doubleJumpUsed = false; 72 | } 73 | 74 | // Detectamos el click izquierdo del ratón. 75 | if (Input.GetMouseButtonDown(0)) 76 | { 77 | // Empezamos el ataque. 78 | StartAttack(); 79 | } 80 | 81 | // Si estamos atacando... 82 | if (currentState == AdventurerState.Attacking) 83 | { 84 | // Detenemos el movimiento. 85 | rb.velocity = Vector2.zero; 86 | return; 87 | } 88 | 89 | // Si el jugador está quieto en el eje X, sin saltar y tocando el suelo... 90 | if (xInput == 0 && currentState != AdventurerState.Jumping && IsGrounded()) 91 | { 92 | // Cambiamos el estado a "Idle". 93 | ChangeState(AdventurerState.Idle); 94 | } 95 | // Si está moviéndose en el eje X, tocando el suelo y sin saltar... 96 | else if (Mathf.Abs(xInput) > 0 && IsGrounded() && currentState != AdventurerState.Jumping) 97 | { 98 | // Cambiamos el estado a "Corriendo". 99 | ChangeState(AdventurerState.Running); 100 | } 101 | // Y si no está tocando el suelo y su velocidad en Y es negativa... 102 | else if (!IsGrounded() && rb.velocity.y < -0.5f) 103 | { 104 | // Cambiamos el estado a "Cayendo". 105 | ChangeState(AdventurerState.Falling); 106 | } 107 | } 108 | 109 | void FixedUpdate () 110 | { 111 | // Llamamos al control de movimiento. 112 | HandleMovement(); 113 | } 114 | 115 | // Este evento se ejecuta cuando se dibujan los gizmos en el editor. 116 | // Lo usaremos para crear nuestro propio gizmo y poder ver el área de ataque. 117 | void OnDrawGizmosSelected () 118 | { 119 | // Cambiamos el color del gizmo, en este caso, al azul. 120 | Gizmos.color = new Color(0, 0.35f, 1, 0.3f); 121 | // Dibujamos la caja que representa el área de ataque para poder ajustarla mejor. 122 | // Gizmos.DrawCube(attackArea.position, attackArea.localScale); 123 | } 124 | 125 | // ----- Private Methods 126 | private void HandleMovement () 127 | { 128 | // Capturamos las teclas ("A" y "D") y ("←" y "→") para conocer la dirección. 129 | xInput = Input.GetAxisRaw("Horizontal"); 130 | 131 | // Si estamos atacando, detenemos la función para que el personaje no se mueva. 132 | if (currentState == AdventurerState.Attacking) return; 133 | 134 | // Creamos un vector para el movimiento horizontal multiplicando la dirección por la velocidad. 135 | // En el eje vertical mantenemos la velocidad del Rigidbody. 136 | Vector2 move = new Vector2(xInput * speed, rb.velocity.y); 137 | 138 | // Le pasamos el movimiento a la velocidad del Rigidbody. 139 | rb.velocity = move; 140 | } 141 | 142 | private void Flip () 143 | { 144 | // Si el personaje se mueve a la derecha... 145 | if (xInput > 0 && transform.localScale.x < 0) 146 | { 147 | // Ponemos la escala en X en positivo (Recuerda cambiar los unos por la escala de tu personaje). 148 | transform.localScale = new Vector3(1, 1, 1); 149 | } 150 | // Si el personaje se mueve a la izquierda... 151 | else if (xInput < 0 && transform.localScale.x > 0) 152 | { 153 | // Ponemos la escala en X en negativo (Recuerda cambiar los unos por la escala de tu personaje). 154 | transform.localScale = new Vector3(-1, 1, 1); 155 | } 156 | } 157 | 158 | private void Jump () 159 | { 160 | // Frenamos cualquier fuerza vertical (como la gravedad) para que no interfiera con el salto. 161 | rb.velocity = new Vector2(rb.velocity.x, 0); 162 | 163 | // Aplicamos una fuerza hacia arriba con la fuerza del salto que elijamos en el inspector. 164 | rb.AddForce(Vector2.up * (jumpForce * Time.fixedDeltaTime), ForceMode2D.Impulse); 165 | 166 | // Cambiamos el estado a "Saltando". 167 | ChangeState(AdventurerState.Jumping); 168 | } 169 | 170 | private void DoubleJump () 171 | { 172 | // Frenamos cualquier fuerza vertical (como la gravedad) para que no interfiera con el salto. 173 | rb.velocity = new Vector2(rb.velocity.x, 0); 174 | 175 | // Aplicamos una fuerza hacia arriba con la fuerza del salto que elijamos en el inspector. 176 | rb.AddForce(Vector2.up * (doubleJumpForce * Time.fixedDeltaTime), ForceMode2D.Impulse); 177 | 178 | // Cambiamos el estado a "Saltando Doble". 179 | ChangeState(AdventurerState.DoubleJumping); 180 | 181 | // Marcamos que el doble salto ha sido usado para no poder usarlo infinitamente. 182 | doubleJumpUsed = true; 183 | } 184 | 185 | private void StartAttack () 186 | { 187 | // Si no estamos tocando el suelo, cancelamos el ataque. 188 | if (!IsGrounded()) return; 189 | 190 | // Cambiamos el estado a "Atacando". 191 | ChangeState(AdventurerState.Attacking); 192 | } 193 | 194 | // Esta función se ejecuta desde la animación de ataque. 195 | private void Attack () 196 | { 197 | // Creamos una caja en la zona del área de ataque y guardamos todos los colisionadores que 198 | // detectemos en su interior en el array "colliders". 199 | Collider2D[] colliders = Physics2D.OverlapBoxAll(attackArea.position, 200 | attackArea.localScale, 0); 201 | 202 | // Comprobamos todos los colisionadores que hemos guardado en la línea anterior... 203 | foreach (Collider2D collider in colliders) 204 | { 205 | // Y si alguno de ellos es "Atacable"... 206 | if (collider.TryGetComponent(out IAttackable attackable)) 207 | { 208 | // Llamamos a su función Herir. 209 | attackable.Hurt(); 210 | } 211 | } 212 | } 213 | 214 | // Esta función se ejecuta desde el último frame de la animación de ataque. 215 | private void FinishAttack () 216 | { 217 | // Cambiamos el estado a "Idle". 218 | ChangeState(AdventurerState.Idle); 219 | } 220 | 221 | private bool IsGrounded () 222 | { 223 | // Creamos un rayo desde el objeto colocado en los pies del personaje. 224 | Ray2D ray = new Ray2D(groundCheck.position, Vector2.down); 225 | 226 | // Lanzamos el rayo y almacenamos la colisión del rayo con los objetos en la capa "Suelo". 227 | RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, 0.05f, whatIsGround); 228 | 229 | // Devolvemos "true" si el rayo ha detectado suelo y "false" si no. 230 | return hit.collider is not null; 231 | } 232 | 233 | private void ChangeState (AdventurerState newState) 234 | { 235 | // Si le pasamos el estado en el que ya está el personaje, paramos la función. 236 | if (newState == currentState) return; 237 | 238 | // Almacenamos el nuevo estado. 239 | currentState = newState; 240 | 241 | // Dependiendo del nuevo estado, notificaremos al Trigger del Animator. 242 | switch (newState) 243 | { 244 | case AdventurerState.Idle: 245 | anim.SetTrigger("Idle"); 246 | break; 247 | case AdventurerState.Running: 248 | anim.SetTrigger("Run"); 249 | break; 250 | case AdventurerState.Jumping: 251 | anim.SetTrigger("Jump"); 252 | break; 253 | case AdventurerState.DoubleJumping: 254 | anim.SetTrigger("DoubleJump"); 255 | break; 256 | case AdventurerState.Falling: 257 | anim.SetTrigger("Fall"); 258 | break; 259 | case AdventurerState.Attacking: 260 | anim.SetTrigger("Attack"); 261 | break; 262 | } 263 | } 264 | } 265 | } 266 | --------------------------------------------------------------------------------