728x90
SMALL

이번에는 NPC 차량과 충돌하였을 때 게임실패하는 경우를 구현하였습니다.
지난번에 Car 클래스 하나로 플레이어 차량과 NPC 차량을 처리하겠다고 했지만 기존에 만든 Car 클래스를 PlayerCar로 바꾸고, NpcCar와 NpcCarManager를 따로 만들었습니다.

 

NPC로 사용할 자동차 프리팹을 만들어 NpcCar 스크립트와 연결하였습니다.

 

빈 오브젝트를 씬에 배치한 후에 NpcCarManager 스크립트 연결하였습니다.
NpcCarManager는 아래의 역할을 위해 만들었습니다.

  • NPC 자동차 끼리는 추돌사고가 나면 안되기 때문에 모두 동일한 속도로 이동해야 된다.
    • 프리팹 마다 속도를 따로 따로 설정할 필요가 없도록 만든다.
    • 게임이 끝나면 각각의 NPC자동차를 전부 멈추게 하지 않고, NpcCarManager만 멈추면 끝나도록 처리한다.
  • 이동을 끝낸 NPC 차량은 Destroy로 파괴하지 않고, 큐에 잠깐 보관해뒀다가 재활용한다.

NpcCarManager는 나중에 맵마다 다른 종류의 NPC 차량이 등장하도록 설정할 수 있도록 리소스에서 자동차들을 불러오지 않고, 참조하고 있던 프리팹을 바로 복사하는 방식으로 구현하였습니다.

 

NPC 차량을 모두 구현한 후에 게임을 실행하니 게임은 NPC 차량이 움직이지 않고, 아래의 에러가 발생하였습니다.

 

인터넷을 찾아 보니 Rigidbody의 Kinematic을 체크하거나 MeshCollider를 제거하라고 하여 MeshCollider를 모두 지웠습니다.
그리고 게임을 실행해 봤습니다.

처음 올릴 때 실수로 추가하지 않아 다시 올립니다...

 

해당 gif는 테스트를 위해 자동차가 등장하는 간격을 줄였습니다.
위 gif를 보시면 도착지점에서 자동차가 잠깐 깜박이는 현상이 발생합니다.
재활용할 자동차를 활성화한 후 시작점으로 좌표를 바꾸는 순간 잠깐 깜박이는 것 처럼 보이는 현상이라고 생각합니다.
어차피 화면 밖에서만 일어나는 현상이기 때문에 고치지 않고 놔두려고 했는데 프리팹을 처음 복사할 때도 (0,0,0)좌표에서 동일한 현상이 발생하였습니다.
그래서 씬을 처음 불러왔을 때 NpcCarManager에서 프리팹을 위치를 바닥밑으로 초기화하는 처리를 추가하였습니다.
이동을 끝낸 자동차들으 바닥밑으로 이동시킨 후 보관하도록 수정하였습니다.

 

테스트해보니 노란 트럭은 너무 큰 것 같아 작은 자동차로 바꿨습니다.

 

NPC 차량의 태그를 NpcCar로 수정하여 플레이어가 NPC 차량과 충돌하였을 때 처리도 구현하였습니다.

using PathCreation;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;

namespace TaxiGame3D
{
    public class GameLogic : MonoBehaviour, InputControls.IPlayerActions
    {
        [SerializeField]
        PathCreator playerPath;
        [SerializeField]
        NpcCarManager npcCarManager; 
        [SerializeField]
        TMP_Text stateText;

        InputControls inputControls;
        bool isAccelPressing = false;

        bool wasCustomerTaken;

        public static GameLogic Instance
        {
            get;
            private set;
        }

        [field: SerializeField]
        public PlayerCar PlayerCar
        {
            get;
            private set;
        }

        void Awake()
        {
            Instance = this;
        }

        IEnumerator Start()
        {
            npcCarManager.Play();

            PlayerCar.SetPath(playerPath.path);
            PlayerCar.OnCrashed += (sender, args) =>
            {
                StartCoroutine(EndGame(false));
            };
            PlayerCar.OnArrive += (sender, args) =>
            {
                StartCoroutine(EndGame(true));
            };
            yield return new WaitForSeconds(1);
            PlayerCar.PlayMoving();
        }

        void OnEnable()
        {
            if (inputControls == null)
                inputControls = new();
            inputControls.Player.SetCallbacks(this);
            inputControls.Player.Enable();
        }

        void OnDisable()
        {
            inputControls?.Player.Disable();
        }

        void Update()
        {
            var s = $"Moving: {PlayerCar.IsEnableMoving}\n";
            s += $"Customer: {wasCustomerTaken}";
            stateText.text = s;

            if (isAccelPressing)
                PlayerCar.PressAccel();
            else
                PlayerCar.PressBrake();
        }

        public void OnAccelerate(InputAction.CallbackContext context)
        {
            isAccelPressing = context.ReadValue<float>() != 0f;
        }

        public void OnCarEnterTrigger(CustomerTrigger trigger)
        {
            if (wasCustomerTaken)
                StartCoroutine(TakeOut());
            else
                StartCoroutine(TakeIn());
        }

        IEnumerator TakeIn()
        {
            PlayerCar.StopMoving();
            yield return new WaitForSeconds(3f);
            wasCustomerTaken = true;
            PlayerCar.PlayMoving();
        }

        IEnumerator TakeOut()
        {
            PlayerCar.StopMoving();
            yield return new WaitForSeconds(3f);
            wasCustomerTaken = false;
            PlayerCar.PlayMoving();
        }

