오늘의 학습 키워드
1. 필수과제 진행

오늘은 튜토리얼 만들었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum TutorialStep
{
Jump,
Slide,
CollectCoin,
TakeDamage,
Complete
}
public class TutorialManager : MonoBehaviour
{
public bool IsStepLocked { get; private set; } = true;
public static TutorialManager Instance;
public TutorialStep currentStep;
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
private void Start()
{
Time.timeScale = 1f; // 게임 흐름은 정상 유지
ShowCurrentStep(); // 시작 시 첫 말풍선 출력
}
public void AdvanceStep()
{
currentStep++;
IsStepLocked = true;
ShowCurrentStep();
if (currentStep == TutorialStep.Complete)
{
OnTutorialComplete();
}
}
public bool IsCurrentStep(TutorialStep step)
{
return currentStep == step;
}
public void UnlockStep()
{
IsStepLocked = false;
Time.timeScale = 1f; // 게임 다시 재생
}
public void ShowCurrentStep()
{
Debug.Log("튜토리얼 다음 단계로: " + currentStep);
TutorialUI.Instance?.ShowStep(currentStep);
}
private void OnTutorialComplete()
{
GameManager.Instance.PauseGame();
TutorialUI.Instance?.gameObject.SetActive(false); // 말풍선 숨김
FindObjectOfType<UIManager>().SetGameOver(); // GameOverUI 표시
}
}
이런식으로 만들었다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : Unit
{
private TutorialManager tutorialManager;
private bool isGodMode = false;
private float godModeTimer = 0f;
private bool isDead = false;
[SerializeField] private int maxJumpCount = 2; // 최대 점프 횟수
private int currentJumpCount = 0;
public float hitDamage = 20f; // 충돌 시 감소할 체력
public float damageByTime = 5f; //시간에 따른 감소 체력
private float limitPosY = -20f;
private bool wasGroundedLastFrame = false;
public float CurrentHp => Hp;
private float defaultSpeed;
private Coroutine speedCoroutine;
private void Fall()
{
if (transform.position.y < limitPosY)
{
Hp = 0f;
isDead = true;
GameManager.Instance.GameOver();
}
}
protected override float Hp
{
get { return hp; }
set
{
hp = Mathf.Clamp(value, 0, fullHP);
}
}
protected override float JumpForce
{
get { return jumpForce; }
set
{
jumpForce = Mathf.Max(0, value);
}
}
public override float Speed
{
get { return speed; }
set
{
speed = Mathf.Max(0, value);
}
}
private void Awake()
{
base.Awake();
Hp = 100f;
JumpForce = 6f;
Speed = 4f;
defaultSpeed = Speed;
tutorialManager = FindObjectOfType<TutorialManager>();
}
private void Update()
{
DecreaseHpByTime();
Fall(); //낙하 게임오버
//땅에 오브젝트가 닿았는지 확인
isGrounded = Physics2D.Raycast(transform.position, Vector2.down, groundRayLength, groundLayer);
Debug.DrawRay(transform.position, Vector2.down * groundRayLength, Color.red);
if (isGrounded && !wasGroundedLastFrame)
{
currentJumpCount = 0;
}
wasGroundedLastFrame = isGrounded;
//점프
if (Input.GetKeyDown(KeyCode.Space))
{
// 튜토리얼 단계 확인
Jump();
if (tutorialManager != null && tutorialManager.IsCurrentStep(TutorialStep.Jump))
{
tutorialManager.AdvanceStep();
}
}
animCtrl.JumpAnim(isGrounded);
//슬라이드
// 현재 LeftShift 키 상태 체크
PressedShift = Input.GetKey(KeyCode.LeftShift) || GameUI.Instance?.IsSlidePressed() == true;
Slide(PressedShift);
if (PressedShift && tutorialManager != null && tutorialManager.IsCurrentStep(TutorialStep.Slide))
{
tutorialManager.AdvanceStep();
}
// 무적 시간 감소
if (isGodMode)
{
godModeTimer -= Time.deltaTime;
if (godModeTimer <= 0f)
{
isGodMode = false;
GetComponentInChildren<SpriteRenderer>().color = Color.white; // 색 복원
}
}
}
private void FixedUpdate()
{
Move();
}
public override void Move()
{
tr.Translate(Vector3.right * Speed * Time.fixedDeltaTime, Space.World);
}
public override void Jump()
{
if (currentJumpCount >= maxJumpCount)
{
Debug.Log("점프 불가: currentJumpCount = " + currentJumpCount);
return;
}
SFXManager.Instance.JumpSFX();
rb.velocity = new Vector2(rb.velocity.x, 0f);
rb.AddForce(Vector2.up * JumpForce, ForceMode2D.Impulse);
currentJumpCount++;
Debug.Log("점프! currentJumpCount = " + currentJumpCount);
}
public override void Slide(bool PressedShift)
{
animCtrl.SlideAnim(PressedShift);
}
public void ActivateGodMode(float duration)
{
isGodMode = true;
godModeTimer = duration;
StartCoroutine(FlashWhileInvincible());
}
private IEnumerator FlashWhileInvincible()
{
SpriteRenderer sr = GetComponentInChildren<SpriteRenderer>();
while (isGodMode)
{
sr.color = new Color(1, 1, 1, 0.5f); // 반투명
yield return new WaitForSeconds(0.1f);
sr.color = Color.white;
yield return new WaitForSeconds(0.1f);
}
}
public override void DecreaseHpByTime()
{
//시간에 따른 체력 감소
Hp -= damageByTime * Time.deltaTime;
}
public void Heal(float amount)
{
if (100f - Hp < amount)
{
Hp += amount - (100f - Hp);
}
else
{
Hp += amount;
}
}
public void SpeedChangeTemporary(float amount, float duration = 2f)
{
if (speedCoroutine != null)
StopCoroutine(speedCoroutine);
speedCoroutine = StartCoroutine(TemporarySpeedChange(amount, duration));
}
private IEnumerator TemporarySpeedChange(float amount, float duration)
{
Speed = defaultSpeed + amount;
yield return new WaitForSeconds(duration);
Speed = defaultSpeed;
}
public void Damaged(float amount)
{
if (isGodMode || isDead) return;
Hp -= amount;
if (Hp <= 0)
{
Hp = 0;
isDead = true;
GameManager.Instance.GameOver();
return;
}
ActivateGodMode(GameManager.Instance.playerGodMode);
// 튜토리얼 단계 처리 추가
if (TutorialManager.Instance != null &&
TutorialManager.Instance.IsCurrentStep(TutorialStep.TakeDamage))
{
TutorialManager.Instance.AdvanceStep();
}
Debug.Log($"체력: {Hp} (무적 모드 활성화됨)");
}
}
그리고 여기에서 튜토리얼 때만, 작동하도록 튜토리얼 단계 처리를 추가해주면 끝이다.
그리고 이제
public void ApplyEffect(PlayerController player)
{
player.Damaged(_data.value);
}
ObstarcleObject.cs 안에있는 이부분이 적용이 안돼었었는데
data 필드를 선언해줘서 됐다.
private GroundObjectData data;
private GroundObjectData _data
{
get { return data; }
set { data = value; }
}
이런식으로