어제 써둔 pseudo 3d game 유니티 공부에 대해 저장해 두려고 글을 쓴다.
일단 내가 만든 방법은 2d 배경 1장에 도로의 가운데 라인과, 장애물이 플레이어의 반대 방향(위에서 아래 방향)으로 이동하게 해서 플레이어는 가만히 있지만 이동하는 느낌을 만들어 봤다. 장애물 만드는 방법과 도로 라인 만드는 방법을 다르게 해 봤다. 사실 두 가지 다 각자 테스트 해보다가 각자 만들어졌지만 원리는 비슷할 것이다.
1. 우선 게임에 필요한 스프라이트들을 구글에서 찾아서 들고온다.
나의 경우는 아래 스프라이트 사이트에서 많이 이용한다.
https://www.spriters-resource.com/
The Spriters Resource
This page does not work well in portrait mode on mobile. Please rotate your device.
www.spriters-resource.com
생각보다 많은 다양한 게임들의 스프라이트들을 다운로드할 수 있다. 만약 배경이 있다면 aseprite 프로그램을 사용해서 제거해 준다. 물론 코드로 뒷배경을 삭제시켜 주는 방법도 있다. 하지만 나는 이 프로그램을 돈 주고 샀기 때문에 뽕을 뽑아야 한다는 마음으로 작은 작업이라도 사용하는 편이다.
ai를 사용해서 나만의 배경을 만들어 볼까도 했지만 2시간 동안 하늘의 사이즈를 줄여주지 않는 ai에게 굴복하고 그만뒀다.(회원가입도 했는데... 하늘을 줄여주는 게 그렇게 싫었던 걸까. 그렇게 소량으로만 줄여주고 싶었던 것일까. 한글도 영어도 전부 먹히지 않았던 너란 ai. 배경 오브젝트들이 점점 한자들이 나오길래 중국인가 싶었지만 난 중국어는 못하는 걸.)
2. 유니티에서 몇 가지 작업을 해준다.
일단 우선적으로 2d project를 만들어 준다. 그리고 Obstacle Manager(장애물 관리 매니저) 오브젝트와, RoadLineManager(도로 라인 관리 매니저)를 만들어 준다. 프리팹들은 장애물 프리팹과, 라인 프리팹만 있으면 된다. UI Canvas를 만들어 주고 필요하다면 플레이어가 이동할 수 있는 버튼을 추가로 만들어 주면 된다. 나의 경우 버튼을 눌러 좌, 우로 이동할 수 있게 작업했다. 속도를 늘리는 버튼과 속도를 줄이는 버튼 또한 만들어 두었다.
3. 이제 스크립트를 짜보자.
우선 도로 라인이 내려오게 만들어 주는 RoadLineManager를 만들어 보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class RoadLineManager : MonoBehaviour
{
[Header("Line Spawn Settings")]
public GameObject roadLinePrefab;
public int poolSize = 10;
public float spawnInterval = 1f;
[Header("Line Position Settings")]
public float startYPosition = 5.7f;
public float startXPosition = 0.15f;
public float endYPosition = -1f;
public float fixedLineSpacing = 1.8f;
[Header("Line Scale Settings")]
public float startScaleX = 0.05f;
public float startScaleY = 0.02f;
public float endScaleX = 0.4f;
public float endScaleY = 0.5f;
[Header("Movement Settings")]
public float initialMoveSpeed = 5f;
public float currentMoveSpeed;
public float maxMoveSpeed = 15f;
public float accelerationRate = 2f;
[Header("Line Settings")]
public int maxVisibleLines = 6;
private List<GameObject> linePool;
private float[] linePositions;
//private float[] spacings = new float[] { 1.0f, 1.2f, 1.5f, 1.8f, 2.2f, 2.7f };
private float[] spacings = new float[] { 0.8f, 1.2f, 1.8f, 2.5f, 3.3f, 4.2f };
private void Start()
{
linePool = new List<GameObject>();
linePositions = new float[maxVisibleLines];
InitializePool();
currentMoveSpeed = initialMoveSpeed;
UpdateLinePositions();
SpawnInitialLines();
}
private void InitializePool()
{
for (int i = 0; i < poolSize; i++)
{
GameObject line = Instantiate(roadLinePrefab);
line.SetActive(false);
linePool.Add(line);
}
}
private void UpdateLinePositions()
{
linePositions[0] = startYPosition;
for (int i = 1; i < maxVisibleLines; i++)
{
float spacing = fixedLineSpacing * spacings[i-1];
linePositions[i] = linePositions[i-1] - spacing;
}
}
private void SpawnInitialLines()
{
for (int i = 0; i < maxVisibleLines; i++)
{
SpawnLineAtPosition(linePositions[i]);
}
}
private void SpawnLineAtPosition(float yPosition)
{
GameObject line = GetInactiveLine();
if (line != null)
{
float t = (startYPosition - yPosition) / (startYPosition - endYPosition);
t = Mathf.Clamp01(t);
float scaleX = Mathf.Lerp(startScaleX, endScaleX, t);
float scaleY = Mathf.Lerp(startScaleY, endScaleY, t);
line.transform.position = new Vector3(startXPosition, yPosition, 0);
line.transform.localScale = new Vector3(scaleX, scaleY, 1);
line.SetActive(true);
}
}
private void Update()
{
UpdateLines();
}
private void UpdateLines()
{
int visibleCount = 0;
float highestY = float.MinValue;
foreach (GameObject line in linePool)
{
if (line.activeInHierarchy)
{
Vector3 pos = line.transform.position;
float t = (startYPosition - pos.y) / (startYPosition - endYPosition);
t = Mathf.Clamp01(t);
float scaleX = Mathf.Lerp(startScaleX, endScaleX, t);
float scaleY = Mathf.Lerp(startScaleY, endScaleY, t);
// 스케일이 클수록 더 빠르게 이동하도록 수정
float speedMultiplier = scaleY / startScaleY;
pos.y -= (currentMoveSpeed * speedMultiplier) * Time.deltaTime;
line.transform.localScale = new Vector3(scaleX, scaleY, 1);
line.transform.position = pos;
if (pos.y < endYPosition)
{
line.SetActive(false);
}
else
{
highestY = Mathf.Max(highestY, pos.y);
visibleCount++;
}
}
}
if (visibleCount < maxVisibleLines)
{
if (highestY == float.MinValue)
{
SpawnLineAtPosition(startYPosition);
}
else
{
int currentIndex = visibleCount - 1;
float spacing = fixedLineSpacing * spacings[currentIndex];
float newYPos = highestY + spacing;
if (newYPos <= startYPosition)
{
SpawnLineAtPosition(newYPos);
}
}
}
}
private GameObject GetInactiveLine()
{
return linePool.Find(line => !line.activeInHierarchy);
}
public void IncreaseSpeed()
{
currentMoveSpeed = Mathf.Min(currentMoveSpeed + accelerationRate, maxMoveSpeed);
}
public void DecreaseSpeed()
{
currentMoveSpeed = Mathf.Max(currentMoveSpeed - accelerationRate, initialMoveSpeed);
}
public void ResetSpeed()
{
currentMoveSpeed = initialMoveSpeed;
}
}
이 코드를 짤 때 몇 가지 골머리 써야 됐었던 부분이 있었다.
그중 가장 큰 게 라인이 프리팹으로 활성화가 될 때 아래로 내려오는 느낌으로 원근감 있게 내려오려면 처음 적용해 줬던 간격을 계속 유지하면 안 되는 문제가 있었다. 이 부분을 수정하기 위해 나에겐 여러 번의 시도가 필요했다. 그래서 UpdateLines() 함수는 정말 쉴 새 없이 수정됐었다. 그리고 마침내 해결된 방법이 바로 위에 적어둔 스케일의 사이즈에 따라 빠르게 이동시키는 것. 이 부분이 사실 그렇게 어려운 부분은 아니었다. 처음부터 수치를 디버깅해서 확인해 봤으면 문제가 되지 않았을 것이다. 변경시켜 주던 수치값이 제대로 늘어나지 않는 오류를 모르고 도대체 왜 내가 원했던 데로 나오지 않는 거지 하다가 시간을 두배로 썼다. 잊지 말자 디버깅.
버튼에 적용시켰던 speed up 버튼과 speed down 버튼에 사용할 함수도 여기 있는 IncreaseSpeed() 함수와 DecreaseSpeed() 함수를 연결해 주면 된다. 필요시 나머지 함수들도 연결시켜줘도 기능은 할 것이다.
나의 경우는 변수들을 많이 만들어 최대한 유니티에서 배경을 바꿨을 때도 사용할 수 있게 작업했다. 지금 보이는 것과 같이 저 배경은 free이기 때문에 가져온 스프라이트지 그렇게 내 마음에 들지 않기 때문일지도 모른다. 도로의 시작과 끝을 잡아줄 위치 변수들과, 라인의 스케일을 관리해 줄 변수들, 라인과 라인 사이의 간격을 정할 변수로 되어있다. 그리고 화면에 나올 라인의 개수 변수가 있는데 이건 간격과 도로의 총길이에 따라 나오지 않을 수도 있다. 프리팹은 오브젝트 풀링을 사용해서 최적화해 두었다.
장애물 매니저는 내일 다시 정리해 두겠다. 여기서는 역시나 오브젝트풀로 장애물을 관리하고, 도로의 좌, 우에 따라붙어서 원근감 있게 스케일이 커지도록 작업했다.
++ 나도 모르게 필요없는 파일인줄 알고 프로젝트 삭제시켜서 장애물 매니저는 그렇게 날라갔다...다시 작업해서 올리도록 하겠다.
'유니티' 카테고리의 다른 글
유니티 ) h8man님의 pseudo 3d game 뜯어봄. + 장애물도 넣어봄. (1) | 2024.12.10 |
---|---|
유니티 ) 내 방식대로 만드는 pseudo 3d game 2. (0) | 2024.12.03 |
유니티에 광고를 넣어보자.(Google AdMob 사용) (4) | 2024.11.09 |
유니티 Post Processing 적용이 안되는 경우 (0) | 2024.01.23 |
VR 프로젝트 빌드하는 방법 (0) | 2023.12.28 |