Menu
CoddyTech

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.

challenge icon

Défi

Moyen

Dé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 swingCooldown secondes : une pression avant la fin du délai ne fait rien. Lis les touches avec Keyboard.current et Mouse.current, puis appelle Swing().
  • Dans Swing(), après l’affichage de la coupure, détruis chaque objet portant l’étiquette Enemy qui se trouve à portée (reach) et devant le héros. À portée signifie que tout collider trouvé par Physics.OverlapSphere autour du héros avec un rayon de reach est 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);
    }
}
quiz iconTestez-vous

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