การพัฒนา Unity AI: การสอน FSM แบบกราฟิกบน xNode
เผยแพร่แล้ว: 2022-08-12ใน “การพัฒนา Unity AI: A Finite-state Machine Tutorial” เราได้สร้างเกมการลอบเร้นที่เรียบง่าย ซึ่งเป็น AI แบบโมดูลาร์ที่ใช้ FSM ในเกม เจ้าหน้าที่ศัตรูลาดตระเวนพื้นที่เกม เมื่อพบผู้เล่น ศัตรูจะเปลี่ยนสถานะและติดตามผู้เล่นแทนการลาดตระเวน
ในเลกที่สองของการเดินทาง Unity ของเรา เราจะสร้างส่วนต่อประสานกราฟิกกับผู้ใช้ (GUI) เพื่อสร้างส่วนประกอบหลักของเครื่อง finite-state (FSM) ของเราอย่างรวดเร็วยิ่งขึ้น และด้วยประสบการณ์นักพัฒนา Unity ที่ได้รับการปรับปรุง
ทบทวนอย่างรวดเร็ว
รายละเอียด FSM ในบทช่วยสอนก่อนหน้านี้สร้างขึ้นจากบล็อกสถาปัตยกรรมเป็นสคริปต์ C# เราได้เพิ่มการกระทำและการตัดสินใจ ScriptableObject
แบบกำหนดเองเป็นคลาส วิธีการ ScriptableObject
ช่วยให้ FSM บำรุงรักษาและปรับแต่งได้ง่าย ในบทช่วยสอนนี้ เราจะแทนที่ ScriptableObject
แบบลากและวางของ FSM ด้วยตัวเลือกแบบกราฟิก
ฉันยังเขียนสคริปต์ที่อัปเดตสำหรับผู้ที่ต้องการทำให้เกมชนะได้ง่ายขึ้น ในการใช้งาน เพียงแค่แทนที่สคริปต์การตรวจหาผู้เล่นด้วยสคริปต์นี้ที่จำกัดขอบเขตการมองเห็นของศัตรูให้แคบลง
เริ่มต้นใช้งาน xNode
เราจะสร้างตัวแก้ไขกราฟิกของเราโดยใช้ xNode ซึ่งเป็นเฟรมเวิร์กสำหรับแผนผังพฤติกรรมแบบโหนดที่จะแสดงโฟลว์ของ FSM ของเราเป็นภาพ แม้ว่า GraphView ของ Unity จะสามารถทำงานได้สำเร็จ แต่ API ของมันก็มีทั้งแบบทดลองและเอกสารเพียงเล็กน้อย อินเทอร์เฟซผู้ใช้ของ xNode มอบประสบการณ์นักพัฒนาที่เหนือกว่า อำนวยความสะดวกในการสร้างต้นแบบและขยาย FSM ของเราอย่างรวดเร็ว
มาเพิ่ม xNode ในโครงการของเราเป็นการพึ่งพา Git โดยใช้ Unity Package Manager:
- ใน Unity คลิก Window > Package Manager เพื่อเปิดหน้าต่าง Package Manager
- คลิก + (เครื่องหมายบวก) ที่มุมซ้ายบนของหน้าต่าง และเลือก Add package from git URL เพื่อแสดงช่องข้อความ
- พิมพ์หรือวาง
https://github.com/siccity/xNode.git
ในกล่องข้อความที่ไม่มีป้ายกำกับ แล้วคลิกปุ่ม เพิ่ม
ตอนนี้เราพร้อมที่จะเจาะลึกและทำความเข้าใจองค์ประกอบหลักของ xNode แล้ว:
คลาส Node | แสดงถึงโหนด ซึ่งเป็นหน่วยพื้นฐานที่สุดของกราฟ ในบทช่วยสอน xNode นี้ เราได้มาจากคลาส Node คลาสใหม่ที่ประกาศโหนดพร้อมกับฟังก์ชันและบทบาทที่กำหนดเอง |
คลาส NodeGraph | แสดงถึงชุดของโหนด (อินสแตนซ์คลาส Node ) และขอบที่เชื่อมต่อ ในบทช่วยสอน xNode นี้ เราได้มาจาก NodeGraph ซึ่งเป็นคลาสใหม่ที่จัดการและประเมินโหนด |
คลาส NodePort | หมายถึงเกตการสื่อสาร พอร์ตของประเภทอินพุตหรือเอาต์พุตประเภท ซึ่งอยู่ระหว่างอินสแตนซ์ของ Node ใน NodeGraph คลาส NodePort มีลักษณะเฉพาะสำหรับ xNode |
[Input] แอตทริบิวต์ | การเพิ่มแอตทริบิวต์ [Input] ให้กับพอร์ตที่กำหนดให้เป็นอินพุต ทำให้พอร์ตสามารถส่งผ่านค่าไปยังโหนดที่เป็นส่วนหนึ่งของ คิดว่าแอตทริบิวต์ [Input] เป็นพารามิเตอร์ของฟังก์ชัน |
[Output] แอตทริบิวต์ | การเพิ่มแอตทริบิวต์ [Output] ให้กับพอร์ตที่กำหนดให้เป็นเอาต์พุต ทำให้พอร์ตสามารถส่งผ่านค่าจากโหนดที่เป็นส่วนหนึ่งของ คิดว่าแอตทริบิวต์ [Output] เป็นค่าส่งคืนของฟังก์ชัน |
การแสดงภาพสภาพแวดล้อมการสร้าง xNode
ใน xNode เราทำงานกับกราฟโดยที่แต่ละ State
และช่วงการ Transition
จะอยู่ในรูปแบบของโหนด การเชื่อมต่ออินพุตและ/หรือเอาต์พุตทำให้โหนดสัมพันธ์กับโหนดใดๆ หรือทั้งหมดในกราฟของเรา
ลองนึกภาพโหนดที่มีค่าอินพุตสามค่า: สองค่าโดยพลการและหนึ่งค่าบูลีน โหนดจะส่งออกค่าอินพุตประเภทใดก็ได้หนึ่งในสองค่า ขึ้นอยู่กับว่าอินพุตบูลีนเป็นจริงหรือเท็จ
ในการแปลง FSM ที่มีอยู่ให้เป็นกราฟ เราแก้ไขคลาส State
และ Transition
เพื่อสืบทอดคลาส Node
แทนคลาส ScriptableObject
เราสร้างวัตถุกราฟประเภท NodeGraph
เพื่อให้มีวัตถุ State
และการ Transition
ทั้งหมดของเรา
การปรับเปลี่ยน BaseStateMachine
เพื่อใช้เป็น Base Type
เริ่มต้นสร้างส่วนต่อประสานกราฟิกโดยเพิ่มวิธีการเสมือนใหม่สองวิธีในคลาส BaseStateMachine
ที่มีอยู่ของเรา:
Init | กำหนดสถานะเริ่มต้นให้กับคุณสมบัติ CurrentState |
Execute | ดำเนินการสถานะปัจจุบัน |
การประกาศเมธอดเหล่านี้เป็นเสมือนช่วยให้เราสามารถแทนที่ได้ ดังนั้นเราสามารถกำหนดพฤติกรรมที่กำหนดเองของคลาสที่สืบทอดคลาส BaseStateMachine
สำหรับการเริ่มต้นและการดำเนินการ:
using System; using System.Collections.Generic; using UnityEngine; namespace Demo.FSM { public class BaseStateMachine : MonoBehaviour { [SerializeField] private BaseState _initialState; private Dictionary<Type, Component> _cachedComponents; private void Awake() { Init(); _cachedComponents = new Dictionary<Type, Component>(); } public BaseState CurrentState { get; set; } private void Update() { Execute(); } public virtual void Init() { CurrentState = _initialState; } public virtual void Execute() { CurrentState.Execute(this); } // Allows us to execute consecutive calls of GetComponent in O(1) time public new T GetComponent<T>() where T : Component { if(_cachedComponents.ContainsKey(typeof(T))) return _cachedComponents[typeof(T)] as T; var component = base.GetComponent<T>(); if(component != null) { _cachedComponents.Add(typeof(T), component); } return component; } } }
ต่อไป ภายใต้โฟลเดอร์ FSM
ของเรา มาสร้าง:
FSMGraph | โฟลเดอร์ |
BaseStateMachineGraph | คลาส AC# ภายใน FSMGraph |
ในขณะนี้ BaseStateMachineGraph
จะสืบทอดเฉพาะคลาส BaseStateMachine
:
using UnityEngine; namespace Demo.FSM.Graph { public class BaseStateMachineGraph : BaseStateMachine { } }
เราไม่สามารถเพิ่มฟังก์ชันการทำงานให้กับ BaseStateMachineGraph
จนกว่าเราจะสร้างประเภทโหนดฐานของเรา มาทำกันต่อไป
การใช้ NodeGraph
และการสร้าง Base Node Type
ภายใต้โฟลเดอร์ FSMGraph
ที่สร้างขึ้นใหม่ เราจะสร้าง:
FSMGraph | ห้องเรียน |
สำหรับตอนนี้ FSMGraph
จะสืบทอดเฉพาะคลาส NodeGraph
(โดยไม่มีฟังก์ชันเพิ่มเติม):
using UnityEngine; using XNode; namespace Demo.FSM.Graph { [CreateAssetMenu(menuName = "FSM/FSM Graph")] public class FSMGraph : NodeGraph { } }
ก่อนที่เราจะสร้างคลาสสำหรับโหนดของเรา ให้เพิ่ม:
FSMNodeBase | คลาสที่จะใช้เป็นคลาสพื้นฐานโดยโหนดทั้งหมดของเรา |
คลาส FSMNodeBase
จะมีอินพุตชื่อ Entry
ของประเภท FSMNodeBase
เพื่อให้เราสามารถเชื่อมต่อโหนดเข้าด้วยกันได้
เราจะเพิ่มฟังก์ชันตัวช่วยสองอย่าง:
GetFirst | ดึงโหนดแรกที่เชื่อมต่อกับเอาต์พุตที่ร้องขอ |
GetAllOnPort | ดึงโหนดที่เหลือทั้งหมดที่เชื่อมต่อกับเอาต์พุตที่ร้องขอ |
using System.Collections.Generic; using XNode; namespace Demo.FSM.Graph { public abstract class FSMNodeBase : Node { [Input(backingValue = ShowBackingValue.Never)] public FSMNodeBase Entry; protected IEnumerable<T> GetAllOnPort<T>(string fieldName) where T : FSMNodeBase { NodePort port = GetOutputPort(fieldName); for (var portIndex = 0; portIndex < port.ConnectionCount; portIndex++) { yield return port.GetConnection(portIndex).node as T; } } protected T GetFirst<T>(string fieldName) where T : FSMNodeBase { NodePort port = GetOutputPort(fieldName); if (port.ConnectionCount > 0) return port.GetConnection(0).node as T; return null; } } }
ในที่สุด เราจะมีโหนดสถานะสองประเภท มาเพิ่มคลาสเพื่อรองรับสิ่งเหล่านี้:
BaseStateNode | คลาสพื้นฐานเพื่อรองรับทั้ง StateNode และ RemainInStateNode |
namespace Demo.FSM.Graph { public abstract class BaseStateNode : FSMNodeBase { } }
ถัดไป แก้ไขคลาส BaseStateMachineGraph
:
using UnityEngine; namespace Demo.FSM.Graph { public class BaseStateMachineGraph : BaseStateMachine { public new BaseStateNode CurrentState { get; set; } } }
ที่นี่ เราได้ซ่อนคุณสมบัติ CurrentState
ที่สืบทอดมาจากคลาสพื้นฐาน และเปลี่ยนประเภทจาก BaseState
เป็น BaseStateNode
การสร้างบล็อคส่วนประกอบสำหรับกราฟ FSM ของเรา
ต่อไป เพื่อสร้างหน่วยการสร้างหลักของ FSM ให้เพิ่มคลาสใหม่สามคลาสในโฟลเดอร์ FSMGraph
ของเรา:
StateNode | แสดงถึงสถานะของตัวแทน ในการดำเนินการ StateNode จะวนซ้ำบน TransitionNode ที่เชื่อมต่อกับพอร์ตเอาต์พุตของ StateNode (ดึงข้อมูลโดยวิธีตัวช่วย) StateNode สอบถามแต่ละรายการว่าจะเปลี่ยนโหนดเป็นสถานะอื่นหรือปล่อยให้สถานะของโหนดเป็นอยู่ |
RemainInStateNode | บ่งชี้ว่าโหนดควรอยู่ในสถานะปัจจุบัน |
TransitionNode | ตัดสินใจเปลี่ยนไปเป็นสถานะอื่นหรืออยู่ในสถานะเดียวกัน |
ในบทช่วยสอน Unity FSM ก่อนหน้านี้ คลาส State
จะวนซ้ำในรายการการเปลี่ยน ที่นี่ใน xNode StateNode
ทำหน้าที่เป็น State
เทียบเท่ากับการวนซ้ำโหนดที่ดึงข้อมูลผ่านวิธีการช่วยเหลือ GetAllOnPort
ของเรา
ตอนนี้เพิ่มแอตทริบิวต์ [Output]
ให้กับการเชื่อมต่อขาออก (โหนดการเปลี่ยนแปลง) เพื่อระบุว่าควรเป็นส่วนหนึ่งของ GUI โดยการออกแบบของ xNode ค่าของแอตทริบิวต์เริ่มต้นในโหนดต้นทาง: โหนดที่มีฟิลด์ที่มีเครื่องหมายแอตทริบิวต์ [Output]
เนื่องจากเราใช้แอตทริบิวต์ [Output]
และ [Input]
เพื่ออธิบายความสัมพันธ์และการเชื่อมต่อที่จะกำหนดโดย xNode GUI เราจึงไม่สามารถปฏิบัติกับค่าเหล่านี้ได้ตามปกติ พิจารณาว่าเราทำซ้ำผ่าน Actions
กับ Transitions
อย่างไร :
using System.Collections.Generic; namespace Demo.FSM.Graph { [CreateNodeMenu("State")] public sealed class StateNode : BaseStateNode { public List<FSMAction> Actions; [Output] public List<TransitionNode> Transitions; public void Execute(BaseStateMachineGraph baseStateMachine) { foreach (var action in Actions) action.Execute(baseStateMachine); foreach (var transition in GetAllOnPort<TransitionNode>(nameof(Transitions))) transition.Execute(baseStateMachine); } } }
ในกรณีนี้ เอาต์พุตของ Transitions
สามารถมีโหนดหลายโหนดติดอยู่ เราต้องเรียกใช้เมธอดตัวช่วย GetAllOnPort
เพื่อรับรายการการเชื่อมต่อ [Output]
RemainInStateNode
เป็นคลาสที่ง่ายที่สุดของเรา ไม่ใช้ตรรกะ RemainInStateNode
เพียงบ่งชี้ตัวแทนของเรา—ในกรณีของเกมของเราคือศัตรู—ให้อยู่ในสถานะปัจจุบัน:
namespace Demo.FSM.Graph { [CreateNodeMenu("Remain In State")] public sealed class RemainInStateNode : BaseStateNode { } }
ณ จุดนี้ คลาส TransitionNode
ยังไม่สมบูรณ์และจะไม่คอมไพล์ ข้อผิดพลาดที่เกี่ยวข้องจะล้างเมื่อเราอัปเดตชั้นเรียน
ในการสร้าง TransitionNode
เราจำเป็นต้องหลีกเลี่ยงข้อกำหนดของ xNode ที่ว่าค่าของเอาต์พุตมีต้นกำเนิดมาจากโหนดต้นทาง เช่นเดียวกับที่เราทำเมื่อเราสร้าง StateNode
ข้อแตกต่างที่สำคัญระหว่าง StateNode
และ TransitionNode
คือเอาต์พุตของ TransitionNode
อาจแนบกับโหนดเดียวเท่านั้น ในกรณีของเรา GetFirst
จะดึงโหนดหนึ่งโหนดที่เชื่อมต่อกับแต่ละพอร์ตของเรา (โหนดสถานะหนึ่งโหนดจะเปลี่ยนเป็นกรณีจริงและอีกโหนดหนึ่งเพื่อเปลี่ยนเป็นกรณีเท็จ):
namespace Demo.FSM.Graph { [CreateNodeMenu("Transition")] public sealed class TransitionNode : FSMNodeBase { public Decision Decision; [Output] public BaseStateNode TrueState; [Output] public BaseStateNode FalseState; public void Execute(BaseStateMachineGraph stateMachine) { var trueState = GetFirst<BaseStateNode>(nameof(TrueState)); var falseState = GetFirst<BaseStateNode>(nameof(FalseState)); var decision = Decision.Decide(stateMachine); if (decision && !(trueState is RemainInStateNode)) { stateMachine.CurrentState = trueState; } else if(!decision && !(falseState is RemainInStateNode)) stateMachine.CurrentState = falseState; } } }
มาดูผลลัพธ์กราฟิกจากโค้ดของเรากัน
การสร้าง Visual Graph
เมื่อแยกประเภทคลาส FSM ทั้งหมดแล้ว เราสามารถดำเนินการสร้างกราฟ FSM สำหรับตัวแทนศัตรูของเกมได้ ในหน้าต่างโปรเจ็กต์ Unity ให้คลิกขวาที่โฟลเดอร์ EnemyAI
แล้วเลือก: Create > FSM > FSM Graph เพื่อให้ระบุกราฟของเราได้ง่ายขึ้น ให้เปลี่ยนชื่อเป็น EnemyGraph
ในหน้าต่างตัวแก้ไขกราฟ xNode ให้คลิกขวาเพื่อแสดงเมนูแบบเลื่อนลงที่แสดงรายการ State , Transition และ RemainInState หากมองไม่เห็นหน้าต่าง ให้ดับเบิลคลิกที่ไฟล์ EnemyGraph
เพื่อเปิดหน้าต่างตัวแก้ไข xNode Graph
ในการสร้างรัฐ
Chase
andPatrol
:คลิกขวาและเลือก สถานะ เพื่อสร้างโหนดใหม่
ตั้งชื่อโหนด
Chase
กลับไปที่เมนูแบบเลื่อนลง เลือก สถานะ อีกครั้งเพื่อสร้างโหนดที่สอง
ตั้งชื่อโหนด
Patrol
ลากและวางการดำเนินการ
Chase
และPatrol
ที่มีอยู่ไปยังสถานะที่เกี่ยวข้องที่สร้างขึ้นใหม่
ในการสร้างการเปลี่ยนแปลง:
คลิกขวาและเลือก การ เปลี่ยน เพื่อสร้างโหนดใหม่
กำหนดออบเจ็กต์
LineOfSightDecision
ให้กับฟิลด์Decision
ของการเปลี่ยนแปลง
ในการสร้างโหนด
RemainInState
:- คลิกขวาและเลือก RemainInState เพื่อสร้างโหนดใหม่
ในการเชื่อมต่อกราฟ:
เชื่อมต่อเอาต์พุต
Transitions
ของโหนดPatrol
กับอินพุตEntry
ของTransition
nodeเชื่อมต่อเอาต์พุต
True State
ของโหนดทTransition
สิชันกับอินพุตEntry
ของโหนดChase
เชื่อมต่อเอาต์พุต
False State
ของโหนดทTransition
ชันกับอินพุตEntry
ของโหนดRemain In State
กราฟควรมีลักษณะดังนี้:
ไม่มีสิ่งใดในกราฟที่บ่งชี้ว่าโหนดใด - รัฐ Patrol
หรือ Chase
- เป็นโหนดเริ่มต้นของเรา คลาส BaseStateMachineGraph
ตรวจพบสี่โหนด แต่ไม่มีตัวบ่งชี้ ไม่สามารถเลือกสถานะเริ่มต้นได้
เพื่อแก้ไขปัญหานี้ มาสร้าง:
FSMInitialNode | คลาสที่มีเอาต์พุตเดี่ยวของประเภท StateNode ชื่อ InitialNode |
เอาต์พุต InitialNode
ของเราแสดงถึงสถานะเริ่มต้น ถัดไป ใน FSMInitialNode
ให้สร้าง:
NextNode | คุณสมบัติที่ช่วยให้เราสามารถดึงโหนดที่เชื่อมต่อกับ InitialNode output |
using XNode; namespace Demo.FSM.Graph { [CreateNodeMenu("Initial Node"), NodeTint("#00ff52")] public class FSMInitialNode : Node { [Output] public StateNode InitialNode; public StateNode NextNode { get { var port = GetOutputPort("InitialNode"); if (port == null || port.ConnectionCount == 0) return null; return port.GetConnection(0).node as StateNode; } } } }
ตอนนี้เราสร้างคลาส FSMInitialNode
แล้ว เราสามารถเชื่อมต่อกับอินพุต Entry
ของสถานะเริ่มต้นและส่งคืนสถานะเริ่มต้นผ่านคุณสมบัติ NextNode
กลับไปที่กราฟของเราและเพิ่มโหนดเริ่มต้น ในหน้าต่างตัวแก้ไข xNode:
- คลิกขวาและเลือก โหนดเริ่มต้น เพื่อสร้างโหนดใหม่
- แนบเอาต์พุตของ โหนด FSM เข้ากับอินพุต
Entry
ของโหนดPatrol
กราฟควรมีลักษณะดังนี้:
เพื่อให้ชีวิตของเราง่ายขึ้น เราจะเพิ่ม FSMGraph
:
InitialState | ทรัพย์สิน |
ครั้งแรกที่เราพยายามดึงค่าคุณสมบัติ InitialState
ตัวรับคุณสมบัติจะข้ามโหนดทั้งหมดในกราฟของเราขณะที่พยายามค้นหา FSMInitialNode
เมื่อ FSMInitialNode
แล้ว เราจะใช้คุณสมบัติ NextNode
เพื่อค้นหาโหนดสถานะเริ่มต้นของเรา:
using System.Linq; using UnityEngine; using XNode; namespace Demo.FSM.Graph { [CreateAssetMenu(menuName = "FSM/FSM Graph")] public sealed class FSMGraph : NodeGraph { private StateNode _initialState; public StateNode InitialState { get { if (_initialState == null) _initialState = FindInitialStateNode(); return _initialState; } } private StateNode FindInitialStateNode() { var initialNode = nodes.FirstOrDefault(x => x is FSMInitialNode); if (initialNode != null) { return (initialNode as FSMInitialNode).NextNode; } return null; } } }
ต่อไป ใน BaseStateMachineGraph
เรามาอ้างอิง FSMGraph
และแทนที่เมธอด Init
และ Execute
ของ BaseStateMachine
การแทนที่ Init
ตั้งค่า CurrentState
เป็นสถานะเริ่มต้นของกราฟ และการแทนที่ Execute
เรียก Execute
บน CurrentState
:
using UnityEngine; namespace Demo.FSM.Graph { public class BaseStateMachineGraph : BaseStateMachine { [SerializeField] private FSMGraph _graph; public new BaseStateNode CurrentState { get; set; } public override void Init() { CurrentState = _graph.InitialState; } public override void Execute() { ((StateNode)CurrentState).Execute(this); } } }
ตอนนี้ ลองใช้กราฟกับออบเจกต์ Enemy และดูการทำงานจริง
การทดสอบกราฟ FSM
ในการเตรียมตัวสำหรับการทดสอบ ในหน้าต่างโครงการของ Unity Editor:
เปิดเนื้อหา SampleScene
ค้นหาวัตถุเกม
Enemy
ของเราในหน้าต่างลำดับชั้นของความสามัคคีแทนที่ส่วนประกอบ
BaseStateMachine
ด้วยส่วนประกอบBaseStateMachineGraph
:คลิก เพิ่มส่วนประกอบ และเลือกสคริปต์
BaseStateMachineGraph
ที่ถูกต้องกำหนดกราฟ FSM ของเรา
EnemyGraph
ให้กับฟิลด์Graph
ขององค์ประกอบBaseStateMachineGraph
ลบองค์ประกอบ
BaseStateMachine
(เนื่องจากไม่จำเป็นอีกต่อไป) โดยคลิกขวาและเลือก Remove Component
วัตถุเกม Enemy
ควรมีลักษณะดังนี้:
แค่นั้นแหละ! ตอนนี้เรามี FSM แบบแยกส่วนพร้อมโปรแกรมแก้ไขกราฟิก การคลิกปุ่ม เล่น แสดงว่า AI ศัตรูที่สร้างแบบกราฟิกทำงานเหมือนกับศัตรู ScriptableObject
ที่เราสร้างไว้ก่อนหน้านี้ทุกประการ
ก้าวไปข้างหน้า: เพิ่มประสิทธิภาพ FSM . ของเรา
คำเตือน: เมื่อคุณพัฒนา AI ที่ซับซ้อนมากขึ้นสำหรับเกมของคุณ จำนวนสถานะและการเปลี่ยนภาพจะเพิ่มขึ้น และ FSM จะสับสนและอ่านยาก ตัวแก้ไขกราฟิกมีลักษณะเป็นเว็บของบรรทัดที่มีต้นกำเนิดในหลายสถานะและสิ้นสุดที่การเปลี่ยนหลายครั้ง และในทางกลับกัน ทำให้ FSM ยากต่อการดีบัก
เช่นเดียวกับในบทช่วยสอนก่อนหน้านี้ เราขอเชิญคุณสร้างโค้ดของคุณเอง เพิ่มประสิทธิภาพเกมการพรางตัวของคุณ และจัดการกับข้อกังวลเหล่านี้ ลองนึกภาพว่าจะเป็นประโยชน์เพียงใดในการเขียนโค้ดสีให้กับโหนดสถานะของคุณเพื่อระบุว่าโหนดทำงานอยู่หรือไม่ใช้งาน หรือปรับขนาดโหนด RemainInState
และ Initial
เพื่อจำกัดพื้นที่หน้าจอของพวกเขา
การปรับปรุงดังกล่าวไม่ได้เป็นเพียงเครื่องสำอางเท่านั้น การอ้างอิงสีและขนาดจะช่วยระบุว่าจะดีบักที่ไหนและเมื่อใด กราฟที่มองเห็นได้ง่ายยังง่ายต่อการประเมิน วิเคราะห์ และทำความเข้าใจอีกด้วย ขั้นตอนถัดไปขึ้นอยู่กับคุณ ด้วยพื้นฐานของตัวแก้ไขกราฟิกของเรา การปรับปรุงประสบการณ์ของนักพัฒนาซอฟต์แวร์ที่คุณทำได้นั้นไม่มีขีดจำกัด
อ่านเพิ่มเติมในบล็อก Toptal Engineering:
- 10 ข้อผิดพลาดที่พบบ่อยที่สุดที่นักพัฒนา Unity สร้างขึ้น
- ความสามัคคีกับ MVC: วิธีเพิ่มระดับการพัฒนาเกมของคุณ
- การเรียนรู้กล้อง 2D ใน Unity: บทช่วยสอนสำหรับนักพัฒนาเกม
- แนวทางปฏิบัติที่ดีที่สุดและเคล็ดลับของ Unity โดย Toptal Developers
บล็อก Toptal Engineering ขอขอบคุณ Goran Lalic สำหรับความเชี่ยวชาญและการตรวจสอบทางเทคนิคของบทความนี้