AI ๊ฐ ๋ชฉํ๋ฌผ์ ํฅํด ์ด๋ํ ๋,
๊ฒฝ๋ก ๋๋ ๋ฐ๋ก ๋ฐ ๋ฐ์ ์ ์ ๋ฒ์๊ณต๊ฒฉ ์ฅํ์ด ๊น๋ฆด๋๊ฐ ์๋ค.
์ด๋ด๊ฒฝ์ฐ ๊ทธ ์ฅํ์ ๋นจ๋ฆฌ ๋น ์ ธ๋์์ ๋ค์ ๋ชฉํ๋ฌผ์ ํฅํด ์ด๋ํด์ผ ํ๋ค.
๊ทธ ํ๋์ A* pathfinding ์ ์ด์ฉํด์ ๊ฐ๋จํ ๋ก์ง์ ๊ตฌํํ๋ค.
AI ์ ํ๋์์
1. ๋ชฉํ๋ฌผ์ ์ด๋์, ๋ฒ์๊ณต๊ฒฉ์ด ๋ฐ์ํ๋ฉด ๊ทธ ๋ฒ์๋ฅผ ๋์๊ฐ๋ ๊ฒฝ๋ก๋ฅผ ๊ณ์ฐ
2. ๋ง์ผ ๋ฒ์๊ณต๊ฒฉ์ด ๊ด๋ฒ์ํด์ ๋๋ฌด ๋ง์ด ๋์๊ฐ์ผ ํ ๊ฒฝ์ฐ, ๋ฒ์๊ณต๊ฒฉ์ ๊ทธ๋๋ก ํต๊ณผํด์ ์ง์ง.
3. ๋ฒ์๊ณต๊ฒฉ์ด ํฌ์ง ์๊ณ ๋์๊ฐ ๋ง ํ๋ค๊ณ ํ๋จ์, ๋ฒ์๊ณต๊ฒฉ์ ๋์์ ๋ชฉํ๋ก ์ด๋.
์๋ ๋์์์ ๊ทธ ์์ง์์ ๋ นํํ ๊ฒ.
๋ก์ง์ AStar Pathfinding project ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด์ ๋งค์ฐ ๋จ์ํ๊ฒ ๊ตฌํํ๋ค.
AStar Pathfinding Project ์ ๊ทธ๋ฆฌ๋ ๊ทธ๋ํ๋ฅผ ์ด์ฉํด์ ์ ์ ์ฒด๋ฅผ ๊ณ์ฐํด์ฃผ๊ณ ,
์ฅํ์ ์ํ๋ ํฌ๊ธฐ์ ์ค๋ธ์ ํธ์ย DynamicGridObstacle๋ฅผ ์ปดํฌ๋ํธ ์ถ๊ฐ ํด์,ย
๋จ์ํ ์ํ๋ ์์น์ ์ค๋ธ์ ํธ๋ฅผ ์จ ์คํ ํ๋ฉด ๊ทธ๋ํ๊ฐ ๊ฒฝ์ ๋์ด ์์์ AI๊ฐ ์ง๋๊ฐ ์ ์๋ ๋ฒ์๋ฅผ ์์ฑํ๊ฒ ํ๋ค.ย
๊ทธ๋๊ณ AI๋ ๊ทธ๋ํ๊ฐ ๋ณํํ ๋๋ง๋ค ๋ค์ ํจ์ค๋ฅผ ๊ณ์ฐํ๋๋ก ํธ๋ฆฌ๊ฑฐ ํด์ฃผ๋ฉด ๋.
ํ์ํ ํด๋์ค๋ ์๋์ ๊ฐ๋ค.
์ฅํ ์ค๋ธ์ ํธ์ ๋ถ์ฌ์ฃผ๋ ํด๋์ค
public class TestDynamicGrid : DynamicGridObstacle { public TestChannel MyChannel; protected override void OnDisable() { base.OnDisable(); MyChannel.RaiseGraphUpdatedEvent(); } public override void OnGraphsPostUpdate() { MyChannel.RaiseGraphUpdatedEvent(); } }์ฑ๋์ ๊ทธ๋ํ์๊ฒ ๋ณ๋์ด ์์ ๊ฒฝ์ฐ ์ด๋ฒคํธ ํธ๋ฆฌ๊ฑฐ์ฉ.
์ฅํ์ด ์จ ์คํ ํ ๋๋ง๋ค RaiseGraphUpdatedEvent ๊ฐ ์คํ๋๋ค.
[CreateAssetMenu(menuName = "SO/TestChannel")] public class TestChannel : ScriptableObject { public UnityAction OnGraphUpdated; public void RaiseGraphUpdatedEvent() { if (OnGraphUpdated != null) { OnGraphUpdated.Invoke(); } else { Debug.LogWarning("Graph updated event raised, but no one was listening"); } } }AI ์ ๊ธธ์ฐพ๊ธฐ ๊ธฐ๋ฅ์ ๋ด๋นํ๋ ํด๋์ค
public class TestAIPathFinder : MonoBehaviour { public Transform Goal; public TestChannel MyChannel; private TestMovement _movement; private Seeker _seeker; private void Start() { _movement = GetComponent<TestMovement>(); _seeker = GetComponent<Seeker>(); } private void OnEnable() { MyChannel.OnGraphUpdated += OnGraphUpdated; } private void OnDisable() { MyChannel.OnGraphUpdated -= OnGraphUpdated; } private void OnGraphUpdated() { _seeker.StartPath(transform.position, Goal.position, OnPathComplete); } private void OnPathComplete(Path p) { _movement.StartPath(p, Goal.position); } }๋ง์ง๋ง์ผ๋ก AI์ ์์ง์์ ๋ด๋นํ๋ ํด๋์ค
using Pathfinding; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestMovement : MonoBehaviour { public float Speed; public float MaxDist; private float _th = 0.3f; private int _nodeNum = 0; private int _nodeNow = 0; private Path _path = null; private bool _isPathMoving = false; private bool _isDirectMoving; private Vector3 _moveDirection; private Vector3 _nodePos; private Vector3 _destPos; internal void StartPath(Path p, Vector3 DestPos) { // TODO // check how to do pooling //if (_path != null) //{ // _path.Release(this); //} _destPos = DestPos; // check if the path is too long. if so, move directly to the target. if (p.GetTotalLength() > MaxDist) { _isDirectMoving = true; _isPathMoving = false; _moveDirection = _destPos - transform.position; } else { _isDirectMoving = false; _isPathMoving = true; _path = p; _nodeNum = p.vectorPath.Count; _nodeNow = 0; _nodePos = _path.vectorPath[_nodeNow]; _moveDirection = _nodePos - transform.position; } } private void Update() { if (_isPathMoving) { // move towards it transform.Translate(_moveDirection.normalized * Speed * Time.deltaTime); // if object is close to the node, move on to the next node float dist = Vector3.Distance(this.transform.position, _nodePos); if (dist < _th) { _nodeNow += 1; if (_nodeNow >= _nodeNum) { // reached the goal, stop moving _isPathMoving = false; return; } // towards the next node _nodePos = _path.vectorPath[_nodeNow]; _moveDirection = _nodePos - transform.position; } } else if (_isDirectMoving) { // move towards destination directly transform.Translate(_moveDirection.normalized * Speed * Time.deltaTime); } } }ํ ์คํธ์ฉ์ผ๋ก ๋์ถฉ ์ด ์ฝ๋๋ผ์ ๋๋ฌ์๋ ์ํด๋ฐ๋
1๋ฒ ์กฐ๊ฑด์์ ๋ฒ์๊ณต๊ฒฉ์ด ๋ฐ์ํ๋ฉด ๋ฌด์กฐ๊ฑด ์ฌ๊ณํ ํ๋๊ฑฐ์?