//구글콘솔 광고 추가가

바로 다음날 작업한 걸 정리하려던 나는 내일 할까 미뤘다가 파일정리를 하면서 Shift + Delete로 프로젝트를 삭제시키는 끔찍한 실수를 저질렀다. 잠시 잠깐 멘붕에 빠져있다가 PuranUtilitiesSetup이라는 복원프로그램을 사용해 봤다.(이 프로그램은 공짜로 풀린 복원 프로그램인 것 같은데 안타깝게도 cs파일은 찾지 못하는 것 같았다. 텍스트 파일이나 사진파일 같은 건 바로 찾을 수 있는 것 같았음.)

https://nomatter-me.tistory.com/209

 

이것 저것 ) 무료 복원 프로그램 Puran Utilities를 아는가.

Puran Utilities 프로그램을 알게 되었다. 복원 프로그램인데 무료로 이용이 가능하다. 현재의 내 경우에는 살리지 못했지만 언젠가 혹시 모를 참담하고도 참담할 상황인, 중요 문서를 쉬프트 딜리

nomatter-me.tistory.com

 

나의 경우는 파일을 찾지도 못했을 뿐더러 중간에 갑자기 노트북이 업데이트되는 해프닝이 있었다. 업데이트가 되길래 약간은 불안했다만 "그저 기우겠지."란 생각으로 더 이상 생각하지 않기로 했다. 그렇게 노트북이 힘겨워하기에 그만두고 다시 구현하는 쪽을 선택해서 지금 다시 올린다. 그래도 한번 만들어 봤다고 시간이 엄청 투자되지는 않아서 다행이었다.

using UnityEngine;
using System.Collections.Generic;

public class ObstacleManager : MonoBehaviour
{
    // 도로의 전체 너비와 카메라 높이 설정
    [Header("Road Settings")]
    public int roadWidth = 1;
    public float cameraHeight = 1000;
    public float speed = 0.8f;
    
    [Header("Obstacle Settings")]
    public GameObject[] obstaclePrefabs;
    [SerializeField] private float obstacleSpeed = 0.8f;
    [SerializeField] private float spawnY = 3.16f;
    [SerializeField] private float endYPosition = -6f;
    [SerializeField] private int poolSize = 5;
    [SerializeField] private int maxObstacles = 3; // 동시에 존재할수 있는 최대 장애물 수
    [SerializeField] private float minObstacleSpacing = 0.5f; // 장애물 X간 최소 간격
    [SerializeField] private float heightCheck = 1f; // 장애물 Y간 최소 간격
    [SerializeField] private int consecutiveNum = 3; //연속 장애물 허용 범위
    [SerializeField] private float spawnRate = 0.02f;
    
    private Queue<GameObject>[] obstaclePools;
    private List<GameObject> activeObstacles = new List<GameObject>();
    private int consecutiveSpawnCount = 0;    // 연속 생성 횟수
    private bool? lastSpawnSide = null;       // 마지막 생성 방향 (null: 초기상태)

    private float spawnTimer;
    private readonly Vector3 scaleVector =Vector3.one;
    private void Awake()
    {
        activeObstacles = new List<GameObject>(maxObstacles);
        InitializeObstaclePools();
    }

    private void InitializeObstaclePools()
    {
        obstaclePools = new Queue<GameObject>[obstaclePrefabs.Length];
    
        for (int i = 0; i < obstaclePrefabs.Length; i++)
        {
            obstaclePools[i] = new Queue<GameObject>();
            for (int j = 0; j < poolSize; j++)
            {
                CreateObstacles(i);
            }
        }
    }

    private void CreateObstacles(int typeIndex)
    {
        GameObject obj = Instantiate(obstaclePrefabs[typeIndex], transform);
        obj.SetActive(false);
        obj.AddComponent<ObstacleData>();
        obstaclePools[typeIndex].Enqueue(obj);
    }

    private void Update()
    {
        spawnTimer += Time.deltaTime;
        if (spawnTimer >= spawnRate)
        {
            spawnTimer = 0f;
            if (Random.value < spawnRate)
            {
                SpawnObstacle();
            }
        }
        UpdateObstacles();
    }

    private void UpdateObstacles()
    {
        Vector3 position;
        float progress, scale, xOffset, newX, newY;
        
        for (int i = activeObstacles.Count - 1; i >= 0; i--)
        {
            GameObject obstacle = activeObstacles[i];
            ObstacleData data = obstacle.GetComponent<ObstacleData>();
            position = obstacle.transform.position;
            
            // 장애물의 진행도 계산 (0~1 사이 값)
            progress = (spawnY - position.y) / (spawnY - (transform.position.y + endYPosition));
            // 진행도에 따른 크기 보간
            scale = Mathf.Lerp(0.1f, 3f, progress);
            // 바깥쪽으로 이동하는 힘을 더 강하게 수정
            xOffset = data.isLeftSide ? -progress * 4f : progress * 4f;
            newX = data.initialX + (xOffset * roadWidth);
            // 스케일이 커질수록 더 빠르게 이동 //scale 0.1_기본속도 1, scale 1.5_기본속도 4.5, scale 최대_기본속도 8.25
            newY = position.y - (obstacleSpeed * (1f + (scale - 0.1f) * 2.5f) * Time.deltaTime);
            
            position.x = newX;  
            position.y = newY;
            obstacle.transform.position = position;
            obstacle.transform.localScale = scaleVector * scale;

            if (newY < transform.position.y + endYPosition)
            {
                ReturnToPool(obstacle);
                activeObstacles.RemoveAt(i);
            }
        }
    }
    

