L’eroe
Fa parte della sezione Architettura dei giochi in Unity del percorso Unity di Coddy. Lezione 41 di 50.
Questo capitolo realizza Arena Survivor, l’ultimo gioco del percorso. Ondate di nemici si riversano in un’arena circondata da mura e tu le affronti con una spada: fanti che ti vengono incontro e sfere che aspettano e poi scattano. Un’ondata sconfitta lascia un potenziamento, un HUD mostra la tua salute e l’ondata, e quando la salute si esaurisce la partita finisce e viene salvata la tua migliore partita. Il gioco usa gli strumenti di questa sezione quando sono adatti: un Enemy astratto con due tipi, IDamageable per tutto ciò che può subire danni, le ondate come risorsa ScriptableObject, gli oggetti raccolti tramite un’interfaccia, un HUD che ascolta solo gli eventi C# e un salvataggio JSON. Tutti e dodici gli script sono presenti nel progetto fin dall’inizio. Solo Hero contiene già del codice, e ogni lezione ne aggiunge altro.
La spada dell’eroe colpisce ciò che si trova davanti, quindi il fendente deve verificare che il bersaglio sia «davanti». Vector3.Dot(a, b) moltiplica le componenti corrispondenti di due vettori e le somma: a.x * b.x + a.y * b.y + a.z * b.z. Per due direzioni di lunghezza 1, il risultato è il coseno dell’angolo che le separa: 1 quando puntano nella stessa direzione, 0.866 a 30 gradi, 0.707 a 45, 0 ad angolo retto e -1 quando puntano in direzioni opposte. Quindi «entro 30 gradi dalla direzione in cui sono rivolto» diventa «un prodotto scalare di almeno 0.866», senza dover calcolare alcun angolo. Questo robot sentinella dà l’allarme quando il giocatore si trova entro 10 unità e nel suo campo visivo di 30 gradi:
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!");
}
}Entrambe le direzioni devono avere lunghezza 1. transform.forward ce l’ha sempre, ma toPlayer ha la stessa lunghezza della distanza, quindi viene normalizzato per primo; altrimenti un giocatore a una distanza doppia produrrebbe un prodotto scalare doppio. Prima di questo passaggio, la sua coordinata y viene impostata a 0, così un giocatore su una passerella più alta viene considerato in base alla sua posizione sulla mappa, non alla sua altezza. Il segno distingue il davanti dal dietro: un prodotto scalare maggiore di 0 indica qualsiasi posizione nella metà anteriore, mentre uno minore di 0 indica qualsiasi posizione alle spalle.
Sfida
MedioInizia Arena Survivor con la spada dell’eroe. Il movimento è descritto così: l’azione Move fa camminare l’eroe con SimpleMove e lo fa ruotare nella direzione in cui cammina. Cinque manichini da addestramento con il tag Enemy sono disposti intorno a lui. Nell’Inspector imposta per Hero reach (2) e swingCooldown (0.5), e la falce che mostra durante un fendente.
- La barra spaziatrice o un clic sinistro eseguono un fendente, al massimo una volta ogni
swingCooldownsecondi: una pressione prima che il tempo di attesa sia terminato non fa nulla. Leggi i tasti conKeyboard.currenteMouse.current, e chiamaSwing(). - In
Swing(), dopo aver mostrato la falce, distruggi ogni oggetto con il tagEnemyche si trova entroreache di fronte all’eroe. Entro la portata si trova qualsiasi collider rilevato daPhysics.OverlapSphereintorno all’eroe con un raggio direach. Di fronte significa che il prodotto scalare tra la direzione in avanti dell’eroe e la direzione verso l’oggetto lungo il terreno è almeno 0.5: fino a 60 gradi da entrambi i lati.
Lascia invariati gli altri script. Premi Play, avvicinati ai manichini e sferra un fendente, poi premi Check.
Provalo tu
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;
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);
}
// Space o un clic sinistro fanno sferrare un colpo, al massimo una volta ogni swingCooldown secondi
}
void Swing()
{
StartCoroutine(ShowSlash());
// distruggi tutto ciò che ha il tag Enemy, si trova entro reach ed è davanti all'eroe
}
IEnumerator ShowSlash()
{
slash.SetActive(true);
yield return new WaitForSeconds(0.1f);
slash.SetActive(false);
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Architettura dei giochi in Unity
1Componenti, non gerarchie
Componenti piccoliUno script base per i nemiciComponenti astrattiComponenti che collaboranoRiepilogo: torretta modulare4Dati serializzati
Classi serializzabiliListe nell'InspectorEnum nell'InspectorRiepilogo: tabella del bottino7Macchine a stati
Gli stati come classiUna macchina a statiTransizioniIntelligenze dei nemiciRiepilogo: stati della porta10Progetto: Sopravvissuto nell’arena
L’eroeTipi di nemici5ScriptableObjects
Asset di datiStatistiche delle armiImpostazioni condiviseOndate come datiRiepilogo: database degli oggetti