3D 콘텐츠 제작

좀짓막(좀비 집짓고 막기) [#2] - 건축물 설치 R&D

송현호 2023. 9. 25. 11:49

저번엔 간단하게 맵을 구성하는 것까지만 했었고 이번 시간엔 A* R&D를 한다음 캐릭터의 이동을 구현하려고 했었는데 2일차의 건축물 설치 R&D를 먼저 하는게 더 효율적일 것 같아서 순서를 바꿔 설치 R&D를 먼저하기로 했다. 그걸 위해서 우클릭을 했을때 게임매니저로부터 좌표를 받아서 캐릭터를 이동시키는 코드를 작성하였다.

 

private void Update()
    {
        if(Input.GetMouseButtonDown(1))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Debug.DrawRay(ray.origin, ray.direction * 5f, Color.red , 3f);
            RaycastHit hit;
            if(Physics.Raycast(ray, out hit, 50f))
            {
                Debug.Log(hit.point);
                if(player != null)
                {
                    Debug.Log(player);
                    if (this.routine != null)
                    {
                        StopCoroutine(this.routine);
                    }
                    this.routine = StartCoroutine(player.CoMove(hit.point));
                }
            }
        }
    }
public IEnumerator CoMove(Vector3 pos)
    {
        this.transform.LookAt(pos);
        while (true)
        {
            var dis = Vector3.Distance(this.transform.position, pos);
            if (dis <= 0.1f)
            {
                break;
            }
            this.transform.Translate(Vector3.forward * speed * Time.deltaTime);
            yield return null;
        }
        
    }

그 다음에 어떻게 할지 고민이었는데 일단 게임매니저에 월 프리펩을 할당하고 캐릭터가 목표 지점에 도달했을때 Instantiate를 하도록 해보았다.

 

그리고 Mathf함수를 적용해서 설치되는 위치가 일관되도록 해주었다.

 

public IEnumerator CoMove(Vector3 pos, GameObject wall)
    {
        this.transform.LookAt(pos);
        while (true)
        {
            var dis = Vector3.Distance(this.transform.position, pos);
            if (dis <= 0.1f)
            {

                Vector3 buildPos = new Vector3(Mathf.Floor(pos.x)+ 0.5f, pos.y, Mathf.Round(pos.z)-0.3f);
                Instantiate(wall,buildPos,wall.transform.rotation);
                break;
            }
            this.transform.Translate(Vector3.forward * speed * Time.deltaTime);
            yield return null;
        }
        
    }

 

이제 게임매니저에 만들어놓은 2차원 bool배열의 값을 반영해서 벽이 설치되는 곳에 bool값이 트루인 좌표가 있다면 설치가 불가능하도록 할 것이다.

 

if (tileMap[(int)(buildPos.x+0.5f),(int)Mathf.Round(hit.point.z)] == true || tileMap[(int)(hit.point.x - 0.5f), (int)Mathf.Round(hit.point.z)] == true)
                    {
                        
                    }
                    else
                    {
                        if (this.routine != null)
                        {
                            StopCoroutine(this.routine);
                        }
                        this.routine = StartCoroutine(player.CoMove(buildPos, wall));
                    }

이제 설치와 이동 코드를 분리해서 벽모양의 UI를 드래그해서 오브젝트를 설치할 수 있도록 할 것이다. 드래그 앤 드롭은 다음 블로그를 참고 했다.

 

https://krapoi.tistory.com/entry/Unity-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD

 

[Unity] 게임 개발 - 드래그 앤 드롭

이번에 만들어볼 기능은 드래그 앤 드롭이다. 게임 개발을 하다 보면 드래그 기능이 필요할 때가 있다. 이런 거 말이다. 지금은 드래그할 때와 드롭할 때의 간단한 부분만 만들어 볼 것이다. 드

krapoi.tistory.com

UI로 이미지를 드래그해서 벽을 설치하는 것까진 쉽게 만들었는데 드래그한 포지션의 설치 여부를 판정해서 색깔로 표시해주는 기능을 구현하는데 애를 많이 먹었다. 처음엔 Drag스크립트에서 GameManager의 배열값을 받아와서 마우스의 위치를 저장해서 그 다음 프레임때 마우스의 위치와 같으면 타일을 생성 같지 않으면 기존 타일을 파괴하고 새 타일을 생성하는 식으로 하려다가 생각보다 코드가 복잡해져서 내가 원하는 의도대로 되지 않았다. 고민을 많이 하다가 Drag 스크립트에 액션변수를 선언하고 드래그 중일때 타일을 활성화해서 머티리얼의 색을 바꿔주는 식으로 문제를 해결 했다.

 

그리고 미리 만들어 놓은 SetFulled 메서드를 사용해서 설치를 한 자리의 bool배열 값을 1(fulled)로 만들어 주었다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.PlayerSettings;

public class GameManager : MonoBehaviour
{
    [SerializeField]
    private GameObject character;
    [SerializeField]
    private CameraCtrl cam;
    [SerializeField]
    private GameObject wall;
    [SerializeField]
    private Drag wallDrag;
    [SerializeField]
    private GameObject tile1;
    [SerializeField]
    private GameObject tile2;

