מוחות האויבים
חלק מהיחידה ארכיטקטורת משחקים ב-Unity במסלול ה-Unity של Coddy. שיעור 31 מתוך 50.
המוח של אויב הוא מכונת מצבים עם מצב אחד לכל התנהגות. השלד הזה הולך בין עמדותיו, רודף אחרי שחקן שמגיע לטווח של 6 יחידות, ומניף את חרבו פעם בשנייה כשהוא קרוב. כל מצב מחליט בעצמו איך לצאת ממנו, והשלד שומר את פונקציות העזר שמשותפות להם:
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; }
}
}המצבים נוצרים פעם אחת ונשארים כל עוד השלד קיים, ולכן השדות שלהם נשמרים בין ביקורים. next של PatrolState עדיין מוגדר אחרי מרדף, והשלד ממשיך ללכת לעמדה שאליה היה בדרכו. Enter מאפס רק את מה שצריך להתחיל מחדש: Enter של AttackState מגדיר את cooldown ל־0, כך שההנפה הראשונה מתבצעת ברגע שהשלד מגיע לשחקן.
כל התנועה מתבצעת דרך WalkTo, שבמקרה הזה משתמשת ב־MoveTowards על ה־transform, ועוברת דרך קירות. כדי להזיז את השלד באמצעות Rigidbody במקום זאת, WalkTo מגדירה את מהירות הגוף, והשלד מתקדם במכונת המצבים ב־FixedUpdate, שבו Time.deltaTime הוא צעד הפיזיקה. שלושת המצבים לא משתנים: הם אומרים לאן ללכת, והאובייקט שמחזיק בהם אומר איך.
אתגר
בינוניאביר שומר על חצר הטירה. החצים מניעים את Player, של־Health שלו יש TakeDamage(int amount) (שניהם מוכנים). בקובץ Knight.cs יש שדות שה־Inspector ממלא (waypoints מ־W0 עד W2, player, playerHealth, speed 3 והטווחים שלהלן), ופונקציות עזר שמשתמשות ב־Rigidbody שלו: DistanceTo(point) ו־PlayerDistance() מודדות על פני הקרקע, WalkTo(target) צועדת לעבר נקודה, Stop() עוצרת במקום. תנו לו מוח:
- ב־
Knight.cs, צרו את המכונה ואת שלושת המצבים (השדותpatrol,chaseו־attack), התחילו בסיור, וקראו ל־tick של המכונה ב־FixedUpdate. - ב־
KnightStates.cs, כתבו אתPatrolState,ChaseStateו־AttackState, כשכל אחד מהם מקבל את האביר בבנאי שלו. - סיור: לכו אל
waypoints[next]; כשאתם במרחק של פחות מ־0.3 ממנו, פנו לנקודה הבאה, ואחרי הנקודה האחרונה חזרו לראשונה. כשהשחקן קרוב יותר מ־sightRange(5), רדפו אחריו. אחרי מרדף, הסיור ממשיך אל נקודת הציון שאליה היה בדרכו. - מרדף: לכו אל השחקן. במרחק קטן מ־
attackRange(1.5): תקפו. במרחק גדול מ־loseRange(8): עברו לסיור. בין 5 ל־8 המרדף נמשך, כך שהאביר לא יחליף שוב ושוב בין שני המצבים בגבול טווח הראייה שלו. - התקפה: עמדו במקום והכו את השחקן מיד ב־1, ואז פעם בכל שנייה, כל עוד הוא נמצא בתוך
attackRange; כשהוא רחוק יותר, חזרו למרדף.
לחצו על Play, תנו לאביר להבחין בכם, חטפו מכה, ואז ברחו, ולבסוף לחצו על Check.
נסו בעצמכם
using UnityEngine;
// PatrolState, ChaseState ו-AttackState
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה ארכיטקטורת משחקים ב-Unity
1רכיבים, לא היררכיות
רכיבים קטניםסקריפט בסיס לאויברכיבים מופשטיםרכיבים שעובדים יחדסיכום: צריח מודולרי