Menu
CoddyTech

Bohater

Część sekcji Architektura gier w Unity ścieżki Unity w Coddy. Lekcja 50 z 64.

W tym rozdziale zbudujesz Arena Survivor, ostatnią grę w kursie. Fale wrogów wlewają się na otoczoną murem arenę, a ty odpierasz je mieczem: są tu piechurzy, którzy idą w twoją stronę, oraz kule, które czekają, a potem wykonują szarżę. Po pokonaniu fali zostaje ulepszenie, HUD pokazuje twoje zdrowie i numer fali, a gdy skończy ci się zdrowie, rozgrywka dobiega końca i zapisywany jest twój najlepszy wynik. W grze wykorzystasz narzędzia z tej sekcji tam, gdzie pasują: abstrakcyjny Enemy z dwoma rodzajami przeciwników, IDamageable dla wszystkiego, co może otrzymać obrażenia, fale jako zasób ScriptableObject, przedmioty do podniesienia obsługiwane przez interfejs, HUD nasłuchujący wyłącznie zdarzeń C# oraz zapis JSON. Wszystkie dwanaście skryptów znajduje się w projekcie od samego początku. Na razie działa tylko Hero; pozostałe zawierają pola potrzebne w późniejszej lekcji, a każda lekcja dodaje kolejne elementy.

Miecz bohatera trafia w to, co znajduje się przed nim, więc zamach wymaga sprawdzenia, czy cel jest „z przodu”. Vector3.Dot(a, b) mnoży odpowiadające sobie składowe dwóch wektorów i dodaje wyniki: a.x * b.x + a.y * b.y + a.z * b.z. Dla dwóch kierunków o długości 1 jest to cosinus kąta między nimi: 1, gdy wskazują ten sam kierunek, 0.866 przy 30 stopniach, 0.707 przy 45 stopniach, 0 pod kątem prostym i -1, gdy wskazują przeciwne kierunki. Zatem „w granicach 30 stopni od kierunku, w którym patrzę” oznacza „iloczyn skalarny wynosi co najmniej 0.866” — bez obliczania kąta. Ten robot wartowniczy podnosi alarm, gdy gracz znajduje się w promieniu 10 jednostek i w jego 30-stopniowym polu widzenia:

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

Oba kierunki muszą mieć długość 1. transform.forward zawsze ma taką długość, ale toPlayer ma długość równą odległości, więc najpierw zostaje znormalizowany; w przeciwnym razie gracz znajdujący się dwa razy dalej dałby dwukrotnie większy iloczyn skalarny. Wartość y jest wcześniej ustawiana na 0, więc położenie gracza na wyższym pomoście liczy się według miejsca, w którym stoi na mapie, a nie jego wysokości. Znak pozwala odróżnić przód od tyłu: iloczyn skalarny większy od 0 oznacza dowolne miejsce w przedniej połowie, a mniejszy od 0 — dowolne miejsce za robotem.

challenge icon

Wyzwanie

Średni

Daj bohaterowi miecz.

  1. W Update(), gdy naciśnięty zostanie klawisz spacji lub lewy przycisk myszy i Time.time >= nextSwing, ustaw nextSwing = Time.time + swingCooldown; i wywołaj Swing().
  2. W Swing() wykonaj pętlę po Physics.OverlapSphere(transform.position, reach).
  3. Zniszcz każde trafione ciało oznaczone tagiem Enemy, którego płaski kierunek toHit (z ustawionym y na 0) daje wynik Vector3.Dot(transform.forward, toHit.normalized) >= 0.5f.

Naciśnij „Graj” i zamachnij się: manekiny z przodu znikną. Następnie naciśnij „Sprawdź”.

Spróbuj swoich sił

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

        // Naciśnięcie spacji lub lewego przycisku myszy wykonuje zamach, nie częściej niż raz na swingCooldown sekund
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // zniszcz wszystko z tagiem Enemy, co znajduje się w zasięgu i przed bohaterem
    }

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

Wszystkie lekcje w sekcji Architektura gier w Unity

Poćwicz samodzielnie: Edytor online Unity