Build TravelRun intro and tutorial flow
This commit is contained in:
@@ -4,13 +4,32 @@ using UnityEngine;
|
||||
[ExecuteAlways]
|
||||
public class BackgroundLoop : MonoBehaviour {
|
||||
public Sprite[] backgroundSprites; // 순서대로 교체할 공항 배경 스프라이트
|
||||
public bool useResourceBackground = true; // Resources 폴더의 맵 배경을 자동으로 사용
|
||||
public string resourceBackgroundPath = ""; // 맵 데이터가 없을 때 사용할 Resources 배경
|
||||
public int startSpriteIndex = 0; // 이 배경 오브젝트가 시작할 스프라이트 번호
|
||||
public int spriteAdvanceOnLoop = 2; // 배경 오브젝트가 두 장이므로 재배치될 때 두 칸씩 진행
|
||||
|
||||
private static string activeResourceBackgroundPath = "";
|
||||
|
||||
private float width; // 배경의 가로 길이
|
||||
private int currentSpriteIndex; // 현재 표시 중인 스프라이트 번호
|
||||
private SpriteRenderer spriteRenderer; // 배경을 표시하는 스프라이트 렌더러
|
||||
private BoxCollider2D backgroundCollider; // 루프 폭 계산용 콜라이더
|
||||
private Sprite[] originalBackgroundSprites; // 맵 배경 리소스가 없을 때 되돌릴 기본 배경
|
||||
private bool originalBackgroundSpritesCached = false;
|
||||
|
||||
public static void ApplyResourceBackground(string resourcePath) {
|
||||
activeResourceBackgroundPath = resourcePath;
|
||||
|
||||
BackgroundLoop[] backgroundLoops = FindObjectsByType<BackgroundLoop>(FindObjectsSortMode.None);
|
||||
for(int i = 0; i < backgroundLoops.Length; i++)
|
||||
{
|
||||
if(backgroundLoops[i] != null)
|
||||
{
|
||||
backgroundLoops[i].InitializeBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake() {
|
||||
InitializeBackground();
|
||||
@@ -39,6 +58,8 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
|
||||
private void InitializeBackground() {
|
||||
CacheComponents();
|
||||
CacheOriginalBackgroundSprites();
|
||||
EnsureResourceBackground();
|
||||
|
||||
currentSpriteIndex = NormalizeSpriteIndex(startSpriteIndex);
|
||||
ApplyBackgroundSprite(currentSpriteIndex);
|
||||
@@ -58,6 +79,46 @@ public class BackgroundLoop : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureResourceBackground() {
|
||||
if(!useResourceBackground)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string finalResourcePath = !string.IsNullOrEmpty(activeResourceBackgroundPath)
|
||||
? activeResourceBackgroundPath
|
||||
: resourceBackgroundPath;
|
||||
|
||||
if(string.IsNullOrEmpty(finalResourcePath))
|
||||
{
|
||||
backgroundSprites = originalBackgroundSprites;
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite resourceSprite = Resources.Load<Sprite>(finalResourcePath);
|
||||
if(resourceSprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(backgroundSprites != null && backgroundSprites.Length == 1 && backgroundSprites[0] == resourceSprite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
backgroundSprites = new Sprite[] { resourceSprite };
|
||||
}
|
||||
|
||||
private void CacheOriginalBackgroundSprites() {
|
||||
if(originalBackgroundSpritesCached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
originalBackgroundSprites = backgroundSprites;
|
||||
originalBackgroundSpritesCached = true;
|
||||
}
|
||||
|
||||
private void RefreshWidth() {
|
||||
if(spriteRenderer != null && spriteRenderer.sprite != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
// 코스 데이터가 발판 위에 따로 얹는 장애물 프리팹을 제어한다.
|
||||
public class CourseObstacle : MonoBehaviour {
|
||||
public enum ObstacleKind {
|
||||
Jump,
|
||||
Slide
|
||||
}
|
||||
|
||||
public ObstacleKind obstacleKind = ObstacleKind.Jump;
|
||||
public Transform lowObstacle;
|
||||
public SlideObstaclePattern slidePattern;
|
||||
public Transform ceilingPlatform;
|
||||
|
||||
private Platform.PlatformSkin reservedSkin = Platform.PlatformSkin.Airport;
|
||||
private Platform.PlatformPattern reservedPlatformPattern = Platform.PlatformPattern.LowMid;
|
||||
private SlideObstaclePattern.SlideLayout reservedSlideLayout = SlideObstaclePattern.SlideLayout.CenterPair;
|
||||
|
||||
public void Configure(
|
||||
Platform.PlatformSkin platformSkin,
|
||||
Platform.PlatformPattern platformPattern,
|
||||
SlideObstaclePattern.SlideLayout slideLayout) {
|
||||
reservedSkin = platformSkin;
|
||||
reservedPlatformPattern = platformPattern;
|
||||
reservedSlideLayout = slideLayout;
|
||||
ApplyConfiguration();
|
||||
}
|
||||
|
||||
private void Awake() {
|
||||
CacheReferences();
|
||||
}
|
||||
|
||||
private void OnEnable() {
|
||||
ApplyConfiguration();
|
||||
}
|
||||
|
||||
private void CacheReferences() {
|
||||
if(lowObstacle == null)
|
||||
{
|
||||
Transform foundObstacle = transform.Find("Jump Obstacle");
|
||||
if(foundObstacle != null)
|
||||
{
|
||||
lowObstacle = foundObstacle;
|
||||
}
|
||||
}
|
||||
|
||||
if(slidePattern == null)
|
||||
{
|
||||
slidePattern = GetComponent<SlideObstaclePattern>();
|
||||
}
|
||||
|
||||
if(ceilingPlatform == null)
|
||||
{
|
||||
Transform foundCeiling = transform.Find("Ceiling Platform");
|
||||
if(foundCeiling != null)
|
||||
{
|
||||
ceilingPlatform = foundCeiling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyConfiguration() {
|
||||
CacheReferences();
|
||||
|
||||
if(obstacleKind == ObstacleKind.Jump)
|
||||
{
|
||||
ApplyJumpPosition();
|
||||
}
|
||||
else if(slidePattern != null)
|
||||
{
|
||||
slidePattern.ConfigureLayout(reservedSlideLayout);
|
||||
}
|
||||
|
||||
ApplyVisualSkin();
|
||||
}
|
||||
|
||||
private void ApplyJumpPosition() {
|
||||
if(lowObstacle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 localPosition = lowObstacle.localPosition;
|
||||
localPosition.x = GetJumpObstacleX(reservedPlatformPattern);
|
||||
lowObstacle.localPosition = localPosition;
|
||||
}
|
||||
|
||||
private float GetJumpObstacleX(Platform.PlatformPattern platformPattern) {
|
||||
switch(platformPattern)
|
||||
{
|
||||
case Platform.PlatformPattern.LowLeft:
|
||||
return -0.44f;
|
||||
case Platform.PlatformPattern.LowRight:
|
||||
return 0.44f;
|
||||
case Platform.PlatformPattern.LowMid:
|
||||
default:
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyVisualSkin() {
|
||||
#if UNITY_EDITOR
|
||||
if(obstacleKind == ObstacleKind.Jump)
|
||||
{
|
||||
ApplySprite(lowObstacle, LoadSpriteAtPath(GetLowObstacleSkinPath(reservedSkin)));
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite hangingSprite = LoadSpriteAtPath(GetHangingObstacleSkinPath(reservedSkin));
|
||||
Sprite ceilingSprite = LoadSpriteAtPath(GetCeilingSkinPath(reservedSkin));
|
||||
|
||||
if(slidePattern != null && slidePattern.obstacles != null)
|
||||
{
|
||||
for(int i = 0; i < slidePattern.obstacles.Length; i++)
|
||||
{
|
||||
ApplySprite(slidePattern.obstacles[i], hangingSprite);
|
||||
}
|
||||
}
|
||||
|
||||
ApplySprite(ceilingPlatform, ceilingSprite);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void ApplySprite(Transform target, Sprite sprite) {
|
||||
if(target == null || sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SpriteRenderer spriteRenderer = target.GetComponent<SpriteRenderer>();
|
||||
if(spriteRenderer != null)
|
||||
{
|
||||
spriteRenderer.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite LoadSpriteAtPath(string assetPath) {
|
||||
if(string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||||
}
|
||||
|
||||
private string GetLowObstacleSkinPath(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_StoneLantern.png";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_MarketCrate.png";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_Cone.png";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_Suitcases.png";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHangingObstacleSkinPath(Platform.PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_RoadSign.png";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_RedLanterns.png";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_Banner.png";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_AirportSign.png";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCeilingSkinPath(Platform.PlatformSkin platformSkin) {
|
||||
if(platformSkin == Platform.PlatformSkin.Airport)
|
||||
{
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_AirportCeiling.png";
|
||||
}
|
||||
|
||||
switch(platformSkin)
|
||||
{
|
||||
case Platform.PlatformSkin.Japan:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_Japan.png";
|
||||
case Platform.PlatformSkin.China:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_China.png";
|
||||
case Platform.PlatformSkin.USA:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_USA.png";
|
||||
case Platform.PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_AirportFloorTile.png";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a11c0b8fb5a84de9a9ef6ef13f000010
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,283 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
public enum FinishGateStyle {
|
||||
None,
|
||||
AirportDeparture,
|
||||
JapanTorii,
|
||||
ChinaPaifang,
|
||||
AmericaRoadSign
|
||||
}
|
||||
|
||||
public class FinishGate : MonoBehaviour {
|
||||
private const string ForegroundSortingLayer = "Foreground";
|
||||
private const float DespawnX = -18f;
|
||||
private const float RuntimeSpritePixelsPerUnit = 256f;
|
||||
|
||||
private static Sprite blockSprite;
|
||||
private bool cleared = false;
|
||||
|
||||
public void Setup(FinishGateStyle style) {
|
||||
cleared = false;
|
||||
|
||||
ScrollingObject scrollingObject = GetComponent<ScrollingObject>();
|
||||
if(scrollingObject == null)
|
||||
{
|
||||
scrollingObject = gameObject.AddComponent<ScrollingObject>();
|
||||
}
|
||||
scrollingObject.speed = 10f;
|
||||
|
||||
BoxCollider2D trigger = GetComponent<BoxCollider2D>();
|
||||
if(trigger == null)
|
||||
{
|
||||
trigger = gameObject.AddComponent<BoxCollider2D>();
|
||||
}
|
||||
|
||||
ConfigureTrigger(trigger);
|
||||
|
||||
BuildGate(style);
|
||||
}
|
||||
|
||||
private void Update() {
|
||||
if(transform.position.x < DespawnX)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other) {
|
||||
if(cleared || !other.CompareTag("Player") || !GameFlowManager.IsGameplayActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cleared = true;
|
||||
|
||||
if(GameManager.instance != null)
|
||||
{
|
||||
GameManager.instance.CompleteCurrentStageFromFinishGate();
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildGate(FinishGateStyle style) {
|
||||
ClearChildren();
|
||||
|
||||
if(TryBuildSpriteGate(style))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(style)
|
||||
{
|
||||
case FinishGateStyle.JapanTorii:
|
||||
BuildJapanTorii();
|
||||
break;
|
||||
case FinishGateStyle.ChinaPaifang:
|
||||
BuildChinaPaifang();
|
||||
break;
|
||||
case FinishGateStyle.AmericaRoadSign:
|
||||
BuildAmericaRoadSign();
|
||||
break;
|
||||
case FinishGateStyle.AirportDeparture:
|
||||
default:
|
||||
BuildAirportDepartureGate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureTrigger(BoxCollider2D trigger) {
|
||||
trigger.isTrigger = true;
|
||||
trigger.offset = new Vector2(0f, 1.75f);
|
||||
trigger.size = new Vector2(2.2f, 4.2f);
|
||||
}
|
||||
|
||||
private bool TryBuildSpriteGate(FinishGateStyle style) {
|
||||
string resourcePath = GetSpriteResourcePath(style);
|
||||
if(string.IsNullOrEmpty(resourcePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Sprite sprite = Resources.Load<Sprite>(resourcePath);
|
||||
if(sprite == null)
|
||||
{
|
||||
Texture2D texture = Resources.Load<Texture2D>(resourcePath);
|
||||
if(texture != null)
|
||||
{
|
||||
sprite = Sprite.Create(
|
||||
texture,
|
||||
new Rect(0f, 0f, texture.width, texture.height),
|
||||
new Vector2(0.5f, 0.5f),
|
||||
RuntimeSpritePixelsPerUnit);
|
||||
}
|
||||
}
|
||||
|
||||
if(sprite == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject spriteObject = new GameObject("Finish Gate Sprite");
|
||||
spriteObject.transform.SetParent(transform, false);
|
||||
|
||||
SpriteRenderer spriteRenderer = spriteObject.AddComponent<SpriteRenderer>();
|
||||
spriteRenderer.sprite = sprite;
|
||||
spriteRenderer.sortingLayerName = ForegroundSortingLayer;
|
||||
spriteRenderer.sortingOrder = 6;
|
||||
|
||||
float targetHeight = GetSpriteGateHeight(style);
|
||||
float spriteHeight = Mathf.Max(0.01f, sprite.bounds.size.y);
|
||||
float scale = targetHeight / spriteHeight;
|
||||
spriteObject.transform.localScale = new Vector3(scale, scale, 1f);
|
||||
spriteObject.transform.localPosition = new Vector3(0f, targetHeight * 0.5f, 0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetSpriteResourcePath(FinishGateStyle style) {
|
||||
switch(style)
|
||||
{
|
||||
case FinishGateStyle.AirportDeparture:
|
||||
return "FinishGate_Tutorial_Incheon";
|
||||
case FinishGateStyle.JapanTorii:
|
||||
return "FinishGate_Japan_Torii";
|
||||
case FinishGateStyle.ChinaPaifang:
|
||||
return "FinishGate_China_Paifang";
|
||||
case FinishGateStyle.AmericaRoadSign:
|
||||
return "FinishGate_America_Highway";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private float GetSpriteGateHeight(FinishGateStyle style) {
|
||||
switch(style)
|
||||
{
|
||||
case FinishGateStyle.ChinaPaifang:
|
||||
return 4.65f;
|
||||
case FinishGateStyle.AmericaRoadSign:
|
||||
return 4.45f;
|
||||
default:
|
||||
return 4.35f;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildAirportDepartureGate() {
|
||||
Color navy = new Color(0.05f, 0.2f, 0.34f, 1f);
|
||||
Color blue = new Color(0.08f, 0.42f, 0.67f, 1f);
|
||||
Color cyan = new Color(0.18f, 0.83f, 0.95f, 1f);
|
||||
Color yellow = new Color(1f, 0.78f, 0.22f, 1f);
|
||||
Color glass = new Color(0.6f, 0.9f, 1f, 0.55f);
|
||||
|
||||
CreateBlock("Airport Left Pillar", new Vector2(-1.15f, 1.45f), new Vector2(0.22f, 3.1f), navy, 3);
|
||||
CreateBlock("Airport Right Pillar", new Vector2(1.15f, 1.45f), new Vector2(0.22f, 3.1f), navy, 3);
|
||||
CreateBlock("Airport Top Sign", new Vector2(0f, 3.05f), new Vector2(2.75f, 0.64f), blue, 4);
|
||||
CreateBlock("Airport Sign Highlight", new Vector2(0f, 3.35f), new Vector2(2.75f, 0.08f), cyan, 5);
|
||||
CreateBlock("Airport Gate Floor", new Vector2(0f, -0.04f), new Vector2(2.9f, 0.12f), yellow, 4);
|
||||
CreateBlock("Airport Glass Left", new Vector2(-0.62f, 1.35f), new Vector2(0.16f, 2.2f), glass, 2);
|
||||
CreateBlock("Airport Glass Right", new Vector2(0.62f, 1.35f), new Vector2(0.16f, 2.2f), glass, 2);
|
||||
CreateBlock("Airport Clear Light", new Vector2(0f, 2.55f), new Vector2(0.26f, 0.14f), yellow, 5);
|
||||
CreateWorldText("DEPARTURE", new Vector2(0f, 3.07f), 0.34f, Color.white, 6);
|
||||
}
|
||||
|
||||
private void BuildJapanTorii() {
|
||||
Color red = new Color(0.86f, 0.08f, 0.05f, 1f);
|
||||
Color darkRed = new Color(0.48f, 0.02f, 0.02f, 1f);
|
||||
Color black = new Color(0.08f, 0.04f, 0.03f, 1f);
|
||||
Color gold = new Color(1f, 0.76f, 0.28f, 1f);
|
||||
|
||||
CreateBlock("Torii Left Pillar", new Vector2(-1.18f, 1.35f), new Vector2(0.28f, 2.9f), red, 4);
|
||||
CreateBlock("Torii Right Pillar", new Vector2(1.18f, 1.35f), new Vector2(0.28f, 2.9f), red, 4);
|
||||
CreateBlock("Torii Left Base", new Vector2(-1.18f, -0.14f), new Vector2(0.48f, 0.22f), black, 3);
|
||||
CreateBlock("Torii Right Base", new Vector2(1.18f, -0.14f), new Vector2(0.48f, 0.22f), black, 3);
|
||||
CreateBlock("Torii Lower Beam", new Vector2(0f, 2.55f), new Vector2(2.75f, 0.24f), darkRed, 5);
|
||||
CreateBlock("Torii Main Beam", new Vector2(0f, 3.05f), new Vector2(3.35f, 0.32f), red, 6);
|
||||
CreateBlock("Torii Top Cap", new Vector2(0f, 3.31f), new Vector2(3.75f, 0.16f), black, 7);
|
||||
CreateBlock("Torii Center Plaque", new Vector2(0f, 2.82f), new Vector2(0.36f, 0.5f), gold, 7);
|
||||
}
|
||||
|
||||
private void BuildChinaPaifang() {
|
||||
Color red = new Color(0.74f, 0.05f, 0.04f, 1f);
|
||||
Color gold = new Color(1f, 0.72f, 0.18f, 1f);
|
||||
Color roof = new Color(0.12f, 0.3f, 0.22f, 1f);
|
||||
Color dark = new Color(0.24f, 0.04f, 0.03f, 1f);
|
||||
|
||||
CreateBlock("Paifang Left Pillar", new Vector2(-1.25f, 1.28f), new Vector2(0.28f, 2.8f), red, 4);
|
||||
CreateBlock("Paifang Right Pillar", new Vector2(1.25f, 1.28f), new Vector2(0.28f, 2.8f), red, 4);
|
||||
CreateBlock("Paifang Center Pillar Left", new Vector2(-0.42f, 1.48f), new Vector2(0.18f, 2.2f), dark, 3);
|
||||
CreateBlock("Paifang Center Pillar Right", new Vector2(0.42f, 1.48f), new Vector2(0.18f, 2.2f), dark, 3);
|
||||
CreateBlock("Paifang Sign", new Vector2(0f, 2.65f), new Vector2(2.45f, 0.46f), gold, 5);
|
||||
CreateBlock("Paifang Roof", new Vector2(0f, 3.18f), new Vector2(3.25f, 0.26f), roof, 6);
|
||||
CreateBlock("Paifang Roof Cap", new Vector2(0f, 3.42f), new Vector2(2.35f, 0.2f), roof, 7);
|
||||
CreateBlock("Paifang Lantern Left", new Vector2(-0.78f, 2.18f), new Vector2(0.26f, 0.34f), red, 7);
|
||||
CreateBlock("Paifang Lantern Right", new Vector2(0.78f, 2.18f), new Vector2(0.26f, 0.34f), red, 7);
|
||||
}
|
||||
|
||||
private void BuildAmericaRoadSign() {
|
||||
Color metal = new Color(0.48f, 0.56f, 0.58f, 1f);
|
||||
Color green = new Color(0.03f, 0.38f, 0.22f, 1f);
|
||||
Color yellow = new Color(1f, 0.8f, 0.18f, 1f);
|
||||
Color white = Color.white;
|
||||
|
||||
CreateBlock("Road Sign Left Pole", new Vector2(-1.3f, 1.35f), new Vector2(0.18f, 3.0f), metal, 3);
|
||||
CreateBlock("Road Sign Right Pole", new Vector2(1.3f, 1.35f), new Vector2(0.18f, 3.0f), metal, 3);
|
||||
CreateBlock("Road Sign Panel", new Vector2(0f, 2.65f), new Vector2(3.05f, 0.9f), green, 5);
|
||||
CreateBlock("Road Sign Stripe", new Vector2(0f, 2.25f), new Vector2(2.75f, 0.08f), white, 6);
|
||||
CreateBlock("Road Sign Arrow", new Vector2(0f, 2.72f), new Vector2(0.5f, 0.16f), yellow, 7);
|
||||
CreateWorldText("ARRIVAL", new Vector2(0f, 2.88f), 0.36f, white, 8);
|
||||
}
|
||||
|
||||
private SpriteRenderer CreateBlock(string objectName, Vector2 localPosition, Vector2 size, Color color, int sortingOrder) {
|
||||
GameObject block = new GameObject(objectName);
|
||||
block.transform.SetParent(transform, false);
|
||||
block.transform.localPosition = new Vector3(localPosition.x, localPosition.y, 0f);
|
||||
block.transform.localScale = new Vector3(size.x, size.y, 1f);
|
||||
|
||||
SpriteRenderer renderer = block.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = GetBlockSprite();
|
||||
renderer.color = color;
|
||||
renderer.sortingLayerName = ForegroundSortingLayer;
|
||||
renderer.sortingOrder = sortingOrder;
|
||||
return renderer;
|
||||
}
|
||||
|
||||
private TextMeshPro CreateWorldText(string text, Vector2 localPosition, float fontSize, Color color, int sortingOrder) {
|
||||
GameObject textObject = new GameObject("Gate Text " + text);
|
||||
textObject.transform.SetParent(transform, false);
|
||||
textObject.transform.localPosition = new Vector3(localPosition.x, localPosition.y, -0.05f);
|
||||
|
||||
TextMeshPro textMesh = textObject.AddComponent<TextMeshPro>();
|
||||
textMesh.text = text;
|
||||
textMesh.fontSize = fontSize;
|
||||
textMesh.alignment = TextAlignmentOptions.Center;
|
||||
textMesh.color = color;
|
||||
textMesh.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
textMesh.rectTransform.sizeDelta = new Vector2(3f, 0.55f);
|
||||
textMesh.renderer.sortingLayerName = ForegroundSortingLayer;
|
||||
textMesh.renderer.sortingOrder = sortingOrder;
|
||||
return textMesh;
|
||||
}
|
||||
|
||||
private static Sprite GetBlockSprite() {
|
||||
if(blockSprite != null)
|
||||
{
|
||||
return blockSprite;
|
||||
}
|
||||
|
||||
Texture2D texture = new Texture2D(1, 1);
|
||||
texture.SetPixel(0, 0, Color.white);
|
||||
texture.Apply();
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
texture.filterMode = FilterMode.Point;
|
||||
|
||||
blockSprite = Sprite.Create(texture, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f), 1f);
|
||||
blockSprite.name = "Runtime Finish Gate Block";
|
||||
return blockSprite;
|
||||
}
|
||||
|
||||
private void ClearChildren() {
|
||||
for(int i = transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(transform.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fa9791b34724eb290a968ddb46f1542
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class FlowIntroFloatMotion : MonoBehaviour
|
||||
{
|
||||
public Vector2 amplitude = Vector2.zero;
|
||||
public Vector2 frequency = Vector2.one;
|
||||
public float phase = 0f;
|
||||
public float scalePulse = 0f;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Vector2 basePosition;
|
||||
private Vector3 baseScale;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
if(rectTransform != null)
|
||||
{
|
||||
basePosition = rectTransform.anchoredPosition;
|
||||
baseScale = rectTransform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if(rectTransform == null)
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
if(rectTransform != null)
|
||||
{
|
||||
basePosition = rectTransform.anchoredPosition;
|
||||
baseScale = rectTransform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(rectTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float time = Time.unscaledTime + phase;
|
||||
Vector2 offset = new Vector2(
|
||||
Mathf.Sin(time * frequency.x) * amplitude.x,
|
||||
Mathf.Sin(time * frequency.y) * amplitude.y);
|
||||
|
||||
rectTransform.anchoredPosition = basePosition + offset;
|
||||
|
||||
if(scalePulse > 0f)
|
||||
{
|
||||
float scale = 1f + (Mathf.Sin(time * 5.2f) * scalePulse);
|
||||
rectTransform.localScale = baseScale * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e6a8a7c6f914625a0c777e6f3fd7101
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class FlowIntroRouteMotion : MonoBehaviour
|
||||
{
|
||||
public Vector2[] points;
|
||||
public Vector2 offset;
|
||||
public float duration = 7.5f;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Vector3 baseScale = Vector3.one;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
if(rectTransform != null)
|
||||
{
|
||||
baseScale = rectTransform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if(rectTransform == null)
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
if(rectTransform != null)
|
||||
{
|
||||
baseScale = rectTransform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(rectTransform == null || points == null || points.Length < 2 || duration <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float totalLength = GetTotalLength();
|
||||
if(totalLength <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float distance = Mathf.Repeat(Time.unscaledTime / duration, 1f) * totalLength;
|
||||
Vector2 position = points[0];
|
||||
Vector2 direction = Vector2.right;
|
||||
|
||||
for(int i = 0; i < points.Length - 1; i++)
|
||||
{
|
||||
Vector2 from = points[i];
|
||||
Vector2 to = points[i + 1];
|
||||
Vector2 delta = to - from;
|
||||
float segmentLength = delta.magnitude;
|
||||
|
||||
if(distance <= segmentLength)
|
||||
{
|
||||
float t = segmentLength > 0f ? distance / segmentLength : 0f;
|
||||
position = Vector2.Lerp(from, to, t);
|
||||
direction = delta.normalized;
|
||||
break;
|
||||
}
|
||||
|
||||
distance -= segmentLength;
|
||||
}
|
||||
|
||||
rectTransform.anchoredPosition = position + offset;
|
||||
|
||||
if(direction.sqrMagnitude > 0.0001f)
|
||||
{
|
||||
Vector2 displayDirection = direction;
|
||||
Vector3 displayScale = baseScale;
|
||||
|
||||
if(displayDirection.x < 0f)
|
||||
{
|
||||
displayDirection.x = -displayDirection.x;
|
||||
displayScale.x = -Mathf.Abs(baseScale.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
displayScale.x = Mathf.Abs(baseScale.x);
|
||||
}
|
||||
|
||||
rectTransform.localScale = displayScale;
|
||||
rectTransform.localRotation = Quaternion.Euler(0f, 0f, Mathf.Atan2(displayDirection.y, displayDirection.x) * Mathf.Rad2Deg - 12f);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetTotalLength()
|
||||
{
|
||||
float totalLength = 0f;
|
||||
|
||||
for(int i = 0; i < points.Length - 1; i++)
|
||||
{
|
||||
totalLength += Vector2.Distance(points[i], points[i + 1]);
|
||||
}
|
||||
|
||||
return totalLength;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbe0f2f87b984865b874f7977f0d9588
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class FlowIntroStampMotion : MonoBehaviour
|
||||
{
|
||||
private const float Duration = 0.18f;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Vector3 baseScale = Vector3.one;
|
||||
private float startTime = -1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
if(rectTransform != null)
|
||||
{
|
||||
baseScale = rectTransform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
if(rectTransform == null)
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
if(rectTransform != null)
|
||||
{
|
||||
baseScale = Vector3.one;
|
||||
rectTransform.localScale = baseScale * 1.42f;
|
||||
}
|
||||
|
||||
startTime = Time.unscaledTime;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(rectTransform == null || startTime < 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float t = Mathf.Clamp01((Time.unscaledTime - startTime) / Duration);
|
||||
float scale = Mathf.Lerp(1.42f, 1f, 1f - Mathf.Pow(1f - t, 3f));
|
||||
rectTransform.localScale = baseScale * scale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c656de70bd9948dca3bc45497f3e073d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -4,10 +4,14 @@ using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public class GameFlowManager : MonoBehaviour {
|
||||
public enum FlowScreen {
|
||||
Intro,
|
||||
OpeningStory,
|
||||
Game,
|
||||
Result,
|
||||
DutyFreeShop,
|
||||
@@ -39,6 +43,11 @@ public class GameFlowManager : MonoBehaviour {
|
||||
private const int LunchboxCost = 80;
|
||||
private const int SpeedShoesCost = 140;
|
||||
private const int MileageCardCost = 160;
|
||||
private static readonly string[] OpeningStoryPageAssetPaths = {
|
||||
"Assets/ConceptArt/Opening/opening_cutscene_comic_jj_03.png",
|
||||
"Assets/ConceptArt/Opening/opening_cutscene_comic_jj_04_prepare.png",
|
||||
"Assets/ConceptArt/Opening/opening_cutscene_final_departure_jj_06_street_strict_stylematch.png"
|
||||
};
|
||||
|
||||
private GameSaveData saveData;
|
||||
private MapDefinition currentMap;
|
||||
@@ -51,9 +60,12 @@ public class GameFlowManager : MonoBehaviour {
|
||||
private int lastResultMileage = 0;
|
||||
private float lastResultTime = 0f;
|
||||
private float lastResultDistance = 0f;
|
||||
private int openingStoryPageIndex = 0;
|
||||
|
||||
private Canvas overlayCanvas;
|
||||
private RectTransform overlayRoot;
|
||||
private Image overlayBackground;
|
||||
private Camera flowCamera;
|
||||
private TMP_FontAsset runtimeFont;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
@@ -165,7 +177,8 @@ public class GameFlowManager : MonoBehaviour {
|
||||
saveData = GameSaveManager.ResetSave();
|
||||
currentMap = MapDatabase.GetMapByIndex(saveData.currentMapIndex);
|
||||
completedMap = null;
|
||||
ShowMapPreview();
|
||||
openingStoryPageIndex = 0;
|
||||
ShowOpeningStory();
|
||||
}
|
||||
|
||||
private void ContinueJourney() {
|
||||
@@ -206,10 +219,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
currentScreen = FlowScreen.Game;
|
||||
gameplayActive = true;
|
||||
|
||||
if(overlayCanvas != null)
|
||||
{
|
||||
overlayCanvas.gameObject.SetActive(false);
|
||||
}
|
||||
SetOverlayVisible(false);
|
||||
|
||||
if(GameManager.instance != null)
|
||||
{
|
||||
@@ -217,6 +227,23 @@ public class GameFlowManager : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowOpeningStory() {
|
||||
currentScreen = FlowScreen.OpeningStory;
|
||||
gameplayActive = false;
|
||||
pendingStartGameplay = false;
|
||||
|
||||
if(TryLoadFlowScene(IntroSceneName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
|
||||
BuildOpeningStoryScreen();
|
||||
}
|
||||
|
||||
private void ShowIntro() {
|
||||
currentScreen = FlowScreen.Intro;
|
||||
gameplayActive = false;
|
||||
@@ -232,12 +259,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
|
||||
AddTitle("Uni-Run");
|
||||
AddBody("여권을 챙기고 공항으로 출발합니다.\n진행도는 자동 저장됩니다.");
|
||||
AddBody("현재 경로: " + currentMap.displayName + "\n해금: " + MapDatabase.GetMapByIndex(saveData.unlockedMapIndex).displayName + "\n마일리지: " + saveData.totalMileage);
|
||||
AddButton("이어하기", ContinueJourney);
|
||||
AddButton("새 여행", StartNewJourney);
|
||||
AddButton("저장 초기화", ResetAndStayIntro);
|
||||
BuildIntroScreen();
|
||||
}
|
||||
|
||||
private void ShowResult() {
|
||||
@@ -249,6 +271,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
SetDefaultOverlayBackground();
|
||||
|
||||
string title = lastResultCleared ? "맵 클리어" : "일정 실패";
|
||||
string body = completedMap.displayName
|
||||
@@ -290,6 +313,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
SetDefaultOverlayBackground();
|
||||
|
||||
AddTitle("면세점");
|
||||
AddBody("보유 마일리지: " + saveData.totalMileage
|
||||
@@ -327,6 +351,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
SetDefaultOverlayBackground();
|
||||
|
||||
AddTitle("다음 경로");
|
||||
AddBody(currentMap.displayName
|
||||
@@ -356,6 +381,9 @@ public class GameFlowManager : MonoBehaviour {
|
||||
case FlowScreen.MapPreview:
|
||||
ShowMapPreview();
|
||||
break;
|
||||
case FlowScreen.OpeningStory:
|
||||
ShowOpeningStory();
|
||||
break;
|
||||
case FlowScreen.Intro:
|
||||
default:
|
||||
ShowIntro();
|
||||
@@ -423,6 +451,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
EnsureOverlay();
|
||||
ClearOverlay();
|
||||
SetOverlayVisible(true);
|
||||
SetDefaultOverlayBackground();
|
||||
AddTitle(title);
|
||||
AddBody(body);
|
||||
}
|
||||
@@ -444,19 +473,21 @@ public class GameFlowManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
private void EnsureOverlay() {
|
||||
EnsureEventSystem();
|
||||
|
||||
if(overlayCanvas != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureEventSystem();
|
||||
|
||||
GameObject canvasObject = new GameObject("Flow Overlay", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||||
DontDestroyOnLoad(canvasObject);
|
||||
|
||||
overlayCanvas = canvasObject.GetComponent<Canvas>();
|
||||
overlayCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
overlayCanvas.worldCamera = null;
|
||||
overlayCanvas.sortingOrder = 100;
|
||||
SetUiLayer(canvasObject);
|
||||
|
||||
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
@@ -465,6 +496,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
|
||||
GameObject rootObject = new GameObject("Flow Overlay Root", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
rootObject.transform.SetParent(canvasObject.transform, false);
|
||||
SetUiLayer(rootObject);
|
||||
|
||||
overlayRoot = rootObject.GetComponent<RectTransform>();
|
||||
overlayRoot.anchorMin = Vector2.zero;
|
||||
@@ -473,15 +505,765 @@ public class GameFlowManager : MonoBehaviour {
|
||||
overlayRoot.offsetMax = Vector2.zero;
|
||||
|
||||
Image background = rootObject.GetComponent<Image>();
|
||||
overlayBackground = background;
|
||||
background.color = new Color(0.02f, 0.035f, 0.045f, 0.92f);
|
||||
background.raycastTarget = true;
|
||||
|
||||
runtimeFont = CreateKoreanFontAsset();
|
||||
}
|
||||
|
||||
private void SetDefaultOverlayBackground() {
|
||||
if(overlayBackground != null)
|
||||
{
|
||||
overlayBackground.color = new Color(0.02f, 0.035f, 0.045f, 0.92f);
|
||||
}
|
||||
|
||||
if(flowCamera != null)
|
||||
{
|
||||
flowCamera.backgroundColor = new Color(0.02f, 0.035f, 0.045f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildIntroScreen() {
|
||||
if(overlayBackground != null)
|
||||
{
|
||||
overlayBackground.color = Color.clear;
|
||||
}
|
||||
|
||||
if(flowCamera != null)
|
||||
{
|
||||
flowCamera.backgroundColor = new Color(0.006f, 0.022f, 0.035f, 1f);
|
||||
}
|
||||
|
||||
RectTransform mapRect = AddIntroWorldMap();
|
||||
Vector2[] routePoints = GetIntroRoutePoints();
|
||||
AddIntroRoute(mapRect, routePoints);
|
||||
AddIntroPlane(mapRect, routePoints);
|
||||
AddIntroPassportAndPhone();
|
||||
AddIntroTitlePanel();
|
||||
AddIntroButtonDock();
|
||||
}
|
||||
|
||||
private void BuildOpeningStoryScreen() {
|
||||
if(overlayBackground != null)
|
||||
{
|
||||
overlayBackground.color = new Color(0.012f, 0.018f, 0.024f, 0.96f);
|
||||
}
|
||||
|
||||
if(flowCamera != null)
|
||||
{
|
||||
flowCamera.backgroundColor = new Color(0.012f, 0.018f, 0.024f, 1f);
|
||||
}
|
||||
|
||||
openingStoryPageIndex = Mathf.Clamp(openingStoryPageIndex, 0, OpeningStoryPageAssetPaths.Length - 1);
|
||||
AddOpeningStoryPage();
|
||||
AddOpeningStoryProgressDots();
|
||||
AddOpeningStoryControls();
|
||||
}
|
||||
|
||||
private void AddOpeningStoryPage() {
|
||||
GameObject shadowObject = CreateUiObject("Opening Story Shadow", overlayRoot, typeof(Image));
|
||||
RectTransform shadowRect = shadowObject.GetComponent<RectTransform>();
|
||||
shadowRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
shadowRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
shadowRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
shadowRect.anchoredPosition = new Vector2(7f, 18f);
|
||||
shadowRect.sizeDelta = new Vector2(1132f, 637f);
|
||||
|
||||
Image shadowImage = shadowObject.GetComponent<Image>();
|
||||
shadowImage.color = new Color(0f, 0f, 0f, 0.34f);
|
||||
shadowImage.raycastTarget = false;
|
||||
|
||||
GameObject pageObject = CreateUiObject("Opening Story Page", overlayRoot, typeof(Image), typeof(Button));
|
||||
RectTransform pageRect = pageObject.GetComponent<RectTransform>();
|
||||
pageRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
pageRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
pageRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
pageRect.anchoredPosition = new Vector2(0f, 24f);
|
||||
pageRect.sizeDelta = new Vector2(1120f, 630f);
|
||||
|
||||
Image pageImage = pageObject.GetComponent<Image>();
|
||||
pageImage.sprite = LoadUiTextureSprite(OpeningStoryPageAssetPaths[openingStoryPageIndex]);
|
||||
pageImage.color = pageImage.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.91f, 0.87f, 0.78f, 1f);
|
||||
pageImage.preserveAspect = true;
|
||||
|
||||
Button pageButton = pageObject.GetComponent<Button>();
|
||||
pageButton.transition = Selectable.Transition.None;
|
||||
pageButton.onClick.AddListener(AdvanceOpeningStory);
|
||||
}
|
||||
|
||||
private void AddOpeningStoryProgressDots() {
|
||||
float startX = -((OpeningStoryPageAssetPaths.Length - 1) * 18f) * 0.5f;
|
||||
|
||||
for(int i = 0; i < OpeningStoryPageAssetPaths.Length; i++)
|
||||
{
|
||||
GameObject dotObject = CreateUiObject("Opening Story Dot " + (i + 1), overlayRoot, typeof(Image));
|
||||
RectTransform dotRect = dotObject.GetComponent<RectTransform>();
|
||||
dotRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
dotRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
dotRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
dotRect.anchoredPosition = new Vector2(startX + (i * 18f), -324f);
|
||||
dotRect.sizeDelta = i == openingStoryPageIndex ? new Vector2(12f, 12f) : new Vector2(8f, 8f);
|
||||
|
||||
Image dotImage = dotObject.GetComponent<Image>();
|
||||
dotImage.color = i == openingStoryPageIndex
|
||||
? new Color(1f, 0.86f, 0.28f, 0.95f)
|
||||
: new Color(0.72f, 0.84f, 0.88f, 0.5f);
|
||||
dotImage.raycastTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddOpeningStoryControls() {
|
||||
if(openingStoryPageIndex > 0)
|
||||
{
|
||||
AddOpeningStoryButton("이전", new Vector2(-450f, -324f), PreviousOpeningStoryPage);
|
||||
}
|
||||
|
||||
string nextLabel = openingStoryPageIndex >= OpeningStoryPageAssetPaths.Length - 1 ? "게임 시작" : "다음";
|
||||
AddOpeningStoryButton(nextLabel, new Vector2(450f, -324f), AdvanceOpeningStory);
|
||||
}
|
||||
|
||||
private void AddOpeningStoryButton(string label, Vector2 position, UnityEngine.Events.UnityAction action) {
|
||||
GameObject buttonObject = CreateUiObject("Opening Story 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 = label == "게임 시작" ? new Vector2(178f, 44f) : new Vector2(126f, 42f);
|
||||
|
||||
Image buttonImage = buttonObject.GetComponent<Image>();
|
||||
buttonImage.color = label == "게임 시작"
|
||||
? new Color(1f, 0.86f, 0.34f, 0.96f)
|
||||
: new Color(0.88f, 0.96f, 1f, 0.88f);
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.onClick.AddListener(action);
|
||||
|
||||
TextMeshProUGUI buttonText = AddTextTo(buttonObject.transform, "Label", label, label == "게임 시작" ? 17f : 15f, 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.035f, 0.065f, 0.08f, 1f);
|
||||
}
|
||||
|
||||
private void AdvanceOpeningStory() {
|
||||
if(openingStoryPageIndex >= OpeningStoryPageAssetPaths.Length - 1)
|
||||
{
|
||||
LoadGameSceneForCurrentMap();
|
||||
return;
|
||||
}
|
||||
|
||||
openingStoryPageIndex++;
|
||||
ShowOpeningStory();
|
||||
}
|
||||
|
||||
private void PreviousOpeningStoryPage() {
|
||||
if(openingStoryPageIndex <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
openingStoryPageIndex--;
|
||||
ShowOpeningStory();
|
||||
}
|
||||
|
||||
private RectTransform AddIntroWorldMap() {
|
||||
GameObject mapObject = CreateUiObject("Intro World Map", overlayRoot, typeof(Image));
|
||||
RectTransform mapRect = mapObject.GetComponent<RectTransform>();
|
||||
mapRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
mapRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
mapRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
mapRect.anchoredPosition = Vector2.zero;
|
||||
mapRect.sizeDelta = new Vector2(1280f, 720f);
|
||||
|
||||
Image mapImage = mapObject.GetComponent<Image>();
|
||||
mapImage.sprite = LoadUiSprite("", "Assets/ConceptArt/Intro/intro_world_map_clean_01.png");
|
||||
mapImage.color = mapImage.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.015f, 0.078f, 0.112f, 0.76f);
|
||||
mapImage.preserveAspect = true;
|
||||
mapImage.raycastTarget = false;
|
||||
|
||||
GameObject vignetteObject = CreateUiObject("Intro Map Vignette", overlayRoot, typeof(Image));
|
||||
RectTransform vignetteRect = vignetteObject.GetComponent<RectTransform>();
|
||||
vignetteRect.anchorMin = Vector2.zero;
|
||||
vignetteRect.anchorMax = Vector2.one;
|
||||
vignetteRect.offsetMin = Vector2.zero;
|
||||
vignetteRect.offsetMax = Vector2.zero;
|
||||
|
||||
Image vignetteImage = vignetteObject.GetComponent<Image>();
|
||||
vignetteImage.color = new Color(0f, 0.006f, 0.012f, 0.08f);
|
||||
vignetteImage.raycastTarget = false;
|
||||
|
||||
return mapRect;
|
||||
}
|
||||
|
||||
private void AddIntroMapGrid(RectTransform mapRect) {
|
||||
Color gridColor = new Color(0.46f, 0.86f, 1f, 0.12f);
|
||||
|
||||
for(int i = -5; i <= 5; i++)
|
||||
{
|
||||
GameObject lineObject = CreateUiObject("Intro Map Longitude " + i, mapRect, typeof(Image));
|
||||
RectTransform lineRect = lineObject.GetComponent<RectTransform>();
|
||||
lineRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
lineRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
lineRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
lineRect.anchoredPosition = new Vector2(i * 92f, 0f);
|
||||
lineRect.sizeDelta = new Vector2(2f, 610f);
|
||||
|
||||
Image lineImage = lineObject.GetComponent<Image>();
|
||||
lineImage.color = gridColor;
|
||||
lineImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
for(int i = -3; i <= 3; i++)
|
||||
{
|
||||
GameObject lineObject = CreateUiObject("Intro Map Latitude " + i, mapRect, typeof(Image));
|
||||
RectTransform lineRect = lineObject.GetComponent<RectTransform>();
|
||||
lineRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
lineRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
lineRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
lineRect.anchoredPosition = new Vector2(0f, i * 74f);
|
||||
lineRect.sizeDelta = new Vector2(1110f, 2f);
|
||||
|
||||
Image lineImage = lineObject.GetComponent<Image>();
|
||||
lineImage.color = gridColor;
|
||||
lineImage.raycastTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddIntroLandMass(RectTransform mapRect, string objectName, Vector2 position, Vector2 size, float rotation) {
|
||||
GameObject landObject = CreateUiObject(objectName, mapRect, typeof(Image));
|
||||
RectTransform landRect = landObject.GetComponent<RectTransform>();
|
||||
landRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
landRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
landRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
landRect.anchoredPosition = position;
|
||||
landRect.sizeDelta = size;
|
||||
landRect.localRotation = Quaternion.Euler(0f, 0f, rotation);
|
||||
|
||||
Image landImage = landObject.GetComponent<Image>();
|
||||
landImage.color = new Color(0.24f, 0.72f, 0.59f, 0.22f);
|
||||
landImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private Vector2[] GetIntroRoutePoints() {
|
||||
return new Vector2[] {
|
||||
new Vector2(356f, 50f),
|
||||
new Vector2(405f, 58f),
|
||||
new Vector2(316f, 42f),
|
||||
new Vector2(-450f, 94f)
|
||||
};
|
||||
}
|
||||
|
||||
private void AddIntroRoute(RectTransform mapRect, Vector2[] routePoints) {
|
||||
Color routeColor = new Color(1f, 0.86f, 0.28f, 0.82f);
|
||||
|
||||
for(int i = 0; i < routePoints.Length - 1; i++)
|
||||
{
|
||||
AddIntroRouteSegment(mapRect, routePoints[i], routePoints[i + 1], routeColor, 4f);
|
||||
}
|
||||
|
||||
AddIntroRouteMarker(mapRect, routePoints[0], "INCHEON", true, 0f, new Vector2(-4f, -25f));
|
||||
AddIntroRouteMarker(mapRect, routePoints[1], "JAPAN", false, 0.4f, new Vector2(28f, -20f));
|
||||
AddIntroRouteMarker(mapRect, routePoints[2], "CHINA", false, 0.8f, new Vector2(-18f, -24f));
|
||||
AddIntroRouteMarker(mapRect, routePoints[3], "AMERICA", false, 1.2f, new Vector2(0f, -25f));
|
||||
}
|
||||
|
||||
private void AddIntroRouteSegment(RectTransform mapRect, Vector2 from, Vector2 to, Color color, float thickness) {
|
||||
GameObject segmentObject = CreateUiObject("Intro Route Segment", mapRect, typeof(Image));
|
||||
RectTransform segmentRect = segmentObject.GetComponent<RectTransform>();
|
||||
Vector2 delta = to - from;
|
||||
segmentRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
segmentRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
segmentRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
segmentRect.anchoredPosition = from + (delta * 0.5f);
|
||||
segmentRect.sizeDelta = new Vector2(delta.magnitude, thickness);
|
||||
segmentRect.localRotation = Quaternion.Euler(0f, 0f, Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg);
|
||||
|
||||
Image segmentImage = segmentObject.GetComponent<Image>();
|
||||
segmentImage.color = color;
|
||||
segmentImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private void AddIntroRouteMarker(RectTransform mapRect, Vector2 position, string label, bool current, float phase, Vector2 labelOffset) {
|
||||
GameObject markerObject = CreateUiObject("Intro Route Marker " + label, mapRect, typeof(Image));
|
||||
RectTransform markerRect = markerObject.GetComponent<RectTransform>();
|
||||
markerRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
markerRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
markerRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
markerRect.anchoredPosition = position;
|
||||
markerRect.sizeDelta = current ? new Vector2(23f, 23f) : new Vector2(17f, 17f);
|
||||
|
||||
Image markerImage = markerObject.GetComponent<Image>();
|
||||
markerImage.color = current
|
||||
? new Color(1f, 0.92f, 0.22f, 0.95f)
|
||||
: new Color(0.78f, 0.96f, 1f, 0.86f);
|
||||
markerImage.raycastTarget = false;
|
||||
|
||||
FlowIntroFloatMotion markerMotion = markerObject.AddComponent<FlowIntroFloatMotion>();
|
||||
markerMotion.scalePulse = current ? 0.18f : 0.09f;
|
||||
markerMotion.frequency = new Vector2(0f, 0.72f);
|
||||
markerMotion.phase = phase;
|
||||
|
||||
TextMeshProUGUI labelText = AddIntroMapLabel(mapRect, "Intro Marker Label " + label, label, position + labelOffset, new Vector2(84f, 20f), 9.5f);
|
||||
labelText.color = current
|
||||
? new Color(1f, 0.94f, 0.44f, 0.92f)
|
||||
: new Color(0.78f, 0.94f, 1f, 0.72f);
|
||||
}
|
||||
|
||||
private TextMeshProUGUI AddIntroMapLabel(RectTransform parent, string objectName, string text, Vector2 position, Vector2 size, float fontSize) {
|
||||
GameObject textObject = CreateUiObject(objectName, parent);
|
||||
RectTransform textRect = textObject.GetComponent<RectTransform>();
|
||||
textRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
textRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
textRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
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 = FontStyles.Bold;
|
||||
textComponent.alignment = TextAlignmentOptions.Center;
|
||||
textComponent.raycastTarget = false;
|
||||
textComponent.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
return textComponent;
|
||||
}
|
||||
|
||||
private void AddIntroPlane(RectTransform mapRect, Vector2[] routePoints) {
|
||||
Sprite planeSprite = LoadUiSprite("", "Assets/Sprites/UIIcons/UI_AirplaneTravel.png");
|
||||
GameObject planeObject = CreateUiObject("Intro Airplane", overlayRoot, typeof(Image));
|
||||
RectTransform rectTransform = planeObject.GetComponent<RectTransform>();
|
||||
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
||||
rectTransform.anchoredPosition = routePoints[0] + mapRect.anchoredPosition;
|
||||
rectTransform.sizeDelta = new Vector2(58f, 58f);
|
||||
|
||||
Image image = planeObject.GetComponent<Image>();
|
||||
image.sprite = planeSprite;
|
||||
image.color = planeSprite != null
|
||||
? new Color(1f, 1f, 1f, 0.95f)
|
||||
: new Color(0.9f, 0.97f, 1f, 0.88f);
|
||||
image.preserveAspect = true;
|
||||
image.raycastTarget = false;
|
||||
|
||||
FlowIntroRouteMotion motion = planeObject.AddComponent<FlowIntroRouteMotion>();
|
||||
motion.points = routePoints;
|
||||
motion.offset = mapRect.anchoredPosition;
|
||||
motion.duration = 7.5f;
|
||||
}
|
||||
|
||||
private void AddIntroPassportAndPhone() {
|
||||
AddIntroPassportCover();
|
||||
AddIntroSmartPhone();
|
||||
}
|
||||
|
||||
private void AddIntroPassportCover() {
|
||||
GameObject passportObject = CreateUiObject("Intro Passport Cover", overlayRoot, typeof(Image));
|
||||
RectTransform passportRect = passportObject.GetComponent<RectTransform>();
|
||||
passportRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
passportRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
passportRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
passportRect.anchoredPosition = new Vector2(424f, -242f);
|
||||
passportRect.sizeDelta = new Vector2(270f, 300f);
|
||||
passportRect.localRotation = Quaternion.Euler(0f, 0f, -24f);
|
||||
|
||||
Image passportImage = passportObject.GetComponent<Image>();
|
||||
passportImage.sprite = LoadUiTextureSprite("Assets/ConceptArt/Intro/UI/intro_passport_clean.png");
|
||||
passportImage.color = passportImage.sprite != null
|
||||
? new Color(1f, 1f, 1f, 0.98f)
|
||||
: new Color(0.034f, 0.18f, 0.22f, 0.96f);
|
||||
passportImage.preserveAspect = true;
|
||||
passportImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private void AddIntroSmartPhone() {
|
||||
GameObject phoneObject = CreateUiObject("Intro Smart Phone", overlayRoot, typeof(Image));
|
||||
RectTransform phoneRect = phoneObject.GetComponent<RectTransform>();
|
||||
phoneRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
phoneRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
phoneRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
phoneRect.anchoredPosition = new Vector2(558f, -218f);
|
||||
phoneRect.sizeDelta = new Vector2(132f, 252f);
|
||||
phoneRect.localRotation = Quaternion.Euler(0f, 0f, 10f);
|
||||
|
||||
Image phoneImage = phoneObject.GetComponent<Image>();
|
||||
phoneImage.sprite = LoadUiTextureSprite("Assets/ConceptArt/Intro/UI/intro_phone_clean.png");
|
||||
phoneImage.color = phoneImage.sprite != null
|
||||
? new Color(1f, 1f, 1f, 0.98f)
|
||||
: new Color(0.015f, 0.018f, 0.022f, 0.98f);
|
||||
phoneImage.preserveAspect = true;
|
||||
phoneImage.raycastTarget = false;
|
||||
|
||||
FlowIntroFloatMotion phoneMotion = phoneObject.AddComponent<FlowIntroFloatMotion>();
|
||||
phoneMotion.amplitude = new Vector2(0f, 5f);
|
||||
phoneMotion.frequency = new Vector2(0f, 0.45f);
|
||||
phoneMotion.phase = 0.6f;
|
||||
}
|
||||
|
||||
private void AddIntroTitlePanel() {
|
||||
GameObject titleObject = CreateUiObject("Intro Title Lockup", overlayRoot, typeof(Image));
|
||||
RectTransform titleRect = titleObject.GetComponent<RectTransform>();
|
||||
titleRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
titleRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
titleRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
titleRect.anchoredPosition = new Vector2(-364f, 196f);
|
||||
titleRect.sizeDelta = new Vector2(448f, 166f);
|
||||
|
||||
Image titleImage = titleObject.GetComponent<Image>();
|
||||
titleImage.sprite = LoadUiTextureSprite("Assets/ConceptArt/Intro/UI/intro_title_lockup.png");
|
||||
titleImage.color = titleImage.sprite != null
|
||||
? Color.white
|
||||
: new Color(0.86f, 0.98f, 1f, 0.94f);
|
||||
titleImage.preserveAspect = true;
|
||||
titleImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private void AddIntroButtonDock() {
|
||||
GameObject dockObject = CreateUiObject("Intro Button Dock", overlayRoot);
|
||||
RectTransform dockRect = dockObject.GetComponent<RectTransform>();
|
||||
dockRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
dockRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
dockRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
dockRect.anchoredPosition = new Vector2(0f, -224f);
|
||||
dockRect.sizeDelta = new Vector2(424f, 136f);
|
||||
|
||||
AddIntroButton(dockRect, "게임 시작", new Vector2(45f, 0f), StartNewJourney, true, 0);
|
||||
AddIntroButton(dockRect, "이어하기", new Vector2(0f, -76f), ContinueJourney, false, 1);
|
||||
AddIntroButton(dockRect, "설정", new Vector2(220f, -76f), ShowIntroSettingsPlaceholder, false, 2);
|
||||
}
|
||||
|
||||
private void AddIntroButton(RectTransform parent, string label, Vector2 position, UnityEngine.Events.UnityAction action, bool primary, int buttonStyle) {
|
||||
GameObject buttonObject = CreateUiObject("Button " + label, 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 = position;
|
||||
buttonRect.sizeDelta = primary ? new Vector2(334f, 74f) : new Vector2(204f, 58f);
|
||||
|
||||
Image buttonImage = buttonObject.GetComponent<Image>();
|
||||
buttonImage.sprite = GetIntroButtonSprite(primary, buttonStyle);
|
||||
buttonImage.color = buttonImage.sprite != null
|
||||
? Color.white
|
||||
: primary
|
||||
? new Color(0.98f, 0.88f, 0.38f, 0.96f)
|
||||
: new Color(0.88f, 0.96f, 1f, 0.9f);
|
||||
buttonImage.preserveAspect = true;
|
||||
buttonImage.raycastTarget = true;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = buttonImage;
|
||||
button.navigation = new Navigation {
|
||||
mode = Navigation.Mode.None
|
||||
};
|
||||
|
||||
GameObject stampObject = CreateUiObject("Departure Stamp", buttonObject.transform, typeof(Image));
|
||||
RectTransform stampRect = stampObject.GetComponent<RectTransform>();
|
||||
stampRect.anchorMin = new Vector2(1f, 0.5f);
|
||||
stampRect.anchorMax = new Vector2(1f, 0.5f);
|
||||
stampRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
stampRect.anchoredPosition = new Vector2(primary ? -47f : -28f, primary ? 1f : 0f);
|
||||
stampRect.sizeDelta = primary ? new Vector2(58f, 58f) : new Vector2(44f, 44f);
|
||||
stampRect.localRotation = Quaternion.Euler(0f, 0f, -12f);
|
||||
|
||||
Image stampImage = stampObject.GetComponent<Image>();
|
||||
stampImage.sprite = LoadUiSpriteRegion("Assets/ConceptArt/Intro/intro_stamps_01.png", new Rect(54f, 48f, 790f, 790f));
|
||||
stampImage.color = stampImage.sprite != null
|
||||
? new Color(1f, 1f, 1f, 0.94f)
|
||||
: new Color(0.76f, 0.08f, 0.06f, 0.96f);
|
||||
stampImage.preserveAspect = true;
|
||||
stampImage.raycastTarget = false;
|
||||
|
||||
TextMeshProUGUI stampText = AddTextTo(stampObject.transform, "Stamp Label", "출국", primary ? 13f : 11f, FontStyles.Bold);
|
||||
RectTransform stampTextRect = stampText.rectTransform;
|
||||
stampTextRect.anchorMin = Vector2.zero;
|
||||
stampTextRect.anchorMax = Vector2.one;
|
||||
stampTextRect.offsetMin = Vector2.zero;
|
||||
stampTextRect.offsetMax = Vector2.zero;
|
||||
stampText.color = new Color(0.74f, 0.04f, 0.03f, 0.98f);
|
||||
stampObject.SetActive(false);
|
||||
|
||||
button.onClick.AddListener(delegate {
|
||||
button.interactable = false;
|
||||
stampObject.SetActive(true);
|
||||
FlowIntroStampMotion stampMotion = stampObject.GetComponent<FlowIntroStampMotion>();
|
||||
if(stampMotion == null)
|
||||
{
|
||||
stampMotion = stampObject.AddComponent<FlowIntroStampMotion>();
|
||||
}
|
||||
|
||||
stampMotion.Play();
|
||||
StartCoroutine(InvokeIntroActionAfterStamp(action));
|
||||
});
|
||||
|
||||
if(primary)
|
||||
{
|
||||
FlowIntroFloatMotion motion = buttonObject.AddComponent<FlowIntroFloatMotion>();
|
||||
motion.scalePulse = 0.018f;
|
||||
motion.frequency = new Vector2(0f, 0.55f);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddIntroButtonIcon(Transform parent, int buttonStyle) {
|
||||
GameObject iconObject = CreateUiObject("Button Icon", parent, typeof(Image));
|
||||
RectTransform iconRect = iconObject.GetComponent<RectTransform>();
|
||||
iconRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
iconRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
iconRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
iconRect.anchoredPosition = new Vector2(-38f, 0f);
|
||||
iconRect.sizeDelta = new Vector2(30f, 30f);
|
||||
|
||||
Image iconImage = iconObject.GetComponent<Image>();
|
||||
iconImage.sprite = GetIntroButtonIconSprite(buttonStyle);
|
||||
iconImage.color = iconImage.sprite != null
|
||||
? new Color(1f, 1f, 1f, 0.96f)
|
||||
: new Color(0.94f, 0.98f, 1f, 0.92f);
|
||||
iconImage.preserveAspect = true;
|
||||
iconImage.raycastTarget = false;
|
||||
}
|
||||
|
||||
private Sprite GetIntroButtonSprite(bool primary, int buttonStyle) {
|
||||
if(primary)
|
||||
{
|
||||
return LoadUiTextureSprite("Assets/ConceptArt/Intro/UI/intro_button_start.png");
|
||||
}
|
||||
|
||||
if(buttonStyle == 2)
|
||||
{
|
||||
return LoadUiTextureSprite("Assets/ConceptArt/Intro/UI/intro_button_settings.png");
|
||||
}
|
||||
|
||||
return LoadUiTextureSprite("Assets/ConceptArt/Intro/UI/intro_button_continue.png");
|
||||
}
|
||||
|
||||
private Sprite GetIntroButtonIconSprite(int buttonStyle) {
|
||||
if(buttonStyle == 2)
|
||||
{
|
||||
return LoadUiTextureSprite("Assets/ConceptArt/Intro/Cutouts/intro_icon_settings.png");
|
||||
}
|
||||
|
||||
return LoadUiTextureSprite("Assets/ConceptArt/Intro/Cutouts/intro_icon_continue.png");
|
||||
}
|
||||
|
||||
private void ShowIntroSettingsPlaceholder() {
|
||||
ShowIntro();
|
||||
}
|
||||
|
||||
private IEnumerator InvokeIntroActionAfterStamp(UnityEngine.Events.UnityAction action) {
|
||||
yield return new WaitForSecondsRealtime(0.32f);
|
||||
|
||||
if(action != null)
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private TextMeshProUGUI AddTextToPanel(RectTransform parent, string objectName, string text, Vector2 position, Vector2 size, float fontSize, FontStyles fontStyle, 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 = new Color(0.95f, 0.98f, 1f, 0.96f);
|
||||
textComponent.raycastTarget = false;
|
||||
textComponent.textWrappingMode = TextWrappingModes.Normal;
|
||||
return textComponent;
|
||||
}
|
||||
|
||||
private GameObject CreateUiObject(string objectName, Transform parent, params System.Type[] components) {
|
||||
System.Type[] finalComponents;
|
||||
if(components == null || components.Length == 0)
|
||||
{
|
||||
finalComponents = new System.Type[] {
|
||||
typeof(RectTransform),
|
||||
typeof(CanvasRenderer)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
finalComponents = new System.Type[components.Length + 2];
|
||||
finalComponents[0] = typeof(RectTransform);
|
||||
finalComponents[1] = typeof(CanvasRenderer);
|
||||
for(int i = 0; i < components.Length; i++)
|
||||
{
|
||||
finalComponents[i + 2] = components[i];
|
||||
}
|
||||
}
|
||||
|
||||
GameObject gameObject = new GameObject(objectName, finalComponents);
|
||||
gameObject.transform.SetParent(parent, false);
|
||||
SetUiLayer(gameObject);
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
private void SetUiLayer(GameObject gameObject) {
|
||||
if(gameObject != null)
|
||||
{
|
||||
gameObject.layer = GetUiLayer();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetUiLayer() {
|
||||
int uiLayer = LayerMask.NameToLayer("UI");
|
||||
return uiLayer >= 0 ? uiLayer : 0;
|
||||
}
|
||||
|
||||
private Sprite LoadUiSprite(string resourcePath, string assetPath) {
|
||||
if(!string.IsNullOrEmpty(resourcePath))
|
||||
{
|
||||
Sprite resourceSprite = Resources.Load<Sprite>(resourcePath);
|
||||
if(resourceSprite != null)
|
||||
{
|
||||
return resourceSprite;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if(!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
|
||||
if(assets != null)
|
||||
{
|
||||
for(int i = 0; i < assets.Length; i++)
|
||||
{
|
||||
Sprite sprite = assets[i] as Sprite;
|
||||
if(sprite != null)
|
||||
{
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sprite directSprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||||
if(directSprite != null)
|
||||
{
|
||||
return directSprite;
|
||||
}
|
||||
|
||||
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
if(texture == null)
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
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), 100f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Sprite LoadUiTextureSprite(string assetPath) {
|
||||
#if UNITY_EDITOR
|
||||
if(string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
if(texture == null)
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
}
|
||||
|
||||
if(texture == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f);
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
private Sprite LoadUiSpriteRegion(string assetPath, Rect pixelRect) {
|
||||
#if UNITY_EDITOR
|
||||
if(string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
if(texture == null)
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
}
|
||||
|
||||
if(texture == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Rect safeRect = ClampSpriteRect(pixelRect, texture.width, texture.height);
|
||||
if(safeRect.width <= 0f || safeRect.height <= 0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Sprite.Create(texture, safeRect, new Vector2(0.5f, 0.5f), 100f);
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
private Rect ClampSpriteRect(Rect pixelRect, int textureWidth, int textureHeight) {
|
||||
float xMin = Mathf.Clamp(pixelRect.xMin, 0f, textureWidth);
|
||||
float yMin = Mathf.Clamp(pixelRect.yMin, 0f, textureHeight);
|
||||
float xMax = Mathf.Clamp(pixelRect.xMax, 0f, textureWidth);
|
||||
float yMax = Mathf.Clamp(pixelRect.yMax, 0f, textureHeight);
|
||||
|
||||
if(xMax < xMin)
|
||||
{
|
||||
xMax = xMin;
|
||||
}
|
||||
|
||||
if(yMax < yMin)
|
||||
{
|
||||
yMax = yMin;
|
||||
}
|
||||
|
||||
return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
|
||||
}
|
||||
|
||||
private void EnsureEventSystem() {
|
||||
if(EventSystem.current != null)
|
||||
{
|
||||
EventSystem.current.enabled = true;
|
||||
EventSystem.current.gameObject.SetActive(true);
|
||||
|
||||
StandaloneInputModule inputModule = EventSystem.current.GetComponent<StandaloneInputModule>();
|
||||
if(inputModule == null)
|
||||
{
|
||||
inputModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
|
||||
inputModule.enabled = true;
|
||||
DontDestroyOnLoad(EventSystem.current.gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -489,6 +1271,25 @@ public class GameFlowManager : MonoBehaviour {
|
||||
DontDestroyOnLoad(eventSystemObject);
|
||||
}
|
||||
|
||||
private void EnsureFlowCamera() {
|
||||
if(flowCamera != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject cameraObject = new GameObject("Flow UI Camera", typeof(Camera));
|
||||
DontDestroyOnLoad(cameraObject);
|
||||
|
||||
flowCamera = cameraObject.GetComponent<Camera>();
|
||||
flowCamera.transform.position = new Vector3(640f, 360f, -10f);
|
||||
flowCamera.clearFlags = CameraClearFlags.SolidColor;
|
||||
flowCamera.backgroundColor = new Color(0.006f, 0.022f, 0.035f, 1f);
|
||||
flowCamera.cullingMask = 1 << GetUiLayer();
|
||||
flowCamera.depth = 100f;
|
||||
flowCamera.orthographic = true;
|
||||
flowCamera.orthographicSize = 360f;
|
||||
}
|
||||
|
||||
private void ClearOverlay() {
|
||||
if(overlayRoot == null)
|
||||
{
|
||||
@@ -506,6 +1307,11 @@ public class GameFlowManager : MonoBehaviour {
|
||||
{
|
||||
overlayCanvas.gameObject.SetActive(visible);
|
||||
}
|
||||
|
||||
if(flowCamera != null)
|
||||
{
|
||||
flowCamera.gameObject.SetActive(visible);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTitle(string text) {
|
||||
@@ -527,6 +1333,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
|
||||
GameObject buttonObject = new GameObject("Button " + label, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||
buttonObject.transform.SetParent(overlayRoot, false);
|
||||
SetUiLayer(buttonObject);
|
||||
|
||||
RectTransform buttonRect = buttonObject.GetComponent<RectTransform>();
|
||||
buttonRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
@@ -581,6 +1388,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
private TextMeshProUGUI AddText(string objectName, string text, float fontSize, FontStyles fontStyle) {
|
||||
GameObject textObject = new GameObject(objectName + " " + GetBodyCount(), typeof(RectTransform), typeof(CanvasRenderer));
|
||||
textObject.transform.SetParent(overlayRoot, false);
|
||||
SetUiLayer(textObject);
|
||||
|
||||
RectTransform rectTransform = textObject.GetComponent<RectTransform>();
|
||||
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
@@ -602,6 +1410,7 @@ public class GameFlowManager : MonoBehaviour {
|
||||
private TextMeshProUGUI AddTextTo(Transform parent, string objectName, string text, float fontSize, FontStyles fontStyle) {
|
||||
GameObject textObject = new GameObject(objectName, typeof(RectTransform), typeof(CanvasRenderer));
|
||||
textObject.transform.SetParent(parent, false);
|
||||
SetUiLayer(textObject);
|
||||
TextMeshProUGUI label = textObject.AddComponent<TextMeshProUGUI>();
|
||||
label.text = text;
|
||||
label.font = runtimeFont != null ? runtimeFont : label.font;
|
||||
|
||||
+690
-22
@@ -1,3 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using TMPro;
|
||||
@@ -40,7 +42,7 @@ public class GameManager : MonoBehaviour {
|
||||
private string tutorialTitle = ""; // 튜토리얼 안내 제목
|
||||
private string tutorialMessage = ""; // 튜토리얼 안내 문구
|
||||
private Image[] lifeIcons; // 왼쪽 HUD의 목숨 도장 표시
|
||||
private GameObject tutorialGuideObject; // 화면 하단 이미지 튜토리얼 패널
|
||||
private GameObject tutorialGuideObject; // 화면 상단 이미지 튜토리얼 패널
|
||||
private Image tutorialMainImage; // 점프/슬라이드 대표 이미지
|
||||
private TextMeshProUGUI tutorialMultiplierText; // 2단 점프 x2 표시
|
||||
private TextMeshProUGUI tutorialTitleText; // 튜토리얼 제목
|
||||
@@ -54,16 +56,28 @@ public class GameManager : MonoBehaviour {
|
||||
private float stageElapsedTime = 0f; // 현재 맵 진행 시간
|
||||
private float stageDistance = 0f; // 현재 맵 진행 거리
|
||||
private bool stageCompleted = false; // 결과 화면이 이미 처리되었는지 여부
|
||||
private Coroutine tutorialGuidePauseRoutine; // 안내를 보여주는 동안 잠깐 멈추는 코루틴
|
||||
private bool tutorialGuidePauseActive = false; // 안내 일시정지 중인지 여부
|
||||
private float tutorialGuidePreviousTimeScale = 1f; // 안내 일시정지 전 시간 배율
|
||||
private int tutorialGuideVersion = 0; // 오래된 자동 숨김 요청을 무시하기 위한 버전
|
||||
private FinishGate activeFinishGate; // 목표 지점에 나타나는 맵별 클리어 게이트
|
||||
private bool finishGateSpawned = false; // 현재 맵에서 도착 게이트를 이미 만들었는지 여부
|
||||
|
||||
public int maxLife = 3; // 시작 목숨
|
||||
public int initialMileage = 500; // 첫 시작시 지급할 초기 마일리지
|
||||
public bool grantInitialMileage = true; // 초기 마일리지 지급 여부
|
||||
public bool saveMileageImmediately = true; // 스테이지 정산 전까지는 획득 즉시 누적한다.
|
||||
public bool playTutorialGuideSequence = false; // 시작 시 입력/아이템 안내를 시간 기준으로 보여준다.
|
||||
public bool playTutorialGuideSequence = false; // 코스 데이터가 없을 때만 시간 기준 안내를 보여준다.
|
||||
public bool pauseGameplayForTutorialGuide = true; // 안내가 뜨는 동안 게임을 잠깐 멈춘다.
|
||||
public float tutorialGuidePauseDuration = 2f; // 안내를 보여주고 자동으로 숨길 시간
|
||||
public float itemInvincibleDuration = 2.2f; // 라멘 아이템 무적 지속 시간
|
||||
public float itemSpeedBoostAmount = 0.35f; // 운동화 아이템 즉시 속도 보정량
|
||||
public float mileageBonusDuration = 7f; // 마일리지 카드 보너스 지속 시간
|
||||
public int mileageBonusMultiplier = 2; // 마일리지 카드 획득 배율
|
||||
public float finishGateSpawnX = 20f; // 도착 게이트가 처음 나타나는 x 위치
|
||||
public float finishGatePlayerX = -6f; // 게이트가 클리어 판정을 내릴 플레이어 기준 x 위치
|
||||
public float finishGateGroundY = -2.42f; // 도착 게이트가 바닥에 붙는 y 위치
|
||||
public float finishGateFallbackDistance = 18f; // 게이트 트리거를 놓쳤을 때의 안전 클리어 거리
|
||||
|
||||
private const string TotalMileageKey = "UniRun.TotalMileage";
|
||||
private const string InitialMileageGrantedKey = "UniRun.InitialMileageGranted";
|
||||
@@ -86,6 +100,10 @@ public class GameManager : MonoBehaviour {
|
||||
private float itemInvincibleEndTime = 0f; // 라멘 무적 종료 시각
|
||||
private float mileageBonusEndTime = 0f; // 마일리지 카드 보너스 종료 시각
|
||||
|
||||
public bool IsTutorialGuidePaused {
|
||||
get { return tutorialGuidePauseActive; }
|
||||
}
|
||||
|
||||
// 게임 시작과 동시에 싱글톤을 구성
|
||||
void Awake() {
|
||||
// 싱글톤 변수 instance가 비어있는가?
|
||||
@@ -120,7 +138,6 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
BuildRuntimeHUD();
|
||||
StartTutorialGuideSequence();
|
||||
BeginMap(GameFlowManager.Instance != null
|
||||
? GameFlowManager.Instance.CurrentMap
|
||||
: MapDatabase.GetMap(MapId.TutorialIncheon));
|
||||
@@ -153,11 +170,15 @@ public class GameManager : MonoBehaviour {
|
||||
gameSpeed = Mathf.Min(gameSpeed, maxGameSpeed);
|
||||
|
||||
stageDistance += 10f * gameSpeed * Time.deltaTime;
|
||||
UpdateFinishGate();
|
||||
|
||||
if(currentMapDefinition != null && currentMapDefinition.targetDistance > 0f && stageDistance >= currentMapDefinition.targetDistance)
|
||||
{
|
||||
CompleteStage(true);
|
||||
return;
|
||||
if(!ShouldUseFinishGate() || stageDistance >= currentMapDefinition.targetDistance + finishGateFallbackDistance)
|
||||
{
|
||||
CompleteStage(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(currentMapDefinition != null && currentMapDefinition.timeLimit > 0f && stageElapsedTime >= currentMapDefinition.timeLimit)
|
||||
@@ -171,9 +192,13 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
public void BeginMap(MapDefinition mapDefinition) {
|
||||
StopTutorialGuidePause(true);
|
||||
Time.timeScale = 1f;
|
||||
|
||||
currentMapDefinition = mapDefinition != null
|
||||
? mapDefinition
|
||||
: MapDatabase.GetMap(MapId.TutorialIncheon);
|
||||
BackgroundLoop.ApplyResourceBackground(currentMapDefinition.backgroundResourcePath);
|
||||
|
||||
score = 0;
|
||||
stageMileage = 0;
|
||||
@@ -185,10 +210,12 @@ public class GameManager : MonoBehaviour {
|
||||
stageElapsedTime = 0f;
|
||||
stageDistance = 0f;
|
||||
stageCompleted = false;
|
||||
finishGateSpawned = false;
|
||||
isGameover = false;
|
||||
tutorialGuideSequenceComplete = false;
|
||||
currentTutorialGuideSequenceIndex = -1;
|
||||
SetTutorialGuide(TutorialGuideType.None, "", "");
|
||||
RemoveActiveFinishGate();
|
||||
|
||||
if(gameoverUI != null)
|
||||
{
|
||||
@@ -201,6 +228,7 @@ public class GameManager : MonoBehaviour {
|
||||
platformSpawner.ApplyMap(currentMapDefinition);
|
||||
}
|
||||
|
||||
StartTutorialGuideSequence();
|
||||
UpdateScoreUI();
|
||||
}
|
||||
|
||||
@@ -220,15 +248,99 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
public void SetTutorialGuide(TutorialGuideType guideType, string title, string message) {
|
||||
tutorialGuideVersion++;
|
||||
tutorialGuideType = guideType;
|
||||
tutorialTitle = title;
|
||||
tutorialMessage = message;
|
||||
|
||||
if(!HasTutorialGuideContent(guideType, title, message))
|
||||
{
|
||||
StopTutorialGuidePause(true);
|
||||
UpdateScoreUI();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateScoreUI();
|
||||
TryPauseForTutorialGuide(tutorialGuideVersion);
|
||||
}
|
||||
|
||||
private bool HasTutorialGuideContent(TutorialGuideType guideType, string title, string message) {
|
||||
return guideType != TutorialGuideType.None
|
||||
|| !string.IsNullOrEmpty(title)
|
||||
|| !string.IsNullOrEmpty(message);
|
||||
}
|
||||
|
||||
private void TryPauseForTutorialGuide(int guideVersion) {
|
||||
if(!pauseGameplayForTutorialGuide
|
||||
|| tutorialGuidePauseDuration <= 0f
|
||||
|| isGameover
|
||||
|| !GameFlowManager.IsGameplayActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tutorialGuidePauseActive)
|
||||
{
|
||||
tutorialGuidePreviousTimeScale = Time.timeScale;
|
||||
tutorialGuidePauseActive = true;
|
||||
}
|
||||
|
||||
Time.timeScale = 0f;
|
||||
|
||||
if(tutorialGuidePauseRoutine != null)
|
||||
{
|
||||
StopCoroutine(tutorialGuidePauseRoutine);
|
||||
}
|
||||
|
||||
tutorialGuidePauseRoutine = StartCoroutine(HideTutorialGuideAfterPause(guideVersion));
|
||||
}
|
||||
|
||||
private IEnumerator HideTutorialGuideAfterPause(int guideVersion) {
|
||||
yield return new WaitForSecondsRealtime(tutorialGuidePauseDuration);
|
||||
|
||||
tutorialGuidePauseRoutine = null;
|
||||
|
||||
if(guideVersion == tutorialGuideVersion)
|
||||
{
|
||||
tutorialGuideVersion++;
|
||||
tutorialGuideType = TutorialGuideType.None;
|
||||
tutorialTitle = "";
|
||||
tutorialMessage = "";
|
||||
UpdateScoreUI();
|
||||
}
|
||||
|
||||
RestoreTutorialGuideTimeScale();
|
||||
}
|
||||
|
||||
private void StopTutorialGuidePause(bool restoreTimeScale) {
|
||||
if(tutorialGuidePauseRoutine != null)
|
||||
{
|
||||
StopCoroutine(tutorialGuidePauseRoutine);
|
||||
tutorialGuidePauseRoutine = null;
|
||||
}
|
||||
|
||||
if(restoreTimeScale)
|
||||
{
|
||||
RestoreTutorialGuideTimeScale();
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreTutorialGuideTimeScale() {
|
||||
if(!tutorialGuidePauseActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Time.timeScale = Mathf.Approximately(tutorialGuidePreviousTimeScale, 0f)
|
||||
? 1f
|
||||
: tutorialGuidePreviousTimeScale;
|
||||
tutorialGuidePauseActive = false;
|
||||
}
|
||||
|
||||
private void StartTutorialGuideSequence() {
|
||||
if(!playTutorialGuideSequence)
|
||||
if(!ShouldUseTimeBasedTutorialGuide())
|
||||
{
|
||||
tutorialGuideSequenceComplete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -239,7 +351,7 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
|
||||
private void UpdateTutorialGuideSequence() {
|
||||
if(!playTutorialGuideSequence || tutorialGuideSequenceComplete)
|
||||
if(!ShouldUseTimeBasedTutorialGuide() || tutorialGuideSequenceComplete)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -256,6 +368,33 @@ public class GameManager : MonoBehaviour {
|
||||
ApplyTutorialGuideSequenceStep(guideIndex);
|
||||
}
|
||||
|
||||
private bool ShouldUseTimeBasedTutorialGuide() {
|
||||
return playTutorialGuideSequence && !HasCourseDrivenTutorialGuide();
|
||||
}
|
||||
|
||||
private bool HasCourseDrivenTutorialGuide() {
|
||||
if(currentMapDefinition == null
|
||||
|| currentMapDefinition.openingSteps == null
|
||||
|| currentMapDefinition.openingSteps.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < currentMapDefinition.openingSteps.Length; i++)
|
||||
{
|
||||
PlatformSpawner.TutorialStep step = currentMapDefinition.openingSteps[i];
|
||||
if(step != null
|
||||
&& (step.guideType != TutorialGuideType.None
|
||||
|| !string.IsNullOrEmpty(step.title)
|
||||
|| !string.IsNullOrEmpty(step.message)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int GetTutorialGuideSequenceIndex(float elapsedTime) {
|
||||
if(elapsedTime < TutorialJumpGuideDuration)
|
||||
{
|
||||
@@ -359,23 +498,38 @@ public class GameManager : MonoBehaviour {
|
||||
case TutorialItemType.HealthSushi:
|
||||
currentLife = Mathf.Min(maxLife, currentLife + 1);
|
||||
AddScore(3);
|
||||
SetTutorialGuide(TutorialGuideType.Items, "초밥", "방금 잃은 목숨을 1 회복합니다.");
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "초밥", "방금 잃은 목숨을 1 회복합니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.InvincibleRamen:
|
||||
itemInvincibleEndTime = Time.time + itemInvincibleDuration;
|
||||
SetTutorialGuide(TutorialGuideType.Items, "라멘", "짧은 시간 동안 다음 충돌 피해를 받지 않습니다.");
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "라멘", "짧은 시간 동안 다음 충돌 피해를 받지 않습니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.Shield:
|
||||
shieldCharges = Mathf.Max(shieldCharges, 1);
|
||||
SetTutorialGuide(TutorialGuideType.Items, "방어막", "다음 충돌 1회를 방어막이 대신 막습니다.");
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "방어막", "다음 충돌 1회를 방어막이 대신 막습니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.SpeedShoes:
|
||||
gameSpeed = Mathf.Min(maxGameSpeed, gameSpeed + itemSpeedBoostAmount);
|
||||
SetTutorialGuide(TutorialGuideType.Items, "운동화", "속도를 즉시 회복해 늦어진 일정을 따라잡습니다.");
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "운동화", "속도를 즉시 회복해 늦어진 일정을 따라잡습니다.");
|
||||
}
|
||||
break;
|
||||
case TutorialItemType.MileageCard:
|
||||
mileageBonusEndTime = Time.time + mileageBonusDuration;
|
||||
SetTutorialGuide(TutorialGuideType.Items, "마일리지 카드", "잠시 동안 먹는 마일리지가 두 배로 들어옵니다.");
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "마일리지 카드", "잠시 동안 먹는 마일리지가 두 배로 들어옵니다.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -390,14 +544,23 @@ public class GameManager : MonoBehaviour {
|
||||
|
||||
if(IsItemInvincibleActive())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "라멘", "무적 중이라 이번 충돌은 피해 없이 지나갑니다.");
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "라멘", "무적 중이라 이번 충돌은 피해 없이 지나갑니다.");
|
||||
}
|
||||
|
||||
SpawnItemBlockFeedback(TutorialItemType.InvincibleRamen);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(shieldCharges > 0)
|
||||
{
|
||||
shieldCharges--;
|
||||
SetTutorialGuide(TutorialGuideType.Items, "방어막", "방어막이 충돌 피해를 한 번 막았습니다.");
|
||||
SpawnItemBlockFeedback(TutorialItemType.Shield);
|
||||
if(ShouldShowItemEffectGuide())
|
||||
{
|
||||
SetTutorialGuide(TutorialGuideType.Items, "방어막", "방어막이 충돌 피해를 한 번 막았습니다.");
|
||||
}
|
||||
UpdateScoreUI();
|
||||
return true;
|
||||
}
|
||||
@@ -405,6 +568,20 @@ public class GameManager : MonoBehaviour {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SpawnItemBlockFeedback(TutorialItemType itemType) {
|
||||
GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
|
||||
if(playerObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ItemFeedbackEffect.Spawn(playerObject.transform.position, null, itemType, true);
|
||||
}
|
||||
|
||||
private bool ShouldShowItemEffectGuide() {
|
||||
return currentMapDefinition == null || !currentMapDefinition.isTutorial;
|
||||
}
|
||||
|
||||
private bool IsItemInvincibleActive() {
|
||||
return Time.time < itemInvincibleEndTime;
|
||||
}
|
||||
@@ -425,12 +602,24 @@ public class GameManager : MonoBehaviour {
|
||||
CompleteStage(false);
|
||||
}
|
||||
|
||||
public void CompleteCurrentStageFromFinishGate() {
|
||||
if(currentMapDefinition != null && currentMapDefinition.targetDistance > 0f)
|
||||
{
|
||||
stageDistance = Mathf.Max(stageDistance, currentMapDefinition.targetDistance);
|
||||
}
|
||||
|
||||
CompleteStage(true);
|
||||
}
|
||||
|
||||
private void CompleteStage(bool cleared) {
|
||||
if(stageCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopTutorialGuidePause(true);
|
||||
RemoveActiveFinishGate();
|
||||
|
||||
stageCompleted = true;
|
||||
isGameover = true;
|
||||
|
||||
@@ -444,6 +633,40 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFinishGate() {
|
||||
if(!ShouldUseFinishGate() || finishGateSpawned || stageCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float spawnLeadDistance = Mathf.Max(0f, finishGateSpawnX - finishGatePlayerX);
|
||||
float spawnDistance = Mathf.Max(0f, currentMapDefinition.targetDistance - spawnLeadDistance);
|
||||
if(stageDistance < spawnDistance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject gateObject = new GameObject("Finish Gate - " + currentMapDefinition.displayName);
|
||||
gateObject.transform.position = new Vector3(finishGateSpawnX, finishGateGroundY, 0f);
|
||||
activeFinishGate = gateObject.AddComponent<FinishGate>();
|
||||
activeFinishGate.Setup(currentMapDefinition.finishGateStyle);
|
||||
finishGateSpawned = true;
|
||||
}
|
||||
|
||||
private bool ShouldUseFinishGate() {
|
||||
return currentMapDefinition != null
|
||||
&& currentMapDefinition.finishGateStyle != FinishGateStyle.None
|
||||
&& currentMapDefinition.targetDistance > 0f;
|
||||
}
|
||||
|
||||
private void RemoveActiveFinishGate() {
|
||||
if(activeFinishGate != null)
|
||||
{
|
||||
Destroy(activeFinishGate.gameObject);
|
||||
activeFinishGate = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncTotalMileageToFlow() {
|
||||
if(GameFlowManager.Instance != null)
|
||||
{
|
||||
@@ -518,6 +741,8 @@ public class GameManager : MonoBehaviour {
|
||||
|
||||
bool showItemRow = tutorialGuideType == TutorialGuideType.Items;
|
||||
Sprite mainSprite = GetTutorialMainSprite(tutorialGuideType);
|
||||
bool showFullText = !showItemRow && mainSprite == null;
|
||||
UpdateTutorialTextLayout(showItemRow, showFullText);
|
||||
|
||||
if(tutorialMainImage != null)
|
||||
{
|
||||
@@ -542,6 +767,40 @@ public class GameManager : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTutorialTextLayout(bool showItemRow, bool showFullText) {
|
||||
Vector2 textPosition = new Vector2(66f, -8f);
|
||||
Vector2 textSize = new Vector2(268f, 17f);
|
||||
Vector2 bodyPosition = new Vector2(66f, -26f);
|
||||
Vector2 bodySize = new Vector2(268f, 31f);
|
||||
|
||||
if(showItemRow)
|
||||
{
|
||||
textPosition = new Vector2(154f, -8f);
|
||||
textSize = new Vector2(178f, 17f);
|
||||
bodyPosition = new Vector2(154f, -26f);
|
||||
bodySize = new Vector2(178f, 31f);
|
||||
}
|
||||
else if(showFullText)
|
||||
{
|
||||
textPosition = new Vector2(16f, -8f);
|
||||
textSize = new Vector2(316f, 17f);
|
||||
bodyPosition = new Vector2(16f, -26f);
|
||||
bodySize = new Vector2(316f, 31f);
|
||||
}
|
||||
|
||||
if(tutorialTitleText != null)
|
||||
{
|
||||
tutorialTitleText.rectTransform.anchoredPosition = textPosition;
|
||||
tutorialTitleText.rectTransform.sizeDelta = textSize;
|
||||
}
|
||||
|
||||
if(tutorialBodyText != null)
|
||||
{
|
||||
tutorialBodyText.rectTransform.anchoredPosition = bodyPosition;
|
||||
tutorialBodyText.rectTransform.sizeDelta = bodySize;
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetActiveLifeIconColor(Image lifeIcon) {
|
||||
if(lifeIcon != null && lifeIcon.sprite == null)
|
||||
{
|
||||
@@ -726,22 +985,22 @@ public class GameManager : MonoBehaviour {
|
||||
tutorialGuideObject.transform.SetParent(canvasTransform, false);
|
||||
|
||||
RectTransform guideRect = tutorialGuideObject.GetComponent<RectTransform>();
|
||||
guideRect.anchorMin = new Vector2(0.5f, 0f);
|
||||
guideRect.anchorMax = new Vector2(0.5f, 0f);
|
||||
guideRect.pivot = new Vector2(0.5f, 0f);
|
||||
guideRect.anchoredPosition = new Vector2(18f, 12f);
|
||||
guideRect.sizeDelta = new Vector2(372f, 72f);
|
||||
guideRect.anchorMin = new Vector2(0.5f, 1f);
|
||||
guideRect.anchorMax = new Vector2(0.5f, 1f);
|
||||
guideRect.pivot = new Vector2(0.5f, 1f);
|
||||
guideRect.anchoredPosition = new Vector2(0f, -12f);
|
||||
guideRect.sizeDelta = new Vector2(348f, 66f);
|
||||
|
||||
Image guideBackground = tutorialGuideObject.GetComponent<Image>();
|
||||
guideBackground.color = new Color(0.035f, 0.055f, 0.07f, 0.76f);
|
||||
guideBackground.color = new Color(0.035f, 0.055f, 0.07f, 0.82f);
|
||||
guideBackground.raycastTarget = false;
|
||||
|
||||
tutorialFontAsset = CreateKoreanTutorialFontAsset();
|
||||
|
||||
CreateTutorialMainImage(tutorialGuideObject.transform);
|
||||
CreateTutorialItemRow(tutorialGuideObject.transform);
|
||||
tutorialTitleText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Title", new Vector2(70f, -9f), new Vector2(286f, 18f), 12f, FontStyles.Bold);
|
||||
tutorialBodyText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Body", new Vector2(70f, -28f), new Vector2(286f, 34f), 9f, FontStyles.Normal);
|
||||
tutorialTitleText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Title", new Vector2(66f, -8f), new Vector2(268f, 17f), 11.5f, FontStyles.Bold);
|
||||
tutorialBodyText = CreateTutorialText(tutorialGuideObject.transform, "Tutorial Body", new Vector2(66f, -26f), new Vector2(268f, 31f), 8.5f, FontStyles.Normal);
|
||||
tutorialGuideObject.SetActive(false);
|
||||
}
|
||||
|
||||
@@ -936,4 +1195,413 @@ public class GameManager : MonoBehaviour {
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
private class CourseMinimapView {
|
||||
private struct MarkerInfo {
|
||||
public string label;
|
||||
public string kind;
|
||||
public Color color;
|
||||
}
|
||||
|
||||
private const float RootWidth = 452f;
|
||||
private const float RootHeight = 72f;
|
||||
private const float CourseWidth = 386f;
|
||||
private const float CourseAreaHeight = 36f;
|
||||
private const float CourseAreaY = -9f;
|
||||
private const float GroundY = -22f;
|
||||
private const float CeilingY = 3f;
|
||||
private const float PlatformHeight = 6f;
|
||||
private const float PlatformDistanceWidth = 7.4f;
|
||||
private const float CourseDistancePerInterval = 10f;
|
||||
private const int MaxCourseObjectCount = 96;
|
||||
|
||||
private readonly List<GameObject> markerObjects = new List<GameObject>();
|
||||
private RectTransform rootRect;
|
||||
private RectTransform progressFillRect;
|
||||
private RectTransform playerMarkerRect;
|
||||
private TextMeshProUGUI routeText;
|
||||
private TextMeshProUGUI progressText;
|
||||
private TMP_FontAsset fontAsset;
|
||||
private bool hasActiveMap = false;
|
||||
private MapId activeMapId;
|
||||
private float activeTargetDistance = -1f;
|
||||
|
||||
public void Build(Transform canvasTransform, TMP_FontAsset minimapFont) {
|
||||
fontAsset = minimapFont;
|
||||
|
||||
GameObject rootObject = new GameObject("Course Minimap", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
rootObject.transform.SetParent(canvasTransform, false);
|
||||
|
||||
rootRect = rootObject.GetComponent<RectTransform>();
|
||||
rootRect.anchorMin = new Vector2(0.5f, 0f);
|
||||
rootRect.anchorMax = new Vector2(0.5f, 0f);
|
||||
rootRect.pivot = new Vector2(0.5f, 0f);
|
||||
rootRect.anchoredPosition = new Vector2(0f, 9f);
|
||||
rootRect.sizeDelta = new Vector2(RootWidth, RootHeight);
|
||||
|
||||
Image background = rootObject.GetComponent<Image>();
|
||||
background.color = new Color(0.025f, 0.045f, 0.055f, 0.78f);
|
||||
background.raycastTarget = false;
|
||||
|
||||
routeText = CreateText(rootObject.transform, "Route Label", new Vector2(-207f, 24f), new Vector2(270f, 14f), 8.4f, TextAlignmentOptions.Left);
|
||||
progressText = CreateText(rootObject.transform, "Route Progress", new Vector2(174f, 24f), new Vector2(94f, 14f), 8.4f, TextAlignmentOptions.Right);
|
||||
|
||||
CreateBlock(rootObject.transform, "Course Drawing Area", new Vector2(0f, CourseAreaY), new Vector2(CourseWidth + 18f, CourseAreaHeight), new Color(0.04f, 0.08f, 0.095f, 0.78f));
|
||||
|
||||
GameObject progressObject = new GameObject("Track Progress", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
progressObject.transform.SetParent(rootObject.transform, false);
|
||||
|
||||
progressFillRect = progressObject.GetComponent<RectTransform>();
|
||||
progressFillRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
progressFillRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
progressFillRect.pivot = new Vector2(0f, 0.5f);
|
||||
progressFillRect.anchoredPosition = new Vector2(-CourseWidth * 0.5f, CourseAreaY);
|
||||
progressFillRect.sizeDelta = new Vector2(0f, CourseAreaHeight);
|
||||
|
||||
Image progressImage = progressObject.GetComponent<Image>();
|
||||
progressImage.color = new Color(0.18f, 0.55f, 0.68f, 0.28f);
|
||||
progressImage.raycastTarget = false;
|
||||
|
||||
Image playerMarker = CreateBlock(rootObject.transform, "Player Marker", new Vector2(GetCourseX(0f), GroundY + 14f), new Vector2(7f, 24f), new Color(1f, 0.78f, 0.21f, 1f));
|
||||
playerMarkerRect = playerMarker.rectTransform;
|
||||
}
|
||||
|
||||
public void Refresh(MapDefinition mapDefinition, float stageDistance) {
|
||||
if(rootRect == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(mapDefinition == null)
|
||||
{
|
||||
rootRect.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
rootRect.gameObject.SetActive(true);
|
||||
|
||||
float targetDistance = Mathf.Max(1f, mapDefinition.targetDistance);
|
||||
bool mapChanged = !hasActiveMap
|
||||
|| activeMapId != mapDefinition.mapId
|
||||
|| !Mathf.Approximately(activeTargetDistance, targetDistance);
|
||||
|
||||
if(mapChanged)
|
||||
{
|
||||
hasActiveMap = true;
|
||||
activeMapId = mapDefinition.mapId;
|
||||
activeTargetDistance = targetDistance;
|
||||
RebuildMarkers(mapDefinition, targetDistance);
|
||||
|
||||
if(routeText != null)
|
||||
{
|
||||
routeText.text = !string.IsNullOrEmpty(mapDefinition.routeName)
|
||||
? mapDefinition.routeName
|
||||
: mapDefinition.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
float progress = Mathf.Clamp01(stageDistance / targetDistance);
|
||||
if(progressFillRect != null)
|
||||
{
|
||||
progressFillRect.sizeDelta = new Vector2(CourseWidth * progress, CourseAreaHeight);
|
||||
}
|
||||
|
||||
if(playerMarkerRect != null)
|
||||
{
|
||||
playerMarkerRect.anchoredPosition = new Vector2(GetCourseX(progress), GroundY + 14f);
|
||||
playerMarkerRect.SetAsLastSibling();
|
||||
}
|
||||
|
||||
if(progressText != null)
|
||||
{
|
||||
progressText.text = Mathf.FloorToInt(stageDistance) + "/" + Mathf.FloorToInt(targetDistance) + "m";
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildMarkers(MapDefinition mapDefinition, float targetDistance) {
|
||||
ClearMarkers();
|
||||
|
||||
float cursor = 0f;
|
||||
AddStepMarkers(mapDefinition.openingSteps, ref cursor, targetDistance);
|
||||
|
||||
PlatformSpawner.TutorialStep[] loopSteps = mapDefinition.loopSteps;
|
||||
int loopGuard = 0;
|
||||
while(cursor < targetDistance
|
||||
&& loopSteps != null
|
||||
&& loopSteps.Length > 0
|
||||
&& markerObjects.Count < MaxCourseObjectCount
|
||||
&& loopGuard < 300)
|
||||
{
|
||||
AddStepMarkers(loopSteps, ref cursor, targetDistance);
|
||||
loopGuard++;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddStepMarkers(PlatformSpawner.TutorialStep[] steps, ref float cursor, float targetDistance) {
|
||||
if(steps == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < steps.Length; i++)
|
||||
{
|
||||
PlatformSpawner.TutorialStep step = steps[i];
|
||||
float stepDistance = GetStepDistance(step);
|
||||
|
||||
if(markerObjects.Count < MaxCourseObjectCount)
|
||||
{
|
||||
float centerDistance = cursor + (stepDistance * 0.5f);
|
||||
float centerProgress = Mathf.Clamp01(centerDistance / targetDistance);
|
||||
float platformWidth = Mathf.Max(8f, CourseWidth * (PlatformDistanceWidth / targetDistance));
|
||||
CreatePlatformPiece(centerProgress, platformWidth, GetPlatformColor(step));
|
||||
|
||||
if(TryGetMarkerInfo(step, out MarkerInfo markerInfo))
|
||||
{
|
||||
CreateCourseFeature(centerProgress, markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
cursor += stepDistance;
|
||||
if(cursor >= targetDistance && markerObjects.Count >= MaxCourseObjectCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float GetStepDistance(PlatformSpawner.TutorialStep step) {
|
||||
if(step == null)
|
||||
{
|
||||
return CourseDistancePerInterval;
|
||||
}
|
||||
|
||||
return Mathf.Max(0.3f, step.spawnInterval) * CourseDistancePerInterval;
|
||||
}
|
||||
|
||||
private bool TryGetMarkerInfo(PlatformSpawner.TutorialStep step, out MarkerInfo markerInfo) {
|
||||
markerInfo = new MarkerInfo();
|
||||
|
||||
if(step == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(step.itemPattern != Platform.ItemPattern.None)
|
||||
{
|
||||
markerInfo.label = GetItemMarkerLabel(step.itemPattern);
|
||||
markerInfo.color = new Color(0.38f, 0.93f, 0.57f, 0.98f);
|
||||
markerInfo.kind = "Item";
|
||||
return true;
|
||||
}
|
||||
|
||||
if(step.coursePlatformType == PlatformSpawner.CoursePlatformType.ForceHit
|
||||
|| step.platformPattern == Platform.PlatformPattern.ForceHit)
|
||||
{
|
||||
markerInfo.label = "!";
|
||||
markerInfo.color = new Color(1f, 0.34f, 0.28f, 0.98f);
|
||||
markerInfo.kind = "ForceHit";
|
||||
return true;
|
||||
}
|
||||
|
||||
if(step.guideType == TutorialGuideType.DoubleJump || step.spawnInterval >= 1.4f)
|
||||
{
|
||||
markerInfo.label = "2";
|
||||
markerInfo.color = new Color(0.40f, 0.89f, 1f, 0.98f);
|
||||
markerInfo.kind = "DoubleJump";
|
||||
return true;
|
||||
}
|
||||
|
||||
if(step.coursePlatformType == PlatformSpawner.CoursePlatformType.Slide
|
||||
|| step.platformPattern == Platform.PlatformPattern.Slide)
|
||||
{
|
||||
markerInfo.label = "S";
|
||||
markerInfo.color = new Color(0.47f, 0.63f, 1f, 0.98f);
|
||||
markerInfo.kind = "Slide";
|
||||
return true;
|
||||
}
|
||||
|
||||
if(step.coursePlatformType == PlatformSpawner.CoursePlatformType.Jump
|
||||
|| step.platformPattern == Platform.PlatformPattern.LowLeft
|
||||
|| step.platformPattern == Platform.PlatformPattern.LowMid
|
||||
|| step.platformPattern == Platform.PlatformPattern.LowRight)
|
||||
{
|
||||
markerInfo.label = "J";
|
||||
markerInfo.color = new Color(1f, 0.83f, 0.28f, 0.98f);
|
||||
markerInfo.kind = "Jump";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetItemMarkerLabel(Platform.ItemPattern itemPattern) {
|
||||
switch(itemPattern)
|
||||
{
|
||||
case Platform.ItemPattern.HealthSushi:
|
||||
return "+";
|
||||
case Platform.ItemPattern.InvincibleRamen:
|
||||
return "R";
|
||||
case Platform.ItemPattern.Shield:
|
||||
return "G";
|
||||
case Platform.ItemPattern.SpeedShoes:
|
||||
return "V";
|
||||
case Platform.ItemPattern.MileageCard:
|
||||
return "M";
|
||||
default:
|
||||
return "I";
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePlatformPiece(float progress, float width, Color color) {
|
||||
Image platformImage = CreateBlock(rootRect, "Course Platform", new Vector2(GetCourseX(progress), GroundY), new Vector2(width, PlatformHeight), color);
|
||||
markerObjects.Add(platformImage.gameObject);
|
||||
}
|
||||
|
||||
private Color GetPlatformColor(PlatformSpawner.TutorialStep step) {
|
||||
if(step != null && step.spawnInterval >= 1.4f)
|
||||
{
|
||||
return new Color(0.28f, 0.50f, 0.57f, 0.96f);
|
||||
}
|
||||
|
||||
return new Color(0.33f, 0.63f, 0.70f, 0.96f);
|
||||
}
|
||||
|
||||
private void CreateCourseFeature(float progress, MarkerInfo markerInfo) {
|
||||
if(markerInfo.kind == "Slide")
|
||||
{
|
||||
CreateSlideFeature(progress, markerInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
if(markerInfo.kind == "ForceHit")
|
||||
{
|
||||
CreateJumpFeature(progress, markerInfo);
|
||||
CreateSlideFeature(progress, markerInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
if(markerInfo.kind == "Item")
|
||||
{
|
||||
CreateItemFeature(progress, markerInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
if(markerInfo.kind == "DoubleJump")
|
||||
{
|
||||
CreateDoubleJumpFeature(progress, markerInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
CreateJumpFeature(progress, markerInfo);
|
||||
}
|
||||
|
||||
private void CreateJumpFeature(float progress, MarkerInfo markerInfo) {
|
||||
float x = GetCourseX(progress);
|
||||
Image obstacle = CreateBlock(rootRect, "Jump Obstacle", new Vector2(x, GroundY + 8f), new Vector2(10f, 12f), markerInfo.color);
|
||||
markerObjects.Add(obstacle.gameObject);
|
||||
CreateTinyLabel(markerInfo.label, new Vector2(x, GroundY + 8f), new Color(0.02f, 0.04f, 0.05f, 0.95f));
|
||||
}
|
||||
|
||||
private void CreateSlideFeature(float progress, MarkerInfo markerInfo) {
|
||||
float x = GetCourseX(progress);
|
||||
Image ceiling = CreateBlock(rootRect, "Slide Ceiling", new Vector2(x, CeilingY), new Vector2(32f, 6f), new Color(0.56f, 0.70f, 0.98f, 0.95f));
|
||||
markerObjects.Add(ceiling.gameObject);
|
||||
Image hanging = CreateBlock(rootRect, "Slide Hanging Obstacle", new Vector2(x, CeilingY - 8f), new Vector2(18f, 10f), markerInfo.color);
|
||||
markerObjects.Add(hanging.gameObject);
|
||||
CreateTinyLabel(markerInfo.label, new Vector2(x, CeilingY - 8f), new Color(0.02f, 0.04f, 0.05f, 0.95f));
|
||||
}
|
||||
|
||||
private void CreateItemFeature(float progress, MarkerInfo markerInfo) {
|
||||
float x = GetCourseX(progress);
|
||||
GameObject markerObject = new GameObject("Course Marker " + markerInfo.label, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
markerObject.transform.SetParent(rootRect, false);
|
||||
|
||||
RectTransform markerRect = markerObject.GetComponent<RectTransform>();
|
||||
markerRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
markerRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
markerRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
markerRect.anchoredPosition = new Vector2(x, GroundY + 15f);
|
||||
markerRect.sizeDelta = new Vector2(11f, 11f);
|
||||
|
||||
Image markerImage = markerObject.GetComponent<Image>();
|
||||
markerImage.color = markerInfo.color;
|
||||
markerImage.raycastTarget = false;
|
||||
|
||||
TextMeshProUGUI markerText = CreateText(markerObject.transform, "Label", Vector2.zero, new Vector2(11f, 11f), 6.4f, TextAlignmentOptions.Center);
|
||||
markerText.color = new Color(0.02f, 0.04f, 0.05f, 0.95f);
|
||||
markerText.fontStyle = FontStyles.Bold;
|
||||
markerText.text = markerInfo.label;
|
||||
|
||||
markerObjects.Add(markerObject);
|
||||
}
|
||||
|
||||
private void CreateDoubleJumpFeature(float progress, MarkerInfo markerInfo) {
|
||||
float x = GetCourseX(progress);
|
||||
Image gapMarker = CreateBlock(rootRect, "Double Jump Gap", new Vector2(x, GroundY + 14f), new Vector2(24f, 3f), markerInfo.color);
|
||||
markerObjects.Add(gapMarker.gameObject);
|
||||
CreateTinyLabel(markerInfo.label, new Vector2(x, GroundY + 21f), markerInfo.color);
|
||||
}
|
||||
|
||||
private void CreateTinyLabel(string label, Vector2 anchoredPosition, Color color) {
|
||||
TextMeshProUGUI markerText = CreateText(rootRect, "Course Label " + label, anchoredPosition, new Vector2(14f, 11f), 6.4f, TextAlignmentOptions.Center);
|
||||
markerText.color = color;
|
||||
markerText.fontStyle = FontStyles.Bold;
|
||||
markerText.text = label;
|
||||
markerObjects.Add(markerText.gameObject);
|
||||
}
|
||||
|
||||
private TextMeshProUGUI CreateText(Transform parent, string objectName, Vector2 anchoredPosition, Vector2 sizeDelta, float fontSize, TextAlignmentOptions alignment) {
|
||||
GameObject textObject = new GameObject(objectName, typeof(RectTransform), typeof(CanvasRenderer));
|
||||
textObject.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform textRect = textObject.GetComponent<RectTransform>();
|
||||
textRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
textRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
textRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
textRect.anchoredPosition = anchoredPosition;
|
||||
textRect.sizeDelta = sizeDelta;
|
||||
|
||||
TextMeshProUGUI text = textObject.AddComponent<TextMeshProUGUI>();
|
||||
text.font = fontAsset;
|
||||
text.alignment = alignment;
|
||||
text.fontSize = fontSize;
|
||||
text.color = new Color(0.88f, 0.96f, 1f, 0.92f);
|
||||
text.raycastTarget = false;
|
||||
text.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
return text;
|
||||
}
|
||||
|
||||
private Image CreateBlock(Transform parent, string objectName, Vector2 anchoredPosition, Vector2 sizeDelta, Color color) {
|
||||
GameObject blockObject = new GameObject(objectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
blockObject.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform blockRect = blockObject.GetComponent<RectTransform>();
|
||||
blockRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
blockRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
blockRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
blockRect.anchoredPosition = anchoredPosition;
|
||||
blockRect.sizeDelta = sizeDelta;
|
||||
|
||||
Image image = blockObject.GetComponent<Image>();
|
||||
image.color = color;
|
||||
image.raycastTarget = false;
|
||||
return image;
|
||||
}
|
||||
|
||||
private float GetCourseX(float progress) {
|
||||
return (-CourseWidth * 0.5f) + (CourseWidth * Mathf.Clamp01(progress));
|
||||
}
|
||||
|
||||
private void ClearMarkers() {
|
||||
for(int i = 0; i < markerObjects.Count; i++)
|
||||
{
|
||||
if(markerObjects[i] != null)
|
||||
{
|
||||
Object.Destroy(markerObjects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
markerObjects.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
public class ItemFeedbackEffect : MonoBehaviour
|
||||
{
|
||||
private const string SortingLayerName = "Foreground";
|
||||
private const float Lifetime = 0.85f;
|
||||
|
||||
private static Sprite ringSprite;
|
||||
private static Sprite dotSprite;
|
||||
private static Sprite sparkleSprite;
|
||||
|
||||
private SpriteRenderer ringRenderer;
|
||||
private SpriteRenderer iconRenderer;
|
||||
private SpriteRenderer[] sparkleRenderers;
|
||||
private TextMeshPro labelText;
|
||||
private Color mainColor;
|
||||
private float elapsedTime;
|
||||
|
||||
public static void Spawn(Vector3 worldPosition, Sprite itemSprite, GameManager.TutorialItemType itemType, bool blockedDamage)
|
||||
{
|
||||
GameObject effectObject = new GameObject(blockedDamage ? "Item Block Feedback" : "Item Pickup Feedback");
|
||||
effectObject.transform.position = worldPosition + new Vector3(0f, blockedDamage ? 1.1f : 0.85f, 0f);
|
||||
|
||||
ItemFeedbackEffect effect = effectObject.AddComponent<ItemFeedbackEffect>();
|
||||
effect.Initialize(itemSprite, itemType, blockedDamage);
|
||||
}
|
||||
|
||||
private void Initialize(Sprite itemSprite, GameManager.TutorialItemType itemType, bool blockedDamage)
|
||||
{
|
||||
mainColor = GetItemColor(itemType, blockedDamage);
|
||||
sparkleRenderers = new SpriteRenderer[5];
|
||||
|
||||
ringRenderer = CreateSpriteChild("Ring", GetRingSprite(), Vector3.zero, 1.4f, 28);
|
||||
iconRenderer = CreateSpriteChild("Icon", itemSprite != null ? itemSprite : GetDotSprite(), new Vector3(0f, 0.03f, 0f), itemSprite != null ? 0.52f : 0.72f, 29);
|
||||
|
||||
labelText = CreateLabel(GetLabel(itemType, blockedDamage));
|
||||
|
||||
for(int i = 0; i < sparkleRenderers.Length; i++)
|
||||
{
|
||||
float angle = (Mathf.PI * 2f / sparkleRenderers.Length) * i;
|
||||
Vector3 localPosition = new Vector3(Mathf.Cos(angle) * 0.58f, Mathf.Sin(angle) * 0.38f, 0f);
|
||||
sparkleRenderers[i] = CreateSpriteChild("Sparkle " + i, GetSparkleSprite(), localPosition, 0.18f, 30);
|
||||
}
|
||||
|
||||
ApplyAlpha(1f);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
elapsedTime += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsedTime / Lifetime);
|
||||
float fade = 1f - Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.55f, 1f, t));
|
||||
float popScale = Mathf.Lerp(0.72f, 1.16f, EaseOut(t));
|
||||
|
||||
transform.localScale = Vector3.one * popScale;
|
||||
transform.position += Vector3.up * (Time.unscaledDeltaTime * 0.28f);
|
||||
|
||||
if(ringRenderer != null)
|
||||
{
|
||||
ringRenderer.transform.localScale = Vector3.one * Mathf.Lerp(0.82f, 1.38f, EaseOut(t));
|
||||
}
|
||||
|
||||
for(int i = 0; i < sparkleRenderers.Length; i++)
|
||||
{
|
||||
if(sparkleRenderers[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sparkleRenderers[i].transform.localScale = Vector3.one * Mathf.Lerp(0.16f, 0.32f, EaseOut(t));
|
||||
sparkleRenderers[i].transform.Rotate(Vector3.forward, 180f * Time.unscaledDeltaTime);
|
||||
}
|
||||
|
||||
ApplyAlpha(fade);
|
||||
|
||||
if(elapsedTime >= Lifetime)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private SpriteRenderer CreateSpriteChild(string objectName, Sprite sprite, Vector3 localPosition, float scale, int sortingOrder)
|
||||
{
|
||||
GameObject child = new GameObject(objectName, typeof(SpriteRenderer));
|
||||
child.transform.SetParent(transform, false);
|
||||
child.transform.localPosition = localPosition;
|
||||
child.transform.localScale = Vector3.one * scale;
|
||||
|
||||
SpriteRenderer spriteRenderer = child.GetComponent<SpriteRenderer>();
|
||||
spriteRenderer.sprite = sprite;
|
||||
spriteRenderer.color = mainColor;
|
||||
spriteRenderer.sortingLayerName = SortingLayerName;
|
||||
spriteRenderer.sortingOrder = sortingOrder;
|
||||
return spriteRenderer;
|
||||
}
|
||||
|
||||
private TextMeshPro CreateLabel(string text)
|
||||
{
|
||||
GameObject textObject = new GameObject("Label", typeof(TextMeshPro));
|
||||
textObject.transform.SetParent(transform, false);
|
||||
textObject.transform.localPosition = new Vector3(0f, 0.62f, 0f);
|
||||
textObject.transform.localScale = Vector3.one * 0.28f;
|
||||
|
||||
TextMeshPro textMesh = textObject.GetComponent<TextMeshPro>();
|
||||
textMesh.text = text;
|
||||
textMesh.alignment = TextAlignmentOptions.Center;
|
||||
textMesh.fontSize = 3.2f;
|
||||
textMesh.fontStyle = FontStyles.Bold;
|
||||
textMesh.color = Color.white;
|
||||
textMesh.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
|
||||
MeshRenderer textRenderer = textMesh.GetComponent<MeshRenderer>();
|
||||
textRenderer.sortingLayerName = SortingLayerName;
|
||||
textRenderer.sortingOrder = 31;
|
||||
return textMesh;
|
||||
}
|
||||
|
||||
private void ApplyAlpha(float alpha)
|
||||
{
|
||||
if(ringRenderer != null)
|
||||
{
|
||||
ringRenderer.color = WithAlpha(mainColor, alpha * 0.72f);
|
||||
}
|
||||
|
||||
if(iconRenderer != null)
|
||||
{
|
||||
iconRenderer.color = WithAlpha(Color.white, alpha);
|
||||
}
|
||||
|
||||
if(labelText != null)
|
||||
{
|
||||
labelText.color = WithAlpha(Color.white, alpha);
|
||||
}
|
||||
|
||||
if(sparkleRenderers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < sparkleRenderers.Length; i++)
|
||||
{
|
||||
if(sparkleRenderers[i] != null)
|
||||
{
|
||||
sparkleRenderers[i].color = WithAlpha(mainColor, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLabel(GameManager.TutorialItemType itemType, bool blockedDamage)
|
||||
{
|
||||
if(blockedDamage)
|
||||
{
|
||||
return itemType == GameManager.TutorialItemType.InvincibleRamen ? "SAFE" : "BLOCK";
|
||||
}
|
||||
|
||||
switch(itemType)
|
||||
{
|
||||
case GameManager.TutorialItemType.InvincibleRamen:
|
||||
return "SAFE";
|
||||
case GameManager.TutorialItemType.Shield:
|
||||
return "SHIELD";
|
||||
case GameManager.TutorialItemType.SpeedShoes:
|
||||
return "SPEED";
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return "x2";
|
||||
case GameManager.TutorialItemType.HealthSushi:
|
||||
default:
|
||||
return "+1";
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetItemColor(GameManager.TutorialItemType itemType, bool blockedDamage)
|
||||
{
|
||||
if(blockedDamage)
|
||||
{
|
||||
return itemType == GameManager.TutorialItemType.InvincibleRamen
|
||||
? new Color(1f, 0.76f, 0.24f, 1f)
|
||||
: new Color(0.36f, 0.82f, 1f, 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:
|
||||
return new Color(0.35f, 1f, 0.5f, 1f);
|
||||
case GameManager.TutorialItemType.MileageCard:
|
||||
return new Color(1f, 0.92f, 0.26f, 1f);
|
||||
case GameManager.TutorialItemType.HealthSushi:
|
||||
default:
|
||||
return new Color(1f, 0.34f, 0.42f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
private static float EaseOut(float value)
|
||||
{
|
||||
return 1f - Mathf.Pow(1f - Mathf.Clamp01(value), 3f);
|
||||
}
|
||||
|
||||
private static Color WithAlpha(Color color, float alpha)
|
||||
{
|
||||
return new Color(color.r, color.g, color.b, Mathf.Clamp01(alpha));
|
||||
}
|
||||
|
||||
private static Sprite GetRingSprite()
|
||||
{
|
||||
if(ringSprite == null)
|
||||
{
|
||||
ringSprite = CreateRingSprite(64, 21f, 29f);
|
||||
}
|
||||
|
||||
return ringSprite;
|
||||
}
|
||||
|
||||
private static Sprite GetDotSprite()
|
||||
{
|
||||
if(dotSprite == null)
|
||||
{
|
||||
dotSprite = CreateDiscSprite(48, 19f);
|
||||
}
|
||||
|
||||
return dotSprite;
|
||||
}
|
||||
|
||||
private static Sprite GetSparkleSprite()
|
||||
{
|
||||
if(sparkleSprite == null)
|
||||
{
|
||||
sparkleSprite = CreateSparkleSprite(24);
|
||||
}
|
||||
|
||||
return sparkleSprite;
|
||||
}
|
||||
|
||||
private static Sprite CreateRingSprite(int size, float innerRadius, float outerRadius)
|
||||
{
|
||||
Texture2D texture = CreateTexture(size);
|
||||
Color[] pixels = new Color[size * size];
|
||||
Vector2 center = new Vector2((size - 1) * 0.5f, (size - 1) * 0.5f);
|
||||
|
||||
for(int y = 0; y < size; y++)
|
||||
{
|
||||
for(int x = 0; x < size; x++)
|
||||
{
|
||||
float distance = Vector2.Distance(new Vector2(x, y), center);
|
||||
float outerAlpha = 1f - Mathf.SmoothStep(outerRadius - 2f, outerRadius, distance);
|
||||
float innerAlpha = Mathf.SmoothStep(innerRadius - 2f, innerRadius, distance);
|
||||
pixels[y * size + x] = new Color(1f, 1f, 1f, outerAlpha * innerAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(pixels);
|
||||
texture.Apply();
|
||||
return Sprite.Create(texture, new Rect(0f, 0f, size, size), new Vector2(0.5f, 0.5f), size);
|
||||
}
|
||||
|
||||
private static Sprite CreateDiscSprite(int size, float radius)
|
||||
{
|
||||
Texture2D texture = CreateTexture(size);
|
||||
Color[] pixels = new Color[size * size];
|
||||
Vector2 center = new Vector2((size - 1) * 0.5f, (size - 1) * 0.5f);
|
||||
|
||||
for(int y = 0; y < size; y++)
|
||||
{
|
||||
for(int x = 0; x < size; x++)
|
||||
{
|
||||
float distance = Vector2.Distance(new Vector2(x, y), center);
|
||||
float alpha = 1f - Mathf.SmoothStep(radius - 2f, radius, distance);
|
||||
pixels[y * size + x] = new Color(1f, 1f, 1f, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(pixels);
|
||||
texture.Apply();
|
||||
return Sprite.Create(texture, new Rect(0f, 0f, size, size), new Vector2(0.5f, 0.5f), size);
|
||||
}
|
||||
|
||||
private static Sprite CreateSparkleSprite(int size)
|
||||
{
|
||||
Texture2D texture = CreateTexture(size);
|
||||
Color[] pixels = new Color[size * size];
|
||||
float center = (size - 1) * 0.5f;
|
||||
|
||||
for(int y = 0; y < size; y++)
|
||||
{
|
||||
for(int x = 0; x < size; x++)
|
||||
{
|
||||
float horizontal = Mathf.Abs(y - center);
|
||||
float vertical = Mathf.Abs(x - center);
|
||||
float diagonalA = Mathf.Abs((x - center) - (y - center));
|
||||
float diagonalB = Mathf.Abs((x - center) + (y - center));
|
||||
float alpha = Mathf.Max(
|
||||
1f - horizontal * 0.42f - Mathf.Abs(x - center) * 0.08f,
|
||||
1f - vertical * 0.42f - Mathf.Abs(y - center) * 0.08f);
|
||||
alpha = Mathf.Max(alpha, 0.62f - Mathf.Min(diagonalA, diagonalB) * 0.28f);
|
||||
pixels[y * size + x] = new Color(1f, 1f, 1f, Mathf.Clamp01(alpha));
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(pixels);
|
||||
texture.Apply();
|
||||
return Sprite.Create(texture, new Rect(0f, 0f, size, size), new Vector2(0.5f, 0.5f), size);
|
||||
}
|
||||
|
||||
private static Texture2D CreateTexture(int size)
|
||||
{
|
||||
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
texture.filterMode = FilterMode.Bilinear;
|
||||
texture.hideFlags = HideFlags.HideAndDontSave;
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65d35f0ff5f04696b7f8c37a6c4e0979
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -46,6 +46,7 @@ public class ItemPickup : MonoBehaviour
|
||||
}
|
||||
|
||||
collected = true;
|
||||
ItemFeedbackEffect.Spawn(transform.position, spriteRenderer != null ? spriteRenderer.sprite : null, itemType, false);
|
||||
|
||||
if(GameManager.instance != null)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,12 @@ using UnityEngine;
|
||||
|
||||
public static class MapDatabase {
|
||||
private static MapDefinition[] maps;
|
||||
private const float GroundY = -2.4f;
|
||||
private const float AerialY = -0.85f;
|
||||
private const float BasicSpacing = 0.58f;
|
||||
private const float ActionSpacing = 0.72f;
|
||||
private const float LandingSpacing = 0.64f;
|
||||
private const float AerialSpacing = 2.05f;
|
||||
|
||||
public static MapDefinition[] AllMaps {
|
||||
get {
|
||||
@@ -71,28 +77,44 @@ public static class MapDatabase {
|
||||
displayName = "Tutorial / Incheon",
|
||||
routeName = "인천공항 출발 준비",
|
||||
isTutorial = true,
|
||||
targetDistance = 850f,
|
||||
timeLimit = 95f,
|
||||
platformSkin = Platform.PlatformSkin.Airport,
|
||||
backgroundResourcePath = "Map_Tutorial_Incheon_Airport",
|
||||
finishGateStyle = FinishGateStyle.AirportDeparture,
|
||||
targetDistance = 320f,
|
||||
timeLimit = 75f,
|
||||
clearMileageReward = 150,
|
||||
hasNextMap = true,
|
||||
nextMapId = MapId.Japan,
|
||||
openingSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.Jump, -2.4f, 1.05f, "점프", "가방 장애물이 보이면 왼쪽 클릭으로 뛰어넘습니다."),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 1f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.DoubleJump, -2.4f, 2.1f, "2단 점프", "다음 발판이 멀리 있으면 공중에서 한 번 더 왼쪽 클릭합니다."),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 1.1f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.Slide, -2.4f, 1.15f, "슬라이드", "천장 표지판이 보이면 오른쪽 클릭을 누르고 지나갑니다."),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 1f),
|
||||
Step(Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.Damage, -2.4f, 1.15f, "피격", "이번 구간은 일부러 맞아 목숨과 속도 감소를 확인합니다."),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.Items, -2.4f, 0.95f, "초밥", "초밥을 먹으면 잃은 목숨을 1 회복합니다.", Platform.ItemPattern.HealthSushi),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.Items, -2.4f, 0.9f, "라멘", "라멘을 먹고 다음 충돌을 피해 없이 지나가 봅니다.", Platform.ItemPattern.InvincibleRamen),
|
||||
Step(Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, -2.4f, 1.05f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.Items, -2.4f, 0.9f, "방어막", "방어막을 먹고 다음 충돌 1회를 막아 봅니다.", Platform.ItemPattern.Shield),
|
||||
Step(Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, -2.4f, 1.05f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.Items, -2.4f, 0.95f, "운동화", "운동화는 속도를 회복해 일정이 늦어지는 것을 줄입니다.", Platform.ItemPattern.SpeedShoes),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.Items, -2.4f, 0.95f, "마일리지 카드", "카드를 먹은 뒤 마일리지를 모으면 획득량이 늘어납니다.", Platform.ItemPattern.MileageCard),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 1f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 1f)
|
||||
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, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "방어막", "다음 충돌 1회를 막습니다.\n먹은 뒤 바로 충돌을 막아 봅니다.", Platform.ItemPattern.Shield),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
ForceHit(GameManager.TutorialGuideType.None, "", ""),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "운동화", "느려진 속도를 회복합니다.\n방금 떨어진 속도를 되돌려 봅니다.", Platform.ItemPattern.SpeedShoes),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.None, GameManager.TutorialGuideType.Items, "마일리지 카드", "잠깐 마일리지가 2배가 됩니다.\n먹고 바로 마일리지를 모아 봅니다.", Platform.ItemPattern.MileageCard),
|
||||
Basic(GroundY, BasicSpacing, Platform.MileagePattern.SafeLine),
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(),
|
||||
Basic(GroundY, LandingSpacing)
|
||||
},
|
||||
loopSteps = CreateEasyLoop()
|
||||
};
|
||||
@@ -104,21 +126,28 @@ public static class MapDatabase {
|
||||
displayName = "Japan",
|
||||
routeName = "후쿠오카 라멘 투어",
|
||||
isTutorial = false,
|
||||
platformSkin = Platform.PlatformSkin.Japan,
|
||||
backgroundResourcePath = "",
|
||||
finishGateStyle = FinishGateStyle.JapanTorii,
|
||||
targetDistance = 1200f,
|
||||
timeLimit = 105f,
|
||||
clearMileageReward = 250,
|
||||
hasNextMap = true,
|
||||
nextMapId = MapId.China,
|
||||
openingSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.2f, 1.8f)
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
loopSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.2f, 2.05f),
|
||||
Step(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.3f, 2.15f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.None, -2.25f, 2.35f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.95f, 2.05f),
|
||||
Step(Platform.PlatformPattern.LowLeft, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.35f, 2.1f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, -2.3f, 2.45f)
|
||||
Basic(),
|
||||
Jump(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Aerial(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
|
||||
Basic(),
|
||||
Jump(Platform.PlatformPattern.LowLeft),
|
||||
Basic()
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -129,21 +158,29 @@ public static class MapDatabase {
|
||||
displayName = "China",
|
||||
routeName = "베이징 시장길",
|
||||
isTutorial = false,
|
||||
platformSkin = Platform.PlatformSkin.China,
|
||||
backgroundResourcePath = "",
|
||||
finishGateStyle = FinishGateStyle.ChinaPaifang,
|
||||
targetDistance = 1450f,
|
||||
timeLimit = 115f,
|
||||
clearMileageReward = 350,
|
||||
hasNextMap = true,
|
||||
nextMapId = MapId.America,
|
||||
openingSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.1f, 1.9f)
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
loopSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.LowRight, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.25f, 2.0f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.8f, 2.05f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.LeftPair, GameManager.TutorialGuideType.None, -2.3f, 2.35f),
|
||||
Step(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 2.05f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.RightPair, GameManager.TutorialGuideType.None, -2.25f, 2.35f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2f, 1.95f)
|
||||
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)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -152,36 +189,166 @@ public static class MapDatabase {
|
||||
return new MapDefinition {
|
||||
mapId = MapId.America,
|
||||
displayName = "America",
|
||||
routeName = "LA 공항 고속도로",
|
||||
routeName = "뉴욕 자유의 여신상",
|
||||
isTutorial = false,
|
||||
platformSkin = Platform.PlatformSkin.USA,
|
||||
backgroundResourcePath = "Map_America_NewYork",
|
||||
finishGateStyle = FinishGateStyle.AmericaRoadSign,
|
||||
targetDistance = 1700f,
|
||||
timeLimit = 125f,
|
||||
clearMileageReward = 500,
|
||||
hasNextMap = false,
|
||||
nextMapId = MapId.America,
|
||||
openingSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.2f, 1.8f)
|
||||
Basic(),
|
||||
Basic()
|
||||
},
|
||||
loopSteps = new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 1.95f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, -2.25f, 2.25f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.8f, 1.9f),
|
||||
Step(Platform.PlatformPattern.LowLeft, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.35f, 1.95f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.None, -2.25f, 2.2f),
|
||||
Step(Platform.PlatformPattern.LowRight, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.1f, 1.9f)
|
||||
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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep[] CreateEasyLoop() {
|
||||
return new PlatformSpawner.TutorialStep[] {
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 2.1f),
|
||||
Step(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 2.2f),
|
||||
Step(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.1f, 2.1f),
|
||||
Step(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.None, -2.35f, 2.35f)
|
||||
Basic(),
|
||||
Jump(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Aerial(),
|
||||
Basic(GroundY, LandingSpacing),
|
||||
Slide(SlideObstaclePattern.SlideLayout.CenterPair),
|
||||
Basic()
|
||||
};
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Basic(
|
||||
float yPosition = GroundY,
|
||||
float spawnInterval = BasicSpacing,
|
||||
Platform.MileagePattern mileagePattern = Platform.MileagePattern.SafeLine,
|
||||
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
|
||||
string title = "",
|
||||
string message = "",
|
||||
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
|
||||
return Step(
|
||||
Platform.PlatformPattern.Empty,
|
||||
mileagePattern,
|
||||
SlideObstaclePattern.SlideLayout.Random,
|
||||
guideType,
|
||||
yPosition,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
itemPattern,
|
||||
PlatformSpawner.CoursePlatformType.Basic);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Jump(
|
||||
Platform.PlatformPattern platformPattern = Platform.PlatformPattern.LowMid,
|
||||
float spawnInterval = ActionSpacing,
|
||||
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
|
||||
string title = "",
|
||||
string message = "") {
|
||||
return Step(
|
||||
platformPattern,
|
||||
Platform.MileagePattern.JumpArc,
|
||||
SlideObstaclePattern.SlideLayout.Random,
|
||||
guideType,
|
||||
GroundY,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
Platform.ItemPattern.None,
|
||||
PlatformSpawner.CoursePlatformType.Jump);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Jump(
|
||||
GameManager.TutorialGuideType guideType,
|
||||
string title,
|
||||
string message) {
|
||||
return Jump(Platform.PlatformPattern.LowMid, ActionSpacing, guideType, title, message);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Aerial(
|
||||
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
|
||||
string title = "",
|
||||
string message = "") {
|
||||
return Aerial(AerialY, AerialSpacing, guideType, title, message);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Aerial(
|
||||
float yPosition,
|
||||
float spawnInterval,
|
||||
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
|
||||
string title = "",
|
||||
string message = "") {
|
||||
return Step(
|
||||
Platform.PlatformPattern.Empty,
|
||||
Platform.MileagePattern.SafeLine,
|
||||
SlideObstaclePattern.SlideLayout.Random,
|
||||
guideType,
|
||||
GroundY,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
Platform.ItemPattern.None,
|
||||
PlatformSpawner.CoursePlatformType.Basic);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Slide(
|
||||
SlideObstaclePattern.SlideLayout slideLayout = SlideObstaclePattern.SlideLayout.CenterPair,
|
||||
float spawnInterval = ActionSpacing,
|
||||
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.None,
|
||||
string title = "",
|
||||
string message = "") {
|
||||
return Step(
|
||||
Platform.PlatformPattern.Slide,
|
||||
Platform.MileagePattern.SlideLine,
|
||||
slideLayout,
|
||||
guideType,
|
||||
GroundY,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
Platform.ItemPattern.None,
|
||||
PlatformSpawner.CoursePlatformType.Slide);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Slide(
|
||||
GameManager.TutorialGuideType guideType,
|
||||
string title,
|
||||
string message) {
|
||||
return Slide(SlideObstaclePattern.SlideLayout.CenterPair, ActionSpacing, guideType, title, message);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep ForceHit(
|
||||
GameManager.TutorialGuideType guideType = GameManager.TutorialGuideType.Damage,
|
||||
string title = "",
|
||||
string message = "",
|
||||
float spawnInterval = ActionSpacing) {
|
||||
return Step(
|
||||
Platform.PlatformPattern.ForceHit,
|
||||
Platform.MileagePattern.None,
|
||||
SlideObstaclePattern.SlideLayout.WidePair,
|
||||
guideType,
|
||||
GroundY,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
Platform.ItemPattern.None,
|
||||
PlatformSpawner.CoursePlatformType.ForceHit);
|
||||
}
|
||||
|
||||
private static PlatformSpawner.TutorialStep Step(
|
||||
Platform.PlatformPattern platformPattern,
|
||||
Platform.MileagePattern mileagePattern,
|
||||
@@ -191,8 +358,10 @@ public static class MapDatabase {
|
||||
float spawnInterval,
|
||||
string title = "",
|
||||
string message = "",
|
||||
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
|
||||
Platform.ItemPattern itemPattern = Platform.ItemPattern.None,
|
||||
PlatformSpawner.CoursePlatformType coursePlatformType = PlatformSpawner.CoursePlatformType.Basic) {
|
||||
return new PlatformSpawner.TutorialStep {
|
||||
coursePlatformType = coursePlatformType,
|
||||
platformPattern = platformPattern,
|
||||
mileagePattern = mileagePattern,
|
||||
slideLayout = slideLayout,
|
||||
@@ -211,6 +380,9 @@ public static class MapDatabase {
|
||||
displayName = source.displayName,
|
||||
routeName = source.routeName,
|
||||
isTutorial = source.isTutorial,
|
||||
platformSkin = source.platformSkin,
|
||||
backgroundResourcePath = source.backgroundResourcePath,
|
||||
finishGateStyle = source.finishGateStyle,
|
||||
targetDistance = source.targetDistance,
|
||||
timeLimit = source.timeLimit,
|
||||
clearMileageReward = source.clearMileageReward,
|
||||
@@ -241,7 +413,8 @@ public static class MapDatabase {
|
||||
sourceStep.spawnInterval,
|
||||
sourceStep.title,
|
||||
sourceStep.message,
|
||||
sourceStep.itemPattern);
|
||||
sourceStep.itemPattern,
|
||||
sourceStep.coursePlatformType);
|
||||
}
|
||||
|
||||
return steps;
|
||||
|
||||
@@ -6,6 +6,9 @@ public class MapDefinition {
|
||||
public string displayName;
|
||||
public string routeName;
|
||||
public bool isTutorial;
|
||||
public Platform.PlatformSkin platformSkin;
|
||||
public string backgroundResourcePath;
|
||||
public FinishGateStyle finishGateStyle;
|
||||
public float targetDistance;
|
||||
public float timeLimit;
|
||||
public int clearMileageReward;
|
||||
|
||||
+205
-7
@@ -32,6 +32,13 @@ public class Platform : MonoBehaviour {
|
||||
MileageCard
|
||||
}
|
||||
|
||||
public enum PlatformSkin {
|
||||
Airport,
|
||||
Japan,
|
||||
China,
|
||||
USA
|
||||
}
|
||||
|
||||
public GameObject[] obstacles; // 장애물 오브젝트들
|
||||
public int emptyPatternWeight = 2; // 장애물이 없는 패턴이 선택될 가중치
|
||||
public Sprite mileageSprite; // 이 발판에서 사용할 마일리지 토큰 스프라이트
|
||||
@@ -46,6 +53,7 @@ public class Platform : MonoBehaviour {
|
||||
private MileagePattern reservedMileagePattern = MileagePattern.Random; // 다음 활성화 때 사용할 마일리지 패턴
|
||||
private SlideObstaclePattern.SlideLayout reservedSlideLayout = SlideObstaclePattern.SlideLayout.Random; // 다음 슬라이딩 장애물 배치
|
||||
private ItemPattern reservedItemPattern = ItemPattern.None; // 다음 활성화 때 사용할 아이템 배치
|
||||
private PlatformSkin reservedSkin = PlatformSkin.Airport; // 현재 맵에서 사용할 발판/장애물 스킨
|
||||
private const int MileagePoolSize = 5;
|
||||
private const int ItemPoolSize = 1;
|
||||
private const float MileageTokenScale = 0.55f;
|
||||
@@ -57,18 +65,21 @@ public class Platform : MonoBehaviour {
|
||||
PlatformPattern pattern,
|
||||
MileagePattern mileagePattern,
|
||||
SlideObstaclePattern.SlideLayout slideLayout = SlideObstaclePattern.SlideLayout.Random,
|
||||
ItemPattern itemPattern = ItemPattern.None) {
|
||||
ItemPattern itemPattern = ItemPattern.None,
|
||||
PlatformSkin platformSkin = PlatformSkin.Airport) {
|
||||
reservedPattern = pattern;
|
||||
reservedMileagePattern = mileagePattern;
|
||||
reservedSlideLayout = slideLayout;
|
||||
reservedItemPattern = itemPattern;
|
||||
reservedSkin = platformSkin;
|
||||
}
|
||||
|
||||
public void ConfigureForRandom() {
|
||||
public void ConfigureForRandom(PlatformSkin platformSkin = PlatformSkin.Airport) {
|
||||
reservedPattern = PlatformPattern.Random;
|
||||
reservedMileagePattern = MileagePattern.Random;
|
||||
reservedSlideLayout = SlideObstaclePattern.SlideLayout.Random;
|
||||
reservedItemPattern = ItemPattern.None;
|
||||
reservedSkin = platformSkin;
|
||||
}
|
||||
|
||||
// 컴포넌트가 활성화될때 마다 매번 실행되는 메서드
|
||||
@@ -76,6 +87,7 @@ public class Platform : MonoBehaviour {
|
||||
// 발판을 리셋하는 처리
|
||||
|
||||
stepped = false;
|
||||
ApplyVisualSkin();
|
||||
EnsureMileagePickups();
|
||||
EnsureItemPickups();
|
||||
|
||||
@@ -361,23 +373,209 @@ public class Platform : MonoBehaviour {
|
||||
}
|
||||
|
||||
private Vector2 GetItemPosition(ItemPattern itemPattern) {
|
||||
const float frontX = -1.45f;
|
||||
|
||||
switch(itemPattern)
|
||||
{
|
||||
case ItemPattern.HealthSushi:
|
||||
return new Vector2(-1.6f, 1.55f);
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.InvincibleRamen:
|
||||
return new Vector2(-0.8f, 1.55f);
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.Shield:
|
||||
return new Vector2(-0.2f, 1.55f);
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.SpeedShoes:
|
||||
return new Vector2(0.7f, 1.55f);
|
||||
return new Vector2(frontX, 1.55f);
|
||||
case ItemPattern.MileageCard:
|
||||
return new Vector2(1.4f, 1.55f);
|
||||
return new Vector2(frontX, 1.55f);
|
||||
default:
|
||||
return new Vector2(0f, 1.55f);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyVisualSkin() {
|
||||
#if UNITY_EDITOR
|
||||
Sprite platformSprite = LoadSpriteAtPath(GetPlatformSkinPath(reservedSkin));
|
||||
Sprite ceilingSprite = LoadSpriteAtPath(GetCeilingSkinPath(reservedSkin));
|
||||
Sprite lowObstacleSprite = LoadSpriteAtPath(GetLowObstacleSkinPath(reservedSkin));
|
||||
Sprite hangingObstacleSprite = LoadSpriteAtPath(GetHangingObstacleSkinPath(reservedSkin));
|
||||
Sprite mileageSkinSprite = LoadSpriteAtPath(GetMileageSkinPath(reservedSkin));
|
||||
|
||||
ApplySprite(GetComponent<SpriteRenderer>(), platformSprite);
|
||||
|
||||
Transform ceilingPlatform = FindChildRecursive(transform, "Ceiling Platform");
|
||||
if(ceilingPlatform != null)
|
||||
{
|
||||
ApplySprite(ceilingPlatform.GetComponent<SpriteRenderer>(), ceilingSprite != null ? ceilingSprite : platformSprite);
|
||||
}
|
||||
|
||||
ApplyLowObstacleSkin(lowObstacleSprite);
|
||||
ApplyHangingObstacleSkin(hangingObstacleSprite);
|
||||
|
||||
if(mileageSkinSprite != null)
|
||||
{
|
||||
mileageSprite = mileageSkinSprite;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void ApplyLowObstacleSkin(Sprite obstacleSprite) {
|
||||
if(obstacleSprite == null || obstacles == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < obstacles.Length; i++)
|
||||
{
|
||||
GameObject obstacle = obstacles[i];
|
||||
if(obstacle == null || obstacle.name.Contains("Slide"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplySprite(obstacle.GetComponent<SpriteRenderer>(), obstacleSprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyHangingObstacleSkin(Sprite obstacleSprite) {
|
||||
if(obstacleSprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject slideObstacle = FindObstacle("Slide Obstacle Pattern");
|
||||
if(slideObstacle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SlideObstaclePattern slideObstaclePattern = slideObstacle.GetComponent<SlideObstaclePattern>();
|
||||
if(slideObstaclePattern == null || slideObstaclePattern.obstacles == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < slideObstaclePattern.obstacles.Length; i++)
|
||||
{
|
||||
Transform hangingObstacle = slideObstaclePattern.obstacles[i];
|
||||
if(hangingObstacle != null)
|
||||
{
|
||||
ApplySprite(hangingObstacle.GetComponent<SpriteRenderer>(), obstacleSprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySprite(SpriteRenderer spriteRenderer, Sprite sprite) {
|
||||
if(spriteRenderer != null && sprite != null)
|
||||
{
|
||||
spriteRenderer.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite LoadSpriteAtPath(string assetPath) {
|
||||
if(string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||||
}
|
||||
|
||||
private Transform FindChildRecursive(Transform parent, string childName) {
|
||||
if(parent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for(int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
Transform child = parent.GetChild(i);
|
||||
|
||||
if(child.name == childName)
|
||||
{
|
||||
return child;
|
||||
}
|
||||
|
||||
Transform match = FindChildRecursive(child, childName);
|
||||
if(match != null)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetPlatformSkinPath(PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case PlatformSkin.Japan:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_Japan.png";
|
||||
case PlatformSkin.China:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_China.png";
|
||||
case PlatformSkin.USA:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_USA.png";
|
||||
case PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_AirportFloorTile.png";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCeilingSkinPath(PlatformSkin platformSkin) {
|
||||
if(platformSkin == PlatformSkin.Airport)
|
||||
{
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Platform_AirportCeiling.png";
|
||||
}
|
||||
|
||||
return GetPlatformSkinPath(platformSkin);
|
||||
}
|
||||
|
||||
private string GetLowObstacleSkinPath(PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case PlatformSkin.Japan:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_StoneLantern.png";
|
||||
case PlatformSkin.China:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_MarketCrate.png";
|
||||
case PlatformSkin.USA:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_Cone.png";
|
||||
case PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Obstacle_Suitcases.png";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHangingObstacleSkinPath(PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case PlatformSkin.Japan:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_RoadSign.png";
|
||||
case PlatformSkin.China:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_RedLanterns.png";
|
||||
case PlatformSkin.USA:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_Banner.png";
|
||||
case PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/ObstacleSkins/Skin_Hanging_AirportSign.png";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMileageSkinPath(PlatformSkin platformSkin) {
|
||||
switch(platformSkin)
|
||||
{
|
||||
case PlatformSkin.Japan:
|
||||
return "Assets/Sprites/Mileage/Mileage_Japan.png";
|
||||
case PlatformSkin.China:
|
||||
return "Assets/Sprites/Mileage/Mileage_China.png";
|
||||
case PlatformSkin.USA:
|
||||
return "Assets/Sprites/Mileage/Mileage_USA.png";
|
||||
case PlatformSkin.Airport:
|
||||
default:
|
||||
return "Assets/Sprites/Mileage/Mileage_Korea.png";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private Sprite LoadItemSprite(GameManager.TutorialItemType itemType) {
|
||||
#if UNITY_EDITOR
|
||||
string assetPath = "";
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
// 발판을 생성하고 주기적으로 재배치하는 스크립트
|
||||
public class PlatformSpawner : MonoBehaviour {
|
||||
public enum CoursePlatformType {
|
||||
Basic,
|
||||
Jump,
|
||||
Slide,
|
||||
ForceHit
|
||||
}
|
||||
|
||||
private enum TutorialStepKind {
|
||||
None,
|
||||
Jump,
|
||||
@@ -19,6 +27,7 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
|
||||
[Serializable]
|
||||
public class TutorialStep {
|
||||
public CoursePlatformType coursePlatformType;
|
||||
public Platform.PlatformPattern platformPattern;
|
||||
public Platform.MileagePattern mileagePattern;
|
||||
public SlideObstaclePattern.SlideLayout slideLayout;
|
||||
@@ -30,25 +39,51 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
public string message;
|
||||
}
|
||||
|
||||
public GameObject platformPrefab; // 생성할 발판의 원본 프리팹
|
||||
public int count = 3; // 생성할 발판의 개수
|
||||
private class PendingTutorialGuide {
|
||||
public Transform target;
|
||||
public GameManager.TutorialGuideType guideType;
|
||||
public string title;
|
||||
public string message;
|
||||
public float triggerX;
|
||||
}
|
||||
|
||||
public GameObject platformPrefab; // 계속 이어지는 기본 발판 프리팹
|
||||
public GameObject jumpObstaclePrefab; // 낮은 점프 장애물 프리팹
|
||||
public GameObject slideObstaclePrefab; // 위쪽 슬라이딩 장애물 프리팹
|
||||
public int count = 12; // 기본 발판 풀 개수
|
||||
public bool usePatternCourse = true; // 검증된 발판 패턴 코스를 사용
|
||||
public bool tutorialMode = true; // 처음에는 튜토리얼 패턴을 먼저 배치
|
||||
public TutorialStep[] tutorialSteps; // 튜토리얼 발판 순서
|
||||
public TutorialStep[] stagePatternSteps; // 튜토리얼 이후 반복할 검증된 발판 순서
|
||||
|
||||
public bool keepWorldSpacingBySpeed = true; // 게임 속도가 빨라져도 발판 사이 실제 거리를 유지
|
||||
public int continuousCoursePoolCount = 12; // 기본 발판을 촘촘히 깔 때 필요한 최소 풀 크기
|
||||
public int obstaclePoolCount = 6; // 점프/슬라이드 장애물별 재사용 풀 크기
|
||||
public float timeBetSpawnMin = 1.25f; // 다음 배치까지의 시간 간격 최솟값
|
||||
public float timeBetSpawnMax = 2.25f; // 다음 배치까지의 시간 간격 최댓값
|
||||
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값
|
||||
public float tutorialGuideTriggerX = 5f; // 발판/장애물이 이 위치까지 오면 설명을 띄운다.
|
||||
private float xPos = 20f; // 배치할 위치의 x 값
|
||||
|
||||
private GameObject[] platforms; // 미리 생성한 발판들
|
||||
private int currentIndex = 0; // 사용할 현재 순번의 발판
|
||||
private GameObject[] platforms; // 기본 발판 풀
|
||||
private GameObject[] jumpObstacles; // 점프 장애물 풀
|
||||
private GameObject[] slideObstacles; // 슬라이드 장애물 풀
|
||||
private int platformIndex = 0; // 기본 발판 풀 순번
|
||||
private int jumpObstacleIndex = 0; // 점프 장애물 풀 순번
|
||||
private int slideObstacleIndex = 0; // 슬라이드 장애물 풀 순번
|
||||
private int courseStepIndex = 0; // 진행 중인 패턴 코스 순번
|
||||
private Platform.PlatformSkin mapPlatformSkin = Platform.PlatformSkin.Airport; // 현재 맵에서 사용할 발판 스킨
|
||||
private readonly List<PendingTutorialGuide> pendingTutorialGuides = new List<PendingTutorialGuide>(); // 실제 체험 직전에 띄울 설명들
|
||||
|
||||
private Vector2 poolPosition = new Vector2(0, -25); // 초반에 생성된 발판들을 화면 밖에 숨겨둘 위치
|
||||
private float lastSpawnTime; // 마지막 배치 시점
|
||||
@@ -61,24 +96,17 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
|
||||
usePatternCourse = true;
|
||||
tutorialMode = mapDefinition.isTutorial;
|
||||
mapPlatformSkin = mapDefinition.platformSkin;
|
||||
tutorialSteps = MapDatabase.CloneSteps(mapDefinition.openingSteps);
|
||||
stagePatternSteps = MapDatabase.CloneSteps(mapDefinition.loopSteps);
|
||||
courseStepIndex = 0;
|
||||
currentIndex = 0;
|
||||
pendingTutorialGuides.Clear();
|
||||
ResetPoolIndices();
|
||||
lastSpawnTime = Time.time;
|
||||
timeBetSpawn = 0f;
|
||||
|
||||
if(platforms != null)
|
||||
{
|
||||
for(int i = 0; i < platforms.Length; i++)
|
||||
{
|
||||
if(platforms[i] != null)
|
||||
{
|
||||
platforms[i].SetActive(false);
|
||||
platforms[i].transform.position = poolPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
count = Mathf.Max(count, continuousCoursePoolCount);
|
||||
EnsurePlatformPool();
|
||||
DeactivateAllPlatformPools();
|
||||
}
|
||||
|
||||
void Start() {
|
||||
@@ -86,16 +114,11 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
|
||||
if(usePatternCourse && tutorialMode)
|
||||
{
|
||||
count = Mathf.Max(count, 8);
|
||||
count = Mathf.Max(count, continuousCoursePoolCount);
|
||||
}
|
||||
|
||||
// 변수들을 초기화하고 사용할 발판들을 미리 생성
|
||||
platforms = new GameObject[count];
|
||||
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
platforms[i] = Instantiate(platformPrefab, poolPosition, Quaternion.identity);
|
||||
}
|
||||
EnsurePlatformPool();
|
||||
|
||||
// 마지막 배치 시점 초기화
|
||||
lastSpawnTime = 0f;
|
||||
@@ -104,6 +127,77 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
timeBetSpawn = 0f;
|
||||
}
|
||||
|
||||
private void EnsurePlatformPool() {
|
||||
int basicCount = usePatternCourse ? Mathf.Max(count, continuousCoursePoolCount) : Mathf.Max(1, count);
|
||||
int obstacleCount = Mathf.Max(1, obstaclePoolCount);
|
||||
|
||||
EnsurePool(ref platforms, basicCount, platformPrefab);
|
||||
EnsurePool(ref jumpObstacles, obstacleCount, jumpObstaclePrefab);
|
||||
EnsurePool(ref slideObstacles, obstacleCount, slideObstaclePrefab);
|
||||
count = platforms != null ? platforms.Length : count;
|
||||
}
|
||||
|
||||
private void EnsurePool(ref GameObject[] pool, int requiredCount, GameObject prefab) {
|
||||
if(prefab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(pool != null && pool.Length >= requiredCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject[] nextPool = new GameObject[requiredCount];
|
||||
|
||||
if(pool != null)
|
||||
{
|
||||
for(int i = 0; i < pool.Length; i++)
|
||||
{
|
||||
nextPool[i] = pool[i];
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < nextPool.Length; i++)
|
||||
{
|
||||
if(nextPool[i] == null)
|
||||
{
|
||||
nextPool[i] = Instantiate(prefab, poolPosition, Quaternion.identity);
|
||||
nextPool[i].SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
pool = nextPool;
|
||||
}
|
||||
|
||||
private void ResetPoolIndices() {
|
||||
platformIndex = 0;
|
||||
jumpObstacleIndex = 0;
|
||||
slideObstacleIndex = 0;
|
||||
}
|
||||
|
||||
private void DeactivateAllPlatformPools() {
|
||||
DeactivatePool(platforms);
|
||||
DeactivatePool(jumpObstacles);
|
||||
DeactivatePool(slideObstacles);
|
||||
}
|
||||
|
||||
private void DeactivatePool(GameObject[] pool) {
|
||||
if(pool == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < pool.Length; i++)
|
||||
{
|
||||
if(pool[i] != null)
|
||||
{
|
||||
pool[i].SetActive(false);
|
||||
pool[i].transform.position = poolPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Update() {
|
||||
// 순서를 돌아가며 주기적으로 발판을 배치
|
||||
if(GameManager.instance == null || GameManager.instance.isGameover || !GameFlowManager.IsGameplayActive)
|
||||
@@ -111,6 +205,14 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePendingTutorialGuides();
|
||||
if(GameManager.instance != null && GameManager.instance.IsTutorialGuidePaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsurePlatformPool();
|
||||
|
||||
if(Time.time >= lastSpawnTime + timeBetSpawn)
|
||||
{
|
||||
lastSpawnTime = Time.time;
|
||||
@@ -122,19 +224,19 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
? Mathf.Clamp(step.yPosition, yMin, yMax)
|
||||
: Random.Range(yMin, yMax);
|
||||
|
||||
platforms[currentIndex].SetActive(false);
|
||||
ConfigurePlatform(platforms[currentIndex], step);
|
||||
platforms[currentIndex].SetActive(true);
|
||||
|
||||
platforms[currentIndex].transform.position = new Vector2(xPos, yPos);
|
||||
|
||||
currentIndex++;
|
||||
|
||||
if(currentIndex >= count)
|
||||
GameObject platformObject = TakeFromPool(platforms, ref platformIndex);
|
||||
if(platformObject == null)
|
||||
{
|
||||
currentIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
platformObject.SetActive(false);
|
||||
ConfigurePlatform(platformObject, step);
|
||||
platformObject.SetActive(true);
|
||||
platformObject.transform.position = new Vector2(xPos, yPos);
|
||||
SpawnCourseObstacles(step, yPos);
|
||||
RegisterTutorialGuide(platformObject.transform, step);
|
||||
|
||||
if(usePatternCourse)
|
||||
{
|
||||
courseStepIndex++;
|
||||
@@ -142,6 +244,153 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePendingTutorialGuides() {
|
||||
if(GameManager.instance == null || pendingTutorialGuides.Count == 0 || GameManager.instance.IsTutorialGuidePaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < pendingTutorialGuides.Count; i++)
|
||||
{
|
||||
PendingTutorialGuide guide = pendingTutorialGuides[i];
|
||||
|
||||
if(guide == null || guide.target == null || !guide.target.gameObject.activeInHierarchy)
|
||||
{
|
||||
pendingTutorialGuides.RemoveAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(guide.target.position.x <= guide.triggerX)
|
||||
{
|
||||
GameManager.instance.SetTutorialGuide(guide.guideType, guide.title, guide.message);
|
||||
pendingTutorialGuides.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterTutorialGuide(Transform target, TutorialStep step) {
|
||||
if(target == null
|
||||
|| step == null
|
||||
|| step.guideType == GameManager.TutorialGuideType.None
|
||||
|| (string.IsNullOrEmpty(step.title) && string.IsNullOrEmpty(step.message)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pendingTutorialGuides.Add(new PendingTutorialGuide {
|
||||
target = target,
|
||||
guideType = step.guideType,
|
||||
title = step.title,
|
||||
message = step.message,
|
||||
triggerX = GetTutorialGuideTriggerX(step)
|
||||
});
|
||||
}
|
||||
|
||||
private float GetTutorialGuideTriggerX(TutorialStep step) {
|
||||
if(step != null && step.guideType == GameManager.TutorialGuideType.DoubleJump)
|
||||
{
|
||||
return -2f;
|
||||
}
|
||||
|
||||
return tutorialGuideTriggerX;
|
||||
}
|
||||
|
||||
private void SpawnCourseObstacles(TutorialStep step, float yPos) {
|
||||
if(step == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(step.coursePlatformType == CoursePlatformType.ForceHit || step.platformPattern == Platform.PlatformPattern.ForceHit)
|
||||
{
|
||||
SpawnJumpObstacle(Platform.PlatformPattern.LowMid, yPos);
|
||||
SpawnSlideObstacle(SlideObstaclePattern.SlideLayout.WidePair, yPos);
|
||||
return;
|
||||
}
|
||||
|
||||
if(step.coursePlatformType == CoursePlatformType.Jump || IsJumpPattern(step.platformPattern))
|
||||
{
|
||||
Platform.PlatformPattern jumpPattern = IsJumpPattern(step.platformPattern)
|
||||
? step.platformPattern
|
||||
: Platform.PlatformPattern.LowMid;
|
||||
SpawnJumpObstacle(jumpPattern, yPos);
|
||||
return;
|
||||
}
|
||||
|
||||
if(step.coursePlatformType == CoursePlatformType.Slide || step.platformPattern == Platform.PlatformPattern.Slide)
|
||||
{
|
||||
SpawnSlideObstacle(step.slideLayout, yPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnJumpObstacle(Platform.PlatformPattern platformPattern, float yPos) {
|
||||
GameObject obstacleObject = TakeFromPool(jumpObstacles, ref jumpObstacleIndex);
|
||||
if(obstacleObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
obstacleObject.SetActive(false);
|
||||
ConfigureObstacle(obstacleObject, platformPattern, SlideObstaclePattern.SlideLayout.Random);
|
||||
obstacleObject.transform.position = new Vector2(xPos, yPos);
|
||||
obstacleObject.SetActive(true);
|
||||
}
|
||||
|
||||
private void SpawnSlideObstacle(SlideObstaclePattern.SlideLayout slideLayout, float yPos) {
|
||||
GameObject obstacleObject = TakeFromPool(slideObstacles, ref slideObstacleIndex);
|
||||
if(obstacleObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
obstacleObject.SetActive(false);
|
||||
ConfigureObstacle(obstacleObject, Platform.PlatformPattern.Slide, slideLayout);
|
||||
obstacleObject.transform.position = new Vector2(xPos, yPos);
|
||||
obstacleObject.SetActive(true);
|
||||
}
|
||||
|
||||
private void ConfigureObstacle(
|
||||
GameObject obstacleObject,
|
||||
Platform.PlatformPattern platformPattern,
|
||||
SlideObstaclePattern.SlideLayout slideLayout) {
|
||||
CourseObstacle obstacle = obstacleObject.GetComponent<CourseObstacle>();
|
||||
|
||||
if(obstacle != null)
|
||||
{
|
||||
obstacle.Configure(mapPlatformSkin, platformPattern, slideLayout);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsJumpPattern(Platform.PlatformPattern platformPattern) {
|
||||
return platformPattern == Platform.PlatformPattern.LowLeft
|
||||
|| platformPattern == Platform.PlatformPattern.LowMid
|
||||
|| platformPattern == Platform.PlatformPattern.LowRight;
|
||||
}
|
||||
|
||||
private GameObject TakeFromPool(GameObject[] pool, ref int poolIndex) {
|
||||
if(pool == null || pool.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(poolIndex < 0 || poolIndex >= pool.Length)
|
||||
{
|
||||
poolIndex = 0;
|
||||
}
|
||||
|
||||
GameObject platformObject = pool[poolIndex];
|
||||
poolIndex++;
|
||||
|
||||
if(poolIndex >= pool.Length)
|
||||
{
|
||||
poolIndex = 0;
|
||||
}
|
||||
|
||||
return platformObject;
|
||||
}
|
||||
|
||||
private void ConfigurePlatform(GameObject platformObject, TutorialStep step) {
|
||||
Platform platform = platformObject.GetComponent<Platform>();
|
||||
|
||||
@@ -152,32 +401,33 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
|
||||
if(step == null)
|
||||
{
|
||||
platform.ConfigureForRandom();
|
||||
platform.ConfigureForRandom(mapPlatformSkin);
|
||||
return;
|
||||
}
|
||||
|
||||
platform.ConfigureForTutorial(step.platformPattern, step.mileagePattern, step.slideLayout, step.itemPattern);
|
||||
platform.ConfigureForTutorial(Platform.PlatformPattern.Empty, step.mileagePattern, step.slideLayout, step.itemPattern, mapPlatformSkin);
|
||||
|
||||
if(GameManager.instance != null && step.guideType != GameManager.TutorialGuideType.None)
|
||||
{
|
||||
GameManager.instance.SetTutorialGuide(step.guideType, step.title, step.message);
|
||||
}
|
||||
|
||||
if(GameManager.instance != null
|
||||
&& step.guideType == GameManager.TutorialGuideType.None
|
||||
&& (!tutorialMode || courseStepIndex >= tutorialSteps.Length))
|
||||
{
|
||||
GameManager.instance.SetTutorialGuide(GameManager.TutorialGuideType.None, "", "");
|
||||
}
|
||||
// 설명은 발판 생성 시점이 아니라 실제 체험 직전에 RegisterTutorialGuide가 띄운다.
|
||||
}
|
||||
|
||||
private float GetStepSpawnInterval(TutorialStep step) {
|
||||
float spawnInterval;
|
||||
|
||||
if(step != null && step.spawnInterval > 0f)
|
||||
{
|
||||
return step.spawnInterval;
|
||||
spawnInterval = step.spawnInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnInterval = tutorialSpawnInterval;
|
||||
}
|
||||
|
||||
return tutorialSpawnInterval;
|
||||
if(keepWorldSpacingBySpeed && GameManager.instance != null)
|
||||
{
|
||||
spawnInterval /= Mathf.Max(0.1f, GameManager.instance.gameSpeed);
|
||||
}
|
||||
|
||||
return Mathf.Max(0.05f, spawnInterval);
|
||||
}
|
||||
|
||||
private TutorialStep GetCurrentCourseStep() {
|
||||
@@ -227,22 +477,22 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
}
|
||||
|
||||
tutorialSteps = new TutorialStep[] {
|
||||
CreateTutorialStep(TutorialStepKind.Jump, Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1.05f),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
|
||||
CreateTutorialStep(TutorialStepKind.DoubleJumpTakeoff, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 2.10f),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1.10f),
|
||||
CreateTutorialStep(TutorialStepKind.Slide, Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, -2.4f, 1.15f),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
|
||||
CreateTutorialStep(TutorialStepKind.ForceHit, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, -2.4f, 1.15f),
|
||||
CreateTutorialStep(TutorialStepKind.HealthItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.95f, Platform.ItemPattern.HealthSushi),
|
||||
CreateTutorialStep(TutorialStepKind.InvincibleItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.9f, Platform.ItemPattern.InvincibleRamen),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, -2.4f, 1.05f),
|
||||
CreateTutorialStep(TutorialStepKind.ShieldItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.9f, Platform.ItemPattern.Shield),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, -2.4f, 1.05f),
|
||||
CreateTutorialStep(TutorialStepKind.SpeedItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.95f, Platform.ItemPattern.SpeedShoes),
|
||||
CreateTutorialStep(TutorialStepKind.MileageCardItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, -2.4f, 0.95f, Platform.ItemPattern.MileageCard),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, -2.4f, 1f),
|
||||
CreateTutorialStep(TutorialStepKind.Jump, Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.DoubleJumpTakeoff, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackAerialSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.Slide, Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.ForceHit, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.HealthItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackLandingSpacing, Platform.ItemPattern.HealthSushi),
|
||||
CreateTutorialStep(TutorialStepKind.InvincibleItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.InvincibleRamen),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.ShieldItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.Shield),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.ForceHit, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.WidePair, FallbackGroundY, FallbackActionSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.SpeedItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.SpeedShoes),
|
||||
CreateTutorialStep(TutorialStepKind.MileageCardItem, Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing, Platform.ItemPattern.MileageCard),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
CreateTutorialStep(TutorialStepKind.None, Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, FallbackGroundY, FallbackBasicSpacing),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -253,20 +503,20 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
}
|
||||
|
||||
stagePatternSteps = new TutorialStep[] {
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.2f, 2.25f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.3f, 2.4f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.95f, 2.2f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.WidePair, GameManager.TutorialGuideType.None, -2.25f, 2.65f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.0f, 2.2f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowLeft, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.35f, 2.35f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.LeftPair, GameManager.TutorialGuideType.None, -2.35f, 2.7f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.1f, 2.2f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowRight, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.0f, 2.35f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.None, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -1.85f, 2.15f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.RightPair, GameManager.TutorialGuideType.None, -2.25f, 2.65f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.LowMid, Platform.MileagePattern.JumpArc, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.4f, 2.35f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Empty, Platform.MileagePattern.SafeLine, SlideObstaclePattern.SlideLayout.Random, GameManager.TutorialGuideType.None, -2.0f, 2.2f, "", ""),
|
||||
CreateStep(Platform.PlatformPattern.Slide, Platform.MileagePattern.SlideLine, SlideObstaclePattern.SlideLayout.CenterPair, GameManager.TutorialGuideType.None, -2.2f, 2.7f, "", ""),
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -279,8 +529,10 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
float spawnInterval,
|
||||
string title,
|
||||
string message,
|
||||
Platform.ItemPattern itemPattern = Platform.ItemPattern.None) {
|
||||
Platform.ItemPattern itemPattern = Platform.ItemPattern.None,
|
||||
CoursePlatformType coursePlatformType = CoursePlatformType.Basic) {
|
||||
TutorialStep step = new TutorialStep();
|
||||
step.coursePlatformType = coursePlatformType;
|
||||
step.platformPattern = platformPattern;
|
||||
step.mileagePattern = mileagePattern;
|
||||
step.slideLayout = slideLayout;
|
||||
@@ -354,6 +606,38 @@ public class PlatformSpawner : MonoBehaviour {
|
||||
break;
|
||||
}
|
||||
|
||||
return CreateStep(platformPattern, mileagePattern, slideLayout, guideType, yPosition, spawnInterval, title, message, itemPattern);
|
||||
return CreateStep(
|
||||
platformPattern,
|
||||
mileagePattern,
|
||||
slideLayout,
|
||||
guideType,
|
||||
yPosition,
|
||||
spawnInterval,
|
||||
title,
|
||||
message,
|
||||
itemPattern,
|
||||
GetCoursePlatformType(stepKind, platformPattern, yPosition));
|
||||
}
|
||||
|
||||
private CoursePlatformType GetCoursePlatformType(TutorialStepKind stepKind, Platform.PlatformPattern platformPattern, float yPosition) {
|
||||
if(stepKind == TutorialStepKind.ForceHit || platformPattern == Platform.PlatformPattern.ForceHit)
|
||||
{
|
||||
return CoursePlatformType.ForceHit;
|
||||
}
|
||||
|
||||
if(stepKind == TutorialStepKind.Slide || platformPattern == Platform.PlatformPattern.Slide)
|
||||
{
|
||||
return CoursePlatformType.Slide;
|
||||
}
|
||||
|
||||
if(stepKind == TutorialStepKind.Jump
|
||||
|| platformPattern == Platform.PlatformPattern.LowLeft
|
||||
|| platformPattern == Platform.PlatformPattern.LowMid
|
||||
|| platformPattern == Platform.PlatformPattern.LowRight)
|
||||
{
|
||||
return CoursePlatformType.Jump;
|
||||
}
|
||||
|
||||
return CoursePlatformType.Basic;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다.
|
||||
@@ -7,6 +8,7 @@ public class PlayerController : MonoBehaviour {
|
||||
public float jumpForce = 700f; // 점프 힘
|
||||
public int maxJumpCount = 2; // 최대 점프 횟수
|
||||
public float invincibleDuration = 1.5f; // 피해를 입은 뒤 무적 시간
|
||||
public float groundStickGraceTime = 0.12f; // 발판 이음새에서 접지가 아주 잠깐 끊겨도 유지하는 시간
|
||||
|
||||
private int jumpCount = 0; // 누적 점프 횟수
|
||||
private bool isGrounded = false; // 바닥에 닿았는지 나타냄
|
||||
@@ -18,6 +20,8 @@ public class PlayerController : MonoBehaviour {
|
||||
private Animator animator; // 사용할 애니메이터 컴포넌트
|
||||
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
|
||||
private SpriteRenderer spriteRenderer; // 플레이어 스프라이트 표시 컴포넌트
|
||||
private readonly HashSet<Collider2D> groundContacts = new HashSet<Collider2D>(); // 현재 밟고 있는 발판 콜라이더
|
||||
private float lastGroundedTime = -999f; // 마지막으로 발판에 닿아 있던 시간
|
||||
|
||||
private CapsuleCollider2D playerCollider; // 플레이어의 콜라이더 충돌 범위
|
||||
private Vector2 originalColliderSize; // 기본 자세일 때 콜라이더 크기
|
||||
@@ -42,7 +46,9 @@ public class PlayerController : MonoBehaviour {
|
||||
}
|
||||
|
||||
private void Update() {
|
||||
if(isDead || !GameFlowManager.IsGameplayActive)
|
||||
if(isDead
|
||||
|| !GameFlowManager.IsGameplayActive
|
||||
|| (GameManager.instance != null && GameManager.instance.IsTutorialGuidePaused))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -67,14 +73,15 @@ public class PlayerController : MonoBehaviour {
|
||||
playerRigidbody.linearVelocity = playerRigidbody.linearVelocity * 0.5f;
|
||||
}
|
||||
|
||||
animator.SetBool("Grounded", isGrounded);
|
||||
bool groundedForMovement = IsGroundedOrRecentlyGrounded();
|
||||
animator.SetBool("Grounded", groundedForMovement);
|
||||
|
||||
// 달리는 중에 마우스 오른쪽 버튼을 누르고 있는 동안 슬라이딩 유지
|
||||
if(Input.GetMouseButton(1) && isGrounded && !isSliding)
|
||||
if(Input.GetMouseButton(1) && groundedForMovement && !isSliding)
|
||||
{
|
||||
StartSlide();
|
||||
}
|
||||
else if((!Input.GetMouseButton(1) || !isGrounded) && isSliding)
|
||||
else if((!Input.GetMouseButton(1) || !groundedForMovement) && isSliding)
|
||||
{
|
||||
EndSlide();
|
||||
}
|
||||
@@ -127,23 +134,43 @@ public class PlayerController : MonoBehaviour {
|
||||
|
||||
private void OnCollisionEnter2D(Collision2D collision) {
|
||||
// 어떤 콜라이더와 닿았으며, 충돌 표면이 위쪽을 보고 있으면
|
||||
if(collision.contacts[0].normal.y > 0.7f)
|
||||
if(IsGroundContact(collision))
|
||||
{
|
||||
isGrounded = true;
|
||||
groundContacts.Add(collision.collider);
|
||||
SetGrounded(true);
|
||||
jumpCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollisionExit2D(Collision2D collision) {
|
||||
// 바닥에서 벗어났음을 감지하는 처리
|
||||
isGrounded = false;
|
||||
|
||||
if(isSliding)
|
||||
if(groundContacts.Remove(collision.collider) && groundContacts.Count == 0)
|
||||
{
|
||||
EndSlide();
|
||||
SetGrounded(false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsGroundContact(Collision2D collision) {
|
||||
for(int i = 0; i < collision.contactCount; i++)
|
||||
{
|
||||
if(collision.GetContact(i).normal.y > 0.7f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetGrounded(bool grounded) {
|
||||
isGrounded = grounded;
|
||||
lastGroundedTime = Time.time;
|
||||
}
|
||||
|
||||
private bool IsGroundedOrRecentlyGrounded() {
|
||||
return isGrounded || Time.time - lastGroundedTime <= groundStickGraceTime;
|
||||
}
|
||||
|
||||
private void StartSlide() {
|
||||
isSliding = true;
|
||||
animator.SetBool("Sliding", true);
|
||||
@@ -170,7 +197,7 @@ public class PlayerController : MonoBehaviour {
|
||||
|
||||
if(GameManager.instance != null && GameManager.instance.TryBlockDamageWithItem())
|
||||
{
|
||||
StartCoroutine(InvincibleRoutine());
|
||||
StartCoroutine(DamageBlockCooldownRoutine());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,6 +212,12 @@ public class PlayerController : MonoBehaviour {
|
||||
StartCoroutine(InvincibleRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator DamageBlockCooldownRoutine() {
|
||||
isInvincible = true;
|
||||
yield return new WaitForSeconds(0.35f);
|
||||
isInvincible = false;
|
||||
}
|
||||
|
||||
private IEnumerator InvincibleRoutine() {
|
||||
isInvincible = true;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user