        IEnumerator EndGame(bool isGoal)
        {
            PlayerCar.StopMoving();
            npcCarManager.Stop();
            yield return new WaitForSeconds(3);
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }
}
using PathCreation;
using System;
using UnityEngine;

namespace TaxiGame3D
{
    public class PlayerCar : MonoBehaviour
    {
        [SerializeField]
        float acceleration = 1f;
        [SerializeField]
        float maxSpeed = 5f;
        [SerializeField]
        float brakeForce = 1f;

        VertexPath path;

        float speed = 1f;
        float movement = 0f;

        Rigidbody rb;

        public bool IsEnableMoving
        {
            get;
            set;
        }

        public event EventHandler OnCrashed;
        public event EventHandler OnArrive;

        void Awake()
        {
            rb = GetComponent<Rigidbody>();
        }

        void Update()
        {
            if (!IsEnableMoving)
                return;

            movement += Time.deltaTime * speed;
            rb.MovePosition(path.GetPointAtDistance(movement, EndOfPathInstruction.Stop));
            rb.MoveRotation(path.GetRotationAtDistance(movement, EndOfPathInstruction.Stop));

            if (movement >= path.length)
                OnArrive?.Invoke(this, EventArgs.Empty);
        }

        void OnCollisionEnter(Collision collision)
        {
            if (collision.gameObject.CompareTag("NpcCar"))
                OnCrashed?.Invoke(this, EventArgs.Empty);
        }

        public void SetPath(VertexPath path)
        {
            this.path = path;
            movement = 0f;
            rb.position = path.GetPoint(0);
            rb.rotation = path.GetRotation(0f, EndOfPathInstruction.Stop);
        }

        public void PlayMoving()
        {
            IsEnableMoving = true;
            speed = 1f;
        }

        public void StopMoving()
        {
            IsEnableMoving = false;
            speed = 0f;
        }

        public void PressAccel()
        {
            if (IsEnableMoving)
                speed = Mathf.Min(speed + Time.deltaTime * acceleration, maxSpeed);
        }

        public void PressBrake()
        {
            if (IsEnableMoving)
                speed = Mathf.Max(speed - Time.deltaTime * brakeForce, 1f);
        }
    }
}
using PathCreation;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace TaxiGame3D
{
    public class NpcCarManager : MonoBehaviour
    {
        [SerializeField]
        GameObject[] carPrefabs;
        [SerializeField]
        PathCreator[] paths;
        [SerializeField]
        Transform poolPosition;
        [SerializeField]
        [Min(0.001f)]
        float minSpawnDelay = 1f;
        [SerializeField]
        [Min(0.001f)]
        float maxSpawnDelay = 1f;
        [SerializeField]
        float moveSpeed = 50f;

        HashSet<NpcCar> activeCars = new();
        Queue<NpcCar> carPool = new();

        public bool IsPlaying
        {
            get;
            private set;
        }

        public void Play()
        {
            IsPlaying = true;
            for (int i = 0; i < paths.Length; i++)
                StartCoroutine(WaitAndSpawn(i));
        }

        public void Stop()
        {
            IsPlaying = false;
            StopAllCoroutines();
        }

        void Start()
        {
            foreach (var prefab in carPrefabs)
            {
                prefab.transform.SetPositionAndRotation(
                    poolPosition.position, poolPosition.rotation
                );
            }
        }

        void Update()
        {
            if (!IsPlaying)
                return;

            var desapwns = new Queue<NpcCar>();
            var moveAmount = moveSpeed * Time.deltaTime;
            foreach (var car in activeCars)
            {
                car.UpdateMoving(moveAmount);
                if (car.IsArrive)
                    desapwns.Enqueue(car);
            }
            while (desapwns.Count > 0)
                Despawn(desapwns.Dequeue());
        }

        IEnumerator WaitAndSpawn(int pathIndex)
        {
            if (!IsPlaying)
                yield break;

            yield return new WaitForSeconds(Random.Range(minSpawnDelay, maxSpawnDelay));

            NpcCar car = null;
            if (carPool.Count > 0)
            {
                car = carPool.Dequeue();
            }
            else
            {
                var go = Instantiate(carPrefabs[Random.Range(0, carPrefabs.Length)]);
                car = go.GetComponent<NpcCar>();
            }
            car.SetPath(paths[pathIndex].path);
            car.gameObject.SetActive(true);
            activeCars.Add(car);

            StartCoroutine(WaitAndSpawn(pathIndex));
        }

        void Despawn(NpcCar car)
        {
            car.gameObject.SetActive(false);
            car.transform.SetPositionAndRotation(
                poolPosition.position, poolPosition.rotation
            );
            activeCars.Remove(car);
            carPool.Enqueue(car);
        }
    }
}
using PathCreation;
using UnityEngine;

public class NpcCar : MonoBehaviour
{
    VertexPath path;
    float movement;
    Rigidbody rb;

    public bool IsArrive => movement >= path.length;

    public void SetPath(VertexPath path)
    {
        this.path = path;
        movement = 0;

        if (rb == null)
            rb = GetComponent<Rigidbody>();
        rb.position = path.GetPoint(0);
        rb.rotation = path.GetRotation(0f, EndOfPathInstruction.Stop);
    }

    public void UpdateMoving(float amount)
    {
        movement += amount;
        if (rb == null)
            return;
        rb.MovePosition(path.GetPointAtDistance(movement, EndOfPathInstruction.Stop));
        rb.MoveRotation(path.GetRotationAtDistance(movement, EndOfPathInstruction.Stop));
    }
}

 

구현결과

 

깃 허브 저장소 : taxi-game-3d-unity

728x90
LIST

+ Recent posts