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.
Wyzwanie
ŚredniDaj bohaterowi miecz.
- W
Update(), gdy naciśnięty zostanie klawisz spacji lub lewy przycisk myszy iTime.time >= nextSwing, ustawnextSwing = Time.time + swingCooldown;i wywołajSwing(). - W
Swing()wykonaj pętlę poPhysics.OverlapSphere(transform.position, reach). - Zniszcz każde trafione ciało oznaczone tagiem
Enemy, którego płaski kierunektoHit(z ustawionym y na 0) daje wynikVector3.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
1Komponenty, nie hierarchie
Małe komponentyBazowy skrypt przeciwnikaKomponenty abstrakcyjneWspółpracujące komponentyPowtórka: modułowa wieżyczkaPowtórka: podwójne działo4Dane serializowane
Klasy serializowalneListy w InspectorzeEnumy w InspectorzePodsumowanie: tabela łupówPodsumowanie: losowanie łupów2Interfejsy w Unity
IDamageableInterfejsy i TryGetComponentGetComponentsPowtórka: klawisz interakcjiPowtórka: najbliższy w zasięgu5ScriptableObjects
Zasoby danychStatystyki broniWspółdzielone ustawieniaOcal wiatrFale jako danePowtórka: Baza przedmiotówPowtórka: Kupowanie w sklepie8Kod wielokrotnego użytku
Metody rozszerzająceGeneryczny singletonGeneryczne metody pomocniczePodsumowanie: FindNearestPoćwicz samodzielnie: Edytor online Unity