Add duty free systems and travel stage updates
This commit is contained in:
@@ -1,15 +1,23 @@
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
// 왼쪽 끝으로 이동한 배경을 오른쪽 끝으로 재배치하는 스크립트
|
||||
[ExecuteAlways]
|
||||
public class BackgroundLoop : MonoBehaviour {
|
||||
public Sprite[] backgroundSprites; // 순서대로 교체할 공항 배경 스프라이트
|
||||
public bool useResourceBackground = true; // Resources 폴더의 맵 배경을 자동으로 사용
|
||||
public bool fitToMainCamera = true; // 맵 배경을 카메라 화면 크기에 맞춰 표시
|
||||
public float cameraFitPadding = 1f; // 가장자리 빈틈 방지용 배경 확대 여유
|
||||
public string resourceBackgroundPath = ""; // 맵 데이터가 없을 때 사용할 Resources 배경
|
||||
public int startSpriteIndex = 0; // 이 배경 오브젝트가 시작할 스프라이트 번호
|
||||
public int spriteAdvanceOnLoop = 2; // 배경 오브젝트가 두 장이므로 재배치될 때 두 칸씩 진행
|
||||
|
||||
private static string activeResourceBackgroundPath = "";
|
||||
private static string[] activeResourceBackgroundPaths = new string[0];
|
||||
private static int activeBackgroundLoopCount = 1;
|
||||
private static float activeCameraFitScaleMultiplier = 1f;
|
||||
private static float activeVerticalOffset = 0f;
|
||||
|
||||
private float width; // 배경의 가로 길이
|
||||
private int currentSpriteIndex; // 현재 표시 중인 스프라이트 번호
|
||||
@@ -17,9 +25,24 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
private BoxCollider2D backgroundCollider; // 루프 폭 계산용 콜라이더
|
||||
private Sprite[] originalBackgroundSprites; // 맵 배경 리소스가 없을 때 되돌릴 기본 배경
|
||||
private bool originalBackgroundSpritesCached = false;
|
||||
private Vector3 originalLocalPosition; // 배경 루프를 초기 배치로 되돌릴 때 사용할 위치
|
||||
private Vector3 originalLocalScale = Vector3.one; // 자동 맞춤 해제 시 되돌릴 기본 스케일
|
||||
private bool originalTransformCached = false;
|
||||
|
||||
public static void ApplyResourceBackground(string resourcePath) {
|
||||
activeResourceBackgroundPath = resourcePath;
|
||||
ApplyResourceBackground(string.IsNullOrEmpty(resourcePath)
|
||||
? new string[0]
|
||||
: new string[] { resourcePath });
|
||||
}
|
||||
|
||||
public static void ApplyResourceBackground(string[] resourcePaths) {
|
||||
ApplyResourceBackground(resourcePaths, 1f, 0f);
|
||||
}
|
||||
|
||||
public static void ApplyResourceBackground(string[] resourcePaths, float cameraFitScaleMultiplier, float verticalOffset) {
|
||||
activeResourceBackgroundPaths = NormalizeResourcePaths(resourcePaths);
|
||||
activeCameraFitScaleMultiplier = Mathf.Max(0.1f, cameraFitScaleMultiplier);
|
||||
activeVerticalOffset = verticalOffset;
|
||||
|
||||
BackgroundLoop[] backgroundLoops = FindObjectsByType<BackgroundLoop>(FindObjectsSortMode.None);
|
||||
for(int i = 0; i < backgroundLoops.Length; i++)
|
||||
@@ -29,6 +52,9 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
backgroundLoops[i].InitializeBackground();
|
||||
}
|
||||
}
|
||||
|
||||
AlignLoopPositions(backgroundLoops);
|
||||
AssignSequentialSpriteIndices(backgroundLoops);
|
||||
}
|
||||
|
||||
private void Awake() {
|
||||
@@ -59,10 +85,12 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
private void InitializeBackground() {
|
||||
CacheComponents();
|
||||
CacheOriginalBackgroundSprites();
|
||||
CacheOriginalTransform();
|
||||
EnsureResourceBackground();
|
||||
|
||||
currentSpriteIndex = NormalizeSpriteIndex(startSpriteIndex);
|
||||
ApplyBackgroundSprite(currentSpriteIndex);
|
||||
FitSpriteToCamera();
|
||||
SyncColliderSize();
|
||||
RefreshWidth();
|
||||
}
|
||||
@@ -80,33 +108,92 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
}
|
||||
|
||||
private void EnsureResourceBackground() {
|
||||
if(!useResourceBackground)
|
||||
bool hasActiveResourceBackground = activeResourceBackgroundPaths != null
|
||||
&& activeResourceBackgroundPaths.Length > 0;
|
||||
if(!useResourceBackground && !hasActiveResourceBackground)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string finalResourcePath = !string.IsNullOrEmpty(activeResourceBackgroundPath)
|
||||
? activeResourceBackgroundPath
|
||||
: resourceBackgroundPath;
|
||||
string[] finalResourcePaths = hasActiveResourceBackground
|
||||
? activeResourceBackgroundPaths
|
||||
: NormalizeResourcePaths(string.IsNullOrEmpty(resourceBackgroundPath)
|
||||
? new string[0]
|
||||
: new string[] { resourceBackgroundPath });
|
||||
|
||||
if(string.IsNullOrEmpty(finalResourcePath))
|
||||
if(finalResourcePaths.Length == 0)
|
||||
{
|
||||
backgroundSprites = originalBackgroundSprites;
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite resourceSprite = Resources.Load<Sprite>(finalResourcePath);
|
||||
if(resourceSprite == null)
|
||||
Sprite[] resourceSprites = LoadResourceBackgroundSprites(finalResourcePaths);
|
||||
if(resourceSprites.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(backgroundSprites != null && backgroundSprites.Length == 1 && backgroundSprites[0] == resourceSprite)
|
||||
if(SpriteArraysMatch(backgroundSprites, resourceSprites))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
backgroundSprites = new Sprite[] { resourceSprite };
|
||||
backgroundSprites = resourceSprites;
|
||||
}
|
||||
|
||||
private Sprite[] LoadResourceBackgroundSprites(string[] resourcePaths) {
|
||||
Sprite[] loadedSprites = new Sprite[resourcePaths.Length];
|
||||
int loadedCount = 0;
|
||||
|
||||
for(int i = 0; i < resourcePaths.Length; i++)
|
||||
{
|
||||
Sprite resourceSprite = LoadResourceBackgroundSprite(resourcePaths[i]);
|
||||
if(resourceSprite == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
loadedSprites[loadedCount] = resourceSprite;
|
||||
loadedCount++;
|
||||
}
|
||||
|
||||
if(loadedCount == loadedSprites.Length)
|
||||
{
|
||||
return loadedSprites;
|
||||
}
|
||||
|
||||
Sprite[] compactSprites = new Sprite[loadedCount];
|
||||
for(int i = 0; i < loadedCount; i++)
|
||||
{
|
||||
compactSprites[i] = loadedSprites[i];
|
||||
}
|
||||
|
||||
return compactSprites;
|
||||
}
|
||||
|
||||
private Sprite LoadResourceBackgroundSprite(string resourcePath) {
|
||||
Sprite resourceSprite = Resources.Load<Sprite>(resourcePath);
|
||||
if(resourceSprite != null)
|
||||
{
|
||||
return resourceSprite;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
string assetPath = "Assets/Resources/" + resourcePath + ".png";
|
||||
Sprite editorSprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||||
if(editorSprite != null)
|
||||
{
|
||||
return editorSprite;
|
||||
}
|
||||
|
||||
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
if(texture != null)
|
||||
{
|
||||
return Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), 8f);
|
||||
}
|
||||
#endif
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CacheOriginalBackgroundSprites() {
|
||||
@@ -119,6 +206,17 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
originalBackgroundSpritesCached = true;
|
||||
}
|
||||
|
||||
private void CacheOriginalTransform() {
|
||||
if(originalTransformCached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
originalLocalPosition = transform.localPosition;
|
||||
originalLocalScale = transform.localScale;
|
||||
originalTransformCached = true;
|
||||
}
|
||||
|
||||
private void RefreshWidth() {
|
||||
if(spriteRenderer != null && spriteRenderer.sprite != null)
|
||||
{
|
||||
@@ -147,13 +245,77 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
backgroundCollider.isTrigger = true;
|
||||
}
|
||||
|
||||
private void FitSpriteToCamera() {
|
||||
if(spriteRenderer == null || spriteRenderer.sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!fitToMainCamera)
|
||||
{
|
||||
transform.localScale = originalLocalScale;
|
||||
ApplyVerticalOffset();
|
||||
return;
|
||||
}
|
||||
|
||||
Camera targetCamera = Camera.main;
|
||||
if(targetCamera == null || !targetCamera.orthographic)
|
||||
{
|
||||
ApplyVerticalOffset();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 spriteSize = spriteRenderer.sprite.bounds.size;
|
||||
if(spriteSize.x <= 0f || spriteSize.y <= 0f)
|
||||
{
|
||||
ApplyVerticalOffset();
|
||||
return;
|
||||
}
|
||||
|
||||
float cameraHeight = targetCamera.orthographicSize * 2f;
|
||||
float cameraWidth = cameraHeight * GetCameraAspect(targetCamera);
|
||||
float parentScaleX = transform.parent != null
|
||||
? Mathf.Abs(transform.parent.lossyScale.x)
|
||||
: 1f;
|
||||
float parentScaleY = transform.parent != null
|
||||
? Mathf.Abs(transform.parent.lossyScale.y)
|
||||
: 1f;
|
||||
float scaleX = cameraWidth / (spriteSize.x * Mathf.Max(parentScaleX, 0.0001f));
|
||||
float scaleY = cameraHeight / (spriteSize.y * Mathf.Max(parentScaleY, 0.0001f));
|
||||
float fitScale = Mathf.Max(scaleX, scaleY)
|
||||
* Mathf.Max(cameraFitPadding, 1f)
|
||||
* activeCameraFitScaleMultiplier;
|
||||
float signX = Mathf.Sign(originalLocalScale.x);
|
||||
float signY = Mathf.Sign(originalLocalScale.y);
|
||||
|
||||
if(Mathf.Approximately(signX, 0f))
|
||||
{
|
||||
signX = 1f;
|
||||
}
|
||||
|
||||
if(Mathf.Approximately(signY, 0f))
|
||||
{
|
||||
signY = 1f;
|
||||
}
|
||||
|
||||
transform.localScale = new Vector3(fitScale * signX, fitScale * signY, originalLocalScale.z);
|
||||
ApplyVerticalOffset();
|
||||
}
|
||||
|
||||
private void ApplyVerticalOffset() {
|
||||
Vector3 localPosition = transform.localPosition;
|
||||
localPosition.y = originalLocalPosition.y + activeVerticalOffset;
|
||||
transform.localPosition = localPosition;
|
||||
}
|
||||
|
||||
// 위치를 리셋하는 메서드
|
||||
private void Reposition() {
|
||||
Vector2 offset = new Vector2(width * 2f, 0);
|
||||
transform.position = (Vector2) transform.position + offset;
|
||||
|
||||
currentSpriteIndex = NormalizeSpriteIndex(currentSpriteIndex + spriteAdvanceOnLoop);
|
||||
currentSpriteIndex = NormalizeSpriteIndex(currentSpriteIndex + GetSpriteAdvanceOnLoop());
|
||||
ApplyBackgroundSprite(currentSpriteIndex);
|
||||
FitSpriteToCamera();
|
||||
SyncColliderSize();
|
||||
RefreshWidth();
|
||||
}
|
||||
@@ -189,4 +351,187 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
spriteRenderer.color = Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCurrentSpriteIndex(int index) {
|
||||
currentSpriteIndex = NormalizeSpriteIndex(index);
|
||||
ApplyBackgroundSprite(currentSpriteIndex);
|
||||
FitSpriteToCamera();
|
||||
SyncColliderSize();
|
||||
RefreshWidth();
|
||||
}
|
||||
|
||||
private float GetCurrentWidth() {
|
||||
if(width > 0f)
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
RefreshWidth();
|
||||
return width;
|
||||
}
|
||||
|
||||
private int GetSpriteAdvanceOnLoop() {
|
||||
if(backgroundSprites != null && backgroundSprites.Length > 1 && activeBackgroundLoopCount > 1)
|
||||
{
|
||||
return activeBackgroundLoopCount;
|
||||
}
|
||||
|
||||
return Mathf.Max(1, spriteAdvanceOnLoop);
|
||||
}
|
||||
|
||||
private static float GetCameraAspect(Camera targetCamera) {
|
||||
if(targetCamera.aspect > 0f)
|
||||
{
|
||||
return targetCamera.aspect;
|
||||
}
|
||||
|
||||
if(Screen.height > 0)
|
||||
{
|
||||
return (float) Screen.width / Screen.height;
|
||||
}
|
||||
|
||||
return 16f / 9f;
|
||||
}
|
||||
|
||||
private static string[] NormalizeResourcePaths(string[] resourcePaths) {
|
||||
if(resourcePaths == null || resourcePaths.Length == 0)
|
||||
{
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
string[] normalizedPaths = new string[resourcePaths.Length];
|
||||
int pathCount = 0;
|
||||
for(int i = 0; i < resourcePaths.Length; i++)
|
||||
{
|
||||
if(string.IsNullOrEmpty(resourcePaths[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedPaths[pathCount] = resourcePaths[i];
|
||||
pathCount++;
|
||||
}
|
||||
|
||||
if(pathCount == normalizedPaths.Length)
|
||||
{
|
||||
return normalizedPaths;
|
||||
}
|
||||
|
||||
string[] compactPaths = new string[pathCount];
|
||||
for(int i = 0; i < pathCount; i++)
|
||||
{
|
||||
compactPaths[i] = normalizedPaths[i];
|
||||
}
|
||||
|
||||
return compactPaths;
|
||||
}
|
||||
|
||||
private static bool SpriteArraysMatch(Sprite[] firstSprites, Sprite[] secondSprites) {
|
||||
if(firstSprites == null || secondSprites == null || firstSprites.Length != secondSprites.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < firstSprites.Length; i++)
|
||||
{
|
||||
if(firstSprites[i] != secondSprites[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void AlignLoopPositions(BackgroundLoop[] backgroundLoops) {
|
||||
if(backgroundLoops == null || backgroundLoops.Length <= 1)
|
||||
{
|
||||
activeBackgroundLoopCount = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
for(int i = 0; i < backgroundLoops.Length; i++)
|
||||
{
|
||||
if(backgroundLoops[i] != null)
|
||||
{
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(validCount <= 1)
|
||||
{
|
||||
activeBackgroundLoopCount = Mathf.Max(1, validCount);
|
||||
return;
|
||||
}
|
||||
|
||||
activeBackgroundLoopCount = validCount;
|
||||
BackgroundLoop[] sortedLoops = new BackgroundLoop[validCount];
|
||||
int sortedIndex = 0;
|
||||
for(int i = 0; i < backgroundLoops.Length; i++)
|
||||
{
|
||||
if(backgroundLoops[i] != null)
|
||||
{
|
||||
sortedLoops[sortedIndex] = backgroundLoops[i];
|
||||
sortedIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
System.Array.Sort(sortedLoops, (first, second) =>
|
||||
first.originalLocalPosition.x.CompareTo(second.originalLocalPosition.x));
|
||||
|
||||
float loopWidth = sortedLoops[0].GetCurrentWidth();
|
||||
if(loopWidth <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float baseX = sortedLoops[0].originalLocalPosition.x;
|
||||
for(int i = 0; i < sortedLoops.Length; i++)
|
||||
{
|
||||
Vector3 localPosition = sortedLoops[i].originalLocalPosition;
|
||||
localPosition.x = baseX + loopWidth * i;
|
||||
sortedLoops[i].transform.localPosition = localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssignSequentialSpriteIndices(BackgroundLoop[] backgroundLoops) {
|
||||
if(backgroundLoops == null || backgroundLoops.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
for(int i = 0; i < backgroundLoops.Length; i++)
|
||||
{
|
||||
if(backgroundLoops[i] != null)
|
||||
{
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(validCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BackgroundLoop[] sortedLoops = new BackgroundLoop[validCount];
|
||||
int sortedIndex = 0;
|
||||
for(int i = 0; i < backgroundLoops.Length; i++)
|
||||
{
|
||||
if(backgroundLoops[i] != null)
|
||||
{
|
||||
sortedLoops[sortedIndex] = backgroundLoops[i];
|
||||
sortedIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
System.Array.Sort(sortedLoops, (first, second) =>
|
||||
first.originalLocalPosition.x.CompareTo(second.originalLocalPosition.x));
|
||||
|
||||
for(int i = 0; i < sortedLoops.Length; i++)
|
||||
{
|
||||
sortedLoops[i].SetCurrentSpriteIndex(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
[Serializable]
|
||||
public class CourseProfile {
|
||||
public CourseStep[] openingSteps;
|
||||
public CourseStep[] loopSteps;
|
||||
|
||||
public CourseStep[] GetOpeningSteps() {
|
||||
return CourseStepFactory.CloneSteps(openingSteps);
|
||||
}
|
||||
|
||||
public CourseStep[] GetLoopSteps() {
|
||||
return CourseStepFactory.CloneSteps(loopSteps);
|
||||
}
|
||||
|
||||
public static CourseProfile Create(CourseStep[] openingSteps, CourseStep[] loopSteps) {
|
||||
return new CourseProfile {
|
||||
openingSteps = CourseStepFactory.CloneSteps(openingSteps),
|
||||
loopSteps = CourseStepFactory.CloneSteps(loopSteps)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b89f600e13ac4d13a6ed5691d11e8e0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -6,7 +6,5 @@ public enum CourseStepKind {
|
||||
ForceHit,
|
||||
HealthItem,
|
||||
InvincibleItem,
|
||||
ShieldItem,
|
||||
SpeedItem,
|
||||
MileageCardItem
|
||||
EnergyDrinkItem
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29d088f4951c41a7a41a6c85a5fd94ff
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class AmericaCourseProfile {
|
||||
public static CourseProfile Create() {
|
||||
return CourseProfile.Create(
|
||||
new CourseStep[] {
|
||||
Basic(GroundY, 0.60f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowMid, 0.58f),
|
||||
Slide(SlideObstaclePattern.SlideLayout.WidePair, 0.58f),
|
||||
Basic(GroundY, 0.56f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.EnergyDrink),
|
||||
Aerial(AerialY + 0.20f, 1.35f),
|
||||
Basic(GroundY, 0.58f, Platform.MileagePattern.SafeLine)
|
||||
},
|
||||
new CourseStep[] {
|
||||
Basic(GroundY, 0.56f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowLeft, 0.54f),
|
||||
Slide(SlideObstaclePattern.SlideLayout.RightPair, 0.52f),
|
||||
Basic(GroundY, 0.50f, Platform.MileagePattern.JumpArc),
|
||||
Jump(Platform.PlatformPattern.LowRight, 0.52f),
|
||||
Basic(GroundY, 0.50f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.HealthSushi),
|
||||
Aerial(AerialY + 0.22f, 1.24f),
|
||||
Slide(SlideObstaclePattern.SlideLayout.WidePair, 0.50f),
|
||||
Basic(GroundY, 0.48f, Platform.MileagePattern.SlideLine),
|
||||
Jump(Platform.PlatformPattern.LowMid, 0.50f),
|
||||
Slide(SlideObstaclePattern.SlideLayout.LeftPair, 0.48f),
|
||||
Basic(GroundY, 0.50f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.EnergyDrink),
|
||||
Jump(Platform.PlatformPattern.LowLeft, 0.50f),
|
||||
Jump(Platform.PlatformPattern.LowRight, 0.50f),
|
||||
Basic(GroundY, 0.52f, Platform.MileagePattern.SafeLine),
|
||||
Aerial(AerialY + 0.12f, 1.18f),
|
||||
Basic(GroundY, 0.50f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.InvincibleRamen),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair, 0.48f),
|
||||
Jump(Platform.PlatformPattern.LowMid, 0.50f),
|
||||
Basic(GroundY, 0.58f, Platform.MileagePattern.SafeLine)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3473696a9ce54b0cbecb0b7c3aa8aebd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class ChinaCourseProfile {
|
||||
public static CourseProfile Create() {
|
||||
return CourseProfile.Create(
|
||||
new CourseStep[] {
|
||||
Basic(GroundY, 0.66f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowRight, 0.68f),
|
||||
Basic(GroundY, 0.62f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.HealthSushi),
|
||||
Slide(SlideObstaclePattern.SlideLayout.LeftPair, 0.66f),
|
||||
Basic(GroundY, 0.62f, Platform.MileagePattern.SafeLine),
|
||||
Aerial(AerialY + 0.12f, 1.72f),
|
||||
Basic(GroundY, 0.66f, Platform.MileagePattern.SafeLine)
|
||||
},
|
||||
new CourseStep[] {
|
||||
Basic(GroundY, 0.62f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowLeft, 0.64f),
|
||||
Basic(GroundY, 0.58f, Platform.MileagePattern.JumpArc),
|
||||
Slide(SlideObstaclePattern.SlideLayout.RightPair, 0.62f),
|
||||
Basic(GroundY, 0.60f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.EnergyDrink),
|
||||
Jump(Platform.PlatformPattern.LowRight, 0.60f),
|
||||
Basic(GroundY, 0.58f, Platform.MileagePattern.SafeLine),
|
||||
Aerial(AerialY + 0.14f, 1.55f),
|
||||
Basic(GroundY, 0.62f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.InvincibleRamen),
|
||||
Slide(SlideObstaclePattern.SlideLayout.LeftPair, 0.60f),
|
||||
Basic(GroundY, 0.58f, Platform.MileagePattern.SlideLine),
|
||||
Jump(Platform.PlatformPattern.LowMid, 0.60f),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair, 0.58f),
|
||||
Basic(GroundY, 0.64f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.HealthSushi),
|
||||
Aerial(AerialY + 0.05f, 1.70f),
|
||||
Basic(GroundY, 0.66f, Platform.MileagePattern.SafeLine)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 513d2fc410734352ab7a51ff2f8b5d9f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class FallbackCourseProfile {
|
||||
public static CourseProfile CreateTutorialFallback() {
|
||||
return CourseProfile.Create(
|
||||
new CourseStep[] {
|
||||
Jump(GameManager.TutorialGuideType.Jump, "점프", "가방 장애물이 보이면 왼쪽 클릭으로 뛰어넘습니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Aerial(GameManager.TutorialGuideType.DoubleJump, "2단 점프", "다음 발판이 멀리 있으면 공중에서 한 번 더 왼쪽 클릭합니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(GameManager.TutorialGuideType.Slide, "슬라이드", "천장 표지판이 보이면 오른쪽 클릭을 누르고 지나갑니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
ForceHit(GameManager.TutorialGuideType.Damage, "피격", "이번 구간은 일부러 맞아 목숨과 속도 감소를 확인합니다."),
|
||||
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, GameplayItemCatalog.GetDisplayName(GameManager.TutorialItemType.HealthSushi, Platform.PlatformSkin.Airport), "김밥을 먹으면 잃은 목숨을 1 회복합니다.", Platform.ItemPattern.HealthSushi),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, GameplayItemCatalog.GetDisplayName(GameManager.TutorialItemType.InvincibleRamen, Platform.PlatformSkin.Airport), "바나나우유를 먹고 다음 충돌을 피해 없이 지나가 봅니다.", Platform.ItemPattern.InvincibleRamen),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, GameplayItemCatalog.GetDisplayName(GameManager.TutorialItemType.EnergyDrink, Platform.PlatformSkin.Airport), "에너지 드링크는 속도를 조금 회복해 일정이 늦어지는 것을 줄입니다.", Platform.ItemPattern.EnergyDrink),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Basic(GroundY, BasicSpacing)
|
||||
},
|
||||
CreateGeneralFallbackLoop());
|
||||
}
|
||||
|
||||
public static CourseProfile CreateGeneralFallback() {
|
||||
return CourseProfile.Create(
|
||||
new CourseStep[] {
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
CreateGeneralFallbackLoop());
|
||||
}
|
||||
|
||||
private static CourseStep[] CreateGeneralFallbackLoop() {
|
||||
return new CourseStep[] {
|
||||
Basic(),
|
||||
Jump(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.WidePair),
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowLeft),
|
||||
Slide(SlideObstaclePattern.SlideLayout.LeftPair),
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowRight),
|
||||
Aerial(AerialY, AerialSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.RightPair),
|
||||
Jump(Platform.PlatformPattern.LowMid),
|
||||
Basic(),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c10f2b5787bb465ab16291a98eb7a51d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class JapanCourseProfile {
|
||||
public static CourseProfile Create() {
|
||||
return CourseProfile.Create(
|
||||
new CourseStep[] {
|
||||
Basic(GroundY, 0.72f, Platform.MileagePattern.SafeLine),
|
||||
Basic(GroundY, 0.70f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowMid, 0.78f),
|
||||
Basic(GroundY, 0.70f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.HealthSushi),
|
||||
Basic(GroundY, 0.68f, Platform.MileagePattern.SafeLine),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair, 0.76f),
|
||||
Basic(GroundY, 0.70f, Platform.MileagePattern.SafeLine)
|
||||
},
|
||||
new CourseStep[] {
|
||||
Basic(GroundY, 0.68f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowMid, 0.74f),
|
||||
Basic(GroundY, 0.70f, Platform.MileagePattern.JumpArc),
|
||||
Basic(GroundY, 0.66f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.HealthSushi),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair, 0.74f),
|
||||
Basic(GroundY, 0.70f, Platform.MileagePattern.SlideLine),
|
||||
Aerial(AerialY, 2.05f),
|
||||
Basic(GroundY, 0.72f, Platform.MileagePattern.SafeLine),
|
||||
Jump(Platform.PlatformPattern.LowLeft, 0.72f),
|
||||
Basic(GroundY, 0.66f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.InvincibleRamen),
|
||||
Basic(GroundY, 0.68f, Platform.MileagePattern.SafeLine),
|
||||
Slide(SlideObstaclePattern.SlideLayout.RightPair, 0.72f),
|
||||
Basic(GroundY, 0.66f, Platform.MileagePattern.None, GameManager.TutorialGuideType.None, "", "", Platform.ItemPattern.EnergyDrink),
|
||||
Jump(Platform.PlatformPattern.LowRight, 0.72f),
|
||||
Basic(GroundY, 0.72f, Platform.MileagePattern.SafeLine),
|
||||
Basic(GroundY, 0.76f, Platform.MileagePattern.SafeLine)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ec45d923be242e3bf69387973545026
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class TutorialCourseProfile {
|
||||
public static CourseProfile Create() {
|
||||
return CourseProfile.Create(
|
||||
new CourseStep[] {
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Jump(GameManager.TutorialGuideType.Jump, "점프 구간", "가방 장애물이 오면 왼쪽 클릭으로 뛰어넘습니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Aerial(GameManager.TutorialGuideType.DoubleJump, "2단 점프 구간", "다음 발판이 멀리 떨어져 있습니다.\n공중에서 왼쪽 클릭을 한 번 더 누릅니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Slide(GameManager.TutorialGuideType.Slide, "슬라이드 구간", "천장 장애물이 오면 오른쪽 클릭을 누르고 지나갑니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "김밥", "잃은 목숨을 1 회복합니다.\n방금 줄어든 목숨을 채워 봅니다.", Platform.ItemPattern.HealthSushi),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "바나나우유", "잠깐 무적이 됩니다.\n먹은 뒤 다음 장애물에 일부러 부딪혀 봅니다.", Platform.ItemPattern.InvincibleRamen),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "에너지 드링크", "느려진 속도를 조금 회복합니다.\n방금 떨어진 속도를 되돌려 봅니다.", Platform.ItemPattern.EnergyDrink),
|
||||
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "실전 연습", "시간이 지나면 달리기 속도는 조금씩 빨라지고, 장애물에 맞으면 느려집니다.\n방금 먹은 에너지 드링크로 회복한 뒤 출국 게이트까지 달려보세요."),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(GroundY, LandingSpacing)
|
||||
},
|
||||
CreateEasyLoop());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 755fb689f3ff4855a3193f4711b10fa5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,27 @@
|
||||
using System;
|
||||
|
||||
public enum DutyFreeItemType {
|
||||
None = 0,
|
||||
Lunchbox = 1,
|
||||
MileageCard = 4,
|
||||
Supplement = 5
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GameSaveData {
|
||||
public int currentMapIndex;
|
||||
public int unlockedMapIndex;
|
||||
public int totalMileage;
|
||||
public int bagSlots;
|
||||
public int shieldStock;
|
||||
public int lunchboxStock;
|
||||
public int speedShoesStock;
|
||||
public int mileageCardStock;
|
||||
public int supplementPurchases;
|
||||
public int dutyFreeLimitStageIndex;
|
||||
public bool purchasedMileageCardThisStage;
|
||||
public bool purchasedSupplementThisStage;
|
||||
public DutyFreeItemType equippedSlot1;
|
||||
public DutyFreeItemType equippedSlot2;
|
||||
public DutyFreeItemType equippedSlot3;
|
||||
public int bestScore;
|
||||
|
||||
public static GameSaveData CreateDefault() {
|
||||
@@ -18,10 +30,15 @@ public class GameSaveData {
|
||||
unlockedMapIndex = 0,
|
||||
totalMileage = 0,
|
||||
bagSlots = 1,
|
||||
shieldStock = 0,
|
||||
lunchboxStock = 0,
|
||||
speedShoesStock = 0,
|
||||
mileageCardStock = 0,
|
||||
supplementPurchases = 0,
|
||||
dutyFreeLimitStageIndex = -1,
|
||||
purchasedMileageCardThisStage = false,
|
||||
purchasedSupplementThisStage = false,
|
||||
equippedSlot1 = DutyFreeItemType.None,
|
||||
equippedSlot2 = DutyFreeItemType.None,
|
||||
equippedSlot3 = DutyFreeItemType.None,
|
||||
bestScore = 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,10 +66,148 @@ public static class GameSaveManager {
|
||||
saveData.currentMapIndex = Mathf.Min(saveData.currentMapIndex, saveData.unlockedMapIndex);
|
||||
saveData.totalMileage = Mathf.Max(0, saveData.totalMileage);
|
||||
saveData.bagSlots = Mathf.Clamp(saveData.bagSlots, 1, 3);
|
||||
saveData.shieldStock = Mathf.Max(0, saveData.shieldStock);
|
||||
saveData.lunchboxStock = Mathf.Max(0, saveData.lunchboxStock);
|
||||
saveData.speedShoesStock = Mathf.Max(0, saveData.speedShoesStock);
|
||||
saveData.mileageCardStock = Mathf.Max(0, saveData.mileageCardStock);
|
||||
saveData.supplementPurchases = Mathf.Clamp(saveData.supplementPurchases, 0, 4);
|
||||
saveData.dutyFreeLimitStageIndex = saveData.dutyFreeLimitStageIndex < 0
|
||||
? -1
|
||||
: Mathf.Clamp(saveData.dutyFreeLimitStageIndex, 0, MapDatabase.LastMapIndex);
|
||||
NormalizeEquippedItems(saveData);
|
||||
NormalizeDutyFreeStockCapacity(saveData);
|
||||
NormalizeEquippedItems(saveData);
|
||||
saveData.bestScore = Mathf.Max(0, saveData.bestScore);
|
||||
}
|
||||
|
||||
private static void NormalizeDutyFreeStockCapacity(GameSaveData saveData) {
|
||||
int bagSlots = Mathf.Clamp(saveData.bagSlots, 1, 3);
|
||||
int equippedLunchboxes = CountEquippedItem(saveData, DutyFreeItemType.Lunchbox, bagSlots);
|
||||
int equippedMileageCards = CountEquippedItem(saveData, DutyFreeItemType.MileageCard, bagSlots);
|
||||
|
||||
saveData.lunchboxStock = Mathf.Max(equippedLunchboxes, Mathf.Min(saveData.lunchboxStock, bagSlots));
|
||||
saveData.mileageCardStock = Mathf.Max(equippedMileageCards, Mathf.Min(saveData.mileageCardStock, bagSlots));
|
||||
|
||||
while(saveData.lunchboxStock + saveData.mileageCardStock > bagSlots)
|
||||
{
|
||||
if(saveData.mileageCardStock > equippedMileageCards)
|
||||
{
|
||||
saveData.mileageCardStock--;
|
||||
}
|
||||
else if(saveData.lunchboxStock > equippedLunchboxes)
|
||||
{
|
||||
saveData.lunchboxStock--;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountEquippedItem(GameSaveData saveData, DutyFreeItemType itemType, int bagSlots) {
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < bagSlots; i++)
|
||||
{
|
||||
if(GetEquippedSlot(saveData, i) == itemType)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void NormalizeEquippedItems(GameSaveData saveData) {
|
||||
int bagSlots = Mathf.Clamp(saveData.bagSlots, 1, 3);
|
||||
if(bagSlots < 3)
|
||||
{
|
||||
saveData.equippedSlot3 = DutyFreeItemType.None;
|
||||
}
|
||||
|
||||
if(bagSlots < 2)
|
||||
{
|
||||
saveData.equippedSlot2 = DutyFreeItemType.None;
|
||||
}
|
||||
|
||||
saveData.equippedSlot1 = NormalizeItemType(saveData.equippedSlot1);
|
||||
saveData.equippedSlot2 = NormalizeItemType(saveData.equippedSlot2);
|
||||
saveData.equippedSlot3 = NormalizeItemType(saveData.equippedSlot3);
|
||||
|
||||
int lunchboxCount = 0;
|
||||
int mileageCardCount = 0;
|
||||
|
||||
ClampEquippedSlot(saveData, 0, ref lunchboxCount, ref mileageCardCount);
|
||||
ClampEquippedSlot(saveData, 1, ref lunchboxCount, ref mileageCardCount);
|
||||
ClampEquippedSlot(saveData, 2, ref lunchboxCount, ref mileageCardCount);
|
||||
}
|
||||
|
||||
private static DutyFreeItemType NormalizeItemType(DutyFreeItemType itemType) {
|
||||
switch(itemType)
|
||||
{
|
||||
case DutyFreeItemType.Lunchbox:
|
||||
case DutyFreeItemType.MileageCard:
|
||||
return itemType;
|
||||
default:
|
||||
return DutyFreeItemType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClampEquippedSlot(
|
||||
GameSaveData saveData,
|
||||
int slotIndex,
|
||||
ref int lunchboxCount,
|
||||
ref int mileageCardCount) {
|
||||
DutyFreeItemType itemType = GetEquippedSlot(saveData, slotIndex);
|
||||
if(itemType == DutyFreeItemType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool overStock = false;
|
||||
switch(itemType)
|
||||
{
|
||||
case DutyFreeItemType.Lunchbox:
|
||||
overStock = lunchboxCount >= saveData.lunchboxStock;
|
||||
lunchboxCount++;
|
||||
break;
|
||||
case DutyFreeItemType.MileageCard:
|
||||
overStock = mileageCardCount >= saveData.mileageCardStock;
|
||||
mileageCardCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
if(overStock)
|
||||
{
|
||||
SetEquippedSlot(saveData, slotIndex, DutyFreeItemType.None);
|
||||
}
|
||||
}
|
||||
|
||||
private static DutyFreeItemType GetEquippedSlot(GameSaveData saveData, int slotIndex) {
|
||||
switch(slotIndex)
|
||||
{
|
||||
case 0:
|
||||
return saveData.equippedSlot1;
|
||||
case 1:
|
||||
return saveData.equippedSlot2;
|
||||
case 2:
|
||||
return saveData.equippedSlot3;
|
||||
default:
|
||||
return DutyFreeItemType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetEquippedSlot(GameSaveData saveData, int slotIndex, DutyFreeItemType itemType) {
|
||||
switch(slotIndex)
|
||||
{
|
||||
case 0:
|
||||
saveData.equippedSlot1 = itemType;
|
||||
break;
|
||||
case 1:
|
||||
saveData.equippedSlot2 = itemType;
|
||||
break;
|
||||
case 2:
|
||||
saveData.equippedSlot3 = itemType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55433eabfe544a199c2b2899e3256b3b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,178 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public partial class GameFlowManager {
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
private void ShowDebugMenu() {
|
||||
EnsureDebugPreviewData();
|
||||
|
||||
gameplayActive = false;
|
||||
pendingStartGameplay = false;
|
||||
currentScreen = FlowScreen.DebugMenu;
|
||||
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
SetDefaultOverlayBackground();
|
||||
|
||||
AddTitle("디버그 메뉴");
|
||||
AddBody("기본 화면과 실전맵을 바로 확인합니다.\n단축키: 1~0, F1/F2");
|
||||
|
||||
AddDebugButton("1. 인트로", ShowDebugIntroPreview, new Vector2(-235f, -32f));
|
||||
AddDebugButton("2. 현재 맵 시작", ShowDebugGameStartPreview, new Vector2(-235f, -74f));
|
||||
AddDebugButton("3. 현재 출국 실패", delegate { ShowDebugResultPreview(false); }, new Vector2(-235f, -116f));
|
||||
AddDebugButton("4. 현재 출국 성공", delegate { ShowDebugResultPreview(true); }, new Vector2(-235f, -158f));
|
||||
AddDebugButton("5. 면세점", ShowDebugDutyFreeShopPreview, new Vector2(-235f, -200f));
|
||||
AddDebugButton("6. 지도", ShowDebugMapPreview, new Vector2(-235f, -242f));
|
||||
|
||||
AddDebugButton("7. 일본 게임시작", delegate { ShowDebugMapGameStartPreview(MapId.Japan); }, new Vector2(235f, -32f));
|
||||
AddDebugButton("8. 일본 출국성공", delegate { ShowDebugMapResultPreview(MapId.Japan, true); }, new Vector2(235f, -74f));
|
||||
AddDebugButton("9. 중국 게임시작", delegate { ShowDebugMapGameStartPreview(MapId.China); }, new Vector2(235f, -116f));
|
||||
AddDebugButton("0. 중국 출국성공", delegate { ShowDebugMapResultPreview(MapId.China, true); }, new Vector2(235f, -158f));
|
||||
AddDebugButton("F1. 미국 게임시작", delegate { ShowDebugMapGameStartPreview(MapId.America); }, new Vector2(235f, -200f));
|
||||
AddDebugButton("F2. 미국 출국성공", delegate { ShowDebugMapResultPreview(MapId.America, true); }, new Vector2(235f, -242f));
|
||||
}
|
||||
|
||||
private void AddDebugButton(string label, UnityEngine.Events.UnityAction action, Vector2 position) {
|
||||
GameObject buttonObject = CreateUiObject("Debug Button " + label, overlayRoot, typeof(Image), typeof(Button));
|
||||
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
|
||||
buttonRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.anchoredPosition = position;
|
||||
buttonRect.sizeDelta = new Vector2(310f, 34f);
|
||||
|
||||
Image buttonImage = buttonObject.GetComponent<Image>();
|
||||
buttonImage.color = new Color(0.92f, 0.96f, 1f, 0.94f);
|
||||
buttonImage.raycastTarget = true;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = buttonImage;
|
||||
button.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
button.onClick.AddListener(action);
|
||||
|
||||
TextMeshProUGUI buttonText = AddTextTo(buttonObject.transform, "Label", label, 14f, FontStyles.Bold);
|
||||
RectTransform textRect = buttonText.rectTransform;
|
||||
textRect.anchorMin = Vector2.zero;
|
||||
textRect.anchorMax = Vector2.one;
|
||||
textRect.offsetMin = Vector2.zero;
|
||||
textRect.offsetMax = Vector2.zero;
|
||||
buttonText.color = new Color(0.04f, 0.07f, 0.09f, 1f);
|
||||
}
|
||||
|
||||
private void ShowDebugIntroPreview() {
|
||||
ShowIntro();
|
||||
}
|
||||
|
||||
private void ShowDebugGameStartPreview() {
|
||||
EnsureDebugPreviewData();
|
||||
LoadGameSceneForCurrentMap();
|
||||
}
|
||||
|
||||
private void ShowDebugMapGameStartPreview(MapId mapId) {
|
||||
SetDebugCurrentMap(mapId);
|
||||
LoadGameSceneForCurrentMap();
|
||||
}
|
||||
|
||||
private void ShowDebugResultPreview(bool cleared) {
|
||||
EnsureDebugPreviewData();
|
||||
|
||||
gameplayActive = false;
|
||||
pendingStartGameplay = false;
|
||||
currentScreen = FlowScreen.Result;
|
||||
completedMap = currentMap;
|
||||
lastResultCleared = cleared;
|
||||
lastResultScore = cleared ? 12800 : 7200;
|
||||
lastResultMileage = cleared ? 263 : 84;
|
||||
lastResultTime = cleared ? 23.9f : 38.4f;
|
||||
lastResultDistance = completedMap != null
|
||||
? (cleared ? completedMap.targetDistance : completedMap.targetDistance * 0.62f)
|
||||
: 380f;
|
||||
|
||||
if(saveData != null)
|
||||
{
|
||||
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
|
||||
}
|
||||
|
||||
ShowResult();
|
||||
}
|
||||
|
||||
private void ShowDebugMapResultPreview(MapId mapId, bool cleared) {
|
||||
SetDebugCurrentMap(mapId);
|
||||
|
||||
gameplayActive = false;
|
||||
pendingStartGameplay = false;
|
||||
currentScreen = FlowScreen.Result;
|
||||
completedMap = currentMap;
|
||||
lastResultCleared = cleared;
|
||||
|
||||
int mapIndex = MapDatabase.GetIndex(mapId);
|
||||
lastResultScore = cleared ? 12800 + (mapIndex * 2200) : 7200 + (mapIndex * 900);
|
||||
lastResultMileage = cleared ? 190 + (mapIndex * 70) : 84;
|
||||
lastResultTime = currentMap != null
|
||||
? Mathf.Max(10f, currentMap.timeLimit * (cleared ? 0.78f : 1.08f))
|
||||
: cleared ? 23.9f : 38.4f;
|
||||
lastResultDistance = currentMap != null
|
||||
? (cleared ? currentMap.targetDistance : currentMap.targetDistance * 0.62f)
|
||||
: 380f;
|
||||
|
||||
if(saveData != null)
|
||||
{
|
||||
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
|
||||
}
|
||||
|
||||
ShowResult();
|
||||
}
|
||||
|
||||
private void ShowDebugDutyFreeShopPreview() {
|
||||
EnsureDebugPreviewData();
|
||||
|
||||
if(saveData != null)
|
||||
{
|
||||
saveData.totalMileage = Mathf.Max(saveData.totalMileage, 4120);
|
||||
}
|
||||
|
||||
ShowDutyFreeShop();
|
||||
}
|
||||
|
||||
private void ShowDebugMapPreview() {
|
||||
EnsureDebugPreviewData();
|
||||
ShowMapPreview();
|
||||
}
|
||||
|
||||
private void SetDebugCurrentMap(MapId mapId) {
|
||||
EnsureDebugPreviewData();
|
||||
currentMap = MapDatabase.GetMap(mapId);
|
||||
|
||||
if(saveData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int mapIndex = MapDatabase.GetIndex(mapId);
|
||||
saveData.currentMapIndex = mapIndex;
|
||||
saveData.unlockedMapIndex = Mathf.Max(saveData.unlockedMapIndex, mapIndex);
|
||||
GameSaveManager.Save(saveData);
|
||||
}
|
||||
|
||||
private void EnsureDebugPreviewData() {
|
||||
if(saveData == null)
|
||||
{
|
||||
saveData = GameSaveManager.Load();
|
||||
}
|
||||
|
||||
if(currentMap == null)
|
||||
{
|
||||
currentMap = MapDatabase.GetMapByIndex(saveData != null ? saveData.currentMapIndex : 0);
|
||||
}
|
||||
|
||||
if(currentMap == null)
|
||||
{
|
||||
currentMap = MapDatabase.GetMapByIndex(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc38b07e718f43dda9e26895c85dbf3f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,390 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public partial class GameFlowManager {
|
||||
private void ShowDutyFreeShop() {
|
||||
currentScreen = FlowScreen.DutyFreeShop;
|
||||
gameplayActive = false;
|
||||
pendingStartGameplay = false;
|
||||
|
||||
if(TryLoadFlowScene(DutyFreeShopSceneName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
|
||||
BuildDutyFreeShopScreen();
|
||||
}
|
||||
|
||||
private void BuildDutyFreeShopScreen() {
|
||||
EnsureDutyFreeStagePurchaseLimits();
|
||||
ApplyDutyFreeShopBackground();
|
||||
|
||||
RectTransform boardRect = AddDutyFreeBoard();
|
||||
AddDutyFreeHeader(boardRect);
|
||||
|
||||
AddDutyFreeItemCard(
|
||||
boardRect,
|
||||
"도시락",
|
||||
DutyFreeItemType.Lunchbox,
|
||||
LunchboxCost,
|
||||
saveData.lunchboxStock,
|
||||
LoadUiSprite("", DutyFreeLunchboxAssetPath),
|
||||
BuyLunchbox,
|
||||
new Vector2(75f, -135f));
|
||||
|
||||
AddDutyFreeSupplementCard(
|
||||
boardRect,
|
||||
"영양제",
|
||||
SupplementCost,
|
||||
saveData.supplementPurchases,
|
||||
LoadUiSprite("", DutyFreeSupplementAssetPath),
|
||||
BuySupplement,
|
||||
new Vector2(303f, -135f));
|
||||
|
||||
AddDutyFreeItemCard(
|
||||
boardRect,
|
||||
"마일리지 카드",
|
||||
DutyFreeItemType.MileageCard,
|
||||
MileageCardCost,
|
||||
saveData.mileageCardStock,
|
||||
LoadUiSprite("", DutyFreeMileageCardAssetPath),
|
||||
BuyMileageCard,
|
||||
new Vector2(760f, -135f));
|
||||
|
||||
AddDutyFreeNextTravelButton();
|
||||
}
|
||||
|
||||
private void ApplyDutyFreeShopBackground() {
|
||||
if(overlayBackground != null)
|
||||
{
|
||||
overlayBackground.sprite = null;
|
||||
overlayBackground.preserveAspect = false;
|
||||
overlayBackground.color = new Color(0.018f, 0.041f, 0.052f, 0.98f);
|
||||
}
|
||||
|
||||
if(flowCamera != null)
|
||||
{
|
||||
flowCamera.backgroundColor = new Color(0.018f, 0.041f, 0.052f, 1f);
|
||||
}
|
||||
|
||||
AddDutyFreeBackdropImage(
|
||||
"Duty Free Shop Interior",
|
||||
LoadUiSprite(DutyFreeBackgroundResourcePath, DutyFreeBackgroundAssetPath),
|
||||
Vector2.zero,
|
||||
new Vector2(1378f, 724f),
|
||||
true,
|
||||
Color.white);
|
||||
|
||||
GameObject scrimObject = CreateUiObject("Duty Free Background Scrim", overlayRoot, typeof(Image));
|
||||
RectTransform scrimRect = scrimObject.GetComponent<RectTransform>();
|
||||
scrimRect.anchorMin = Vector2.zero;
|
||||
scrimRect.anchorMax = Vector2.one;
|
||||
scrimRect.offsetMin = Vector2.zero;
|
||||
scrimRect.offsetMax = Vector2.zero;
|
||||
|
||||
Image scrimImage = scrimObject.GetComponent<Image>();
|
||||
scrimImage.color = new Color(0.01f, 0.012f, 0.014f, 0.22f);
|
||||
scrimImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private Image AddDutyFreeBackdropImage(string objectName, Sprite sprite, Vector2 position, Vector2 size, bool preserveAspect, Color color) {
|
||||
GameObject imageObject = CreateUiObject(objectName, overlayRoot, typeof(Image));
|
||||
RectTransform imageRect = imageObject.GetComponent<RectTransform>();
|
||||
imageRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
imageRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
imageRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
imageRect.anchoredPosition = position;
|
||||
imageRect.sizeDelta = size;
|
||||
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.sprite = sprite;
|
||||
image.color = sprite != null ? color : new Color(0.30f, 0.46f, 0.54f, 0.28f);
|
||||
image.preserveAspect = preserveAspect;
|
||||
image.raycastTarget = false;
|
||||
return image;
|
||||
}
|
||||
|
||||
private RectTransform AddDutyFreeBoard() {
|
||||
GameObject boardObject = CreateUiObject("Duty Free Product Board", overlayRoot, typeof(Image));
|
||||
RectTransform boardRect = boardObject.GetComponent<RectTransform>();
|
||||
boardRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
boardRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
boardRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
boardRect.anchoredPosition = new Vector2(0f, 20f);
|
||||
boardRect.sizeDelta = new Vector2(1038f, 552f);
|
||||
|
||||
Image boardImage = boardObject.GetComponent<Image>();
|
||||
boardImage.sprite = LoadUiSprite(DutyFreeWindowPanelResourcePath, DutyFreeWindowPanelAssetPath);
|
||||
boardImage.color = boardImage.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.92f, 0.98f, 1f, 0.78f);
|
||||
boardImage.preserveAspect = false;
|
||||
boardImage.raycastTarget = false;
|
||||
|
||||
return boardRect;
|
||||
}
|
||||
|
||||
private void AddDutyFreeHeader(RectTransform parent) {
|
||||
AddResultImage(parent, "Duty Free Equip Slots Panel", LoadUiSprite(DutyFreeEquipSlotPanelResourcePath, "Assets/Resources/UI_DutyFreeBagSlotPanel.png"), new Vector2(50f, -570f), new Vector2(276f, 124f), true, Color.white);
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
AddDutyFreeEquipSlot(parent, i, new Vector2(-65f + (i * 82f), -540f));
|
||||
}
|
||||
|
||||
AddDutyFreeBagUpgradeButton(parent, new Vector2(190f, -560f));
|
||||
|
||||
AddResultImage(parent, "Duty Free Mileage Price Tag", LoadDutyFreeResourceSprite(DutyFreePriceTagResourcePath), new Vector2(1000f, -48f), new Vector2(230f, 100f), false, Color.white);
|
||||
AddResultText(parent, "Duty Free Mileage Label", "보유 마일리지", new Vector2(990f, -26f), new Vector2(118f, 20f), 14f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 0.96f), TextAlignmentOptions.MidlineLeft);
|
||||
AddResultText(parent, "Duty Free Mileage Value", saveData.totalMileage + " M", new Vector2(1000f, -45f), new Vector2(122f, 28f), 21f, FontStyles.Bold, new Color(1f, 0.90f, 0.38f, 1f), TextAlignmentOptions.MidlineLeft);
|
||||
}
|
||||
|
||||
private void AddDutyFreeEquipSlot(RectTransform parent, int slotIndex, Vector2 topLeftPosition) {
|
||||
bool unlocked = slotIndex < saveData.bagSlots;
|
||||
DutyFreeItemType itemType = unlocked ? GetEquippedDutyFreeItem(slotIndex) : DutyFreeItemType.None;
|
||||
bool hasItem = itemType != DutyFreeItemType.None;
|
||||
|
||||
GameObject slotObject = CreateUiObject("Duty Free Equip Slot " + (slotIndex + 1), parent, typeof(Image), typeof(Button));
|
||||
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 = topLeftPosition;
|
||||
slotRect.sizeDelta = new Vector2(64f, 64f);
|
||||
|
||||
Image slotImage = slotObject.GetComponent<Image>();
|
||||
if(hasItem)
|
||||
{
|
||||
slotImage.sprite = LoadDutyFreeResourceSprite(DutyFreeOccupiedSlotResourcePath);
|
||||
slotImage.color = slotImage.sprite != null ? Color.white : new Color(0.05f, 0.16f, 0.20f, 0.88f);
|
||||
}
|
||||
else if(unlocked)
|
||||
{
|
||||
slotImage.sprite = LoadDutyFreeResourceSprite(DutyFreeActiveSlotResourcePath);
|
||||
slotImage.color = slotImage.sprite != null ? Color.white : new Color(0.58f, 0.36f, 0.18f, 0.88f);
|
||||
}
|
||||
else
|
||||
{
|
||||
slotImage.sprite = LoadDutyFreeResourceSprite(DutyFreeInactiveSlotResourcePath);
|
||||
slotImage.color = slotImage.sprite != null ? Color.white : new Color(0.35f, 0.35f, 0.35f, 0.78f);
|
||||
}
|
||||
|
||||
slotImage.preserveAspect = true;
|
||||
slotImage.raycastTarget = hasItem;
|
||||
|
||||
Button slotButton = slotObject.GetComponent<Button>();
|
||||
slotButton.targetGraphic = slotImage;
|
||||
slotButton.interactable = hasItem;
|
||||
slotButton.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
|
||||
if(hasItem)
|
||||
{
|
||||
int capturedSlotIndex = slotIndex;
|
||||
slotButton.onClick.AddListener(delegate { UnequipDutyFreeSlot(capturedSlotIndex); });
|
||||
}
|
||||
|
||||
AddResultText(slotRect, "Duty Free Slot Number", (slotIndex + 1).ToString(), new Vector2(3f, -3f), new Vector2(18f, 18f), 10f, FontStyles.Bold, new Color(0.78f, 0.92f, 0.97f, 0.94f), TextAlignmentOptions.Center);
|
||||
|
||||
if(hasItem)
|
||||
{
|
||||
AddResultImage(slotRect, "Duty Free Slot Icon", GetDutyFreeItemSprite(itemType), new Vector2(32f, -31f), new Vector2(36f, 36f), true, Color.white);
|
||||
AddResultText(slotRect, "Duty Free Slot Name", GetDutyFreeItemShortName(itemType), new Vector2(5f, -50f), new Vector2(54f, 12f), 8f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 0.96f), TextAlignmentOptions.Center);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDutyFreeBagUpgradeButton(RectTransform parent, Vector2 topLeftPosition) {
|
||||
bool isMax = saveData.bagSlots >= 3;
|
||||
bool canBuy = !isMax && saveData.totalMileage >= BagSlotUpgradeCost;
|
||||
|
||||
GameObject buttonObject = CreateUiObject("Duty Free Bag Upgrade", parent, typeof(Image), typeof(Button));
|
||||
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
|
||||
buttonRect.anchorMin = new Vector2(0f, 1f);
|
||||
buttonRect.anchorMax = new Vector2(0f, 1f);
|
||||
buttonRect.pivot = new Vector2(0f, 1f);
|
||||
buttonRect.anchoredPosition = topLeftPosition;
|
||||
buttonRect.sizeDelta = new Vector2(160f, 64f);
|
||||
|
||||
Image buttonImage = buttonObject.GetComponent<Image>();
|
||||
buttonImage.sprite = LoadUiSprite(
|
||||
canBuy ? DutyFreeButtonPrimaryResourcePath : DutyFreeButtonDisabledResourcePath,
|
||||
canBuy ? "Assets/Resources/UI_DutyFreeButton_PrimaryYellow.png" : "Assets/Resources/UI_DutyFreeButton_DisabledGray.png");
|
||||
buttonImage.color = canBuy
|
||||
? Color.white
|
||||
: (buttonImage.sprite != null ? Color.white : new Color(0.36f, 0.43f, 0.47f, 0.76f));
|
||||
buttonImage.preserveAspect = false;
|
||||
buttonImage.raycastTarget = canBuy;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = buttonImage;
|
||||
button.interactable = canBuy;
|
||||
button.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
button.onClick.AddListener(BuyBagSlotUpgrade);
|
||||
|
||||
AddResultImage(buttonRect, "Duty Free Bag Upgrade Icon", LoadUiSprite("", DutyFreeBackpackAssetPath), new Vector2(45f, -32f), new Vector2(42f, 42f), true, canBuy ? Color.white : new Color(1f, 1f, 1f, 0.48f));
|
||||
AddResultText(buttonRect, "Duty Free Bag Upgrade Label", isMax ? "가방 MAX" : "가방 +1", new Vector2(75f, -11f), new Vector2(66f, 24f), 16f, FontStyles.Bold, canBuy ? new Color(0.06f, 0.16f, 0.22f, 1f) : new Color(0.72f, 0.80f, 0.84f, 0.92f), TextAlignmentOptions.MidlineLeft);
|
||||
AddResultText(buttonRect, "Duty Free Bag Upgrade Cost", isMax ? "" : BagSlotUpgradeCost + " M", new Vector2(85f, -36f), new Vector2(66f, 18f), 14f, FontStyles.Bold, canBuy ? new Color(0.87f, 0.55f, 0.08f, 1f) : new Color(0.62f, 0.68f, 0.70f, 0.92f), TextAlignmentOptions.MidlineLeft);
|
||||
}
|
||||
|
||||
private void AddDutyFreeItemCard(RectTransform parent, string itemName, DutyFreeItemType itemType, int cost, int stock, Sprite itemSprite, UnityEngine.Events.UnityAction buyAction, Vector2 topLeftPosition) {
|
||||
bool canBuy = CanBuyDutyFreeItem(itemType, cost);
|
||||
bool hasFreeSlot = HasFreeDutyFreeSlot();
|
||||
int equippedCount = GetEquippedDutyFreeItemCount(itemType);
|
||||
bool canEquip = stock > equippedCount && hasFreeSlot;
|
||||
bool canInteract = canEquip || canBuy;
|
||||
GameObject cardObject = CreateUiObject("Duty Free Item " + itemName, parent, typeof(Image));
|
||||
RectTransform cardRect = cardObject.GetComponent<RectTransform>();
|
||||
cardRect.anchorMin = new Vector2(0f, 1f);
|
||||
cardRect.anchorMax = new Vector2(0f, 1f);
|
||||
cardRect.pivot = new Vector2(0f, 1f);
|
||||
cardRect.anchoredPosition = topLeftPosition;
|
||||
cardRect.sizeDelta = new Vector2(206f, 288f);
|
||||
|
||||
Image cardImage = cardObject.GetComponent<Image>();
|
||||
cardImage.sprite = GetDutyFreeProductCardSprite(itemType);
|
||||
cardImage.color = cardImage.sprite != null
|
||||
? (canInteract ? Color.white : new Color(0.68f, 0.70f, 0.72f, 0.92f))
|
||||
: canInteract
|
||||
? new Color(0.97f, 0.91f, 0.78f, 0.96f)
|
||||
: new Color(0.40f, 0.45f, 0.48f, 0.88f);
|
||||
cardImage.preserveAspect = false;
|
||||
cardImage.raycastTarget = false;
|
||||
|
||||
Color textColor = canInteract
|
||||
? new Color(0.06f, 0.16f, 0.22f, 1f)
|
||||
: new Color(0.78f, 0.84f, 0.86f, 1f);
|
||||
|
||||
AddResultImage(cardRect, "Duty Free " + itemName + " Image", itemSprite, new Vector2(103f, -107f), new Vector2(92f, 96f), true, canInteract ? Color.white : new Color(1f, 1f, 1f, 0.54f));
|
||||
Image priceTagImage = AddResultImage(cardRect, "Duty Free " + itemName + " Price Tag", LoadDutyFreeResourceSprite(DutyFreeProductPriceTagResourcePath), new Vector2(151f, -150f), new Vector2(52f, 78f), true, canInteract ? Color.white : new Color(1f, 1f, 1f, 0.62f));
|
||||
priceTagImage.rectTransform.localEulerAngles = new Vector3(0f, 0f, 110f);
|
||||
TextMeshProUGUI priceText = AddResultCenteredText(cardRect, "Duty Free " + itemName + " Price", cost + "M", new Vector2(153f, -150f), new Vector2(40f, 18f), 11f, FontStyles.Bold, canInteract ? new Color(0.03f, 0.03f, 0.028f, 1f) : new Color(0.36f, 0.36f, 0.34f, 0.92f), TextAlignmentOptions.Center);
|
||||
priceText.rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
||||
priceText.rectTransform.anchoredPosition = new Vector2(155f, -150f);
|
||||
priceText.rectTransform.localEulerAngles = new Vector3(0f, 0f, 20f);
|
||||
TextMeshProUGUI nameText = AddResultCenteredText(cardRect, "Duty Free " + itemName + " Name", itemName + "\n" + GetDutyFreeItemEffectText(itemType), new Vector2(103f, -199f), new Vector2(126f, 34f), 13f, FontStyles.Bold, textColor, TextAlignmentOptions.Center);
|
||||
nameText.lineSpacing = -8f;
|
||||
|
||||
GameObject buyObject = CreateUiObject("Duty Free " + itemName + " Buy Area", cardRect, typeof(Image), typeof(Button));
|
||||
RectTransform buyRect = buyObject.GetComponent<RectTransform>();
|
||||
buyRect.anchorMin = new Vector2(0f, 1f);
|
||||
buyRect.anchorMax = new Vector2(0f, 1f);
|
||||
buyRect.pivot = new Vector2(0f, 1f);
|
||||
buyRect.anchoredPosition = new Vector2(39f, -232f);
|
||||
buyRect.sizeDelta = new Vector2(128f, 22f);
|
||||
|
||||
Image buyImage = buyObject.GetComponent<Image>();
|
||||
buyImage.color = Color.clear;
|
||||
buyImage.raycastTarget = canInteract;
|
||||
|
||||
Button buyButton = buyObject.GetComponent<Button>();
|
||||
buyButton.targetGraphic = buyImage;
|
||||
buyButton.interactable = canInteract;
|
||||
buyButton.transition = Selectable.Transition.None;
|
||||
buyButton.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
buyButton.onClick.AddListener(buyAction);
|
||||
|
||||
TextMeshProUGUI buyText = AddResultText(buyRect, "Duty Free " + itemName + " Buy Label", GetDutyFreePurchaseLabel(itemType, canEquip, canBuy, hasFreeSlot, stock, equippedCount), Vector2.zero, new Vector2(128f, 22f), 11f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
|
||||
buyText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
|
||||
buyText.outlineWidth = 0.12f;
|
||||
}
|
||||
|
||||
private void AddDutyFreeSupplementCard(RectTransform parent, string itemName, int cost, int purchaseCount, Sprite itemSprite, UnityEngine.Events.UnityAction buyAction, Vector2 topLeftPosition) {
|
||||
bool isMax = purchaseCount >= MaxSupplementPurchases;
|
||||
bool alreadyPurchasedThisStage = HasPurchasedSupplementInDutyFreeStage();
|
||||
bool canBuy = CanBuySupplement();
|
||||
|
||||
GameObject cardObject = CreateUiObject("Duty Free Item " + itemName, parent, typeof(Image));
|
||||
RectTransform cardRect = cardObject.GetComponent<RectTransform>();
|
||||
cardRect.anchorMin = new Vector2(0f, 1f);
|
||||
cardRect.anchorMax = new Vector2(0f, 1f);
|
||||
cardRect.pivot = new Vector2(0f, 1f);
|
||||
cardRect.anchoredPosition = topLeftPosition;
|
||||
cardRect.sizeDelta = new Vector2(206f, 288f);
|
||||
|
||||
Image cardImage = cardObject.GetComponent<Image>();
|
||||
cardImage.sprite = GetDutyFreeProductCardSprite(DutyFreeItemType.Supplement);
|
||||
cardImage.color = cardImage.sprite != null
|
||||
? (canBuy || isMax ? Color.white : new Color(0.68f, 0.70f, 0.72f, 0.92f))
|
||||
: canBuy
|
||||
? new Color(0.97f, 0.91f, 0.78f, 0.96f)
|
||||
: new Color(0.40f, 0.45f, 0.48f, 0.88f);
|
||||
cardImage.preserveAspect = false;
|
||||
cardImage.raycastTarget = false;
|
||||
|
||||
Color textColor = canBuy || isMax
|
||||
? new Color(0.06f, 0.16f, 0.22f, 1f)
|
||||
: new Color(0.78f, 0.84f, 0.86f, 1f);
|
||||
|
||||
AddResultImage(cardRect, "Duty Free " + itemName + " Image", itemSprite, new Vector2(103f, -107f), new Vector2(92f, 96f), true, canBuy || isMax ? Color.white : new Color(1f, 1f, 1f, 0.54f));
|
||||
Image priceTagImage = AddResultImage(cardRect, "Duty Free " + itemName + " Price Tag", LoadDutyFreeResourceSprite(DutyFreeProductPriceTagResourcePath), new Vector2(151f, -150f), new Vector2(52f, 78f), true, canBuy || isMax ? Color.white : new Color(1f, 1f, 1f, 0.62f));
|
||||
priceTagImage.rectTransform.localEulerAngles = new Vector3(0f, 0f, 110f);
|
||||
|
||||
TextMeshProUGUI priceText = AddResultCenteredText(cardRect, "Duty Free " + itemName + " Price", isMax ? "MAX" : cost + "M", new Vector2(153f, -150f), new Vector2(40f, 18f), 11f, FontStyles.Bold, canBuy || isMax ? new Color(0.03f, 0.03f, 0.028f, 1f) : new Color(0.36f, 0.36f, 0.34f, 0.92f), TextAlignmentOptions.Center);
|
||||
priceText.rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
||||
priceText.rectTransform.anchoredPosition = new Vector2(155f, -150f);
|
||||
priceText.rectTransform.localEulerAngles = new Vector3(0f, 0f, 20f);
|
||||
|
||||
TextMeshProUGUI nameText = AddResultCenteredText(cardRect, "Duty Free " + itemName + " Name", itemName + "\n" + GetDutyFreeSupplementEffectText(purchaseCount), new Vector2(103f, -199f), new Vector2(126f, 34f), 13f, FontStyles.Bold, textColor, TextAlignmentOptions.Center);
|
||||
nameText.lineSpacing = -8f;
|
||||
|
||||
GameObject buyObject = CreateUiObject("Duty Free " + itemName + " Buy Area", cardRect, typeof(Image), typeof(Button));
|
||||
RectTransform buyRect = buyObject.GetComponent<RectTransform>();
|
||||
buyRect.anchorMin = new Vector2(0f, 1f);
|
||||
buyRect.anchorMax = new Vector2(0f, 1f);
|
||||
buyRect.pivot = new Vector2(0f, 1f);
|
||||
buyRect.anchoredPosition = new Vector2(39f, -232f);
|
||||
buyRect.sizeDelta = new Vector2(128f, 22f);
|
||||
|
||||
Image buyImage = buyObject.GetComponent<Image>();
|
||||
buyImage.color = Color.clear;
|
||||
buyImage.raycastTarget = canBuy;
|
||||
|
||||
Button buyButton = buyObject.GetComponent<Button>();
|
||||
buyButton.targetGraphic = buyImage;
|
||||
buyButton.interactable = canBuy;
|
||||
buyButton.transition = Selectable.Transition.None;
|
||||
buyButton.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
buyButton.onClick.AddListener(buyAction);
|
||||
|
||||
TextMeshProUGUI buyText = AddResultText(buyRect, "Duty Free " + itemName + " Buy Label", GetDutyFreeSupplementPurchaseLabel(canBuy, isMax, alreadyPurchasedThisStage), Vector2.zero, new Vector2(128f, 22f), 11f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
|
||||
buyText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
|
||||
buyText.outlineWidth = 0.12f;
|
||||
}
|
||||
|
||||
private void AddDutyFreeNextTravelButton() {
|
||||
GameObject buttonObject = CreateUiObject("Duty Free Next Travel Sign", overlayRoot, typeof(Image), typeof(Button));
|
||||
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
|
||||
buttonRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.anchoredPosition = new Vector2(548f, -247f);
|
||||
buttonRect.sizeDelta = new Vector2(200f, 160f);
|
||||
|
||||
Image buttonImage = buttonObject.GetComponent<Image>();
|
||||
buttonImage.sprite = LoadDutyFreeResourceSprite(DutyFreeDirectionSignResourcePath);
|
||||
buttonImage.color = buttonImage.sprite != null ? Color.white : new Color(0.82f, 0.91f, 0.96f, 0.90f);
|
||||
buttonImage.preserveAspect = true;
|
||||
buttonImage.raycastTarget = true;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = buttonImage;
|
||||
button.onClick.AddListener(ShowMapPreview);
|
||||
button.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8edc62b4b60248ce8001165fb0588bf8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,377 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public partial class GameFlowManager {
|
||||
private void ShowResult() {
|
||||
if(TryLoadFlowScene(ResultSceneName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
BuildResultScreen();
|
||||
}
|
||||
|
||||
private void BuildResultScreen() {
|
||||
ApplyResultAirportBackground();
|
||||
|
||||
MapDefinition resultMap = completedMap != null ? completedMap : currentMap;
|
||||
int rewardMileage = lastResultCleared && resultMap != null ? resultMap.clearMileageReward : 0;
|
||||
int gainedMileage = Mathf.Max(0, lastResultMileage) + rewardMileage;
|
||||
int targetDistance = resultMap != null ? Mathf.FloorToInt(resultMap.targetDistance) : 0;
|
||||
int shownDistance = targetDistance > 0
|
||||
? Mathf.Clamp(Mathf.FloorToInt(lastResultDistance), 0, targetDistance)
|
||||
: Mathf.Max(0, Mathf.FloorToInt(lastResultDistance));
|
||||
|
||||
RectTransform passportRect = AddResultPassportPanel();
|
||||
AddResultHeader(passportRect, resultMap);
|
||||
AddResultStats(passportRect, gainedMileage, shownDistance, targetDistance);
|
||||
AddResultTickets(passportRect, resultMap, rewardMileage);
|
||||
|
||||
if(lastResultCleared)
|
||||
{
|
||||
AddResultActionButton("면세점으로", ShowDutyFreeShop, new Vector2(-166f, -282f), new Vector2(250f, 48f), true, LoadUiSprite("", DutyFreeIconAssetPath));
|
||||
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(160f, -282f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddResultActionButton("다시 도전", RetryCurrentMap, new Vector2(-166f, -282f), new Vector2(250f, 48f), true, LoadUiSprite("", CheckpointIconAssetPath));
|
||||
AddResultActionButton("다음 여행지 가기", ShowMapPreview, new Vector2(160f, -282f), new Vector2(292f, 48f), false, LoadUiSprite("", AirplaneTravelIconAssetPath));
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyResultAirportBackground() {
|
||||
if(overlayBackground != null)
|
||||
{
|
||||
overlayBackground.sprite = LoadUiSprite(ResultAirportBackgroundResourcePath, "Assets/Resources/Map_Tutorial_Incheon_Airport.png");
|
||||
overlayBackground.preserveAspect = false;
|
||||
overlayBackground.color = overlayBackground.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.008f, 0.024f, 0.034f, 0.96f);
|
||||
}
|
||||
|
||||
if(flowCamera != null)
|
||||
{
|
||||
flowCamera.backgroundColor = new Color(0.006f, 0.022f, 0.035f, 1f);
|
||||
}
|
||||
|
||||
GameObject scrimObject = CreateUiObject("Result Airport Background Scrim", overlayRoot, typeof(Image));
|
||||
RectTransform scrimRect = scrimObject.GetComponent<RectTransform>();
|
||||
scrimRect.anchorMin = Vector2.zero;
|
||||
scrimRect.anchorMax = Vector2.one;
|
||||
scrimRect.offsetMin = Vector2.zero;
|
||||
scrimRect.offsetMax = Vector2.zero;
|
||||
|
||||
Image scrimImage = scrimObject.GetComponent<Image>();
|
||||
scrimImage.color = new Color(0.005f, 0.015f, 0.020f, 0.30f);
|
||||
scrimImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private RectTransform AddResultPassportPanel() {
|
||||
GameObject panelObject = CreateUiObject("Result Passport Page", overlayRoot, typeof(Image));
|
||||
RectTransform panelRect = panelObject.GetComponent<RectTransform>();
|
||||
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
panelRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
panelRect.anchoredPosition = new Vector2(0f, 38f);
|
||||
panelRect.sizeDelta = new Vector2(1092f, 330f);
|
||||
|
||||
Image panelImage = panelObject.GetComponent<Image>();
|
||||
panelImage.sprite = LoadUiSprite(ResultPassportPanelResourcePath, ResultPassportPanelAssetPath);
|
||||
panelImage.color = panelImage.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.95f, 0.91f, 0.80f, 0.98f);
|
||||
panelImage.preserveAspect = true;
|
||||
panelImage.raycastTarget = false;
|
||||
|
||||
return panelRect;
|
||||
}
|
||||
|
||||
private void AddResultHeader(RectTransform parent, MapDefinition resultMap) {
|
||||
Sprite rankSprite = LoadUiSprite("", ClearRankIconAssetPath);
|
||||
|
||||
AddResultImage(parent, "Result Rank Badge", rankSprite, new Vector2(150f, -140f), new Vector2(48f, 48f), true, Color.white);
|
||||
|
||||
string title = lastResultCleared ? "출국 성공" : "일정 실패";
|
||||
Color titleColor = lastResultCleared
|
||||
? new Color(0.05f, 0.17f, 0.27f, 1f)
|
||||
: new Color(0.52f, 0.08f, 0.07f, 1f);
|
||||
AddResultCenteredText(parent, "Result Title", title, new Vector2(244f, -140f), new Vector2(220f, 50f), 25f, FontStyles.Bold, titleColor, TextAlignmentOptions.Center);
|
||||
|
||||
AddResultRouteBox(parent, "From", GetResultOriginAirportCode(resultMap), GetResultOriginCityName(resultMap), new Vector2(440f, -122f));
|
||||
AddResultRouteBox(parent, "To", GetResultDestinationAirportCode(resultMap), GetResultDestinationCityName(resultMap), new Vector2(660f, -122f));
|
||||
AddResultBoardingField(parent, "Flight", "항공편", GetResultFlightName(resultMap), new Vector2(400f, -253f));
|
||||
AddResultBoardingField(parent, "Seat", "좌석", GetResultSeatName(resultMap), new Vector2(497f, -253f));
|
||||
AddResultBoardingField(parent, "Boarding", "탑승", System.DateTime.Now.ToString("HH:mm"), new Vector2(595f, -253f));
|
||||
AddResultBoardingField(parent, "Class", "클래스", lastResultCleared ? "일반석" : "대기", new Vector2(695f, -253f));
|
||||
}
|
||||
|
||||
private void AddResultStats(RectTransform parent, int gainedMileage, int shownDistance, int targetDistance) {
|
||||
AddResultStat(parent, "Mileage", LoadUiSprite("", MileageIconAssetPath), "마일리지", "+" + gainedMileage, new Vector2(812f, -140f));
|
||||
AddResultStat(parent, "Time", LoadUiSprite("", TimerIconAssetPath), "도착 시간", lastResultTime.ToString("0.0") + "s", new Vector2(903f, -140f));
|
||||
|
||||
string distance = targetDistance > 0
|
||||
? shownDistance + "/" + targetDistance + "m"
|
||||
: shownDistance + "m";
|
||||
AddResultStat(parent, "Distance", LoadUiSprite("", CheckpointIconAssetPath), "이동 거리", distance, new Vector2(993f, -140f));
|
||||
}
|
||||
|
||||
private void AddResultTickets(RectTransform parent, MapDefinition resultMap, int rewardMileage) {
|
||||
AddResultTicket(
|
||||
parent,
|
||||
"Destination",
|
||||
LoadUiSprite("", CheckpointIconAssetPath),
|
||||
"목적지",
|
||||
GetResultDestinationCityName(resultMap),
|
||||
new Vector2(812f, -251f));
|
||||
|
||||
AddResultTicket(
|
||||
parent,
|
||||
"Souvenir",
|
||||
lastResultCleared ? GetResultSouvenirSprite(resultMap) : LoadUiSprite("", SouvenirCollectionIconAssetPath),
|
||||
"보상",
|
||||
lastResultCleared ? GetResultSouvenirName(resultMap) : "다음 도전",
|
||||
new Vector2(900f, -251f));
|
||||
|
||||
AddResultTicket(
|
||||
parent,
|
||||
"Total Mileage",
|
||||
LoadUiSprite("", MileageIconAssetPath),
|
||||
"보유",
|
||||
saveData.totalMileage + " M",
|
||||
new Vector2(991f, -251f));
|
||||
}
|
||||
|
||||
private void AddResultStat(RectTransform parent, string objectName, Sprite iconSprite, string label, string value, Vector2 centerPosition) {
|
||||
AddResultImage(parent, "Result " + objectName + " Icon", iconSprite, centerPosition + new Vector2(0f, 5f), new Vector2(26f, 26f), true, Color.white);
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Value", value, centerPosition + new Vector2(0f, -17f), new Vector2(90f, 22f), 12f, FontStyles.Bold, new Color(0.05f, 0.17f, 0.25f, 1f), TextAlignmentOptions.Center);
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Label", label, centerPosition + new Vector2(0f, -32f), new Vector2(90f, 16f), 8f, FontStyles.Bold, new Color(0.42f, 0.58f, 0.64f, 0.92f), TextAlignmentOptions.Center);
|
||||
}
|
||||
|
||||
private void AddResultTicket(RectTransform parent, string objectName, Sprite iconSprite, string label, string value, Vector2 centerPosition) {
|
||||
AddResultImage(parent, "Result " + objectName + " Icon", iconSprite, centerPosition + new Vector2(0f, 27f), new Vector2(34f, 34f), true, Color.white);
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Label", label, centerPosition + new Vector2(0f, -9f), new Vector2(110f, 18f), 11f, FontStyles.Bold, new Color(0.43f, 0.58f, 0.64f, 0.96f), TextAlignmentOptions.Center);
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Value", value, centerPosition + new Vector2(0f, -35f), new Vector2(118f, 26f), 11f, FontStyles.Bold, new Color(0.05f, 0.18f, 0.27f, 1f), TextAlignmentOptions.Center);
|
||||
}
|
||||
|
||||
private void AddResultRouteBox(RectTransform parent, string objectName, string code, string cityName, Vector2 centerPosition) {
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Airport Code", code, centerPosition + new Vector2(0f, 7f), new Vector2(150f, 48f), 34f, FontStyles.Bold, new Color(0.05f, 0.17f, 0.27f, 1f), TextAlignmentOptions.Center);
|
||||
AddResultCenteredText(parent, "Result " + objectName + " City", cityName, centerPosition + new Vector2(0f, -30f), new Vector2(160f, 22f), 12f, FontStyles.Bold, new Color(0.43f, 0.58f, 0.64f, 0.96f), TextAlignmentOptions.Center);
|
||||
}
|
||||
|
||||
private void AddResultBoardingField(RectTransform parent, string objectName, string label, string value, Vector2 centerPosition) {
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Label", label, centerPosition + new Vector2(0f, 13f), new Vector2(104f, 17f), 9f, FontStyles.Bold, new Color(0.43f, 0.58f, 0.64f, 0.96f), TextAlignmentOptions.Center);
|
||||
AddResultCenteredText(parent, "Result " + objectName + " Value", value, centerPosition + new Vector2(0f, -12f), new Vector2(108f, 28f), 14f, FontStyles.Bold, new Color(0.05f, 0.18f, 0.27f, 1f), TextAlignmentOptions.Center);
|
||||
}
|
||||
|
||||
private void AddResultActionButton(string label, UnityEngine.Events.UnityAction action, Vector2 position, Vector2 size, bool primary, Sprite iconSprite) {
|
||||
GameObject buttonObject = CreateUiObject("Result Button " + label, overlayRoot, typeof(Image), typeof(Button));
|
||||
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
|
||||
buttonRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
buttonRect.anchoredPosition = position;
|
||||
buttonRect.sizeDelta = size;
|
||||
|
||||
Image buttonImage = buttonObject.GetComponent<Image>();
|
||||
buttonImage.sprite = LoadUiSprite(ResultButtonResourcePath, ResultButtonAssetPath);
|
||||
buttonImage.color = buttonImage.sprite != null
|
||||
? (primary ? Color.white : new Color(0.86f, 0.94f, 1f, 0.94f))
|
||||
: primary
|
||||
? new Color(1f, 0.82f, 0.28f, 0.98f)
|
||||
: new Color(0.82f, 0.91f, 0.96f, 0.90f);
|
||||
buttonImage.preserveAspect = false;
|
||||
buttonImage.raycastTarget = true;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.onClick.AddListener(action);
|
||||
|
||||
if(iconSprite != null)
|
||||
{
|
||||
AddResultImage(buttonRect, "Result Button Icon", iconSprite, new Vector2(34f, -(size.y * 0.5f)), new Vector2(24f, 24f), true, Color.white);
|
||||
}
|
||||
|
||||
TextMeshProUGUI buttonText = AddResultText(buttonRect, "Result Button Label", label, new Vector2(iconSprite != null ? 54f : 0f, -1f), new Vector2(iconSprite != null ? size.x - 70f : size.x, size.y), 16f, FontStyles.Bold, new Color(0.94f, 0.99f, 1f, 1f), TextAlignmentOptions.Center);
|
||||
RectTransform textRect = buttonText.rectTransform;
|
||||
textRect.anchorMin = new Vector2(0f, 0.5f);
|
||||
textRect.anchorMax = new Vector2(0f, 0.5f);
|
||||
textRect.pivot = new Vector2(0f, 0.5f);
|
||||
buttonText.outlineColor = new Color(0f, 0.04f, 0.07f, 0.98f);
|
||||
buttonText.outlineWidth = 0.12f;
|
||||
}
|
||||
|
||||
private Image AddResultImage(RectTransform parent, string objectName, Sprite sprite, Vector2 position, Vector2 size, bool preserveAspect, Color color) {
|
||||
GameObject imageObject = CreateUiObject(objectName, parent, typeof(Image));
|
||||
RectTransform imageRect = imageObject.GetComponent<RectTransform>();
|
||||
imageRect.anchorMin = new Vector2(0f, 1f);
|
||||
imageRect.anchorMax = new Vector2(0f, 1f);
|
||||
imageRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
imageRect.anchoredPosition = position;
|
||||
imageRect.sizeDelta = size;
|
||||
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.sprite = sprite;
|
||||
image.color = sprite != null ? color : new Color(0.30f, 0.46f, 0.54f, 0.28f);
|
||||
image.preserveAspect = preserveAspect;
|
||||
image.raycastTarget = false;
|
||||
return image;
|
||||
}
|
||||
|
||||
private TextMeshProUGUI AddResultText(RectTransform parent, string objectName, string text, Vector2 position, Vector2 size, float fontSize, FontStyles fontStyle, Color color, TextAlignmentOptions alignment) {
|
||||
GameObject textObject = CreateUiObject(objectName, parent);
|
||||
RectTransform textRect = textObject.GetComponent<RectTransform>();
|
||||
textRect.anchorMin = new Vector2(0f, 1f);
|
||||
textRect.anchorMax = new Vector2(0f, 1f);
|
||||
textRect.pivot = new Vector2(0f, 1f);
|
||||
textRect.anchoredPosition = position;
|
||||
textRect.sizeDelta = size;
|
||||
|
||||
TextMeshProUGUI textComponent = textObject.AddComponent<TextMeshProUGUI>();
|
||||
textComponent.text = text;
|
||||
textComponent.font = runtimeFont != null ? runtimeFont : textComponent.font;
|
||||
textComponent.fontSize = fontSize;
|
||||
textComponent.fontStyle = fontStyle;
|
||||
textComponent.alignment = alignment;
|
||||
textComponent.color = color;
|
||||
textComponent.raycastTarget = false;
|
||||
textComponent.textWrappingMode = TextWrappingModes.Normal;
|
||||
textComponent.overflowMode = TextOverflowModes.Ellipsis;
|
||||
textComponent.enableAutoSizing = true;
|
||||
textComponent.fontSizeMin = Mathf.Max(8f, fontSize * 0.66f);
|
||||
textComponent.fontSizeMax = fontSize;
|
||||
return textComponent;
|
||||
}
|
||||
|
||||
private TextMeshProUGUI AddResultCenteredText(RectTransform parent, string objectName, string text, Vector2 centerPosition, Vector2 size, float fontSize, FontStyles fontStyle, Color color, TextAlignmentOptions alignment) {
|
||||
Vector2 topLeftPosition = centerPosition + new Vector2(-size.x * 0.5f, size.y * 0.5f);
|
||||
return AddResultText(parent, objectName, text, topLeftPosition, size, fontSize, fontStyle, color, alignment);
|
||||
}
|
||||
|
||||
private string GetResultOriginAirportCode(MapDefinition resultMap) {
|
||||
return GetAirportCode(GetResultOriginMapId(resultMap));
|
||||
}
|
||||
|
||||
private string GetResultDestinationAirportCode(MapDefinition resultMap) {
|
||||
return GetAirportCode(GetResultDestinationMapId(resultMap));
|
||||
}
|
||||
|
||||
private string GetResultOriginCityName(MapDefinition resultMap) {
|
||||
return GetAirportCityName(GetResultOriginMapId(resultMap));
|
||||
}
|
||||
|
||||
private string GetResultDestinationCityName(MapDefinition resultMap) {
|
||||
return GetAirportCityName(GetResultDestinationMapId(resultMap));
|
||||
}
|
||||
|
||||
private MapId GetResultOriginMapId(MapDefinition resultMap) {
|
||||
if(resultMap == null)
|
||||
{
|
||||
return MapId.TutorialIncheon;
|
||||
}
|
||||
|
||||
return resultMap.mapId;
|
||||
}
|
||||
|
||||
private MapId GetResultDestinationMapId(MapDefinition resultMap) {
|
||||
if(resultMap == null)
|
||||
{
|
||||
return MapId.Japan;
|
||||
}
|
||||
|
||||
if(resultMap.hasNextMap)
|
||||
{
|
||||
return resultMap.nextMapId;
|
||||
}
|
||||
|
||||
return resultMap.mapId;
|
||||
}
|
||||
|
||||
private string GetAirportCode(MapId mapId) {
|
||||
switch(mapId)
|
||||
{
|
||||
case MapId.TutorialIncheon:
|
||||
return "ICN";
|
||||
case MapId.Japan:
|
||||
return "FUK";
|
||||
case MapId.China:
|
||||
return "PVG";
|
||||
case MapId.America:
|
||||
return "LAX";
|
||||
default:
|
||||
return "TRP";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAirportCityName(MapId mapId) {
|
||||
switch(mapId)
|
||||
{
|
||||
case MapId.TutorialIncheon:
|
||||
return "인천공항";
|
||||
case MapId.Japan:
|
||||
return "후쿠오카";
|
||||
case MapId.China:
|
||||
return "상하이";
|
||||
case MapId.America:
|
||||
return "로스앤젤레스";
|
||||
default:
|
||||
return "여행지";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetResultFlightName(MapDefinition resultMap) {
|
||||
int mapNumber = resultMap != null ? (int)resultMap.mapId + 1 : 1;
|
||||
return "UNR-" + mapNumber.ToString("000");
|
||||
}
|
||||
|
||||
private string GetResultSeatName(MapDefinition resultMap) {
|
||||
int mapNumber = resultMap != null ? (int)resultMap.mapId + 1 : 1;
|
||||
return (10 + mapNumber).ToString() + "A";
|
||||
}
|
||||
|
||||
private string GetResultSouvenirName(MapDefinition resultMap) {
|
||||
if(resultMap == null)
|
||||
{
|
||||
return "여행 도장";
|
||||
}
|
||||
|
||||
switch(resultMap.mapId)
|
||||
{
|
||||
case MapId.TutorialIncheon:
|
||||
return "공항 마그넷";
|
||||
case MapId.Japan:
|
||||
return "일본 마그넷";
|
||||
case MapId.China:
|
||||
return "여행 부적";
|
||||
case MapId.America:
|
||||
return "스카이라인";
|
||||
default:
|
||||
return "여행 도장";
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite GetResultSouvenirSprite(MapDefinition resultMap) {
|
||||
if(resultMap == null)
|
||||
{
|
||||
return LoadUiSprite("", SouvenirCollectionIconAssetPath);
|
||||
}
|
||||
|
||||
switch(resultMap.mapId)
|
||||
{
|
||||
case MapId.TutorialIncheon:
|
||||
return LoadUiSprite("", AirportSouvenirAssetPath);
|
||||
case MapId.Japan:
|
||||
return LoadUiSprite("", JapanSouvenirAssetPath);
|
||||
case MapId.China:
|
||||
return LoadUiSprite("", ChinaSouvenirAssetPath);
|
||||
case MapId.America:
|
||||
return LoadUiSprite("", AmericaSouvenirAssetPath);
|
||||
default:
|
||||
return LoadUiSprite("", SouvenirCollectionIconAssetPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bf10c40d6cc4b319e90f0bf9279966b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+370
-82
@@ -22,8 +22,7 @@ public class GameManager : MonoBehaviour {
|
||||
public enum TutorialItemType {
|
||||
HealthSushi,
|
||||
InvincibleRamen,
|
||||
Shield,
|
||||
SpeedShoes,
|
||||
EnergyDrink,
|
||||
MileageCard
|
||||
}
|
||||
|
||||
@@ -43,6 +42,8 @@ public class GameManager : MonoBehaviour {
|
||||
private Image rightInfoHudBackground; // 오른쪽 거리/마일리지 수하물 라벨 배경
|
||||
private TextMeshProUGUI rightMileageText; // 오른쪽 HUD의 적립 마일리지 텍스트
|
||||
private Image[] lifeIcons; // 왼쪽 HUD의 목숨 도장 표시
|
||||
private Image[] itemSlotIcons; // 왼쪽 HUD의 장착 아이템 표시
|
||||
private DutyFreeItemType[] preparedDutyFreeItems; // 면세점에서 장착한 스테이지 준비 아이템
|
||||
private GameObject tutorialGuideObject; // 화면 상단 이미지 튜토리얼 패널
|
||||
private Image tutorialMainImage; // 점프/슬라이드 대표 이미지
|
||||
private TextMeshProUGUI tutorialMultiplierText; // 2단 점프 x2 표시
|
||||
@@ -74,8 +75,8 @@ public class GameManager : MonoBehaviour {
|
||||
public bool playTutorialGuideSequence = false; // 코스 데이터가 없을 때만 시간 기준 안내를 보여준다.
|
||||
public bool pauseGameplayForTutorialGuide = true; // 안내가 뜨는 동안 게임을 잠깐 멈춘다.
|
||||
public float tutorialGuidePauseDuration = 2f; // 이전 자동 숨김 시간 값입니다. 현재 안내는 확인 클릭으로만 닫습니다.
|
||||
public float itemInvincibleDuration = 2.2f; // 라멘 아이템 무적 지속 시간
|
||||
public float itemSpeedBoostAmount = 0.35f; // 운동화 아이템 즉시 속도 보정량
|
||||
public float itemInvincibleDuration = 2.2f; // 무적 아이템 지속 시간
|
||||
public float itemSpeedBoostAmount = 0.35f; // 에너지 드링크 즉시 속도 보정량
|
||||
public float mileageBonusDuration = 7f; // 마일리지 카드 보너스 지속 시간
|
||||
public int mileageBonusMultiplier = 2; // 마일리지 카드 획득 배율
|
||||
public float finishGateSpawnX = 20f; // 도착 게이트가 처음 나타나는 x 위치
|
||||
@@ -86,21 +87,29 @@ public class GameManager : MonoBehaviour {
|
||||
private const string TotalMileageKey = "UniRun.TotalMileage";
|
||||
private const string InitialMileageGrantedKey = "UniRun.InitialMileageGranted";
|
||||
private const string CharacterFaceResourcePath = "UI_JJ_PassportPhoto";
|
||||
private const string PassportPanelResourcePath = "UI_LeftHudPassportCompactPanel";
|
||||
private const string PassportPanelResourcePath = "UI_LeftHudPassportHorizontalGridPanel";
|
||||
private const string PassportPhotoFrameResourcePath = "UI_PassportHudPhotoFrame";
|
||||
private const string PassportPhotoHeaderResourcePath = "UI_PassportHudPhotoHeader";
|
||||
private const string LifeStampResourcePath = "UI_LifeStamp";
|
||||
private const string PassportItemSlotResourcePath = "UI_PassportItemSlot";
|
||||
private const string PassportLifeCircleFrameResourcePath = "UI_PassportHudLifeCircleFrame";
|
||||
private const string PassportItemSlotResourcePath = "UI_PassportHudItemSlotFrame";
|
||||
private const string PassportFlightMarkerResourcePath = "UI_PassportHudFlightMarker";
|
||||
private const string PassportItemMarkerResourcePath = "UI_PassportHudItemMarker";
|
||||
private const string TutorialGuideFrameResourcePath = "UI_TutorialAirportMonitorFrame";
|
||||
private const string TutorialNextButtonResourcePath = "UI_TutorialNextButton";
|
||||
private const string RightHudLuggageLabelKoreaResourcePath = "UI_RightHudLuggageLabel_Korea";
|
||||
private const string RightHudLuggageLabelJapanResourcePath = "UI_RightHudLuggageLabel_Japan";
|
||||
private const string RightHudLuggageLabelChinaResourcePath = "UI_RightHudLuggageLabel_China";
|
||||
private const string RightHudLuggageLabelAmericaResourcePath = "UI_RightHudLuggageLabel_America";
|
||||
private const int MaxSupplementPurchases = 4;
|
||||
private const int MaxLifeWithSupplements = 5;
|
||||
private const float PassportHudWidth = 261f;
|
||||
private const float PassportHudHeight = 102f;
|
||||
private const float PassportHudScale = 0.5f;
|
||||
private const float RightInfoHudWidth = 260f;
|
||||
private const float RightInfoHudHeight = 64f;
|
||||
private const float RightInfoHudScale = 0.6f;
|
||||
private const int PreparedItemSlotCount = 3;
|
||||
private const float TutorialJumpGuideDuration = 3.5f;
|
||||
private const float TutorialDoubleJumpGuideDuration = 3.5f;
|
||||
private const float TutorialSlideGuideDuration = 3.5f;
|
||||
@@ -115,9 +124,9 @@ public class GameManager : MonoBehaviour {
|
||||
public float speedIncreaseRate = 0.05f;
|
||||
public float maxGameSpeed = 2f;
|
||||
public float hitSpeedPenalty = 0.3f;
|
||||
private int shieldCharges = 0; // 방어막 아이템으로 막을 수 있는 남은 피격 횟수
|
||||
private float itemInvincibleEndTime = 0f; // 라멘 무적 종료 시각
|
||||
private float itemInvincibleEndTime = 0f; // 무적 아이템 종료 시각
|
||||
private float mileageBonusEndTime = 0f; // 마일리지 카드 보너스 종료 시각
|
||||
private int baseMaxLife;
|
||||
|
||||
public bool IsTutorialGuidePaused {
|
||||
get { return tutorialGuidePauseActive || tutorialGuideDismissFrame == Time.frameCount; }
|
||||
@@ -130,6 +139,7 @@ public class GameManager : MonoBehaviour {
|
||||
{
|
||||
// instance가 비어있다면(null) 그곳에 자기 자신을 할당
|
||||
instance = this;
|
||||
baseMaxLife = maxLife;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -142,7 +152,21 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySupplementLifeBonus() {
|
||||
if(baseMaxLife <= 0)
|
||||
{
|
||||
baseMaxLife = maxLife;
|
||||
}
|
||||
|
||||
int supplementPurchases = GameFlowManager.Instance != null && GameFlowManager.Instance.SaveData != null
|
||||
? Mathf.Clamp(GameFlowManager.Instance.SaveData.supplementPurchases, 0, MaxSupplementPurchases)
|
||||
: 0;
|
||||
int bonusLife = supplementPurchases / 2;
|
||||
maxLife = Mathf.Min(MaxLifeWithSupplements, baseMaxLife + bonusLife);
|
||||
}
|
||||
|
||||
private void Start() {
|
||||
ApplySupplementLifeBonus();
|
||||
currentLife = maxLife;
|
||||
totalMileage = GameFlowManager.Instance != null
|
||||
? GameFlowManager.Instance.SaveData.totalMileage
|
||||
@@ -213,18 +237,21 @@ public class GameManager : MonoBehaviour {
|
||||
public void BeginMap(MapDefinition mapDefinition) {
|
||||
StopTutorialGuidePause(true);
|
||||
Time.timeScale = 1f;
|
||||
ApplySupplementLifeBonus();
|
||||
|
||||
currentMapDefinition = mapDefinition != null
|
||||
? mapDefinition
|
||||
: MapDatabase.GetMap(MapId.TutorialIncheon);
|
||||
BackgroundLoop.ApplyResourceBackground(currentMapDefinition.backgroundResourcePath);
|
||||
BackgroundLoop.ApplyResourceBackground(
|
||||
currentMapDefinition.GetBackgroundResourcePaths(),
|
||||
currentMapDefinition.backgroundFitScaleMultiplier,
|
||||
currentMapDefinition.backgroundVerticalOffset);
|
||||
UpdateRightInfoHudSkin();
|
||||
|
||||
score = 0;
|
||||
stageMileage = 0;
|
||||
currentLife = maxLife;
|
||||
gameSpeed = minGameSpeed;
|
||||
shieldCharges = 0;
|
||||
itemInvincibleEndTime = 0f;
|
||||
mileageBonusEndTime = 0f;
|
||||
stageElapsedTime = 0f;
|
||||
@@ -234,6 +261,7 @@ public class GameManager : MonoBehaviour {
|
||||
isGameover = false;
|
||||
tutorialGuideSequenceComplete = false;
|
||||
currentTutorialGuideSequenceIndex = -1;
|
||||
LoadPreparedDutyFreeItems();
|
||||
SetTutorialGuide(TutorialGuideType.None, "", "");
|
||||
RemoveActiveFinishGate();
|
||||
|
||||
@@ -249,6 +277,8 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
StartTutorialGuideSequence();
|
||||
UpdateTutorialItemRowSprites();
|
||||
UpdatePreparedItemSlotUI();
|
||||
UpdateScoreUI();
|
||||
}
|
||||
|
||||
@@ -370,20 +400,25 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
private bool ShouldUseTimeBasedTutorialGuide() {
|
||||
return playTutorialGuideSequence && !HasCourseDrivenTutorialGuide();
|
||||
return playTutorialGuideSequence
|
||||
&& currentMapDefinition != null
|
||||
&& currentMapDefinition.isTutorial
|
||||
&& !HasCourseDrivenTutorialGuide();
|
||||
}
|
||||
|
||||
private bool HasCourseDrivenTutorialGuide() {
|
||||
if(currentMapDefinition == null
|
||||
|| currentMapDefinition.openingSteps == null
|
||||
|| currentMapDefinition.openingSteps.Length == 0)
|
||||
CourseStep[] openingSteps = currentMapDefinition != null
|
||||
? currentMapDefinition.GetOpeningSteps()
|
||||
: null;
|
||||
|
||||
if(openingSteps == null || openingSteps.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < currentMapDefinition.openingSteps.Length; i++)
|
||||
for(int i = 0; i < openingSteps.Length; i++)
|
||||
{
|
||||
CourseStep step = currentMapDefinition.openingSteps[i];
|
||||
CourseStep step = openingSteps[i];
|
||||
if(step != null
|
||||
&& (step.guideType != TutorialGuideType.None
|
||||
|| !string.IsNullOrEmpty(step.title)
|
||||
@@ -436,7 +471,14 @@ public class GameManager : MonoBehaviour {
|
||||
SetTutorialGuide(TutorialGuideType.Slide, "슬라이드", "오른쪽 클릭을 누르고 있으면 낮게 지나갑니다.");
|
||||
break;
|
||||
case 3:
|
||||
SetTutorialGuide(TutorialGuideType.Items, "아이템", "초밥 -> 라멘 -> 방어막 -> 마일리지 카드 -> 운동화 순서입니다.");
|
||||
SetTutorialGuide(
|
||||
TutorialGuideType.Items,
|
||||
"아이템",
|
||||
string.Format(
|
||||
"{0} -> {1} -> {2} 순서입니다.",
|
||||
GetGameplayItemDisplayName(TutorialItemType.HealthSushi),
|
||||
GetGameplayItemDisplayName(TutorialItemType.InvincibleRamen),
|
||||
GetGameplayItemDisplayName(TutorialItemType.EnergyDrink)));
|
||||
break;
|
||||
default:
|
||||
tutorialGuideSequenceComplete = true;
|
||||
@@ -452,6 +494,12 @@ public class GameManager : MonoBehaviour {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!IsMileageBonusActive() && TryConsumePreparedDutyFreeItem(DutyFreeItemType.MileageCard))
|
||||
{
|
||||
mileageBonusEndTime = Time.time + mileageBonusDuration;
|
||||
SpawnPreparedItemFeedback(DutyFreeItemType.MileageCard, false);
|
||||
}
|
||||
|
||||
int finalAmount = IsMileageBonusActive()
|
||||
? amount * Mathf.Max(1, mileageBonusMultiplier)
|
||||
: amount;
|
||||
@@ -483,6 +531,12 @@ public class GameManager : MonoBehaviour {
|
||||
gameSpeed -= hitSpeedPenalty;
|
||||
gameSpeed = Mathf.Max(gameSpeed, minGameSpeed);
|
||||
|
||||
if(currentLife < maxLife && TryConsumePreparedDutyFreeItem(DutyFreeItemType.Lunchbox))
|
||||
{
|
||||
currentLife = Mathf.Min(maxLife, currentLife + 1);
|
||||
SpawnPreparedItemFeedback(DutyFreeItemType.Lunchbox, false);
|
||||
}
|
||||
|
||||
UpdateScoreUI();
|
||||
|
||||
return currentLife <= 0;
|
||||
@@ -501,35 +555,24 @@ public class GameManager : MonoBehaviour {
|
||||
AddScore(3);
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "초밥", "방금 잃은 목숨을 1 회복합니다.");
|
||||
string itemName = GetGameplayItemDisplayName(itemType);
|
||||
SetTutorialGuide(TutorialGuideType.Items, itemName, itemName + "을 먹어 목숨을 1 회복합니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.InvincibleRamen:
|
||||
itemInvincibleEndTime = Time.time + itemInvincibleDuration;
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "라멘", "짧은 시간 동안 다음 충돌 피해를 받지 않습니다.");
|
||||
string itemName = GetGameplayItemDisplayName(itemType);
|
||||
SetTutorialGuide(TutorialGuideType.Items, itemName, itemName + " 효과로 짧은 시간 동안 충돌 피해를 받지 않습니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.Shield:
|
||||
shieldCharges = Mathf.Max(shieldCharges, 1);
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "방어막", "다음 충돌 1회를 방어막이 대신 막습니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.SpeedShoes:
|
||||
case TutorialItemType.EnergyDrink:
|
||||
gameSpeed = Mathf.Min(maxGameSpeed, gameSpeed + itemSpeedBoostAmount);
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "운동화", "속도를 즉시 회복해 늦어진 일정을 따라잡습니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.MileageCard:
|
||||
mileageBonusEndTime = Time.time + mileageBonusDuration;
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "마일리지 카드", "잠시 동안 먹는 마일리지가 두 배로 들어옵니다.");
|
||||
string itemName = GetGameplayItemDisplayName(itemType);
|
||||
SetTutorialGuide(TutorialGuideType.Items, itemName, itemName + "로 속도를 조금 회복합니다.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -547,25 +590,14 @@ public class GameManager : MonoBehaviour {
|
||||
{
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "라멘", "무적 중이라 이번 충돌은 피해 없이 지나갑니다.");
|
||||
string itemName = GetGameplayItemDisplayName(TutorialItemType.InvincibleRamen);
|
||||
SetTutorialGuide(TutorialGuideType.Items, itemName, itemName + " 효과로 이번 충돌은 피해 없이 지나갑니다.");
|
||||
}
|
||||
|
||||
SpawnItemBlockFeedback(TutorialItemType.InvincibleRamen);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(shieldCharges > 0)
|
||||
{
|
||||
shieldCharges--;
|
||||
SpawnItemBlockFeedback(TutorialItemType.Shield);
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "방어막", "방어막이 충돌 피해를 한 번 막았습니다.");
|
||||
}
|
||||
UpdateScoreUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -580,7 +612,21 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
private bool ShouldShowItemEffectGuide() {
|
||||
return currentMapDefinition == null || !currentMapDefinition.isTutorial;
|
||||
return currentMapDefinition != null && currentMapDefinition.isTutorial;
|
||||
}
|
||||
|
||||
private Platform.PlatformSkin GetCurrentPlatformSkin() {
|
||||
return currentMapDefinition != null
|
||||
? currentMapDefinition.platformSkin
|
||||
: Platform.PlatformSkin.Airport;
|
||||
}
|
||||
|
||||
private string GetGameplayItemDisplayName(TutorialItemType itemType) {
|
||||
return GameplayItemCatalog.GetDisplayName(itemType, GetCurrentPlatformSkin());
|
||||
}
|
||||
|
||||
private string GetGameplayItemSpritePath(TutorialItemType itemType) {
|
||||
return GameplayItemCatalog.GetSpriteAssetPath(itemType, GetCurrentPlatformSkin());
|
||||
}
|
||||
|
||||
private bool IsItemInvincibleActive() {
|
||||
@@ -843,34 +889,31 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
string itemText = (tutorialTitle + " " + tutorialMessage).Trim();
|
||||
if(itemText.Contains("초밥"))
|
||||
if(ContainsGameplayItemName(itemText, TutorialItemType.HealthSushi))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(itemText.Contains("라멘"))
|
||||
if(ContainsGameplayItemName(itemText, TutorialItemType.InvincibleRamen))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(itemText.Contains("방어막"))
|
||||
if(ContainsGameplayItemName(itemText, TutorialItemType.EnergyDrink))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(itemText.Contains("운동화"))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
if(itemText.Contains("마일리지"))
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private bool ContainsGameplayItemName(string text, TutorialItemType itemType) {
|
||||
return text.Contains(GameplayItemCatalog.GetDisplayName(itemType, Platform.PlatformSkin.Airport))
|
||||
|| text.Contains(GameplayItemCatalog.GetDisplayName(itemType, Platform.PlatformSkin.Japan))
|
||||
|| text.Contains(GameplayItemCatalog.GetDisplayName(itemType, Platform.PlatformSkin.China))
|
||||
|| text.Contains(GameplayItemCatalog.GetDisplayName(itemType, Platform.PlatformSkin.USA));
|
||||
}
|
||||
|
||||
private Color GetActiveLifeIconColor(Image lifeIcon) {
|
||||
if(lifeIcon != null && lifeIcon.sprite == null)
|
||||
{
|
||||
@@ -990,15 +1033,54 @@ public class GameManager : MonoBehaviour {
|
||||
hudBackground.color = hudBackground.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.04f, 0.08f, 0.11f, 0.68f);
|
||||
hudBackground.preserveAspect = true;
|
||||
hudBackground.preserveAspect = false;
|
||||
hudBackground.raycastTarget = false;
|
||||
|
||||
CreateFaceIcon(hudObject.transform);
|
||||
CreatePassportHudMarkers(hudObject.transform);
|
||||
CreateLifeIcons(hudObject.transform);
|
||||
CreateItemSlots(hudObject.transform);
|
||||
}
|
||||
|
||||
private void CreatePassportHudMarkers(Transform parent) {
|
||||
CreatePassportHudImage(
|
||||
parent,
|
||||
"Flight Marker",
|
||||
PassportFlightMarkerResourcePath,
|
||||
new Vector2(132f, -12f),
|
||||
new Vector2(118f, 17f),
|
||||
Color.white,
|
||||
false);
|
||||
|
||||
CreatePassportHudImage(
|
||||
parent,
|
||||
"Item Marker",
|
||||
PassportItemMarkerResourcePath,
|
||||
new Vector2(132f, -50f),
|
||||
new Vector2(118f, 17f),
|
||||
Color.white,
|
||||
false);
|
||||
}
|
||||
|
||||
private void CreateFaceIcon(Transform parent) {
|
||||
CreatePassportHudImage(
|
||||
parent,
|
||||
"Photo Header",
|
||||
PassportPhotoHeaderResourcePath,
|
||||
new Vector2(45f, -8f),
|
||||
new Vector2(72f, 14f),
|
||||
Color.white,
|
||||
false);
|
||||
|
||||
CreatePassportHudImage(
|
||||
parent,
|
||||
"Photo Frame",
|
||||
PassportPhotoFrameResourcePath,
|
||||
new Vector2(28f, -14f),
|
||||
new Vector2(96f, 76f),
|
||||
Color.white,
|
||||
false);
|
||||
|
||||
GameObject faceFrame = new GameObject("Face Frame", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
faceFrame.transform.SetParent(parent, false);
|
||||
|
||||
@@ -1006,8 +1088,8 @@ public class GameManager : MonoBehaviour {
|
||||
frameRect.anchorMin = new Vector2(0f, 1f);
|
||||
frameRect.anchorMax = new Vector2(0f, 1f);
|
||||
frameRect.pivot = new Vector2(0f, 1f);
|
||||
frameRect.anchoredPosition = new Vector2(32f, -20f);
|
||||
frameRect.sizeDelta = new Vector2(72f, 68f);
|
||||
frameRect.anchoredPosition = new Vector2(33f, -22f);
|
||||
frameRect.sizeDelta = new Vector2(86f, 64f);
|
||||
|
||||
Image frameImage = faceFrame.GetComponent<Image>();
|
||||
frameImage.color = new Color(1f, 1f, 1f, 0f);
|
||||
@@ -1032,12 +1114,22 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
private void CreateLifeIcons(Transform parent) {
|
||||
Sprite frameSprite = LoadHudSprite(PassportLifeCircleFrameResourcePath);
|
||||
Sprite lifeSprite = LoadHudSprite(LifeStampResourcePath);
|
||||
int iconCount = Mathf.Max(maxLife, 1);
|
||||
lifeIcons = new Image[iconCount];
|
||||
|
||||
for(int i = 0; i < iconCount; i++)
|
||||
{
|
||||
CreatePassportHudImage(
|
||||
parent,
|
||||
"Life Circle Frame " + (i + 1),
|
||||
PassportLifeCircleFrameResourcePath,
|
||||
new Vector2(134f + (i * 39f), -18f),
|
||||
new Vector2(34f, 34f),
|
||||
frameSprite != null ? Color.white : new Color(0.93f, 0.73f, 0.28f, 0.75f),
|
||||
true);
|
||||
|
||||
GameObject lifeObject = new GameObject("Life Stamp " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
lifeObject.transform.SetParent(parent, false);
|
||||
|
||||
@@ -1045,8 +1137,8 @@ public class GameManager : MonoBehaviour {
|
||||
lifeRect.anchorMin = new Vector2(0f, 1f);
|
||||
lifeRect.anchorMax = new Vector2(0f, 1f);
|
||||
lifeRect.pivot = new Vector2(0f, 1f);
|
||||
lifeRect.anchoredPosition = new Vector2(125f + (i * 45f), -23f);
|
||||
lifeRect.sizeDelta = new Vector2(28f, 28f);
|
||||
lifeRect.anchoredPosition = new Vector2(139f + (i * 39f), -23f);
|
||||
lifeRect.sizeDelta = new Vector2(24f, 24f);
|
||||
|
||||
Image lifeImage = lifeObject.GetComponent<Image>();
|
||||
lifeImage.sprite = lifeSprite;
|
||||
@@ -1061,8 +1153,9 @@ public class GameManager : MonoBehaviour {
|
||||
|
||||
private void CreateItemSlots(Transform parent) {
|
||||
Sprite slotSprite = LoadHudSprite(PassportItemSlotResourcePath);
|
||||
itemSlotIcons = new Image[PreparedItemSlotCount];
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
for(int i = 0; i < PreparedItemSlotCount; i++)
|
||||
{
|
||||
GameObject slotObject = new GameObject("Item Slot " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
slotObject.transform.SetParent(parent, false);
|
||||
@@ -1071,8 +1164,8 @@ public class GameManager : MonoBehaviour {
|
||||
slotRect.anchorMin = new Vector2(0f, 1f);
|
||||
slotRect.anchorMax = new Vector2(0f, 1f);
|
||||
slotRect.pivot = new Vector2(0f, 1f);
|
||||
slotRect.anchoredPosition = new Vector2(125f + (i * 45f), -62f);
|
||||
slotRect.sizeDelta = new Vector2(30f, 30f);
|
||||
slotRect.anchoredPosition = new Vector2(132f + (i * 40f), -65f);
|
||||
slotRect.sizeDelta = new Vector2(36f, 24f);
|
||||
|
||||
Image slotImage = slotObject.GetComponent<Image>();
|
||||
slotImage.sprite = slotSprite;
|
||||
@@ -1081,9 +1174,189 @@ public class GameManager : MonoBehaviour {
|
||||
: new Color(0.92f, 0.97f, 1f, 0.22f);
|
||||
slotImage.preserveAspect = true;
|
||||
slotImage.raycastTarget = false;
|
||||
|
||||
GameObject iconObject = new GameObject("Prepared Item Icon " + (i + 1), typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
iconObject.transform.SetParent(slotObject.transform, false);
|
||||
|
||||
RectTransform iconRect = iconObject.GetComponent<RectTransform>();
|
||||
iconRect.anchorMin = Vector2.zero;
|
||||
iconRect.anchorMax = Vector2.one;
|
||||
iconRect.offsetMin = new Vector2(7f, 2f);
|
||||
iconRect.offsetMax = new Vector2(-7f, -2f);
|
||||
|
||||
Image iconImage = iconObject.GetComponent<Image>();
|
||||
iconImage.preserveAspect = true;
|
||||
iconImage.raycastTarget = false;
|
||||
iconImage.gameObject.SetActive(false);
|
||||
itemSlotIcons[i] = iconImage;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPreparedDutyFreeItems() {
|
||||
if(preparedDutyFreeItems == null || preparedDutyFreeItems.Length != PreparedItemSlotCount)
|
||||
{
|
||||
preparedDutyFreeItems = new DutyFreeItemType[PreparedItemSlotCount];
|
||||
}
|
||||
|
||||
for(int i = 0; i < preparedDutyFreeItems.Length; i++)
|
||||
{
|
||||
preparedDutyFreeItems[i] = DutyFreeItemType.None;
|
||||
}
|
||||
|
||||
GameSaveData saveData = GameFlowManager.Instance != null
|
||||
? GameFlowManager.Instance.SaveData
|
||||
: null;
|
||||
if(saveData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int slotCount = Mathf.Clamp(saveData.bagSlots, 1, PreparedItemSlotCount);
|
||||
for(int i = 0; i < slotCount; i++)
|
||||
{
|
||||
preparedDutyFreeItems[i] = NormalizePreparedItem(GetPreparedItemFromSave(saveData, i));
|
||||
}
|
||||
}
|
||||
|
||||
private DutyFreeItemType GetPreparedItemFromSave(GameSaveData saveData, int slotIndex) {
|
||||
switch(slotIndex)
|
||||
{
|
||||
case 0:
|
||||
return saveData.equippedSlot1;
|
||||
case 1:
|
||||
return saveData.equippedSlot2;
|
||||
case 2:
|
||||
return saveData.equippedSlot3;
|
||||
default:
|
||||
return DutyFreeItemType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private DutyFreeItemType NormalizePreparedItem(DutyFreeItemType itemType) {
|
||||
switch(itemType)
|
||||
{
|
||||
case DutyFreeItemType.Lunchbox:
|
||||
case DutyFreeItemType.MileageCard:
|
||||
return itemType;
|
||||
default:
|
||||
return DutyFreeItemType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryConsumePreparedDutyFreeItem(DutyFreeItemType itemType) {
|
||||
if(preparedDutyFreeItems == null || itemType == DutyFreeItemType.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < preparedDutyFreeItems.Length; i++)
|
||||
{
|
||||
if(preparedDutyFreeItems[i] != itemType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
preparedDutyFreeItems[i] = DutyFreeItemType.None;
|
||||
if(GameFlowManager.Instance != null)
|
||||
{
|
||||
GameFlowManager.Instance.ConsumeEquippedDutyFreeItem(i, itemType);
|
||||
}
|
||||
|
||||
UpdatePreparedItemSlotUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdatePreparedItemSlotUI() {
|
||||
if(itemSlotIcons == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < itemSlotIcons.Length; i++)
|
||||
{
|
||||
Image iconImage = itemSlotIcons[i];
|
||||
if(iconImage == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DutyFreeItemType itemType = preparedDutyFreeItems != null && i < preparedDutyFreeItems.Length
|
||||
? preparedDutyFreeItems[i]
|
||||
: DutyFreeItemType.None;
|
||||
Sprite itemSprite = GetPreparedDutyFreeItemSprite(itemType);
|
||||
iconImage.sprite = itemSprite;
|
||||
iconImage.color = itemSprite != null ? Color.white : new Color(1f, 1f, 1f, 0f);
|
||||
iconImage.gameObject.SetActive(itemSprite != null);
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite GetPreparedDutyFreeItemSprite(DutyFreeItemType itemType) {
|
||||
switch(itemType)
|
||||
{
|
||||
case DutyFreeItemType.Lunchbox:
|
||||
return LoadTutorialSprite("Assets/Sprites/Items/Item_Lunchbox.png");
|
||||
case DutyFreeItemType.MileageCard:
|
||||
return LoadTutorialSprite("Assets/Sprites/Items/Item_Mileage_Card.png");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnPreparedItemFeedback(DutyFreeItemType itemType, bool blockedDamage) {
|
||||
GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
|
||||
if(playerObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ItemFeedbackEffect.Spawn(
|
||||
playerObject.transform.position,
|
||||
GetPreparedDutyFreeItemSprite(itemType),
|
||||
GetPreparedItemFeedbackType(itemType),
|
||||
blockedDamage);
|
||||
}
|
||||
|
||||
private TutorialItemType GetPreparedItemFeedbackType(DutyFreeItemType itemType) {
|
||||
switch(itemType)
|
||||
{
|
||||
case DutyFreeItemType.Lunchbox:
|
||||
return TutorialItemType.HealthSushi;
|
||||
case DutyFreeItemType.MileageCard:
|
||||
return TutorialItemType.MileageCard;
|
||||
default:
|
||||
return TutorialItemType.HealthSushi;
|
||||
}
|
||||
}
|
||||
|
||||
private Image CreatePassportHudImage(
|
||||
Transform parent,
|
||||
string objectName,
|
||||
string resourcePath,
|
||||
Vector2 anchoredPosition,
|
||||
Vector2 sizeDelta,
|
||||
Color fallbackColor,
|
||||
bool preserveAspect) {
|
||||
GameObject imageObject = new GameObject(objectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
imageObject.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rectTransform = imageObject.GetComponent<RectTransform>();
|
||||
rectTransform.anchorMin = new Vector2(0f, 1f);
|
||||
rectTransform.anchorMax = new Vector2(0f, 1f);
|
||||
rectTransform.pivot = new Vector2(0f, 1f);
|
||||
rectTransform.anchoredPosition = anchoredPosition;
|
||||
rectTransform.sizeDelta = sizeDelta;
|
||||
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.sprite = LoadHudSprite(resourcePath);
|
||||
image.color = image.sprite != null ? Color.white : fallbackColor;
|
||||
image.preserveAspect = preserveAspect;
|
||||
image.raycastTarget = false;
|
||||
return image;
|
||||
}
|
||||
|
||||
private Sprite LoadHudSprite(string resourcePath) {
|
||||
Sprite sprite = Resources.Load<Sprite>(resourcePath);
|
||||
if(sprite != null)
|
||||
@@ -1189,11 +1462,9 @@ public class GameManager : MonoBehaviour {
|
||||
|
||||
private void CreateTutorialItemRow(Transform parent) {
|
||||
string[] itemAssetPaths = {
|
||||
"Assets/Sprites/Items/Item_Health_Sushi.png",
|
||||
"Assets/Sprites/Items/Item_Invincible_Ramen.png",
|
||||
"Assets/Sprites/Items/Item_Shield.png",
|
||||
"Assets/Sprites/Items/Item_Speed_Shoes.png",
|
||||
"Assets/Sprites/Items/Item_Mileage_Card.png"
|
||||
GetGameplayItemSpritePath(TutorialItemType.HealthSushi),
|
||||
GetGameplayItemSpritePath(TutorialItemType.InvincibleRamen),
|
||||
GetGameplayItemSpritePath(TutorialItemType.EnergyDrink)
|
||||
};
|
||||
|
||||
tutorialItemImages = new Image[itemAssetPaths.Length];
|
||||
@@ -1219,6 +1490,27 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTutorialItemRowSprites() {
|
||||
if(tutorialItemImages == null || tutorialItemImages.Length < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TutorialItemType[] itemTypes = {
|
||||
TutorialItemType.HealthSushi,
|
||||
TutorialItemType.InvincibleRamen,
|
||||
TutorialItemType.EnergyDrink
|
||||
};
|
||||
|
||||
for(int i = 0; i < itemTypes.Length; i++)
|
||||
{
|
||||
if(tutorialItemImages[i] != null)
|
||||
{
|
||||
tutorialItemImages[i].sprite = LoadTutorialSprite(GetGameplayItemSpritePath(itemTypes[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTutorialNextButton(Transform parent) {
|
||||
GameObject buttonObject = new GameObject("Tutorial Confirm Button", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||
buttonObject.transform.SetParent(parent, false);
|
||||
@@ -1519,9 +1811,9 @@ public class GameManager : MonoBehaviour {
|
||||
ClearMarkers();
|
||||
|
||||
float cursor = 0f;
|
||||
AddStepMarkers(mapDefinition.openingSteps, ref cursor, targetDistance);
|
||||
AddStepMarkers(mapDefinition.GetOpeningSteps(), ref cursor, targetDistance);
|
||||
|
||||
CourseStep[] loopSteps = mapDefinition.loopSteps;
|
||||
CourseStep[] loopSteps = mapDefinition.GetLoopSteps();
|
||||
int loopGuard = 0;
|
||||
while(cursor < targetDistance
|
||||
&& loopSteps != null
|
||||
@@ -1638,12 +1930,8 @@ public class GameManager : MonoBehaviour {
|
||||
return "+";
|
||||
case Platform.ItemPattern.InvincibleRamen:
|
||||
return "R";
|
||||
case Platform.ItemPattern.Shield:
|
||||
return "G";
|
||||
case Platform.ItemPattern.SpeedShoes:
|
||||
case Platform.ItemPattern.EnergyDrink:
|
||||
return "V";
|
||||
case Platform.ItemPattern.MileageCard:
|
||||
return "M";
|
||||
default:
|
||||
return "I";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
public static class GameplayItemCatalog {
|
||||
public static string GetDisplayName(GameManager.TutorialItemType itemType, Platform.PlatformSkin platformSkin) {
|
||||
switch(itemType)
|
||||
{
|
||||
case GameManager.TutorialItemType.HealthSushi:
|
||||
return GetHealthItemName(platformSkin);
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
return GetInvincibleItemName(platformSkin);
|
||||
case GameManager.TutorialItemType.EnergyDrink:
|
||||
return "에너지 드링크";
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return "마일리지 카드";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetSpriteAssetPath(GameManager.TutorialItemType itemType, Platform.PlatformSkin platformSkin) {
|
||||
switch(itemType)
|
||||
{
|
||||
case GameManager.TutorialItemType.HealthSushi:
|
||||
return GetHealthItemAssetPath(platformSkin);
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
return GetInvincibleItemAssetPath(platformSkin);
|
||||
case GameManager.TutorialItemType.EnergyDrink:
|
||||
return GetEnergyDrinkAssetPath(platformSkin);
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return "Assets/Sprites/Items/Item_Mileage_Card.png";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static GameManager.TutorialItemType ToTutorialItemType(Platform.ItemPattern itemPattern) {
|
||||
switch(itemPattern)
|
||||
{
|
||||
case Platform.ItemPattern.InvincibleRamen:
|
||||
return GameManager.TutorialItemType.InvincibleRamen;
|
||||
case Platform.ItemPattern.EnergyDrink:
|
||||
return GameManager.TutorialItemType.EnergyDrink;
|
||||
case Platform.ItemPattern.HealthSushi:
|
||||
default:
|
||||
return GameManager.TutorialItemType.HealthSushi;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetHealthItemName(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "초밥";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "만두";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "햄버거";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "김밥";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetInvincibleItemName(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "라멘";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "훠궈";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "핫도그";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "바나나우유";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetHealthItemAssetPath(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "Assets/Sprites/RegionalItems/Regional_Japan_Sushi.png";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "Assets/Sprites/RegionalItems/Regional_China_Dumpling.png";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "Assets/Sprites/RegionalItems/Regional_USA_Burger.png";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/RegionalItems/Regional_Korea_Kimbap.png";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetInvincibleItemAssetPath(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "Assets/Sprites/RegionalItems/Regional_Japan_Ramen.png";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "Assets/Sprites/RegionalItems/Regional_China_Hotpot.png";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "Assets/Sprites/RegionalItems/Regional_USA_Hotdog.png";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/RegionalItems/Regional_Korea_BananaMilk.png";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetEnergyDrinkAssetPath(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "Assets/Sprites/RegionalItems/Regional_Japan_EnergyDrink.png";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "Assets/Sprites/RegionalItems/Regional_China_EnergyDrink.png";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "Assets/Sprites/RegionalItems/Regional_USA_EnergyDrink.png";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/RegionalItems/Regional_Korea_EnergyDrink.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ea4c33f88974b12862ce6f4a258f1f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -151,16 +151,14 @@ public class ItemFeedbackEffect : MonoBehaviour
|
||||
{
|
||||
if(blockedDamage)
|
||||
{
|
||||
return itemType == GameManager.TutorialItemType.InvincibleRamen ? "SAFE" : "BLOCK";
|
||||
return "SAFE";
|
||||
}
|
||||
|
||||
switch(itemType)
|
||||
{
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
return "SAFE";
|
||||
case GameManager.TutorialItemType.Shield:
|
||||
return "SHIELD";
|
||||
case GameManager.TutorialItemType.SpeedShoes:
|
||||
case GameManager.TutorialItemType.EnergyDrink:
|
||||
return "SPEED";
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return "x2";
|
||||
@@ -174,18 +172,14 @@ public class ItemFeedbackEffect : MonoBehaviour
|
||||
{
|
||||
if(blockedDamage)
|
||||
{
|
||||
return itemType == GameManager.TutorialItemType.InvincibleRamen
|
||||
? new Color(1f, 0.76f, 0.24f, 1f)
|
||||
: new Color(0.36f, 0.82f, 1f, 1f);
|
||||
return new Color(1f, 0.76f, 0.24f, 1f);
|
||||
}
|
||||
|
||||
switch(itemType)
|
||||
{
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
return new Color(1f, 0.58f, 0.12f, 1f);
|
||||
case GameManager.TutorialItemType.Shield:
|
||||
return new Color(0.32f, 0.78f, 1f, 1f);
|
||||
case GameManager.TutorialItemType.SpeedShoes:
|
||||
case GameManager.TutorialItemType.EnergyDrink:
|
||||
return new Color(0.35f, 1f, 0.5f, 1f);
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return new Color(1f, 0.92f, 0.26f, 1f);
|
||||
|
||||
@@ -62,9 +62,7 @@ public class ItemPickup : MonoBehaviour
|
||||
{
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
return new Color(1f, 0.58f, 0.12f, 1f);
|
||||
case GameManager.TutorialItemType.Shield:
|
||||
return new Color(0.32f, 0.78f, 1f, 1f);
|
||||
case GameManager.TutorialItemType.SpeedShoes:
|
||||
case GameManager.TutorialItemType.EnergyDrink:
|
||||
return new Color(0.35f, 1f, 0.5f, 1f);
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return new Color(1f, 0.92f, 0.26f, 1f);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class AmericaMap {
|
||||
public static MapDefinition Create() {
|
||||
return new MapDefinition {
|
||||
@@ -15,24 +13,7 @@ public static class AmericaMap {
|
||||
clearMileageReward = 500,
|
||||
hasNextMap = false,
|
||||
nextMapId = MapId.America,
|
||||
openingSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
loopSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowMid),
|
||||
Basic(GroundY, 0.86f),
|
||||
Slide(SlideObstaclePattern.SlideLayout.WidePair),
|
||||
Basic(),
|
||||
Aerial(AerialY + 0.2f, 1.0f),
|
||||
Basic(GroundY, 0.86f),
|
||||
Jump(Platform.PlatformPattern.LowLeft),
|
||||
Basic(),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowRight)
|
||||
}
|
||||
courseProfile = AmericaCourseProfile.Create()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class ChinaMap {
|
||||
public static MapDefinition Create() {
|
||||
return new MapDefinition {
|
||||
@@ -8,29 +6,14 @@ public static class ChinaMap {
|
||||
routeName = "베이징 시장길",
|
||||
isTutorial = false,
|
||||
platformSkin = Platform.PlatformSkin.China,
|
||||
backgroundResourcePath = "",
|
||||
backgroundResourcePath = "Map_China_Beijing",
|
||||
finishGateStyle = FinishGateStyle.ChinaPaifang,
|
||||
targetDistance = 1450f,
|
||||
timeLimit = 115f,
|
||||
clearMileageReward = 350,
|
||||
hasNextMap = true,
|
||||
nextMapId = MapId.America,
|
||||
openingSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
loopSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowRight),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.LeftPair),
|
||||
Basic(),
|
||||
Aerial(AerialY + 0.15f, 1.08f),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Jump(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.RightPair)
|
||||
}
|
||||
courseProfile = ChinaCourseProfile.Create()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class JapanMap {
|
||||
public static MapDefinition Create() {
|
||||
return new MapDefinition {
|
||||
@@ -8,28 +6,14 @@ public static class JapanMap {
|
||||
routeName = "후쿠오카 라멘 투어",
|
||||
isTutorial = false,
|
||||
platformSkin = Platform.PlatformSkin.Japan,
|
||||
backgroundResourcePath = "",
|
||||
backgroundResourcePath = "Map_Japan_Fukuoka",
|
||||
finishGateStyle = FinishGateStyle.JapanTorii,
|
||||
targetDistance = 1200f,
|
||||
timeLimit = 105f,
|
||||
clearMileageReward = 250,
|
||||
hasNextMap = true,
|
||||
nextMapId = MapId.China,
|
||||
openingSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
loopSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Jump(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Aerial(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowLeft),
|
||||
Basic()
|
||||
}
|
||||
courseProfile = JapanCourseProfile.Create()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using static CourseStepFactory;
|
||||
|
||||
public static class TutorialIncheonMap {
|
||||
public static MapDefinition Create() {
|
||||
return new MapDefinition {
|
||||
@@ -8,49 +6,23 @@ public static class TutorialIncheonMap {
|
||||
routeName = "인천공항 출발 준비",
|
||||
isTutorial = true,
|
||||
platformSkin = Platform.PlatformSkin.Airport,
|
||||
backgroundResourcePath = "Map_Tutorial_Incheon_Airport",
|
||||
backgroundResourcePath = "StageBackgrounds/Incheon/Stage_Incheon_DarkLower_Segment_01_CheckInHall",
|
||||
backgroundResourcePaths = new string[] {
|
||||
"StageBackgrounds/Incheon/Stage_Incheon_DarkLower_Segment_01_CheckInHall",
|
||||
"StageBackgrounds/Incheon/Stage_Incheon_DarkLower_Segment_02_DepartureSecurity",
|
||||
"StageBackgrounds/Incheon/Stage_Incheon_DarkLower_Segment_03_DutyFreeConcourse",
|
||||
"StageBackgrounds/Incheon/Stage_Incheon_DarkLower_Segment_04_MovingWalkwayWindows",
|
||||
"StageBackgrounds/Incheon/Stage_Incheon_DarkLower_Segment_05_BoardingGate"
|
||||
},
|
||||
backgroundFitScaleMultiplier = 1f,
|
||||
backgroundVerticalOffset = 0f,
|
||||
finishGateStyle = FinishGateStyle.AirportDeparture,
|
||||
targetDistance = 380f,
|
||||
timeLimit = 85f,
|
||||
clearMileageReward = 150,
|
||||
hasNextMap = true,
|
||||
nextMapId = MapId.Japan,
|
||||
openingSteps = new CourseStep[] {
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Jump(GameManager.TutorialGuideType.Jump, "점프 구간", "가방 장애물이 오면 왼쪽 클릭으로 뛰어넘습니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Aerial(GameManager.TutorialGuideType.DoubleJump, "2단 점프 구간", "다음 발판이 멀리 떨어져 있습니다.\n공중에서 왼쪽 클릭을 한 번 더 누릅니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Slide(GameManager.TutorialGuideType.Slide, "슬라이드 구간", "천장 장애물이 오면 오른쪽 클릭을 누르고 지나갑니다."),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "초밥", "잃은 목숨을 1 회복합니다.\n방금 줄어든 목숨을 채워 봅니다.", Platform.ItemPattern.HealthSushi),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "라멘", "잠깐 무적이 됩니다.\n먹은 뒤 다음 장애물에 일부러 부딪혀 봅니다.", Platform.ItemPattern.InvincibleRamen),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Basic(GroundY, BasicSpacing),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "방어막", "다음 충돌 1회를 막습니다.\n먹은 뒤 바로 충돌을 막아 봅니다.", Platform.ItemPattern.Shield),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "마일리지 카드", "잠깐 마일리지가 2배가 됩니다.\n먹고 바로 마일리지를 모아 봅니다.", Platform.ItemPattern.MileageCard),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "운동화", "느려진 속도를 회복합니다.\n방금 떨어진 속도를 되돌려 봅니다.", Platform.ItemPattern.SpeedShoes),
|
||||
Basic(GroundY, LandingSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "실전 연습", "시간이 지나면 달리기 속도는 조금씩 빨라지고, 장애물에 맞으면 느려집니다.\n방금 먹은 신발 아이템으로 회복한 뒤 출국 게이트까지 달려보세요."),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(GroundY, LandingSpacing)
|
||||
},
|
||||
loopSteps = CreateEasyLoop()
|
||||
courseProfile = TutorialCourseProfile.Create()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,14 +73,20 @@ public static class MapDatabase {
|
||||
isTutorial = source.isTutorial,
|
||||
platformSkin = source.platformSkin,
|
||||
backgroundResourcePath = source.backgroundResourcePath,
|
||||
backgroundResourcePaths = source.backgroundResourcePaths != null
|
||||
? (string[]) source.backgroundResourcePaths.Clone()
|
||||
: null,
|
||||
backgroundFitScaleMultiplier = source.backgroundFitScaleMultiplier,
|
||||
backgroundVerticalOffset = source.backgroundVerticalOffset,
|
||||
finishGateStyle = source.finishGateStyle,
|
||||
targetDistance = source.targetDistance,
|
||||
timeLimit = source.timeLimit,
|
||||
clearMileageReward = source.clearMileageReward,
|
||||
hasNextMap = source.hasNextMap,
|
||||
nextMapId = source.nextMapId,
|
||||
openingSteps = CourseStepFactory.CloneSteps(source.openingSteps),
|
||||
loopSteps = CourseStepFactory.CloneSteps(source.loopSteps)
|
||||
courseProfile = CourseProfile.Create(source.GetOpeningSteps(), source.GetLoopSteps()),
|
||||
openingSteps = source.GetOpeningSteps(),
|
||||
loopSteps = source.GetLoopSteps()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,39 @@ public class MapDefinition {
|
||||
public bool isTutorial;
|
||||
public Platform.PlatformSkin platformSkin;
|
||||
public string backgroundResourcePath;
|
||||
public string[] backgroundResourcePaths;
|
||||
public float backgroundFitScaleMultiplier = 1f;
|
||||
public float backgroundVerticalOffset = 0f;
|
||||
public FinishGateStyle finishGateStyle;
|
||||
public float targetDistance;
|
||||
public float timeLimit;
|
||||
public int clearMileageReward;
|
||||
public bool hasNextMap;
|
||||
public MapId nextMapId;
|
||||
public CourseProfile courseProfile;
|
||||
public CourseStep[] openingSteps;
|
||||
public CourseStep[] loopSteps;
|
||||
|
||||
public CourseStep[] GetOpeningSteps() {
|
||||
return courseProfile != null
|
||||
? courseProfile.GetOpeningSteps()
|
||||
: CourseStepFactory.CloneSteps(openingSteps);
|
||||
}
|
||||
|
||||
public CourseStep[] GetLoopSteps() {
|
||||
return courseProfile != null
|
||||
? courseProfile.GetLoopSteps()
|
||||
: CourseStepFactory.CloneSteps(loopSteps);
|
||||
}
|
||||
|
||||
public string[] GetBackgroundResourcePaths() {
|
||||
if(backgroundResourcePaths != null && backgroundResourcePaths.Length > 0)
|
||||
{
|
||||
return (string[]) backgroundResourcePaths.Clone();
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(backgroundResourcePath)
|
||||
? new string[0]
|
||||
: new string[] { backgroundResourcePath };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,7 @@ public class Platform : MonoBehaviour {
|
||||
None,
|
||||
HealthSushi,
|
||||
InvincibleRamen,
|
||||
Shield,
|
||||
SpeedShoes,
|
||||
MileageCard
|
||||
EnergyDrink
|
||||
}
|
||||
|
||||
public enum PlatformSkin {
|
||||
@@ -224,30 +222,13 @@ public class Platform : MonoBehaviour {
|
||||
return;
|
||||
}
|
||||
|
||||
GameManager.TutorialItemType itemType = GetTutorialItemType(reservedItemPattern);
|
||||
GameManager.TutorialItemType itemType = GameplayItemCatalog.ToTutorialItemType(reservedItemPattern);
|
||||
ItemPickup pickup = itemPickups[0];
|
||||
pickup.Setup(LoadItemSprite(itemType), itemType);
|
||||
pickup.transform.localPosition = GetParentScaleCompensatedPosition(GetItemPosition(reservedItemPattern));
|
||||
pickup.ResetPickup();
|
||||
}
|
||||
|
||||
private GameManager.TutorialItemType GetTutorialItemType(ItemPattern itemPattern) {
|
||||
switch(itemPattern)
|
||||
{
|
||||
case ItemPattern.InvincibleRamen:
|
||||
return GameManager.TutorialItemType.InvincibleRamen;
|
||||
case ItemPattern.Shield:
|
||||
return GameManager.TutorialItemType.Shield;
|
||||
case ItemPattern.SpeedShoes:
|
||||
return GameManager.TutorialItemType.SpeedShoes;
|
||||
case ItemPattern.MileageCard:
|
||||
return GameManager.TutorialItemType.MileageCard;
|
||||
case ItemPattern.HealthSushi:
|
||||
default:
|
||||
return GameManager.TutorialItemType.HealthSushi;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetItemPosition(ItemPattern itemPattern) {
|
||||
const float frontX = -1.45f;
|
||||
|
||||
@@ -257,11 +238,7 @@ public class Platform : MonoBehaviour {
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.InvincibleRamen:
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.Shield:
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.SpeedShoes:
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.MileageCard:
|
||||
case ItemPattern.EnergyDrink:
|
||||
return new Vector2(frontX, 1.55f);
|
||||
default:
|
||||
return new Vector2(0f, 1.55f);
|
||||
@@ -328,31 +305,12 @@ public class Platform : MonoBehaviour {
|
||||
return "Assets/Sprites/Mileage/Mileage_Korea.png";
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private Sprite LoadItemSprite(GameManager.TutorialItemType itemType) {
|
||||
#if UNITY_EDITOR
|
||||
string assetPath = "";
|
||||
|
||||
switch(itemType)
|
||||
{
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
assetPath = "Assets/Sprites/Items/Item_Invincible_Ramen.png";
|
||||
break;
|
||||
case GameManager.TutorialItemType.Shield:
|
||||
assetPath = "Assets/Sprites/Items/Item_Shield.png";
|
||||
break;
|
||||
case GameManager.TutorialItemType.SpeedShoes:
|
||||
assetPath = "Assets/Sprites/Items/Item_Speed_Shoes.png";
|
||||
break;
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
assetPath = "Assets/Sprites/Items/Item_Mileage_Card.png";
|
||||
break;
|
||||
case GameManager.TutorialItemType.HealthSushi:
|
||||
default:
|
||||
assetPath = "Assets/Sprites/Items/Item_Health_Sushi.png";
|
||||
break;
|
||||
}
|
||||
string assetPath = GameplayItemCatalog.GetSpriteAssetPath(itemType, reservedSkin);
|
||||
|
||||
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||||
#else
|
||||
|
||||
@@ -29,11 +29,7 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
public float tutorialSpawnInterval = 2.2f; // 튜토리얼 발판 배치 간격
|
||||
private float timeBetSpawn; // 다음 배치까지의 시간 간격
|
||||
|
||||
private const float FallbackGroundY = -2.4f;
|
||||
private const float FallbackBasicSpacing = 0.58f;
|
||||
private const float FallbackActionSpacing = 0.72f;
|
||||
private const float FallbackLandingSpacing = 0.64f;
|
||||
private const float FallbackAerialSpacing = 2.05f;
|
||||
|
||||
public float yMin = -3.5f; // 배치할 위치의 최소 y값
|
||||
public float yMax = 1.5f; // 배치할 위치의 최대 y값
|
||||
@@ -64,8 +60,8 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
usePatternCourse = true;
|
||||
tutorialMode = mapDefinition.isTutorial;
|
||||
mapPlatformSkin = mapDefinition.platformSkin;
|
||||
tutorialSteps = CourseStepFactory.CloneSteps(mapDefinition.openingSteps);
|
||||
stagePatternSteps = CourseStepFactory.CloneSteps(mapDefinition.loopSteps);
|
||||
tutorialSteps = mapDefinition.GetOpeningSteps();
|
||||
stagePatternSteps = mapDefinition.GetLoopSteps();
|
||||
courseStepIndex = 0;
|
||||
courseSpawningStopped = false;
|
||||
pendingTutorialGuides.Clear();
|
||||
@@ -479,63 +475,19 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
}
|
||||
|
||||
private void EnsurePatternSteps() {
|
||||
EnsureTutorialSteps();
|
||||
EnsureStagePatternSteps();
|
||||
}
|
||||
CourseProfile fallbackProfile = tutorialMode
|
||||
? FallbackCourseProfile.CreateTutorialFallback()
|
||||
: FallbackCourseProfile.CreateGeneralFallback();
|
||||
|
||||
private void EnsureTutorialSteps() {
|
||||
if(tutorialSteps != null && tutorialSteps.Length > 0)
|
||||
if(tutorialSteps == null || tutorialSteps.Length == 0)
|
||||
{
|
||||
return;
|
||||
tutorialSteps = fallbackProfile.GetOpeningSteps();
|
||||
}
|
||||
|
||||
tutorialSteps = new CourseStep[] {
|
||||
CreateTutorialStep(CourseStepKind.Jump, Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(CourseStepKind.DoubleJumpTakeoff, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackAerialSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(CourseStepKind.Slide, Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(CourseStepKind.ForceHit, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(CourseStepKind.HealthItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing, Platform.ItemPattern.HealthSushi),
|
||||
CreateTutorialStep(CourseStepKind.InvincibleItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.InvincibleRamen),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
CreateTutorialStep(CourseStepKind.ShieldItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.Shield),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(CourseStepKind.MileageCardItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.MileageCard),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(CourseStepKind.SpeedItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.SpeedShoes),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
CreateTutorialStep(CourseStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
};
|
||||
}
|
||||
|
||||
private void EnsureStagePatternSteps() {
|
||||
if(stagePatternSteps != null && stagePatternSteps.Length > 0)
|
||||
if(stagePatternSteps == null || stagePatternSteps.Length == 0)
|
||||
{
|
||||
return;
|
||||
stagePatternSteps = fallbackProfile.GetLoopSteps();
|
||||
}
|
||||
|
||||
stagePatternSteps = new CourseStep[] {
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackBasicSpacing, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Jump),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackLandingSpacing, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Slide),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackBasicSpacing, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowLeft, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Jump),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.LeftPair, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Slide),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackBasicSpacing, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowRight, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Jump),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackAerialSpacing, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.RightPair, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Slide),
|
||||
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Jump),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackBasicSpacing, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.None, FallbackGroundY, FallbackActionSpacing, "", "", Platform.ItemPattern.None, CoursePlatformType.Slide),
|
||||
};
|
||||
}
|
||||
|
||||
private CourseStep CreateStep(
|
||||
@@ -563,99 +515,4 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
return step;
|
||||
}
|
||||
|
||||
private CourseStep CreateTutorialStep(
|
||||
CourseStepKind 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 CourseStepKind.Jump:
|
||||
guideType = GameManager.TutorialGuideType.Jump;
|
||||
title = "점프";
|
||||
message = "가방 장애물이 보이면 왼쪽 클릭으로 뛰어넘습니다.";
|
||||
break;
|
||||
case CourseStepKind.DoubleJumpTakeoff:
|
||||
guideType = GameManager.TutorialGuideType.DoubleJump;
|
||||
title = "2단 점프";
|
||||
message = "다음 발판이 멀리 있으면 공중에서 한 번 더 왼쪽 클릭합니다.";
|
||||
break;
|
||||
case CourseStepKind.Slide:
|
||||
guideType = GameManager.TutorialGuideType.Slide;
|
||||
title = "슬라이드";
|
||||
message = "천장 표지판이 보이면 오른쪽 클릭을 누르고 지나갑니다.";
|
||||
break;
|
||||
case CourseStepKind.ForceHit:
|
||||
guideType = GameManager.TutorialGuideType.Damage;
|
||||
title = "피격";
|
||||
message = "이번 구간은 일부러 맞아 목숨과 속도 감소를 확인합니다.";
|
||||
break;
|
||||
case CourseStepKind.HealthItem:
|
||||
guideType = GameManager.TutorialGuideType.Items;
|
||||
title = "초밥";
|
||||
message = "초밥을 먹으면 잃은 목숨을 1 회복합니다.";
|
||||
break;
|
||||
case CourseStepKind.InvincibleItem:
|
||||
guideType = GameManager.TutorialGuideType.Items;
|
||||
title = "라멘";
|
||||
message = "라멘을 먹고 다음 충돌을 피해 없이 지나가 봅니다.";
|
||||
break;
|
||||
case CourseStepKind.ShieldItem:
|
||||
guideType = GameManager.TutorialGuideType.Items;
|
||||
title = "방어막";
|
||||
message = "방어막을 먹고 다음 충돌 1회를 막아 봅니다.";
|
||||
break;
|
||||
case CourseStepKind.SpeedItem:
|
||||
guideType = GameManager.TutorialGuideType.Items;
|
||||
title = "운동화";
|
||||
message = "운동화는 속도를 회복해 일정이 늦어지는 것을 줄입니다.";
|
||||
break;
|
||||
case CourseStepKind.MileageCardItem:
|
||||
guideType = GameManager.TutorialGuideType.Items;
|
||||
title = "마일리지 카드";
|
||||
message = "카드를 먹은 뒤 마일리지를 모으면 획득량이 늘어납니다.";
|
||||
break;
|
||||
}
|
||||
|
||||
return CreateStep(
|
||||
platformPattern,
|
||||
mileagePattern,
|
||||
slideLayout,
|
||||
guideType,
|
||||
yPosition,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
itemPattern,
|
||||
GetCoursePlatformType(stepKind, platformPattern, yPosition));
|
||||
}
|
||||
|
||||
private CoursePlatformType GetCoursePlatformType(CourseStepKind stepKind, Platform.PlatformPattern platformPattern, float yPosition) {
|
||||
if(stepKind == CourseStepKind.ForceHit || platformPattern == Platform.PlatformPattern.ForceHit)
|
||||
{
|
||||
return CoursePlatformType.ForceHit;
|
||||
}
|
||||
|
||||
if(stepKind == CourseStepKind.Slide || platformPattern == Platform.PlatformPattern.Slide)
|
||||
{
|
||||
return CoursePlatformType.Slide;
|
||||
}
|
||||
|
||||
if(stepKind == CourseStepKind.Jump
|
||||
|| platformPattern == Platform.PlatformPattern.LowLeft
|
||||
|| platformPattern == Platform.PlatformPattern.LowMid
|
||||
|| platformPattern == Platform.PlatformPattern.LowRight)
|
||||
{
|
||||
return CoursePlatformType.Jump;
|
||||
}
|
||||
|
||||
return CoursePlatformType.Basic;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ using UnityEngine;
|
||||
// PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다.
|
||||
public class PlayerController : MonoBehaviour {
|
||||
public AudioClip deathClip; // 사망시 재생할 오디오 클립
|
||||
public AudioClip jumpClip; // 점프시 재생할 오디오 클립
|
||||
public AudioClip doubleJumpClip; // 2단 점프시 재생할 오디오 클립
|
||||
public AudioClip slideClip; // 슬라이딩 시작시 재생할 오디오 클립
|
||||
[Range(0f, 1f)]
|
||||
public float movementSfxVolume = 0.72f; // 이동 효과음 볼륨
|
||||
public float jumpForce = 600f; // 점프 힘
|
||||
public int maxJumpCount = 2; // 최대 점프 횟수
|
||||
public float invincibleDuration = 1.5f; // 피해를 입은 뒤 무적 시간
|
||||
@@ -37,6 +42,8 @@ public class PlayerController : MonoBehaviour {
|
||||
animator = GetComponent<Animator>();
|
||||
playerAudio = GetComponent<AudioSource>();
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
ConfigureAudioSource();
|
||||
LoadMovementSfxDefaults();
|
||||
|
||||
// 게임 콜라이더를 가져와서 현 상태를 저장
|
||||
playerCollider = GetComponent<CapsuleCollider2D>();
|
||||
@@ -64,7 +71,7 @@ public class PlayerController : MonoBehaviour {
|
||||
|
||||
// 리지드바디에 위쪽으로 힘을 주기
|
||||
playerRigidbody.AddForce(new Vector2(0, jumpForce));
|
||||
playerAudio.Play();
|
||||
PlayJumpSfx();
|
||||
}
|
||||
else if(Input.GetMouseButtonUp(0) && playerRigidbody.linearVelocityY > 0)
|
||||
{
|
||||
@@ -174,6 +181,7 @@ public class PlayerController : MonoBehaviour {
|
||||
private void StartSlide() {
|
||||
isSliding = true;
|
||||
animator.SetBool("Sliding", true);
|
||||
PlayMovementSfx(slideClip, movementSfxVolume * 0.72f);
|
||||
|
||||
playerCollider.size = slidingColliderSize;
|
||||
playerCollider.offset = slidingColliderOffset;
|
||||
@@ -238,4 +246,50 @@ public class PlayerController : MonoBehaviour {
|
||||
spriteRenderer.color = Color.white;
|
||||
isInvincible = false;
|
||||
}
|
||||
|
||||
private void ConfigureAudioSource() {
|
||||
if(playerAudio == null)
|
||||
{
|
||||
playerAudio = gameObject.AddComponent<AudioSource>();
|
||||
}
|
||||
|
||||
playerAudio.playOnAwake = false;
|
||||
playerAudio.spatialBlend = 0f;
|
||||
}
|
||||
|
||||
private void LoadMovementSfxDefaults() {
|
||||
if(jumpClip == null)
|
||||
{
|
||||
jumpClip = Resources.Load<AudioClip>("SFX/SFX_Player_Jump");
|
||||
}
|
||||
|
||||
if(doubleJumpClip == null)
|
||||
{
|
||||
doubleJumpClip = Resources.Load<AudioClip>("SFX/SFX_Player_DoubleJump");
|
||||
}
|
||||
|
||||
if(slideClip == null)
|
||||
{
|
||||
slideClip = Resources.Load<AudioClip>("SFX/SFX_Player_Slide");
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayJumpSfx() {
|
||||
AudioClip clip = jumpCount > 1 && doubleJumpClip != null
|
||||
? doubleJumpClip
|
||||
: jumpClip;
|
||||
float volume = jumpCount > 1
|
||||
? movementSfxVolume * 0.9f
|
||||
: movementSfxVolume;
|
||||
PlayMovementSfx(clip, volume);
|
||||
}
|
||||
|
||||
private void PlayMovementSfx(AudioClip clip, float volume) {
|
||||
if(playerAudio == null || clip == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
playerAudio.PlayOneShot(clip, Mathf.Clamp01(volume));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user