Add travel map flow and update docs

This commit is contained in:
jongjae0305
2026-06-24 15:34:09 +09:00
parent 8561fab0a2
commit af26dd4311
37 changed files with 3114 additions and 165 deletions
+240 -35
View File
@@ -4,18 +4,38 @@ using Random = UnityEngine.Random;
// 발판을 생성하고 주기적으로 재배치하는 스크립트
public class PlatformSpawner : MonoBehaviour {
private enum TutorialStepKind {
None,
Jump,
DoubleJumpTakeoff,
Slide,
ForceHit,
HealthItem,
InvincibleItem,
ShieldItem,
SpeedItem,
MileageCardItem
}
[Serializable]
public class TutorialStep {
public Platform.PlatformPattern platformPattern;
public Platform.MileagePattern mileagePattern;
public SlideObstaclePattern.SlideLayout slideLayout;
public Platform.ItemPattern itemPattern;
public GameManager.TutorialGuideType guideType;
public float yPosition;
public float spawnInterval;
public string title;
public string message;
}
public GameObject platformPrefab; // 생성할 발판의 원본 프리팹
public int count = 3; // 생성할 발판의 개수
public bool tutorialMode = true; // 튜토리얼에서는 랜덤 대신 정해진 순서로 배치
public bool usePatternCourse = true; // 검증된 발판 패턴 코스를 사용
public bool tutorialMode = true; // 처음에는 튜토리얼 패턴을 먼저 배치
public TutorialStep[] tutorialSteps; // 튜토리얼 발판 순서
public TutorialStep[] stagePatternSteps; // 튜토리얼 이후 반복할 검증된 발판 순서
public float timeBetSpawnMin = 1.25f; // 다음 배치까지의 시간 간격 최솟값
public float timeBetSpawnMax = 2.25f; // 다음 배치까지의 시간 간격 최댓값
@@ -28,13 +48,46 @@ public class PlatformSpawner : MonoBehaviour {
private GameObject[] platforms; // 미리 생성한 발판들
private int currentIndex = 0; // 사용할 현재 순번의 발판
private int tutorialStepIndex = 0; // 진행 중인 튜토리얼 순번
private int courseStepIndex = 0; // 진행 중인 패턴 코스 순번
private Vector2 poolPosition = new Vector2(0, -25); // 초반에 생성된 발판들을 화면 밖에 숨겨둘 위치
private float lastSpawnTime; // 마지막 배치 시점
public void ApplyMap(MapDefinition mapDefinition) {
if(mapDefinition == null)
{
return;
}
usePatternCourse = true;
tutorialMode = mapDefinition.isTutorial;
tutorialSteps = MapDatabase.CloneSteps(mapDefinition.openingSteps);
stagePatternSteps = MapDatabase.CloneSteps(mapDefinition.loopSteps);
courseStepIndex = 0;
currentIndex = 0;
lastSpawnTime = Time.time;
timeBetSpawn = 0f;
if(platforms != null)
{
for(int i = 0; i < platforms.Length; i++)
{
if(platforms[i] != null)
{
platforms[i].SetActive(false);
platforms[i].transform.position = poolPosition;
}
}
}
}
void Start() {
EnsureTutorialSteps();
EnsurePatternSteps();
if(usePatternCourse && tutorialMode)
{
count = Mathf.Max(count, 8);
}
// 변수들을 초기화하고 사용할 발판들을 미리 생성
platforms = new GameObject[count];
@@ -53,7 +106,7 @@ public class PlatformSpawner : MonoBehaviour {
void Update() {
// 순서를 돌아가며 주기적으로 발판을 배치
if(GameManager.instance.isGameover)
if(GameManager.instance == null || GameManager.instance.isGameover || !GameFlowManager.IsGameplayActive)
{
return;
}
@@ -62,12 +115,15 @@ public class PlatformSpawner : MonoBehaviour {
{
lastSpawnTime = Time.time;
timeBetSpawn = tutorialMode ? tutorialSpawnInterval : Random.Range(timeBetSpawnMin, timeBetSpawnMax);
TutorialStep step = usePatternCourse ? GetCurrentCourseStep() : null;
timeBetSpawn = step != null ? GetStepSpawnInterval(step) : Random.Range(timeBetSpawnMin, timeBetSpawnMax);
float yPos = tutorialMode ? GetTutorialYPosition() : Random.Range(yMin, yMax);
float yPos = step != null
? Mathf.Clamp(step.yPosition, yMin, yMax)
: Random.Range(yMin, yMax);
platforms[currentIndex].SetActive(false);
ConfigurePlatform(platforms[currentIndex]);
ConfigurePlatform(platforms[currentIndex], step);
platforms[currentIndex].SetActive(true);
platforms[currentIndex].transform.position = new Vector2(xPos, yPos);
@@ -79,14 +135,14 @@ public class PlatformSpawner : MonoBehaviour {
currentIndex = 0;
}
if(tutorialMode)
if(usePatternCourse)
{
tutorialStepIndex++;
courseStepIndex++;
}
}
}
private void ConfigurePlatform(GameObject platformObject) {
private void ConfigurePlatform(GameObject platformObject, TutorialStep step) {
Platform platform = platformObject.GetComponent<Platform>();
if(platform == null)
@@ -94,37 +150,74 @@ public class PlatformSpawner : MonoBehaviour {
return;
}
if(!tutorialMode)
if(step == null)
{
platform.ConfigureForRandom();
return;
}
TutorialStep step = GetCurrentTutorialStep();
platform.ConfigureForTutorial(step.platformPattern, step.mileagePattern);
platform.ConfigureForTutorial(step.platformPattern, step.mileagePattern, step.slideLayout, step.itemPattern);
if(GameManager.instance != null)
if(GameManager.instance != null && step.guideType != GameManager.TutorialGuideType.None)
{
GameManager.instance.SetTutorialMessage(step.message);
GameManager.instance.SetTutorialGuide(step.guideType, step.title, step.message);
}
if(GameManager.instance != null
&& step.guideType == GameManager.TutorialGuideType.None
&& (!tutorialMode || courseStepIndex >= tutorialSteps.Length))
{
GameManager.instance.SetTutorialGuide(GameManager.TutorialGuideType.None, "", "");
}
}
private float GetTutorialYPosition() {
return Mathf.Clamp(GetCurrentTutorialStep().yPosition, yMin, yMax);
private float GetStepSpawnInterval(TutorialStep step) {
if(step != null && step.spawnInterval > 0f)
{
return step.spawnInterval;
}
return tutorialSpawnInterval;
}
private TutorialStep GetCurrentTutorialStep() {
private TutorialStep GetCurrentCourseStep() {
EnsurePatternSteps();
if(tutorialMode && courseStepIndex < tutorialSteps.Length)
{
return tutorialSteps[courseStepIndex];
}
int loopIndex = tutorialMode
? courseStepIndex - tutorialSteps.Length
: courseStepIndex;
if(stagePatternSteps == null || stagePatternSteps.Length == 0)
{
return CreateStep(
Platform.PlatformPattern.Empty,
Platform.MileagePattern.SafeLine,
SlideObstaclePattern.SlideLayout.Random,
GameManager.TutorialGuideType.None,
-2.2f,
tutorialSpawnInterval,
"",
"");
}
loopIndex %= stagePatternSteps.Length;
if(loopIndex < 0)
{
loopIndex += stagePatternSteps.Length;
}
return stagePatternSteps[loopIndex];
}
private void EnsurePatternSteps() {
EnsureTutorialSteps();
if(tutorialStepIndex < tutorialSteps.Length)
{
return tutorialSteps[tutorialStepIndex];
}
int reviewStartIndex = Mathf.Max(0, tutorialSteps.Length - 3);
int reviewLength = tutorialSteps.Length - reviewStartIndex;
int reviewIndex = reviewStartIndex + ((tutorialStepIndex - tutorialSteps.Length) % reviewLength);
return tutorialSteps[reviewIndex];
EnsureStagePatternSteps();
}
private void EnsureTutorialSteps() {
@@ -134,21 +227,133 @@ public class PlatformSpawner : MonoBehaviour {
}
tutorialSteps = new TutorialStep[] {
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, -2.4f, "Tutorial 1: Run forward and collect mileage."),
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, -2.4f, "Tutorial 2: Left click to jump over suitcase obstacles."),
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, -2.4f, "Tutorial 3: Hold right click to slide under airport signs."),
CreateStep(Platform.PlatformPattern.LowLeft, Platform.MileagePattern.JumpArc, -2.1f, "Tutorial 4: Suitcases mean jump."),
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, -2.2f, "Tutorial 5: Hanging signs mean slide."),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, -1.8f, "Tutorial complete: collect mileage and keep moving."),
CreateTutorialStep(TutorialStepKind.Jump, Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1.05f),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
CreateTutorialStep(TutorialStepKind.DoubleJumpTakeoff, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 2.10f),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1.10f),
CreateTutorialStep(TutorialStepKind.Slide, Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, -2.4f, 1.15f),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
CreateTutorialStep(TutorialStepKind.ForceHit, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, -2.4f, 1.15f),
CreateTutorialStep(TutorialStepKind.HealthItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.95f, Platform.ItemPattern.HealthSushi),
CreateTutorialStep(TutorialStepKind.InvincibleItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.9f, Platform.ItemPattern.InvincibleRamen),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, -2.4f, 1.05f),
CreateTutorialStep(TutorialStepKind.ShieldItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.9f, Platform.ItemPattern.Shield),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, -2.4f, 1.05f),
CreateTutorialStep(TutorialStepKind.SpeedItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.95f, Platform.ItemPattern.SpeedShoes),
CreateTutorialStep(TutorialStepKind.MileageCardItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.95f, Platform.ItemPattern.MileageCard),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
};
}
private TutorialStep CreateStep(Platform.PlatformPattern platformPattern, Platform.MileagePattern mileagePattern, float yPosition, string message) {
private void EnsureStagePatternSteps() {
if(stagePatternSteps != null && stagePatternSteps.Length > 0)
{
return;
}
stagePatternSteps = new TutorialStep[] {
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.2f, 2.25f, "", ""),
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.3f, 2.4f, "", ""),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.95f, 2.2f, "", ""),
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, -2.25f, 2.65f, "", ""),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.0f, 2.2f, "", ""),
CreateStep(Platform.PlatformPattern.LowLeft, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.35f, 2.35f, "", ""),
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.LeftPair, GameManager.TutorialGuideType.None, -2.35f, 2.7f, "", ""),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.1f, 2.2f, "", ""),
CreateStep(Platform.PlatformPattern.LowRight, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.0f, 2.35f, "", ""),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.85f, 2.15f, "", ""),
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.RightPair, GameManager.TutorialGuideType.None, -2.25f, 2.65f, "", ""),
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 2.35f, "", ""),
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.0f, 2.2f, "", ""),
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.None, -2.2f, 2.7f, "", ""),
};
}
private TutorialStep CreateStep(
Platform.PlatformPattern platformPattern,
Platform.MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout,
GameManager.TutorialGuideType guideType,
float yPosition,
float spawnInterval,
string title,
string message,
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
TutorialStep step = new TutorialStep();
step.platformPattern = platformPattern;
step.mileagePattern = mileagePattern;
step.slideLayout = slideLayout;
step.itemPattern = itemPattern;
step.guideType = guideType;
step.yPosition = yPosition;
step.spawnInterval = spawnInterval;
step.title = title;
step.message = message;
return step;
}
private TutorialStep CreateTutorialStep(
TutorialStepKind stepKind,
Platform.PlatformPattern platformPattern,
Platform.MileagePattern mileagePattern,
SlideObstaclePattern.SlideLayout slideLayout,
float yPosition,
float spawnInterval,
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None;
string title = "";
string message = "";
switch(stepKind)
{
case TutorialStepKind.Jump:
guideType = GameManager.TutorialGuideType.Jump;
title = "점프";
message = "가방 장애물이 보이면 왼쪽 클릭으로 뛰어넘습니다.";
break;
case TutorialStepKind.DoubleJumpTakeoff:
guideType = GameManager.TutorialGuideType.DoubleJump;
title = "2단 점프";
message = "다음 발판이 멀리 있으면 공중에서 한 번 더 왼쪽 클릭합니다.";
break;
case TutorialStepKind.Slide:
guideType = GameManager.TutorialGuideType.Slide;
title = "슬라이드";
message = "천장 표지판이 보이면 오른쪽 클릭을 누르고 지나갑니다.";
break;
case TutorialStepKind.ForceHit:
guideType = GameManager.TutorialGuideType.Damage;
title = "피격";
message = "이번 구간은 일부러 맞아 목숨과 속도 감소를 확인합니다.";
break;
case TutorialStepKind.HealthItem:
guideType = GameManager.TutorialGuideType.Items;
title = "초밥";
message = "초밥을 먹으면 잃은 목숨을 1 회복합니다.";
break;
case TutorialStepKind.InvincibleItem:
guideType = GameManager.TutorialGuideType.Items;
title = "라멘";
message = "라멘을 먹고 다음 충돌을 피해 없이 지나가 봅니다.";
break;
case TutorialStepKind.ShieldItem:
guideType = GameManager.TutorialGuideType.Items;
title = "방어막";
message = "방어막을 먹고 다음 충돌 1회를 막아 봅니다.";
break;
case TutorialStepKind.SpeedItem:
guideType = GameManager.TutorialGuideType.Items;
title = "운동화";
message = "운동화는 속도를 회복해 일정이 늦어지는 것을 줄입니다.";
break;
case TutorialStepKind.MileageCardItem:
guideType = GameManager.TutorialGuideType.Items;
title = "마일리지 카드";
message = "카드를 먹은 뒤 마일리지를 모으면 획득량이 늘어납니다.";
break;
}
return CreateStep(platformPattern, mileagePattern, slideLayout, guideType, yPosition, spawnInterval, title, message, itemPattern);
}
}