우선 지난글에 이어 플레이어가 이동하는것에서 스테이지 전체가 왼쪽으로 이동하는 방식으로
바꿔보도록 하겠다
public class StageControl : MonoBehaviour
{
PlayerControl PlayerControl;
void Start()
{
PlayerControl = FindObjectOfType<PlayerControl>();
}
void FixedUpdate()
{
if(transform.position.x < -100f)
{
Destroy(gameObject);
}
if(PlayerControl.isDead) return;
float speed = GameManager.instance.globalspeed;
transform.Translate(Vector3.left * speed * Time.fixedDeltaTime);
}
}
어려울건 없다. 같은 원리로 스테이지를 왼쪽으로 옮기면 그만.
하지만 특이한 점이 두가지 있다면 첫번째는 속도를 GameManager에서 가지고 있는 변수
globalspeed를 사용하기로 했다.
왜냐면 후에 아이템 같은것이 추가되어 플레이어의 속도가 달라질수도 있기때문
두번째는 플레이어를 이동시킬때 쓰던 velocity말고 transform을 사용했다
그 이유는 다음과 같다
물리 엔진(Velocity) vs 단순 이동(Translate)
Player: Rigidbody2D.velocity를 쓰는 이유
플레이어는 **"살아있는 물리 객체"**입니다.
- 중력(Gravity)을 받아 떨어져야 하고,
- 땅이랑 부딪혀서(Collision) 멈춰야 하고,
- 점프할 때 힘(Force)을 받아야 합니다.
이 모든 복잡한 계산(마찰력, 반발력, 중력 가속도 등)을 물리 엔진이 대신 해주기 때문에 무겁더라도 Rigidbody와 velocity를 씁니다.
B. Ground/Map: transform.Translate를 쓰는 이유
반면, 맵(땅, 배경, 장애물)은 물리 법칙을 따를 필요가 없습니다.
- 땅이 중력을 받아서 아래로 떨어지면 안 됩니다.
- 장애물이 다른 장애물과 부딪혔다고 튕겨 나가면 안 됩니다.
- 그저 "기계적으로" 왼쪽으로 가기만 하면 됩니다.
이런 단순한 움직임에 Rigidbody 컴포넌트를 붙이고 velocity를 주는 것은, "종이 한 장 옮기는 데 덤프트럭을 쓰는 격"입니다. 컴퓨터가 불필요한 물리 연산을 계속해야 하므로 렉이 걸릴 확률이 높아집니다.
아주아주 좋은 설명. 이런 이유때문에 스테이지를 통째로 옮기는거라면 transform을 사용하는 것이다
------------------------------
그 다음엔 스테이지 무한생성을 해보겠다

우선 스테이지 맨 끝에 빈 오브젝트를 하나 만든뒤 이름을 "EndPoint"로 붙여준다
이게 기준점이 될것이다

