Le héros
Fait partie de la section Architecture de jeu dans Unity du Journey Unity de Coddy. Leçon 50 sur 64.
Ce chapitre permet de créer Arena Survivor, le dernier jeu du cours. Des vagues d’ennemis envahissent une arène entourée de murs et tu les repousses avec une épée : des grognards qui avancent vers toi et des orbes qui attendent, puis foncent. Une vague vaincue laisse un bonus, un HUD affiche ta santé et la vague, et quand ta santé tombe à zéro, la partie se termine et ton meilleur résultat est sauvegardé. Le jeu utilise les outils de cette section là où ils sont utiles : un Enemy abstrait avec deux types, IDamageable pour tout ce qui peut être blessé, 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 présents dans le projet dès le début. Seul Hero fonctionne pour l’instant ; les autres contiennent les champs nécessaires à une leçon ultérieure, et chaque leçon les complète un peu plus.
L’épée du héros frappe ce qui se trouve devant lui, il faut donc vérifier que la cible est « devant ». Vector3.Dot(a, b) multiplie les éléments correspondants 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 correspond au cosinus de l’angle entre elles : 1 lorsqu’elles pointent dans la même direction, 0.866 à 30 degrés, 0.707 à 45, 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 surélevée est ainsi pris en compte selon sa position sur la carte, et non selon son altitude. Le signe permet de distinguer l’avant de l’arrière : un produit scalaire supérieur à 0 correspond à la moitié avant, et un produit inférieur à 0 à toute la partie arrière.
Défi
MoyenDonne une épée au héros.
- Dans
Update(), lorsque la barre d’espace ou un clic gauche est enfoncé et queTime.time >= nextSwing, définisnextSwing = Time.time + swingCooldown;et appelleSwing(). - Dans
Swing(), parcoursPhysics.OverlapSphere(transform.position, reach). - Détruis chaque objet touché portant l’étiquette
Enemydont la direction à plattoHit(avec y défini sur 0) donneVector3.Dot(transform.forward, toHit.normalized) >= 0.5f.
Clique sur « Jouer » et donne un coup d’épée : les mannequins devant toi disparaissent. 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;
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 touche 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);
}
}
Toutes les leçons de Architecture de jeu dans Unity
1Des composants, pas des hiérarchies
De petits composantsUn script de base pour les ennemisComposants abstraitsDes composants qui fonctionnent ensembleRécapitulatif : tourelle modulaireRécapitulatif : canon double4Données sérialisées
Classes sérialisablesListes dans l’InspecteurÉnumérations dans l’InspecteurRécapitulatif : table de butinRécapitulatif : tirer le butin2Les interfaces dans Unity
IDamageableInterfaces et TryGetComponentGetComponentsRécapitulatif : touche d’interactionLe plus proche à portée5ScriptableObjects
Ressources de donnéesStatistiques des armesParamètres partagésSauver le ventLes vagues comme donnéesRécapitulatif : base de données des objetsRécapitulatif : acheter dans la boutique3Événements
Les événements C# dans UnityS’abonner dans OnEnableChamps UnityEventLes événements pilotent l’interfaceRécapitulatif : fenêtre de succèsRécapitulatif : minuteur de la fenêtreEntraînez-vous par vous-même : Playground Unity