Menu
CoddyTech

Der Held

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

In diesem Kapitel entsteht Arena Survivor, das letzte Spiel des Kurses. Gegnerwellen strömen in eine ummauerte Arena, und du wehrst sie mit einem Schwert ab: Grunts, die auf dich zulaufen, und Orbs, die warten und dann lossprinten. Nach einer besiegten Welle bleibt ein Power-up zurück, 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 dort, wo sie passen: einen abstrakten Enemy mit zwei Arten, IDamageable für alles, das Schaden nehmen kann, die Wellen als ScriptableObject-Asset, Pick-ups 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. Bisher funktioniert nur Hero; die anderen enthalten die Felder, die eine spätere Lektion benötigt, und in jeder Lektion kommen weitere Funktionen hinzu.

Das Schwert des Helden trifft, was sich vor ihm befindet. Daher braucht der Schwerthieb einen Test dafür, ob etwas „vor ihm“ liegt. 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 entspricht das dem Kosinus des Winkels zwischen ihnen: 1, wenn sie in dieselbe Richtung zeigen, 0.866 bei 30 Grad, 0.707 bei 45 Grad, 0 bei einem rechten Winkel und -1, wenn sie in entgegengesetzte Richtungen zeigen. „Innerhalb von 30 Grad zu meiner Blickrichtung“ bedeutet also „ein Skalarprodukt von mindestens 0.866“, ohne dass ein Winkel berechnet werden muss. Dieser Wachroboter schlägt Alarm, wenn sich der Spieler innerhalb von 10 Einheiten und in seinem 30-Grad-Sichtfeld 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 immer diese Länge, aber toPlayer ist so lang wie die Entfernung und wird daher zuerst normalisiert; andernfalls wäre das Skalarprodukt bei einem doppelt so weit entfernten Spieler doppelt so groß. Der y-Wert wird zuvor auf 0 gesetzt, sodass ein Spieler auf einem höher gelegenen Steg danach zählt, wo er auf der Karte steht, nicht danach, wie hoch er sich befindet. Das Vorzeichen unterscheidet vorne von hinten: Ein Skalarprodukt über 0 bedeutet, dass sich etwas irgendwo in der vorderen Hälfte befindet, unter 0 irgendwo dahinter.

challenge icon

Aufgabe

Mittel

Gib dem Helden ein Schwert.

  1. Setze in Update(), wenn die Leertaste oder ein Linksklick gedrückt wird und Time.time >= nextSwing gilt, nextSwing = Time.time + swingCooldown; und rufe Swing() auf.
  2. Durchlaufe in Swing() Physics.OverlapSphere(transform.position, reach).
  3. Zerstöre jeden Treffer mit dem Tag Enemy, dessen abgeflachte Richtung toHit (y auf 0 gesetzt) Vector3.Dot(transform.forward, toHit.normalized) >= 0.5f ergibt.

Drücke „Spielen“ und schwinge das Schwert: Die Übungspuppen vor dir verschwinden. Drücke dann „Prüfen“.

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

        // Mit der Leertaste oder einem Linksklick wird ein Schlag ausgeführt, höchstens einmal alle swingCooldown Sekunden
    }

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

Alle Lektionen in Spielarchitektur in Unity

Übe selbstständig: Unity-Playground