Le héros
Fait partie de la section Architecture de jeu dans Unity du Journey Unity de Coddy. Leçon 41 sur 50.
Ce chapitre permet de créer Arena Survivor, le dernier jeu du parcours. Des vagues d’ennemis déferlent dans une arène entourée de murs et tu les repousses à l’épée : des soldats qui marchent vers toi et des orbes qui attendent avant de foncer. Une vague vaincue laisse un bonus, un HUD affiche ta santé et la vague en cours, et quand ta santé est épuisée, la partie se termine et ta meilleure partie est sauvegardée. Le jeu utilise les outils de cette section lorsqu’ils sont adaptés : un Enemy abstrait avec deux types, IDamageable pour tout ce qui peut subir des dégâts, les vagues sous forme d’asset ScriptableObject, des objets à ramasser derrière une interface, un HUD qui écoute uniquement les événements C#, et une sauvegarde JSON. Les douze scripts sont dans le projet dès le départ. Seul Hero contient déjà du code, et chaque leçon en ajoute davantage.
L’épée du héros touche ce qui se trouve devant lui : le coup doit donc vérifier si la cible est « devant ». Vector3.Dot(a, b) multiplie les composantes correspondantes de deux vecteurs et les additionne : a.x * b.x + a.y * b.y + a.z * b.z. Pour deux directions de longueur 1, le résultat est le cosinus de l’angle qui les sépare : 1 lorsqu’elles pointent dans la même direction, 0.866 à 30 degrés, 0.707 à 45 degrés, 0 à angle droit et -1 lorsqu’elles pointent dans des directions opposées. Ainsi, « à moins de 30 degrés de la direction vers laquelle je regarde » devient « un produit scalaire d’au moins 0.866 », sans avoir à calculer d’angle. Ce robot sentinelle donne l’alarme lorsque le joueur se trouve à moins de 10 unités et dans son champ de vision de 30 degrés :
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!");
}
}Les deux directions doivent avoir une longueur de 1. transform.forward a toujours cette longueur, mais toPlayer est aussi long que la distance : il faut donc d’abord le normaliser. Sinon, un joueur deux fois plus éloigné donnerait un produit scalaire deux fois plus grand. Sa coordonnée y est mise à 0 avant cela : un joueur sur une passerelle plus élevée est donc pris en compte selon sa position sur la carte, et non selon sa hauteur. Le signe distingue l’avant de l’arrière : un produit scalaire supérieur à 0 indique n’importe quelle position dans la moitié avant, tandis qu’un résultat inférieur à 0 indique n’importe quelle position derrière.
Défi
MoyenDémarre Arena Survivor avec l’épée du héros. Le déplacement est implémenté ainsi : l’action Move fait avancer le héros avec SimpleMove et le tourne pour qu’il regarde dans la direction où il avance. Cinq mannequins d’entraînement portant l’étiquette Enemy se tiennent autour de lui. Dans l’Inspector, donne à Hero une reach de (2) et un swingCooldown de (0.5), ainsi que l’objet de coupure affiché pendant une attaque.
- La barre d’espace ou un clic gauche déclenche une attaque, au plus une fois toutes les
swingCooldownsecondes : une pression avant la fin du délai ne fait rien. Lis les touches avecKeyboard.currentetMouse.current, puis appelleSwing(). - Dans
Swing(), après l’affichage de la coupure, détruis chaque objet portant l’étiquetteEnemyqui se trouve à portée (reach) et devant le héros. À portée signifie que tout collider trouvé parPhysics.OverlapSphereautour du héros avec un rayon dereachest concerné. Devant signifie que le produit scalaire entre l’avant du héros et la direction vers l’objet, projetée au sol, est d’au moins 0.5 : jusqu’à 60 degrés de chaque côté.
Laisse les autres scripts tels quels. Appuie sur Jouer, approche-toi des mannequins et attaque, puis clique sur Vérifier.
Essayez vous-même
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);
}
// La barre d’espace ou un clic gauche déclenche une attaque, au maximum une fois toutes les swingCooldown secondes
}
void Swing()
{
StartCoroutine(ShowSlash());
// détruire tout ce qui porte le tag Enemy, se trouve à portée et devant le héros
}
IEnumerator ShowSlash()
{
slash.SetActive(true);
yield return new WaitForSeconds(0.1f);
slash.SetActive(false);
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Architecture de jeu dans Unity
1Des composants, pas des hiérarchies
Des composants simplesUn script de base pour les ennemisDes composants abstraitsDes composants qui fonctionnent ensembleRécapitulatif : tourelle modulaire4Données sérialisées
Classes sérialisablesListes dans l’inspecteurÉnumérations dans l’inspecteurRécapitulatif : table de butin7Machines à états
Les états sous forme de classesUne machine à étatsTransitionsLes cerveaux des ennemisRécapitulatif : états d’une porte10Projet : Survivant de l’arène
Le hérosTypes d’ennemis2Interfaces dans Unity
IDamageableInterfaces et TryGetComponentGetComponentsRécapitulatif : touche d’interaction5ScriptableObjects
Assets de donnéesStatistiques des armesParamètres partagésLes vagues sous forme de donnéesRécapitulatif : base de données d’objets