    public class ObstacleData : MonoBehaviour // 장애물의 초기 x위치와 방향을 저장하는 컴포넌트
    {
        public float initialX;
        public bool isLeftSide;
    }
    
    private bool IsValidSpawnPosition(Vector3 newPosition)
    {
        
        foreach (GameObject obstacle in activeObstacles)
        {
            // y축 간격이 heightCheck의 간격보다 작은지 체크
            if (Mathf.Abs(obstacle.transform.position.y - newPosition.y) < heightCheck)
            {
                // x축 간격이 최소 간격보다 작으면 생성 불가
                if (Mathf.Abs(obstacle.transform.position.x - newPosition.x) < minObstacleSpacing)
                {
                    return false;
                }
            }
        }
        return true;
    }

    private void SpawnObstacle()
    {
        if (activeObstacles.Count >= maxObstacles)
        {
            return;
        }

        //50% 확률로 장애물 왼쪽, 오른쪽으로 스폰
        bool spawnOnLeft = Random.value > 0.5f;
        HandleConsecutiveSpawns(ref spawnOnLeft);

        int typeIndex = Random.Range(0, obstaclePrefabs.Length);
        GameObject obstacle = GetObstacleFromPool(typeIndex);

        if (obstacle == null)
        {
            return;
        }

        // 초기 생성 위치를 도로 중앙 근처로 수정 //0.1 수치 증가시키면 장애물이 도로의 바깥쪽 생성,감소 >>중앙에 가깝게 생성.
        float xPos = spawnOnLeft ? -roadWidth * 0.1f : roadWidth * 0.1f;
        Vector3 newPosition = new Vector3(xPos, spawnY, 0);

        // 새로운 위치가 적절한 간격을 유지하는지 확인
        if (!IsValidSpawnPosition(newPosition))
        {
            ReturnToPool(obstacle);
            return;
        }

        SetupObstacle(obstacle, xPos, spawnOnLeft);
    }

    private void HandleConsecutiveSpawns(ref bool spawnOnLeft) //연속으로 같은 방향에 장애물이 생성되는 것을 제어
    {
        // 이전과 같은 방향으로 생성하려는 경우
        if (lastSpawnSide.HasValue && spawnOnLeft == lastSpawnSide.Value)
        {
            consecutiveSpawnCount++;
            // consecutiveNum번 연속 생성되면 반대편에 생성
            if (consecutiveSpawnCount >= consecutiveNum)
            {
                spawnOnLeft = !lastSpawnSide.Value;
                consecutiveSpawnCount = 0;
            }
        }
        else
        {
            consecutiveSpawnCount = 0;
        }
        lastSpawnSide =spawnOnLeft;
    }

    private void SetupObstacle(GameObject obstacle, float xPos, bool isLeft) // 생성된 장애물의 초기 설정을 담당
    {
        obstacle.transform.position = new Vector3(xPos, spawnY, 0);
        obstacle.transform.localScale = scaleVector * 0.1f;

        var data = obstacle.GetComponent<ObstacleData>();
        data.initialX = xPos;
        data.isLeftSide = isLeft;
        
        obstacle.SetActive(true);
        activeObstacles.Add(obstacle);
    }

    private void ReturnToPool(GameObject obstacle)
    {
        obstacle.SetActive(false);
        for (int i = 0; i < obstaclePrefabs.Length; i++)
        {
            if (obstacle.name.Contains(obstaclePrefabs[i].name))
            {
                obstaclePools[i].Enqueue(obstacle);
                break;
            }
        }
    }

    private GameObject GetObstacleFromPool(int typeIndex)
    {
        return obstaclePools[typeIndex].Count > 0 ? obstaclePools[typeIndex].Dequeue() : null;
    }
}

 

장애물은 왼쪽과 오른쪽에 생성되는 장애물에 따라 도로 벽 쪽으로 붙어서 떨어지게 작업했다. 

만들다 보니까 겹쳐서 생성될 때도 있길래 최소 x, y값의 간격을 정해서 생성되게 작업했다. 그렇게 만들었더니 간격은 마음에 드는데 뜸하게 만들어지는 것 같기도 하고 이건 수치 조절하면 될 것 같은데 그 적정한 선을 찾기가 쉽지가 않구만.

장애물이 연속해서 나오는 것도 별로길래 몇 개 같은 쪽으로 나오면 다시 만들어질 때는 다른 쪽으로 만들어지게 만들어 줬다.

주석처리를 많이 해두어서 코드를 보는 데는 문제가 없을 듯하다.

하지만 솔직하게 이 코드는 pseudo 3d game라 할 수 없다. 그래서 시간이 될 때 다시 도전해 보는 걸로 마무리 지었다. 

 

https://github.com/h8man/TurboTrack2D

 

GitHub - h8man/TurboTrack2D: Prototype of 2D arcade style racing game

Prototype of 2D arcade style racing game. Contribute to h8man/TurboTrack2D development by creating an account on GitHub.

github.com

여기 있는 프로젝트는 누군가가 만들어둔 pseudo 3d game 유니티 프로젝트이다. 

이 코드를 좀 뜯어보면서 다시 공부를 해봐야겠다.

728x90
반응형

+ Recent posts