using System; using UnityEngine; 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 usePatternCourse = true; // 검증된 발판 패턴 코스를 사용 public bool tutorialMode = true; // 처음에는 튜토리얼 패턴을 먼저 배치 public TutorialStep[] tutorialSteps; // 튜토리얼 발판 순서 public TutorialStep[] stagePatternSteps; // 튜토리얼 이후 반복할 검증된 발판 순서 public float timeBetSpawnMin = 1.25f; // 다음 배치까지의 시간 간격 최솟값 public float timeBetSpawnMax = 2.25f; // 다음 배치까지의 시간 간격 최댓값 public float tutorialSpawnInterval = 2.2f; // 튜토리얼 발판 배치 간격 private float timeBetSpawn; // 다음 배치까지의 시간 간격 public float yMin = -3.5f; // 배치할 위치의 최소 y값 public float yMax = 1.5f; // 배치할 위치의 최대 y값 private float xPos = 20f; // 배치할 위치의 x 값 private GameObject[] platforms; // 미리 생성한 발판들 private int currentIndex = 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() { EnsurePatternSteps(); if(usePatternCourse && tutorialMode) { count = Mathf.Max(count, 8); } // 변수들을 초기화하고 사용할 발판들을 미리 생성 platforms = new GameObject[count]; for(int i = 0; i < count; i++) { platforms[i] = Instantiate(platformPrefab, poolPosition, Quaternion.identity); } // 마지막 배치 시점 초기화 lastSpawnTime = 0f; // 다음번 배치까지의 시간 간격을 0으로 초기화 timeBetSpawn = 0f; } void Update() { // 순서를 돌아가며 주기적으로 발판을 배치 if(GameManager.instance == null || GameManager.instance.isGameover || !GameFlowManager.IsGameplayActive) { return; } if(Time.time >= lastSpawnTime + timeBetSpawn) { lastSpawnTime = Time.time; TutorialStep step = usePatternCourse ? GetCurrentCourseStep() : null; timeBetSpawn = step != null ? GetStepSpawnInterval(step) : Random.Range(timeBetSpawnMin, timeBetSpawnMax); float yPos = step != null ? Mathf.Clamp(step.yPosition, yMin, yMax) : Random.Range(yMin, yMax); platforms[currentIndex].SetActive(false); ConfigurePlatform(platforms[currentIndex], step); platforms[currentIndex].SetActive(true); platforms[currentIndex].transform.position = new Vector2(xPos, yPos); currentIndex++; if(currentIndex >= count) { currentIndex = 0; } if(usePatternCourse) { courseStepIndex++; } } } private void ConfigurePlatform(GameObject platformObject, TutorialStep step) { Platform platform = platformObject.GetComponent(); if(platform == null) { return; } if(step == null) { platform.ConfigureForRandom(); return; } platform.ConfigureForTutorial(step.platformPattern, step.mileagePattern, step.slideLayout, step.itemPattern); if(GameManager.instance != null && step.guideType != GameManager.TutorialGuideType.None) { 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 GetStepSpawnInterval(TutorialStep step) { if(step != null && step.spawnInterval > 0f) { return step.spawnInterval; } return tutorialSpawnInterval; } 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(); EnsureStagePatternSteps(); } private void EnsureTutorialSteps() { if(tutorialSteps != null && tutorialSteps.Length > 0) { return; } tutorialSteps = new TutorialStep[] { 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 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); } }