Menu
CoddyTech

Der Held

Teil des Abschnitts Spielarchitektur in Unity der Unity-Journey von Coddy. Lektion 41 von 50.

In diesem Kapitel entsteht Arena Survivor, das letzte Spiel der Reise. Wellen von Gegnern strömen in eine ummauerte Arena, und du wehrst sie mit einem Schwert ab: Grunts, die auf dich zugehen, und Orbs, die warten und dann lossprinten. Eine besiegte Welle hinterlässt ein Power-up, ein HUD zeigt deine Gesundheit und die Welle an, und wenn deine Gesundheit aufgebraucht ist, endet der Durchlauf und dein bester Durchlauf wird gespeichert. Das Spiel verwendet die Werkzeuge aus diesem Abschnitt, wo sie passen: einen abstrakten Enemy mit zwei Arten, IDamageable für alles, dem Schaden zugefügt werden kann, die Wellen als ScriptableObject-Asset, Pickups hinter einer Schnittstelle, ein HUD, das nur auf C#-Ereignisse hört, und einen JSON-Spielstand. Alle zwölf Skripte sind von Anfang an im Projekt. Nur Hero enthält bereits Code, und in jeder Lektion kommt mehr dazu.

Das Schwert des Helden trifft, was sich vor ihm befindet, daher braucht der Hieb einen Test dafür, ob etwas „vor ihm“ ist. Vector3.Dot(a, b) multipliziert die entsprechenden Bestandteile zweier Vektoren und addiert sie: a.x * b.x + a.y * b.y + a.z * b.z. Bei zwei Richtungen der Länge 1 ist das der Kosinus des Winkels zwischen ihnen: 1, wenn sie in dieselbe Richtung zeigen, 0.866 bei 30 Grad, 0.707 bei 45 Grad, 0 bei rechten Winkeln und -1, wenn sie in entgegengesetzte Richtungen zeigen. „Innerhalb von 30 Grad in Blickrichtung“ bedeutet also „ein Skalarprodukt von mindestens 0.866“, ohne dass ein Winkel berechnet werden muss. Dieser Wachroboter schlägt Alarm, wenn der Spieler höchstens 10 Einheiten entfernt ist und sich innerhalb seines 30-Grad-Sichtfelds befindet:

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!");
    }
}

Beide Richtungen müssen die Länge 1 haben. transform.forward hat diese Länge immer, aber toPlayer ist so lang wie die Entfernung, daher wird der Vektor zuerst normalisiert; andernfalls würde ein Spieler in doppelter Entfernung das doppelte Skalarprodukt ergeben. Der y-Wert wird davor auf 0 gesetzt, damit ein Spieler auf einem höher gelegenen Laufsteg anhand seiner Position auf der Karte zählt und nicht danach, wie hoch er sich befindet. Das Vorzeichen unterscheidet vorne von hinten: Ein Skalarprodukt über 0 bedeutet, dass etwas irgendwo in der vorderen Hälfte ist, unter 0, dass es irgendwo hinter dir ist.

challenge icon

Aufgabe

Mittel

Starte Arena Survivor mit dem Schwert des Helden. Das Gehen ist bereits implementiert: Die Aktion Move bewegt den Helden mit SimpleMove und dreht ihn in die Richtung, in die er geht. Fünf Trainingspuppen mit dem Tag Enemy stehen um ihn herum. Im Inspector sind für Hero reach (2) und swingCooldown (0.5) sowie der Hieb zugewiesen, der während eines Schwungs angezeigt wird.

  • Mit der Leertaste oder einem Linksklick wird geschwungen, höchstens einmal alle swingCooldown Sekunden: Ein Drücken, bevor die Abklingzeit vorbei ist, bewirkt nichts. Lies die Tasten mit Keyboard.current und Mouse.current aus und rufe Swing() auf.
  • Zerstöre in Swing(), nachdem der Hieb angezeigt wurde, jedes Objekt mit dem Tag Enemy, das sich innerhalb von reach und vor dem Helden befindet. Innerhalb der Reichweite befindet sich jeder Collider, den Physics.OverlapSphere mit einem Radius von reach um den Helden herum findet. Vor dem Helden bedeutet, dass das Skalarprodukt zwischen der Vorwärtsrichtung des Helden und der Richtung zum Objekt entlang des Bodens mindestens 0.5 beträgt: bis zu 60 Grad auf jeder Seite.

Lass die anderen Skripte unverändert. Drücke Play, gehe auf die Trainingspuppen zu und schwinge, dann Check.

Probier es selbst

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);
        }

        // Mit der Leertaste oder einem Linksklick wird höchstens einmal alle swingCooldown Sekunden zugeschlagen
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // Zerstöre alles mit dem Tag Enemy, das sich in Reichweite und vor dem Helden befindet
    }

    IEnumerator ShowSlash()
    {
        slash.SetActive(true);
        yield return new WaitForSeconds(0.1f);
        slash.SetActive(false);
    }
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Spielarchitektur in Unity