Menu
CoddyTech

Bohater

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

W tym rozdziale zbudujesz Arena Survivor, ostatnią grę na ścieżce. Fale wrogów wdzierają się na otoczoną murem arenę, a ty odpierasz je mieczem: są tu szeregowi przeciwnicy, 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. Gra wykorzystuje narzędzia z tej sekcji tam, gdzie pasują: abstrakcyjny Enemy z dwoma rodzajami przeciwników, IDamageable dla wszystkiego, co może otrzymywać obrażenia, fale jako zasób ScriptableObject, przedmioty do podniesienia obsługiwane przez interfejs, HUD nasłuchujący wyłącznie zdarzeń C# oraz zapis w formacie JSON. Wszystkie dwanaście skryptów znajduje się w projekcie od samego początku. Kod ma na razie tylko Hero, a kolejne lekcje dodają go do następnych skryptów.

Miecz bohatera trafia w to, co znajduje się przed nim, więc podczas zamachu trzeba sprawdzić, 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 dla kierunków prostopadłych 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 wartownik podnosi alarm, gdy gracz znajduje się w odległości do 10 jednostek i w jego polu widzenia o kącie 30 stopni:

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 długość toPlayer odpowiada odległości, więc najpierw jest normalizowany; w przeciwnym razie gracz znajdujący się dwa razy dalej dałby dwa razy większy iloczyn skalarny. Wartość y jest wcześniej ustawiana na 0, więc pozycja gracza na wyższym pomoście jest uwzględniana na podstawie jego położenia na mapie, a nie 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 z tyłu.

challenge icon

Wyzwanie

Średni

Rozpocznij Arena Survivor z mieczem bohatera. Ruch jest już zaimplementowany: akcja Move porusza bohaterem za pomocą SimpleMove i obraca go w kierunku, w którym się porusza. Wokół niego stoi pięć manekinów treningowych oznaczonych tagiem Enemy. W Inspectorze ustawiono dla Hero wartość reach (2) i swingCooldown (0.5), a także cięcie wyświetlane podczas zamachu.

  • Spacja lub kliknięcie lewym przyciskiem myszy wykonuje zamach, najwyżej raz na swingCooldown sekund: naciśnięcie przed upływem czasu odnowienia nic nie robi. Odczytaj klawisze za pomocą Keyboard.current i Mouse.current, a następnie wywołaj Swing().
  • W Swing(), po wyświetleniu cięcia, zniszcz każdy obiekt oznaczony tagiem Enemy, który znajduje się w zasięgu reach i przed bohaterem. W zasięgu znajduje się każdy obiekt z colliderem, który Physics.OverlapSphere wykryje wokół bohatera w promieniu reach. Przed bohaterem oznacza, że iloczyn skalarny kierunku do obiektu wzdłuż podłoża i wektora skierowanego do przodu bohatera wynosi co najmniej 0.5: do 60 stopni w obie strony.

Pozostaw pozostałe skrypty bez zmian. Naciśnij Play, podejdź do manekinów i wykonaj zamach, a następnie naciśnij Check.

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;

    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 oznaczone 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);
    }
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Architektura gier w Unity