El héroe
Parte de la sección Arquitectura de juegos en Unity del Journey de Unity de Coddy. Lección 50 de 64.
En este capítulo crearás Arena Survivor, el último juego del curso. Oleadas de enemigos invaden una arena amurallada y tendrás que defenderte de ellos con una espada: soldados rasos que caminan hacia ti y orbes que esperan y luego se lanzan al ataque. Al derrotar una oleada, esta deja una mejora; un HUD muestra tu salud y la oleada, y cuando se agota tu salud, la partida termina y se guarda tu mejor partida. El juego utiliza las herramientas de esta sección donde encajan: un Enemy abstracto con dos tipos, IDamageable para todo lo que puede recibir daño, las oleadas como recurso de ScriptableObject, los objetos que se recogen mediante una interfaz, un HUD que solo escucha eventos de C# y un guardado en JSON. Los doce scripts están en el proyecto desde el principio. Por ahora solo funciona Hero; los demás contienen los campos que necesitará una lección posterior, y en cada lección se completan más.
La espada del héroe golpea lo que está delante de él, así que el ataque necesita comprobar qué está «delante». Vector3.Dot(a, b) multiplica las partes correspondientes de dos vectores y las suma: a.x * b.x + a.y * b.y + a.z * b.z. Para dos direcciones de longitud 1, el resultado es el coseno del ángulo que forman: 1 cuando apuntan en la misma dirección, 0.866 a 30 grados, 0.707 a 45, 0 en ángulo recto y -1 cuando apuntan en direcciones opuestas. Así que «a menos de 30 grados de donde miro» se convierte en «un producto escalar de al menos 0.866», sin tener que calcular ningún ángulo. Este robot centinela da la alarma cuando el jugador está a menos de 10 unidades y dentro de su campo de visión de 30 grados:
public Transform player;
public float range = 10f;
void Update()
{
Vector3 toPlayer = player.position - transform.position;
toPlayer.y = 0f;
bool near = toPlayer.magnitude <= range;
bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
if (near && inView)
{
Debug.Log("Alarm!");
}
}Ambas direcciones deben tener longitud 1. transform.forward siempre la tiene, pero toPlayer tiene la longitud de la distancia, así que primero se normaliza; de lo contrario, si el jugador estuviera el doble de lejos, el producto escalar sería el doble. Su componente y se establece en 0 antes de eso, así que la posición de un jugador en una pasarela más elevada se cuenta según dónde está en el mapa, no según su altura. El signo permite distinguir delante de detrás: un producto escalar mayor que 0 indica cualquier lugar de la mitad delantera; uno menor que 0, cualquier lugar detrás.
Desafío
IntermedioDale una espada al héroe.
- En
Update(), cuando se pulse Space o se haga clic con el botón izquierdo yTime.time >= nextSwing, establecenextSwing = Time.time + swingCooldown;y llama aSwing(). - En
Swing(), recorrePhysics.OverlapSphere(transform.position, reach). - Destruye cada impacto con la etiqueta
Enemycuya dirección horizontaltoHit(con y establecido en 0) haga queVector3.Dot(transform.forward, toHit.normalized) >= 0.5f.
Pulsa «Jugar» y da un golpe: los muñecos de entrenamiento que estén delante desaparecerán. Después, pulsa «Comprobar».
Pruébalo tú mismo
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class Hero : MonoBehaviour
{
[SerializeField] private float speed = 5f;
[SerializeField] private float reach = 2f;
[SerializeField] private float swingCooldown = 0.5f;
[SerializeField] private GameObject slash;
private CharacterController controller;
private InputAction moveAction;
private float nextSwing;
void Start()
{
controller = GetComponent<CharacterController>();
moveAction = InputSystem.actions.FindAction("Move");
}
void Update()
{
Vector2 input = moveAction.ReadValue<Vector2>();
Vector3 move = new Vector3(input.x, 0f, input.y);
controller.SimpleMove(move * speed);
if (move != Vector3.zero)
{
transform.rotation = Quaternion.LookRotation(move);
}
// La barra espaciadora o un clic izquierdo ejecutan un ataque, como máximo una vez cada swingCooldown segundos
}
void Swing()
{
StartCoroutine(ShowSlash());
// destruye todo lo que tenga la etiqueta Enemy, esté dentro del alcance y delante del héroe
}
IEnumerator ShowSlash()
{
slash.SetActive(true);
yield return new WaitForSeconds(0.1f);
slash.SetActive(false);
}
}
Todas las lecciones de Arquitectura de juegos en Unity
1Componentes, no jerarquías
Componentes pequeñosUn script base para enemigosComponentes abstractosComponentes que trabajan juntosRepaso: torreta modularRepaso: cañón doble4Datos serializados
Clases serializablesListas en el InspectorEnumeraciones en el InspectorRepaso: tabla de botínRepaso: sortear el botín7Máquinas de estados
Estados como clasesEl estado de alertaUna máquina de estadosTransicionesCerebros enemigosEl caballero atacaRepaso: estados de puertas10Proyecto: Superviviente de la arena
El héroeTipos de enemigos2Interfaces en Unity
IDamageableInterfaces y TryGetComponentGetComponentsRepaso: Tecla de interacciónRepaso: El más cercano al alcance5ScriptableObjects
Activos de datosEstadísticas de armasConfiguración compartidaGuardar el vientoOleadas como datosRepaso: base de datos de objetosRepaso: comprar en la tienda3Eventos
Eventos de C# en UnitySuscribirse en OnEnableCampos UnityEventLos eventos controlan la interfazRepaso: ventana emergente de logrosRepaso: temporizador de la ventana emergentePractica por tu cuenta: Playground de Unity