    public bool[,] tileMap = new bool[128, 128];
    private Player player;
    private Coroutine routine;
    // Start is called before the first frame update
    public static readonly GameManager instance = new GameManager();
    private GameManager() { }
    private void Awake()
    {
        Init();
    }
    void Start()
    {

        wallDrag.onBuild = () =>
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 50f))
            {
                Debug.Log(hit.point);
                Vector3 buildPos = new Vector3(Mathf.Floor(hit.point.x) + 0.5f, hit.point.y, Mathf.Round(hit.point.z) - 0.3f);
                int tile1PosX = (int)(buildPos.x + 0.5f);
                int tile2PosX = (int)(buildPos.x - 0.5f);
                int tilePosZ = (int)Mathf.Round(hit.point.z);
                if (player != null)
                {
                    if (tileMap[tile1PosX, tilePosZ] == true || tileMap[tile2PosX, tilePosZ] == true)
                    {

                    }
                    else
                    {
                        //if (this.routine != null)
                        //{
                        //    StopCoroutine(this.routine);
                        //}
                        //this.routine = StartCoroutine(player.CoMove(buildPos, wall));
                        Instantiate(wall, buildPos, wall.transform.rotation);
                        SetFulled(tile1PosX, tilePosZ);
                        SetFulled(tile2PosX, tilePosZ);
                    }

                }
            }
            tile1.SetActive(false);
            tile2.SetActive(false);
        };

        wallDrag.onBuildable = () =>
        {
            tile1.SetActive(true);
            tile2.SetActive(true);
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 50f))
            {
                Vector3 buildPos = new Vector3(Mathf.Floor(hit.point.x) + 0.5f, hit.point.y, Mathf.Round(hit.point.z) - 0.3f);

                tile1.transform.position = new Vector3((buildPos.x + 0.5f), -0.49f, Mathf.Round(hit.point.z));
                tile2.transform.position = new Vector3((buildPos.x - 0.5f), -0.49f, Mathf.Round(hit.point.z));
                Debug.Log(tileMap[(int)tile1.transform.position.x, (int)tile1.transform.position.z]);
                if (tileMap[(int)tile1.transform.position.x, (int)tile1.transform.position.z] == false)
                {
                    this.tile1.GetComponent<Renderer>().materials[0].color = Color.green;
                }
                else
                {
                    this.tile1.GetComponent<Renderer>().materials[0].color = Color.red;
                }

                if (tileMap[(int)tile2.transform.position.x, (int)tile2.transform.position.z] == false)
                {
                    this.tile2.GetComponent<Renderer>().materials[0].color = Color.green;
                }
                else
                {
                    this.tile2.GetComponent<Renderer>().materials[0].color = Color.red;
                }
            }
        };
    }

    private void Update()
    {
        if(Input.GetMouseButtonDown(1))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if(Physics.Raycast(ray, out hit, 50f))
            {
                Debug.Log(hit.point);
                Vector3 buildPos = new Vector3(Mathf.Floor(hit.point.x) + 0.5f, hit.point.y, Mathf.Round(hit.point.z) - 0.3f);
                if (player != null)
                {
                    if (this.routine != null)
                    {
                        StopCoroutine(this.routine);
                    }
                    this.routine = StartCoroutine(player.CoMove(buildPos));
                }
            }
        }
    }

    private void SetEmpty(int x, int y)
    {
        if (x >= 0 && x < 128 && y >= 0 && y < 128)
        {
            tileMap[x, y] = false;
        }
    }

    private void SetFulled(int x, int y)
    {
        if (x >= 0 && x < 128 && y >= 0 && y < 128)
        {
            tileMap[x, y] = true;
        }
    }

    private void Init()
    {
        tile1.SetActive(false);
        tile2.SetActive(false);
        for (int i = 0; i < 128; i++)
        {
            for(int j = 0; j < 128; j++)
            {
                SetFulled(i, j);
            }
        }

        for (int i = 2; i < 126; i++)
        {
            for (int j = 2; j < 126; j++)
            {
                SetEmpty(i, j);
            }
        }

        GameObject ch = Instantiate(character);
        ch.transform.position = new Vector3(5, 0, 5);
        cam.Init(ch);
        this.player = ch.GetComponent<Player>();
        Debug.Log(player);
    }

    public bool[,] returnTileMap()
    {
        return this.tileMap;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class Drag : MonoBehaviour, IBeginDragHandler, IEndDragHandler,IDragHandler
{
    [SerializeField]
    private Material red;
    [SerializeField]
    private Material green;


    public static Vector2 defaultPos;
    public System.Action onBuildable;
    public System.Action onBuild;
    
    void IBeginDragHandler.OnBeginDrag(UnityEngine.EventSystems.PointerEventData eventData)
    {
        defaultPos = this.transform.position;
    }
    void IDragHandler.OnDrag(UnityEngine.EventSystems.PointerEventData eventData)
    {
        this.transform.position = eventData.position;
        onBuildable();

    }
    void IEndDragHandler.OnEndDrag(UnityEngine.EventSystems.PointerEventData eventData)
    {
        onBuild();
        this.transform.position = defaultPos;
    }
}

 

이 다음에는 구조물을 추가해줘서 맵을 좀 더 풍성하게 꾸며 볼 예정이다. 구조물을 설치한 자리의 배열 값을 바꿔줘야하기 때문에 아마도 커스텀 에디터를 사용해서 구조물자리의 위치를 저장한 맵을 만들어서 스크립트를 초기화할때 넣어줘야 할 것 같다.