그리고 이 하나의 긴 스테이지를 프리팹으로 만들어준다
using UnityEngine;
public class LevelGenerator : MonoBehaviour
{
[Header("프리팹 설정")]
public GameObject[] stagePrefabs; // 다양한 스테이지 조각들을 여기에 드래그해서 넣으세요
public GameObject startStage; // 맨 처음 시작할 때 이미 놓여있는 맵 (없으면 비워도 됨)
// [핵심] 마지막으로 생성된 맵의 '끝 지점'을 기억하는 변수
private Transform lastEndPosition;
void Start()
{
// 1. 게임 시작 시 초기 맵이 있다면, 그 맵의 EndPoint를 찾아서 등록
if (startStage != null)
{
lastEndPosition = startStage.transform.Find("EndPoint");
}
// 2. 만약 초기 맵이 없다면, 첫 위치에서 바로 생성 시작
if (lastEndPosition == null)
{
SpawnStage(Vector3.zero);
}
// 3. 화면을 꽉 채울 만큼 미리 몇 개 더 생성 (예: 2개)
for (int i = 0; i < 2; i++)
{
SpawnStage(lastEndPosition.position);
}
}
void Update()
{
// 마지막으로 만든 맵의 꼬리(lastEndPosition)가
// 화면 오른쪽 끝(예: x 좌표 10)보다 안쪽으로 들어왔다면? -> 새 맵 생성!
if (lastEndPosition.position.x < 20f)
{
SpawnStage(lastEndPosition.position);
}
}
void SpawnStage(Vector3 spawnPos)
{
// 1. 랜덤으로 프리팹 하나 뽑기
int randomIndex = Random.Range(0, stagePrefabs.Length);
GameObject selectedPrefab = stagePrefabs[randomIndex];
// 2. 뽑은 프리팹을 '꼬리 위치(spawnPos)'에 생성
GameObject newStage = Instantiate(selectedPrefab, spawnPos, Quaternion.identity);
// 3. [중요] '바통 터치': 방금 만든 맵의 EndPoint를 새로운 lastEndPosition으로 갱신
lastEndPosition = newStage.transform.Find("EndPoint");
// (안전장치) 만약 EndPoint를 안 만들었으면 에러가 날 테니 로그 띄우기
if (lastEndPosition == null)
{
Debug.LogError(newStage.name + " 프리팹에 'EndPoint' 자식 오브젝트가 없습니다!");
}
}
}
그 다음은 간단하다. 배열에 사용할 프리팹들을 넣어주고
EndPoint가 x좌표 기준 일정 이상 넘어가면 새로운 프리팹을 만들어주게 하면 된다
위 코드에서는 20보다 안쪽으로 들어오면 새로운 맵을 만들어준다
public class StageControl : MonoBehaviour
{
PlayerControl PlayerControl;
void Start()
{
PlayerControl = FindObjectOfType<PlayerControl>();
}
void FixedUpdate()
{
if(transform.position.x < -100f)
{
Destroy(gameObject);
}
물론 스테이지가 무한으로 길게 늘어지도록 만들순없으니 -100보다 더 뒤로 밀려가면 없어지도록 해주었다

그럼 게임을 실행시 스테이지 3개가 연달아 생성되고
일정 구간 이상 도달시 새로운 스테이지가 무한으로 계속 생성되는것을 확인할 수 있다
-------------------------------------
이번엔 점프 횟수 제한을 걸어두도록 해보겠다
기본적인 점프는 1회로 제한하며 후에 아이템 구매나 업그레이드를 고려하여 2,3단 점프를 할 수도 있기에
언제든지 변경이 가능하도록 코드를 짜야 한다
추가로 아무때나 점프할 수 있는게 아니라 몸이 땅에 닿아있는 상태에서만 점프해야 하기 때문에
바닥인식기능 역시 추가해주도록 하겠다
[Header("최적화된 바닥 감지")]
public Transform feetPos; // 발바닥 위치 (빈 오브젝트)
public Vector2 boxSize = new Vector2(0.8f, 0.2f); // 감지 박스 크기
public LayerMask groundLayer; // 땅 레이어 (연산 대상을 확 줄여줌)

우선 코드로 FeetPos, Box Size, ground layer 3가지를 준비해준다
void FixedUpdate()
{
// [최적화 핵심]
// 1. OverlapBox: 사각형 모양으로 검사 (원형보다 땅 감지에 유리)
// 2. LayerMask: 'Ground' 레이어만 검사 (다른 물체는 무시하므로 성능 UP)
isGrounded = Physics2D.OverlapBox(feetPos.position, boxSize, 0f, groundLayer);
// 땅에 있고 + 떨어지는 중일 때만 점프 리필 (점프해서 올라가는 중에는 리필 X)
if (isGrounded && rb.velocity.y <= 0.1f)
{
currentJumpCount = maxJumpCount;
}
}
public void Jump()
{
if (currentJumpCount > 0)
{
// [팁] 점프 전 Y속도 초기화 (더블 점프 시 일정한 높이 보장)
rb.velocity = new Vector2(rb.velocity.x, 0);
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
currentJumpCount--;
}
}
// 에디터에서 박스 범위를 눈으로 확인하기 위함 (게임 성능엔 영향 없음)
void OnDrawGizmos()
{
if (feetPos != null)
{
Gizmos.color = isGrounded ? Color.green : Color.red;
Gizmos.DrawWireCube(feetPos.position, boxSize);
}
}
}
Fixedupdate에서는 바닥을 감지하고 점프 횟수를 리필해주는 코드
Jump() 에서는 점프할때마다 점프횟수를 1씩 빼주는 코드
OnDrawGizmos() 에서는 박스 범위를 눈으로 확인하기 위한 코드인데....사실 없어도 된다

그리고 플레이어 발밑에 자식 오브젝트로 FeetPos를 하나 만들어준뒤 발 살짝 밑에 배치시켜준다
그럼 빨간색 박스가 눈에 보이도록 뜰텐데 이게 바닥을 감지할 것이다

그리고 방금 쓴 스크립트 Inspector에 FeetPos를 부착해주고
Ground Layer를 Ground로 잡아주자

물론 저 레이어는 내가 만든것
Ground 레이어는 당연히 타일맵에서 땅을 담당하는곳에 넣어주면 된다

그럼 이제 점프 횟수가 제한되면서 땅에 붙어있을때만 점프가 되는것을 확인할 수 있다
다음 글에서는 맵 생성 방식을 오브젝트 풀링 방식으로 수정하도록 하겠다
'유니티 > 유니티 공부' 카테고리의 다른 글
| #5 맵 생성 보완(점선 메터리얼, 강조 효과) (0) | 2026.02.26 |
|---|---|
| #4 슬레이 더 스파이어 방식 스테이지 랜덤 생성 (1) | 2026.02.24 |
| #2 플레이어 타격, 게임오버 기본 구현 (1) | 2026.02.14 |
| 타일맵 사용법 (0) | 2026.01.30 |
| 도트 필터 씌우기 - 렌더 텍스처(Render Texture) (1) | 2026.01.21 |