Mózgi przeciwników
Część sekcji Architektura gier w Unity ścieżki Unity w Coddy. Lekcja 31 z 50.
Mózg wroga to maszyna stanów z jednym stanem dla każdego zachowania. Ten szkielet chodzi między swoimi posterunkami, ściga gracza, który znajdzie się w odległości 6 jednostek, i raz na sekundę macha mieczem, gdy jest blisko. Każdy stan określa własny sposób wyjścia, a szkielet zachowuje współdzielone przez nie pomocnicze elementy:
public class Skeleton : MonoBehaviour
{
public Transform player;
public Transform[] posts;
public float speed = 2f;
public StateMachine machine = new StateMachine();
public IState patrol, chase, attack;
void Start()
{
patrol = new PatrolState(this);
chase = new ChaseState(this);
attack = new AttackState(this);
machine.ChangeState(patrol);
}
void Update() { machine.Tick(); }
public float PlayerDistance() { return Vector3.Distance(transform.position, player.position); }
public void WalkTo(Vector3 target)
{
target.y = transform.position.y;
transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
}
}
public class PatrolState : IState
{
private Skeleton skel;
private int next;
public PatrolState(Skeleton skel) { this.skel = skel; }
public void Enter() { }
public void Exit() { }
public void Tick()
{
if (skel.PlayerDistance() < 6f) { skel.machine.ChangeState(skel.chase); return; }
Vector3 post = skel.posts[next].position;
post.y = skel.transform.position.y;
skel.WalkTo(post);
if (skel.transform.position == post) next = (next + 1) % skel.posts.Length;
}
}
public class ChaseState : IState
{
private Skeleton skel;
public ChaseState(Skeleton skel) { this.skel = skel; }
public void Enter() { }
public void Exit() { }
public void Tick()
{
float d = skel.PlayerDistance();
if (d > 6f) { skel.machine.ChangeState(skel.patrol); return; }
if (d < 1.5f) { skel.machine.ChangeState(skel.attack); return; }
skel.WalkTo(skel.player.position);
}
}
public class AttackState : IState
{
private Skeleton skel;
private float cooldown;
public AttackState(Skeleton skel) { this.skel = skel; }
public void Enter() { cooldown = 0f; }
public void Exit() { }
public void Tick()
{
if (skel.PlayerDistance() > 1.5f) { skel.machine.ChangeState(skel.chase); return; }
cooldown -= Time.deltaTime;
if (cooldown <= 0f) { Debug.Log("Swing!"); cooldown = 1f; }
}
}Stany są tworzone raz i istnieją tak długo jak szkielet, więc ich pola zachowują wartości między kolejnymi wejściami do danego stanu. Wartość next w PatrolState pozostaje ustawiona także po pościgu, a szkielet idzie dalej do posterunku, do którego zmierzał. Enter resetuje tylko to, co powinno zacząć się od nowa: w AttackState ustawia cooldown na 0, więc pierwszy cios następuje, gdy tylko szkielet dotrze do gracza.
Cały ruch odbywa się przez WalkTo, który w tym przypadku używa MoveTowards na transformie, więc szkielet przenika przez ściany. Aby zamiast tego poruszać szkieletem za pomocą Rigidbody, WalkTo ustawia prędkość ciała, a szkielet wykonuje maszynę stanów w FixedUpdate, gdzie Time.deltaTime oznacza krok fizyki. Trzy stany pozostają bez zmian: określają, dokąd iść, a właściciel — jak.
Wyzwanie
ŚredniRycerz strzeże dziedzińca zamku. Strzałki prowadzą gracza, którego Health ma metodę TakeDamage(int amount) (obie są gotowe). Knight.cs zawiera pola uzupełniane w Inspectorze (waypoints od W0 do W2, player, playerHealth, speed ustawione na 3 oraz zakresy podane niżej) oraz metody pomocnicze korzystające z jego Rigidbody: DistanceTo(point) i PlayerDistance() mierzą odległość po ziemi, WalkTo(target) prowadzi do punktu, a Stop() zatrzymuje rycerza. Dodaj mu mózg:
- W
Knight.csutwórz maszynę stanów i trzy stany (polapatrol,chaseiattack), rozpocznij patrolowanie i wywołuj Tick maszyny w FixedUpdate. - W
KnightStates.csnapiszPatrolState,ChaseStateiAttackState, z których każdy przyjmuje rycerza w konstruktorze. - Patrol: idź do
waypoints[next]; gdy znajdziesz się w odległości mniejszej niż 0.3 od niego, skieruj się do następnego punktu, a po ostatnim wróć do pierwszego. Gdy gracz znajdzie się bliżej niżsightRange(5), rusz za nim w pościg. Po pościgu patroluj dalej w kierunku punktu, do którego rycerz zmierzał. - Pościg: idź do gracza. Gdy znajdzie się bliżej niż
attackRange(1.5), zaatakuj. Gdy będzie dalej niżloseRange(8), wróć do patrolowania. Między 5 a 8 pościg trwa dalej, dzięki czemu rycerz nie będzie przełączać się między tymi dwoma stanami na granicy zasięgu wzroku. - Atak: stój w miejscu i od razu zadaj graczowi 1 punkt obrażeń, a następnie ponawiaj atak co sekundę, dopóki gracz znajduje się w zasięgu
attackRange; gdy oddali się bardziej, ponownie rusz w pościg.
Naciśnij Play, pozwól rycerzowi cię zauważyć, otrzymaj cios, a następnie ucieknij i naciśnij Check.
Spróbuj swoich sił
using UnityEngine;
// PatrolState, ChaseState i AttackState
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
1Komponenty, nie hierarchie
Małe komponentyPodstawowy skrypt przeciwnikaKomponenty abstrakcyjneWspółpracujące komponentyPodsumowanie: modułowa wieżyczka4Dane serializowane
Klasy serializowalneListy w InspectorzeWyliczenia w InspectorzePodsumowanie: tabela łupów10Projekt: Arena przetrwania
BohaterTypy wrogów2Interfejsy w Unity
IDamageableInterfejsy i TryGetComponentGetComponentsPodsumowanie: klawisz interakcji5ScriptableObjects
Zasoby danychStatystyki broniWspółdzielone ustawieniaFale jako danePowtórka: baza danych przedmiotów8Kod wielokrotnego użytku
Metody rozszerzająceGeneryczny singletonGeneryczne metody pomocniczePodsumowanie: FindNearest