Add airport tutorial assets and long background

This commit is contained in:
jongjae0305
2026-06-24 11:13:22 +09:00
parent 7e29aa465c
commit d4ab6210d4
24 changed files with 1557 additions and 91 deletions
+44 -1
View File
@@ -2,13 +2,23 @@
// 왼쪽 끝으로 이동한 배경을 오른쪽 끝으로 재배치하는 스크립트
public class BackgroundLoop : MonoBehaviour {
public Sprite[] backgroundSprites; // 순서대로 교체할 공항 배경 스프라이트
public int startSpriteIndex = 0; // 이 배경 오브젝트가 시작할 스프라이트 번호
public int spriteAdvanceOnLoop = 2; // 배경 오브젝트가 두 장이므로 재배치될 때 두 칸씩 진행
private float width; // 배경의 가로 길이
private int currentSpriteIndex; // 현재 표시 중인 스프라이트 번호
private SpriteRenderer spriteRenderer; // 배경을 표시하는 스프라이트 렌더러
private void Awake() {
// 가로 길이를 측정하는 처리
BoxCollider2D backgroundCollider = GetComponent<BoxCollider2D>();
width = backgroundCollider.size.x;
spriteRenderer = GetComponent<SpriteRenderer>();
currentSpriteIndex = NormalizeSpriteIndex(startSpriteIndex);
ApplyBackgroundSprite(currentSpriteIndex);
}
private void Update() {
@@ -23,5 +33,38 @@ public class BackgroundLoop : MonoBehaviour {
private void Reposition() {
Vector2 offset = new Vector2(width * 2f, 0);
transform.position = (Vector2) transform.position + offset;
currentSpriteIndex = NormalizeSpriteIndex(currentSpriteIndex + spriteAdvanceOnLoop);
ApplyBackgroundSprite(currentSpriteIndex);
}
}
private int NormalizeSpriteIndex(int index) {
if(backgroundSprites == null || backgroundSprites.Length == 0)
{
return 0;
}
int normalizedIndex = index % backgroundSprites.Length;
if(normalizedIndex < 0)
{
normalizedIndex += backgroundSprites.Length;
}
return normalizedIndex;
}
private void ApplyBackgroundSprite(int index) {
if(spriteRenderer == null || backgroundSprites == null || backgroundSprites.Length == 0)
{
return;
}
Sprite backgroundSprite = backgroundSprites[index];
if(backgroundSprite != null)
{
spriteRenderer.sprite = backgroundSprite;
}
}
}
+202 -9
View File
@@ -1,6 +1,7 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
using UnityEngine.UI;
// 게임 오버 상태를 표현하고, 게임 점수와 UI를 관리하는 게임 매니저
// 씬에는 단 하나의 게임 매니저만 존재할 수 있다.
@@ -15,6 +16,9 @@ public class GameManager : MonoBehaviour {
private int currentLife; // 현재 남은 목숨
private int stageMileage = 0; // 현재 플레이 중 획득한 마일리지
private int totalMileage = 0; // 누적 보유 마일리지
private string tutorialMessage = ""; // 튜토리얼 안내 문구
private Image[] lifeIcons; // 왼쪽 HUD의 목숨 도장 표시
private TextMeshProUGUI tutorialText; // 화면 하단 튜토리얼 안내
public int maxLife = 3; // 시작 목숨
public int initialMileage = 500; // 첫 시작시 지급할 초기 마일리지
@@ -23,6 +27,10 @@ public class GameManager : MonoBehaviour {
private const string TotalMileageKey = "UniRun.TotalMileage";
private const string InitialMileageGrantedKey = "UniRun.InitialMileageGranted";
private const string CharacterFaceResourcePath = "UI_JJ_Face";
private const string PassportPanelResourcePath = "UI_PassportPanel";
private const string LifeStampResourcePath = "UI_LifeStamp";
private const string PassportItemSlotResourcePath = "UI_PassportItemSlot";
// 게임의 스피드를 올리는 변수
public float gameSpeed = 1f;
@@ -34,7 +42,7 @@ public class GameManager : MonoBehaviour {
// 게임 시작과 동시에 싱글톤을 구성
void Awake() {
// 싱글톤 변수 instance가 비어있는가?
if (instance == null)
if(instance == null)
{
// instance가 비어있다면(null) 그곳에 자기 자신을 할당
instance = this;
@@ -62,6 +70,7 @@ public class GameManager : MonoBehaviour {
PlayerPrefs.Save();
}
BuildRuntimeHUD();
UpdateScoreUI();
}
@@ -72,11 +81,11 @@ public class GameManager : MonoBehaviour {
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
if (!isGameover)
if(!isGameover)
{
// 게임 진행시 게임 속도는 시간에 따라 증가
gameSpeed += speedIncreaseRate * Time.deltaTime;
// 게임 최대 속도는 max를 넘어서지 못하게
// 게임 최대 속도는 max를 넘어서지 못하게
gameSpeed = Mathf.Min(gameSpeed, maxGameSpeed);
UpdateScoreUI();
}
@@ -93,6 +102,11 @@ public class GameManager : MonoBehaviour {
}
public void SetTutorialMessage(string message) {
tutorialMessage = message;
UpdateScoreUI();
}
// 마일리지 토큰을 먹었을 때 호출되는 메서드
public void AddMileage(int amount) {
if(isGameover)
@@ -138,10 +152,189 @@ public class GameManager : MonoBehaviour {
}
private void UpdateScoreUI() {
scoreText.text = "Score : " + score
+ "\nLife : " + currentLife + "/" + maxLife
+ "\nMileage : " + stageMileage
+ "\nTotal : " + totalMileage
+ "\nSpeed : " + gameSpeed.ToString("0.0");
if(scoreText != null)
{
scoreText.text = "SCORE " + score
+ "\nMILE " + stageMileage
+ "\nTOTAL " + totalMileage
+ "\nSPD " + gameSpeed.ToString("0.0");
}
if(lifeIcons != null)
{
for(int i = 0; i < lifeIcons.Length; i++)
{
if(lifeIcons[i] == null)
{
continue;
}
bool hasLife = i < currentLife;
lifeIcons[i].color = hasLife
? Color.white
: new Color(0.34f, 0.34f, 0.34f, 0.72f);
}
}
if(tutorialText != null)
{
tutorialText.text = tutorialMessage;
tutorialText.gameObject.SetActive(!string.IsNullOrEmpty(tutorialMessage));
}
}
private void BuildRuntimeHUD() {
if(scoreText == null)
{
return;
}
Transform canvasTransform = scoreText.transform.parent;
ConfigureRightInfoText();
CreateStatusHUD(canvasTransform);
CreateTutorialText(canvasTransform);
}
private void ConfigureRightInfoText() {
RectTransform rectTransform = scoreText.rectTransform;
rectTransform.anchorMin = new Vector2(1f, 1f);
rectTransform.anchorMax = new Vector2(1f, 1f);
rectTransform.pivot = new Vector2(1f, 1f);
rectTransform.anchoredPosition = new Vector2(-12f, -9f);
rectTransform.sizeDelta = new Vector2(132f, 62f);
scoreText.alignment = TextAlignmentOptions.TopRight;
scoreText.fontSize = 9.5f;
scoreText.lineSpacing = -10f;
scoreText.color = new Color(0.94f, 0.97f, 1f, 0.92f);
scoreText.raycastTarget = false;
scoreText.textWrappingMode = TextWrappingModes.NoWrap;
}
private void CreateStatusHUD(Transform canvasTransform) {
GameObject hudObject = new GameObject("Status HUD", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
hudObject.transform.SetParent(canvasTransform, false);
RectTransform hudRect = hudObject.GetComponent<RectTransform>();
hudRect.anchorMin = new Vector2(0f, 1f);
hudRect.anchorMax = new Vector2(0f, 1f);
hudRect.pivot = new Vector2(0f, 1f);
hudRect.anchoredPosition = new Vector2(10f, -8f);
hudRect.sizeDelta = new Vector2(176f, 54f);
Image hudBackground = hudObject.GetComponent<Image>();
hudBackground.sprite = Resources.Load<Sprite>(PassportPanelResourcePath);
hudBackground.color = hudBackground.sprite != null
? Color.white
: new Color(0.04f, 0.08f, 0.11f, 0.68f);
hudBackground.raycastTarget = false;
CreateFaceIcon(hudObject.transform);
CreateLifeIcons(hudObject.transform);
CreateItemSlots(hudObject.transform);
}
private void CreateFaceIcon(Transform parent) {
GameObject faceFrame = new GameObject("Face Frame", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
faceFrame.transform.SetParent(parent, false);
RectTransform frameRect = faceFrame.GetComponent<RectTransform>();
frameRect.anchorMin = new Vector2(0f, 1f);
frameRect.anchorMax = new Vector2(0f, 1f);
frameRect.pivot = new Vector2(0f, 1f);
frameRect.anchoredPosition = new Vector2(9f, -10f);
frameRect.sizeDelta = new Vector2(29f, 29f);
Image frameImage = faceFrame.GetComponent<Image>();
frameImage.color = new Color(0.84f, 0.87f, 0.80f, 0.58f);
frameImage.raycastTarget = false;
GameObject faceObject = new GameObject("Character Face", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
faceObject.transform.SetParent(faceFrame.transform, false);
RectTransform faceRect = faceObject.GetComponent<RectTransform>();
faceRect.anchorMin = Vector2.zero;
faceRect.anchorMax = Vector2.one;
faceRect.offsetMin = new Vector2(2f, 2f);
faceRect.offsetMax = new Vector2(-2f, -2f);
Image faceImage = faceObject.GetComponent<Image>();
faceImage.sprite = Resources.Load<Sprite>(CharacterFaceResourcePath);
faceImage.preserveAspect = true;
faceImage.raycastTarget = false;
}
private void CreateLifeIcons(Transform parent) {
Sprite lifeSprite = Resources.Load<Sprite>(LifeStampResourcePath);
int iconCount = Mathf.Max(maxLife, 1);
lifeIcons = new Image[iconCount];
for(int i = 0; i < iconCount; i++)
{
GameObject lifeObject = new GameObject("Life Stamp " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
lifeObject.transform.SetParent(parent, false);
RectTransform lifeRect = lifeObject.GetComponent<RectTransform>();
lifeRect.anchorMin = new Vector2(0f, 1f);
lifeRect.anchorMax = new Vector2(0f, 1f);
lifeRect.pivot = new Vector2(0f, 1f);
lifeRect.anchoredPosition = new Vector2(50f + (i * 21f), -10f);
lifeRect.sizeDelta = new Vector2(18f, 18f);
Image lifeImage = lifeObject.GetComponent<Image>();
lifeImage.sprite = lifeSprite;
lifeImage.color = Color.white;
lifeImage.preserveAspect = true;
lifeImage.raycastTarget = false;
lifeIcons[i] = lifeImage;
}
}
private void CreateItemSlots(Transform parent) {
Sprite slotSprite = Resources.Load<Sprite>(PassportItemSlotResourcePath);
for(int i = 0; i < 3; i++)
{
GameObject slotObject = new GameObject("Item Slot " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
slotObject.transform.SetParent(parent, false);
RectTransform slotRect = slotObject.GetComponent<RectTransform>();
slotRect.anchorMin = new Vector2(0f, 1f);
slotRect.anchorMax = new Vector2(0f, 1f);
slotRect.pivot = new Vector2(0f, 1f);
slotRect.anchoredPosition = new Vector2(50f + (i * 21f), -31f);
slotRect.sizeDelta = new Vector2(18f, 18f);
Image slotImage = slotObject.GetComponent<Image>();
slotImage.sprite = slotSprite;
slotImage.color = slotSprite != null
? new Color(1f, 1f, 1f, 0.92f)
: new Color(0.92f, 0.97f, 1f, 0.22f);
slotImage.preserveAspect = true;
slotImage.raycastTarget = false;
}
}
private void CreateTutorialText(Transform canvasTransform) {
GameObject tutorialObject = new GameObject("Tutorial Text", typeof(RectTransform), typeof(CanvasRenderer));
tutorialObject.transform.SetParent(canvasTransform, false);
RectTransform tutorialRect = tutorialObject.GetComponent<RectTransform>();
tutorialRect.anchorMin = new Vector2(0.5f, 0f);
tutorialRect.anchorMax = new Vector2(0.5f, 0f);
tutorialRect.pivot = new Vector2(0.5f, 0f);
tutorialRect.anchoredPosition = new Vector2(0f, 18f);
tutorialRect.sizeDelta = new Vector2(360f, 24f);
tutorialText = tutorialObject.AddComponent<TextMeshProUGUI>();
tutorialText.font = scoreText.font;
tutorialText.alignment = TextAlignmentOptions.Center;
tutorialText.fontSize = 9.5f;
tutorialText.fontStyle = FontStyles.Bold;
tutorialText.color = new Color(0.96f, 0.98f, 1f, 0.95f);
tutorialText.raycastTarget = false;
tutorialText.textWrappingMode = TextWrappingModes.NoWrap;
tutorialText.gameObject.SetActive(false);
}
}
+137 -17
View File
@@ -2,6 +2,23 @@ using UnityEngine;
// 발판으로서 필요한 동작을 담은 스크립트
public class Platform : MonoBehaviour {
public enum PlatformPattern {
Random,
Empty,
LowLeft,
LowMid,
LowRight,
Slide
}
public enum MileagePattern {
Random,
None,
SafeLine,
JumpArc,
SlideLine
}
public GameObject[] obstacles; // 장애물 오브젝트들
public int emptyPatternWeight = 2; // 장애물이 없는 패턴이 선택될 가중치
public Sprite mileageSprite; // 이 발판에서 사용할 마일리지 토큰 스프라이트
@@ -11,7 +28,21 @@ public class Platform : MonoBehaviour {
private bool stepped = false; // 플레이어 캐릭터가 밟았었는가
private MileagePickup[] mileagePickups; // 런타임에 생성하는 마일리지 토큰 풀
private PlatformPattern reservedPattern = PlatformPattern.Random; // 다음 활성화 때 사용할 수동 패턴
private MileagePattern reservedMileagePattern = MileagePattern.Random; // 다음 활성화 때 사용할 마일리지 패턴
private const int MileagePoolSize = 5;
private const float MileageTokenScale = 0.55f;
private const float MileageColliderRadius = 0.4f;
public void ConfigureForTutorial(PlatformPattern pattern, MileagePattern mileagePattern) {
reservedPattern = pattern;
reservedMileagePattern = mileagePattern;
}
public void ConfigureForRandom() {
reservedPattern = PlatformPattern.Random;
reservedMileagePattern = MileagePattern.Random;
}
// 컴포넌트가 활성화될때 마다 매번 실행되는 메서드
private void OnEnable() {
@@ -27,6 +58,12 @@ public class Platform : MonoBehaviour {
HideMileagePickups();
if(reservedPattern != PlatformPattern.Random)
{
ApplyTutorialPattern();
return;
}
if(obstacles.Length == 0)
{
TryPlaceMileagePattern(null);
@@ -67,7 +104,7 @@ public class Platform : MonoBehaviour {
{
GameObject pickupObject = new GameObject("Mileage Pickup " + (i + 1));
pickupObject.transform.SetParent(transform);
pickupObject.transform.localScale = Vector3.one;
pickupObject.transform.localScale = GetParentScaleCompensatedVector(MileageTokenScale);
SpriteRenderer spriteRenderer = pickupObject.AddComponent<SpriteRenderer>();
spriteRenderer.sprite = mileageSprite;
@@ -76,7 +113,7 @@ public class Platform : MonoBehaviour {
CircleCollider2D pickupCollider = pickupObject.AddComponent<CircleCollider2D>();
pickupCollider.isTrigger = true;
pickupCollider.radius = 0.35f;
pickupCollider.radius = MileageColliderRadius;
mileagePickups[i] = pickupObject.AddComponent<MileagePickup>();
mileagePickups[i].Setup(mileageSprite, smallMileageValue);
@@ -118,17 +155,84 @@ public class Platform : MonoBehaviour {
}
else
{
PlaceJumpMileageArc(activeObstacle.transform.localPosition.x);
PlaceJumpMileageArc(GetWorldRelativeX(activeObstacle.transform));
}
}
private void ApplyTutorialPattern() {
GameObject activeObstacle = null;
switch(reservedPattern)
{
case PlatformPattern.LowLeft:
activeObstacle = FindObstacle("Obstacle Left");
break;
case PlatformPattern.LowMid:
activeObstacle = FindObstacle("Obstacle Mid");
break;
case PlatformPattern.LowRight:
activeObstacle = FindObstacle("Obstacle Right");
break;
case PlatformPattern.Slide:
activeObstacle = FindObstacle("Slide Obstacle Pattern");
break;
}
if(activeObstacle != null)
{
activeObstacle.SetActive(true);
}
PlaceReservedMileagePattern(activeObstacle);
}
private GameObject FindObstacle(string obstacleName) {
for(int i = 0; i < obstacles.Length; i++)
{
if(obstacles[i] != null && obstacles[i].name == obstacleName)
{
return obstacles[i];
}
}
return null;
}
private void PlaceReservedMileagePattern(GameObject activeObstacle) {
if(mileageSprite == null || mileagePickups == null || reservedMileagePattern == MileagePattern.None)
{
return;
}
if(reservedMileagePattern == MileagePattern.SafeLine)
{
PlaceSafeMileageLine();
return;
}
if(reservedMileagePattern == MileagePattern.JumpArc)
{
float obstacleX = activeObstacle != null ? GetWorldRelativeX(activeObstacle.transform) : 0f;
PlaceJumpMileageArc(obstacleX);
return;
}
if(reservedMileagePattern == MileagePattern.SlideLine)
{
PlaceSlideMileageLine();
return;
}
TryPlaceMileagePattern(activeObstacle);
}
private void PlaceSafeMileageLine() {
Vector2[] positions = {
new Vector2(-2f, 2.25f),
new Vector2(-1f, 2.25f),
new Vector2(0f, 2.25f),
new Vector2(1f, 2.25f),
new Vector2(2f, 2.25f)
new Vector2(-2f, 1.35f),
new Vector2(-1f, 1.35f),
new Vector2(0f, 1.35f),
new Vector2(1f, 1.35f),
new Vector2(2f, 1.35f)
};
PlaceMileageTokens(positions, smallMileageValue);
@@ -136,10 +240,10 @@ public class Platform : MonoBehaviour {
private void PlaceJumpMileageArc(float obstacleX) {
Vector2[] positions = {
new Vector2(obstacleX - 1.2f, 2.7f),
new Vector2(obstacleX, 3.45f),
new Vector2(obstacleX + 1.2f, 2.7f),
new Vector2(obstacleX + 1.9f, 2.25f)
new Vector2(obstacleX - 1.2f, 1.65f),
new Vector2(obstacleX, 2.25f),
new Vector2(obstacleX + 1.2f, 1.65f),
new Vector2(obstacleX + 1.9f, 1.35f)
};
PlaceMileageTokens(positions, smallMileageValue);
@@ -148,10 +252,10 @@ public class Platform : MonoBehaviour {
private void PlaceSlideMileageLine() {
Vector2[] positions = {
new Vector2(-1.5f, 1.45f),
new Vector2(-0.5f, 1.45f),
new Vector2(0.5f, 1.45f),
new Vector2(1.5f, 1.45f)
new Vector2(-1.5f, 1.05f),
new Vector2(-0.5f, 1.05f),
new Vector2(0.5f, 1.05f),
new Vector2(1.5f, 1.05f)
};
PlaceMileageTokens(positions, smallMileageValue);
@@ -164,11 +268,27 @@ public class Platform : MonoBehaviour {
for(int i = 0; i < count; i++)
{
mileagePickups[i].Setup(mileageSprite, value);
mileagePickups[i].transform.localPosition = positions[i];
mileagePickups[i].transform.localPosition = GetParentScaleCompensatedPosition(positions[i]);
mileagePickups[i].ResetPickup();
}
}
private Vector3 GetParentScaleCompensatedVector(float scale) {
float parentScaleX = Mathf.Approximately(transform.localScale.x, 0f) ? 1f : transform.localScale.x;
float parentScaleY = Mathf.Approximately(transform.localScale.y, 0f) ? 1f : transform.localScale.y;
return new Vector3(scale / parentScaleX, scale / parentScaleY, 1f);
}
private Vector3 GetParentScaleCompensatedPosition(Vector2 position) {
float parentScaleX = Mathf.Approximately(transform.localScale.x, 0f) ? 1f : transform.localScale.x;
float parentScaleY = Mathf.Approximately(transform.localScale.y, 0f) ? 1f : transform.localScale.y;
return new Vector3(position.x / parentScaleX, position.y / parentScaleY, 0f);
}
private float GetWorldRelativeX(Transform child) {
return child.localPosition.x * transform.localScale.x;
}
private void SetPickupValue(int index, int value) {
if(index < 0 || index >= mileagePickups.Length || !mileagePickups[index].gameObject.activeSelf)
{
+91 -6
View File
@@ -1,14 +1,25 @@
using System;
using System;
using UnityEngine;
using Random = UnityEngine.Random;
// 발판을 생성하고 주기적으로 재배치하는 스크립트
public class PlatformSpawner : MonoBehaviour {
[Serializable]
public class TutorialStep {
public Platform.PlatformPattern platformPattern;
public Platform.MileagePattern mileagePattern;
public float yPosition;
public string message;
}
public GameObject platformPrefab; // 생성할 발판의 원본 프리팹
public int count = 3; // 생성할 발판의 개수
public bool tutorialMode = true; // 튜토리얼에서는 랜덤 대신 정해진 순서로 배치
public TutorialStep[] tutorialSteps; // 튜토리얼 발판 순서
public float timeBetSpawnMin = 1.25f; // 다음 배치까지의 시간 간격 최솟값
public float timeBetSpawnMax = 2.25f; // 다음 배치까지의 시간 간격 최댓값
public float tutorialSpawnInterval = 2.2f; // 튜토리얼 발판 배치 간격
private float timeBetSpawn; // 다음 배치까지의 시간 간격
public float yMin = -3.5f; // 배치할 위치의 최소 y값
@@ -17,12 +28,14 @@ public class PlatformSpawner : MonoBehaviour {
private GameObject[] platforms; // 미리 생성한 발판들
private int currentIndex = 0; // 사용할 현재 순번의 발판
private int tutorialStepIndex = 0; // 진행 중인 튜토리얼 순번
private Vector2 poolPosition = new Vector2(0, -25); // 초반에 생성된 발판들을 화면 밖에 숨겨둘 위치
private float lastSpawnTime; // 마지막 배치 시점
void Start() {
EnsureTutorialSteps();
// 변수들을 초기화하고 사용할 발판들을 미리 생성
platforms = new GameObject[count];
@@ -47,13 +60,14 @@ public class PlatformSpawner : MonoBehaviour {
if(Time.time >= lastSpawnTime + timeBetSpawn)
{
lastSpawnTime = Time. time;
lastSpawnTime = Time.time;
timeBetSpawn = Random.Range(timeBetSpawnMin, timeBetSpawnMax);
timeBetSpawn = tutorialMode ? tutorialSpawnInterval : Random.Range(timeBetSpawnMin, timeBetSpawnMax);
float yPos = Random.Range(yMin, yMax);
float yPos = tutorialMode ? GetTutorialYPosition() : Random.Range(yMin, yMax);
platforms[currentIndex].SetActive(false);
ConfigurePlatform(platforms[currentIndex]);
platforms[currentIndex].SetActive(true);
platforms[currentIndex].transform.position = new Vector2(xPos, yPos);
@@ -64,6 +78,77 @@ public class PlatformSpawner : MonoBehaviour {
{
currentIndex = 0;
}
if(tutorialMode)
{
tutorialStepIndex++;
}
}
}
}
private void ConfigurePlatform(GameObject platformObject) {
Platform platform = platformObject.GetComponent<Platform>();
if(platform == null)
{
return;
}
if(!tutorialMode)
{
platform.ConfigureForRandom();
return;
}
TutorialStep step = GetCurrentTutorialStep();
platform.ConfigureForTutorial(step.platformPattern, step.mileagePattern);
if(GameManager.instance != null)
{
GameManager.instance.SetTutorialMessage(step.message);
}
}
private float GetTutorialYPosition() {
return Mathf.Clamp(GetCurrentTutorialStep().yPosition, yMin, yMax);
}
private TutorialStep GetCurrentTutorialStep() {
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];
}
private void EnsureTutorialSteps() {
if(tutorialSteps != null && tutorialSteps.Length > 0)
{
return;
}
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."),
};
}
private TutorialStep CreateStep(Platform.PlatformPattern platformPattern, Platform.MileagePattern mileagePattern, float yPosition, string message) {
TutorialStep step = new TutorialStep();
step.platformPattern = platformPattern;
step.mileagePattern = mileagePattern;
step.yPosition = yPosition;
step.message = message;
return step;
}
}