쯔꾸르식 유니티 게임 공부

11.이벤트 캐릭터 이동명령 이벤트

ruripanda 2025. 1. 15. 16:00

using System.Collections;
using UnityEngine;

[System.Serializable]
public class TestMove
{
    public string name;
    public string direction;
}
public class TestOrder : MonoBehaviour
{
    //[SerializeField]
    //public TestMove[] move;
    public string direction;
    private PlayerManager thePlayer;
    private OrderManager theOrder;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        theOrder = FindAnyObjectByType<OrderManager>();
        thePlayer = FindAnyObjectByType<PlayerManager>();
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Player")
        {
            theOrder.PreLoadCharacter();
            //for (int i = 0; i < move.Length; i++)
            //{
            //    theOrder.Move(move[i].name, move[i].direction);
            //}
            theOrder.Turn("NPC", direction);
        }
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovingObjcet : MonoBehaviour
{
    public string characterName;

    //케릭터 이동속도
    public float speed;
    //픽셀 단위로 움직이기 구현
    public int walkCount;
    protected int currentWalkCount;

    private bool notCoroutine = false;
    //벡터(케릭터의 이동 방향)
    protected Vector3 vector;//protected는 부모자식간에 참조는 가능하지만 인스펙터창과 외부클레스는 접근불가

    public Queue<string> queue;//FIFO, 선입선출 자료구조, 큐
    public BoxCollider2D boxCollider;//박스콜라이더
    public LayerMask layerMask;//통과불가 에이리어 설정
    public Animator animator;//애니메이터

    public void Move(string _dir, int _frequency = 5)
    {
        queue.Enqueue(_dir);
        if (!notCoroutine)
        {
            notCoroutine = true;
            StartCoroutine(MoveCoroutine(_dir, _frequency));
        }
    }

    IEnumerator MoveCoroutine(string _dir, int _frequency)
    {
        while(queue.Count != 0)
        {
            string direction = queue.Dequeue();
            vector.Set(0, 0, vector.z);
            //queue.Enqueue;큐에서 넣는 것
            //queue.Dequeue;큐에서 빼는 것
            switch (direction)
            {
                case "UP":
                    vector.y = 1f;
                    break;
                case "DOWN":
                    vector.y = -1f;
                    break;
                case "RIGHT":
                    vector.x = 1f;
                    break;
                case "LEFT":
                    vector.x = -1f;
                    break;
            }

            //에니메이터 불러오기
            animator.SetFloat("DirX", vector.x);
            animator.SetFloat("DirY", vector.y);
            animator.SetBool("Walking", true);

            while (currentWalkCount < walkCount)
            {
                transform.Translate(vector.x * speed, vector.y * speed, 0);
                currentWalkCount++;
                yield return new WaitForSeconds(0.01f);
            }
            currentWalkCount = 0;
            if (_frequency != 5)
            {
                animator.SetBool("Walking", false);
            }
            animator.SetBool("Walking", false);
        }
        animator.SetBool("Walking", false);
        notCoroutine = false;
    }
      
    protected bool CheckCollsion()
    {
        RaycastHit2D hit;//레이케스트

        Vector2 start = transform.position; //캐릭터의 현제 위치 값
        Vector2 end = start + new Vector2(vector.x * speed * walkCount, vector.y * speed * walkCount);
        //캐릭터가 이동하고자하는 위치 값 그리고 사이즈

        boxCollider.enabled = false;
        hit = Physics2D.Linecast(start, end, layerMask);
        boxCollider.enabled = true;

        if (hit.transform != null)
            return true;
        return false;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class NPCMove
{
    [Tooltip("NPCMove를 체크하면 NPC가 움직임")]
    public bool NPCmove;

    public string[] direction;//NPC가 움직일 방향설정

    [Range(1, 5)]
    [Tooltip("1=천천히, 2=조금 천천히, 3= 보통, 4.빠르게, 5=연속적으로")]
    public int frequency;//NPC가 얼마나 빠른 속도로 움직일 것인가
}

public class NPCManager : MovingObjcet
{
    [SerializeField]
    public NPCMove npc;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        queue = new Queue<string>();
        StartCoroutine(MoveCoroutine());
    }

    public void SetMove()
    {

    }

    public void SetNoMove()
    {

    }

    IEnumerator MoveCoroutine()
    {
        if(npc.direction.Length != 0)
        {
            for(int i =0; i< npc.direction.Length; i++)
            {
                switch (npc.frequency)
                {
                    case 1:
                        yield return new WaitForSeconds(4f);
                        break;

                    case 2:
                        yield return new WaitForSeconds(3f);
                        break;

                    case 3:
                        yield return new WaitForSeconds(2f);
                        break;

                    case 4:
                        yield return new WaitForSeconds(1f);
                        break;

                    case 5:
                        break;
                }

                yield return new WaitUntil(() => queue.Count < 2);

                base.Move(npc.direction[i], npc.frequency);
                //실질적인 이동 구간
                if (i == npc.direction.Length - 1)
                {
                    i = -1;
                }
            }
        }
    }
}
sing System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class OrderManager : MonoBehaviour
{

    private PlayerManager thePlayer;//이벤트 중 키 입력 방지
    private List<MovingObjcet> characters;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        thePlayer = FindAnyObjectByType<PlayerManager>();
    }

    public void PreLoadCharacter()
    {
        characters = ToList();
    }

    public List<MovingObjcet> ToList()
    {
        List<MovingObjcet> tempList = new List<MovingObjcet>();
        MovingObjcet[] temp = FindObjectsByType<MovingObjcet>(FindObjectsSortMode.None);

        for (int i = 0; i < temp.Length; i++)
        {
            tempList.Add(temp[i]);
        }

        return tempList;//이 함수가 없어서 null이 나온다
    }

    public void Turn(string _name, string _dir)
    {
        for (int i = 0; i < characters.Count; i++)
        {
            if (_name == characters[i].characterName)
            {
                characters[i].animator.SetFloat("DirY", 0f);
                characters[i].animator.SetFloat("DirX", 0f);
                switch (_dir)
                {
                case "UP":
                    characters[i].animator.SetFloat("DirY", 1f);
                    break;
                case "DOWN":
                    characters[i].animator.SetFloat("DirY", -1f);
                    break;
                case "LEFT":
                    characters[i].animator.SetFloat("DirX", -1f);
                    break;
                case "RIGHT":
                    characters[i].animator.SetFloat("DirX", 1f);
                    break;
                }
            }
        }
    }

    public void Move(string _name, string _dir)
    {
        for (int i = 0; i < characters.Count; i++)
        {
            if (_name == characters[i].characterName)
            {
                characters[i].Move(_dir);
            }
        }
    }
}

 

이번 강좌는 버전 차이가 심한 유니티6로 작성해서 아다리가 안 맞는 경우가 많다

최대한 맞추면서 진행하겠다

 

강좌에 앞서 유니티 기능을 설명하겠다

오버라이드 타일 처럼 오버라이드 애니메이터다

NPC들이 마구 많을때 애니메이터를 일일이 만들면 시간과 기술이 많이들어가지만

똑같은 캐릭터에 외형만 틀린 경우라면 이렇게 오버라이드 애니메이터로 찍어 낼 수 있다

 

이제 강좌로 돌아가서

OrderManager 스크립트를 만들어 준다

private MovingObject[] characters;//를

private List<MovingObjcet> characters;//로 작성해준다

 

배열의 크기는 한번 써주면 고정되어서 변경이 안된다

NPC는 마을마다 다른 숫자가 배치되어 있을 수 있다

그래서 배열은 쓰지말고 List를 사용해야 된다

 

리스트는 기본적으로 Add(), Remove(), Clear()가 있다

이걸 이용하면 늘렸다가 줄였다가 초기화가 가능하다

 

우리는 이 함수로 캐릭터를 채워 넣을 것이다

ToList()함수를 characters에 대입시켜 줄 것이다

 

그를 위해 List<MovingObject> tempList = new List<MovingObject>();를 선언해주고

배열로 MovingObjcet[] Temp를 사용해주자

위에서 배열은 안된다고 하지 않았느냐고 말하자면 yes 그렇게 말했다

하지만 함수로 적용되면서 return으로 반환해주기 때문에 이 경우에는 사용해도 된다

 

FindObjectsType<MovingObject>//를

FindObjectsByType<MovingObjcet>(FindObjectsSortMode.None);//처럼 작성

강좌에는 구버전이라서 위처럼 작성되었지만

유니티6 버전에서는 이렇게 작성해야 적용된다

안그러면 사용하지 않는 함수라고 오류를 낸다

 

이걸 주의해서 마저 작성해준다

 

다음은 이동을 작성해준다

 

string _name 로 캐릭터를 지정해주고 string _dir로 움직임을 지정해주자

이름과 일치하는 캐릭터를 찾아서 작성을해야 하기 때문에

charactersName가 들어가 있다

 

이렇게 무빙 오브젝트에 작성해주자

이걸 받아와서 캐릭터를 움직일 것이다

 

다시 여기로 돌아와서 캐릭터를 받아오는데 성공하면 Move함수로 움직임을 구현한다

 

그리고 함수에서 Move함수에 protected로 선언되어 있는데 public로 선언해서 공개여부를 확장해 준다

인스펙터에 나오게!

 

그리고 쓰다가 보면 프리컨시에 기본값이 없어서 오류가 나느데 이렇게 =5를 대입해주자

 

그리고 빈 객체를 만들어서 오더 메니저를 유니티 엔진에 넣어주자

 

그리고 발판을 만들어주자

 

테스트를 위한 커스텀 클레스가 들어간다

위 스크립트 TestOrder이다

 

찾아와야 할게 플레이어와 오더 메니져이다

 

또 이렇게 찾성해준다

필자가 작성한 스크립트에 빨간 줄이 나오는데 이건 완성본을 가지고 보여줘서 그런 문제가 생긴 것이다

완성본을 쓰게되면 빨간줄을 그은 것이 주석처리 되어 있다

 

이렇게 써주고 플레이 테스트를 하면 튕기는 효과가 발생되는데

 

 

이 함수를 호출하지 않았기 때문에 문제가 생긴 것이다

 

이렇게 PreLoadCharacter() 함수를 불러와주자!

 

이렇게 해주면 플레이 테스트 시 움직여준다!

 

그런데 이렇게 해주면 문제점이 생기는데 너무 빠르게 움직여버린다

이걸 해결하기 위해 Queue를 사용하겠다

 

무빙오브젝트에 큐를 이렇게 작성해준다

큐라고 하는 것은 FIFO, 선입선출 자료구조로 queue에 a를 넣는다면 a가 나오지만

a b c이렇게 넣으면 순서대로 c b a로 값이 빠지게 된다

 

스크립트 PlayerManager에 큐를 작성해주자

똑같이 NPC메니져에도 넣어주자

 

무빙오브젝트로 돌아와서 큐를 사용해주자

 

이렇게

그리고 기존에 써놓았던 무빙오브젝트의 이동 스크립트를 이렇게 안으로 넣어준다

 

그리고 사용을 위해서 무빙오브젝트 위에 큐를 작성해준다

 

이렇게 해주면 이동 후 워킹이 실행된다

그런데 문제는 이렇게 작성해주면 한번에 빠르게 실행된다

 

MovingObject에

이렇게 작성해준다

코루팅이 확 움직이는 것을 방질하기 위해!

이렇게 true일 때만 코루틴이 실행되게 한다

 

그리고 아래에 notCoroutine를 =  거짓(false)로 되게 맞춘다

 

조건문이 시작되면 큐가 참이되면서 실행을하고

while문 반복이 끝나면 큐가 false로 바뀌면서 문제를 해결해준다

 

그런데 문제는 빠르게 실행하면 또 빠르게 이동하면서 한발한발 움직이는 애니메이션이 실행이 안된다

다시 잡아준다

 

NPC메니져에 enw WaitUntil()) => queue.Count < 2);로 바꿔준다

 

큐가 2 이상이면 계속 기다리다가 큐가 빠지면 다시 실행해주는 것이다

큐가 2이상 못 오르게 해준다

이렇게 하면 문제가 없어진다

 

이렇게 되면 npcCanMove로 만들어 놓은 수치가 필요없어지므로 모두 지워준다

 

그리고 바라보는 방향만 하는 것도 추가해준다

Order매니져에 바라보는 방향도 작성해준다

 

우리가 에니메이션 만들때 사용한 파라미터를 감고해서 만들게 된다

테스트할 스크립트에는 이렇게 작성해준다

그리고 확인하면 턴이 될 것이다

 

이번 강좌는 케이디님이 한번에 많은 량의 스크립틀 작성하고 수정하셔서 필자도 어벙벙하다...

'쯔꾸르식 유니티 게임 공부' 카테고리의 다른 글

13.대화시스템  (0) 2025.01.21
12.NPC 충돌방지(Box콜라이더 이동)  (0) 2025.01.16
10.5 추가설명  (0) 2025.01.14
10.NPC구현하기  (0) 2025.01.10
6.씬과 씬 이동 개량 완료  (0) 2025.01.09