Add runner mechanics and travel asset library

This commit is contained in:
jongjae0305
2026-06-17 14:38:08 +09:00
parent ad4cd57a48
commit 1a7293ce6b
240 changed files with 17525 additions and 136 deletions
+35 -2
View File
@@ -12,11 +12,16 @@ public class GameManager : MonoBehaviour {
public GameObject gameoverUI; // 게임 오버시 활성화 할 UI 게임 오브젝트
private int score = 0; // 게임 점수
private int currentLife; // 현재 남은 목숨
public int maxLife = 3; // 시작 목숨
// 게임의 스피드를 올리는 변수
public float gameSpeed = 1f;
public float minGameSpeed = 1f;
public float speedIncreaseRate = 0.05f;
public float maxGameSpeed = 2f;
public float hitSpeedPenalty = 0.3f;
// 게임 시작과 동시에 싱글톤을 구성
void Awake() {
@@ -37,6 +42,11 @@ public class GameManager : MonoBehaviour {
}
}
private void Start() {
currentLife = maxLife;
UpdateScoreUI();
}
void Update() {
// 게임 오버 상태에서 게임을 재시작할 수 있게 하는 처리
if(isGameover && Input.GetMouseButtonDown(0))
@@ -50,6 +60,7 @@ public class GameManager : MonoBehaviour {
gameSpeed += speedIncreaseRate * Time.deltaTime;
// 게임 최대 속도는 max를 넘어서지 못하게
gameSpeed = Mathf.Min(gameSpeed, maxGameSpeed);
UpdateScoreUI();
}
}
@@ -59,14 +70,36 @@ public class GameManager : MonoBehaviour {
if(!isGameover)
{
score += newScore;
scoreText.text = "Score : " + score;
UpdateScoreUI();
}
}
// 플레이어가 장애물에 부딪혔을 때 목숨과 속도를 줄이는 메서드
public bool TakeDamage(int damage = 1) {
if(isGameover)
{
return false;
}
currentLife -= damage;
currentLife = Mathf.Max(currentLife, 0);
gameSpeed -= hitSpeedPenalty;
gameSpeed = Mathf.Max(gameSpeed, minGameSpeed);
UpdateScoreUI();
return currentLife <= 0;
}
// 플레이어 캐릭터가 사망시 게임 오버를 실행하는 메서드
public void OnPlayerDead() {
isGameover = true;
gameoverUI.SetActive(true);
}
}
private void UpdateScoreUI() {
scoreText.text = "Score : " + score + "\nLife : " + currentLife + "/" + maxLife + "\nSpeed : " + gameSpeed.ToString("0.0");
}
}
+17 -10
View File
@@ -3,6 +3,7 @@
// 발판으로서 필요한 동작을 담은 스크립트
public class Platform : MonoBehaviour {
public GameObject[] obstacles; // 장애물 오브젝트들
public int emptyPatternWeight = 2; // 장애물이 없는 패턴이 선택될 가중치
private bool stepped = false; // 플레이어 캐릭터가 밟았었는가
// 컴포넌트가 활성화될때 마다 매번 실행되는 메서드
@@ -11,16 +12,22 @@ public class Platform : MonoBehaviour {
stepped = false;
for( int i = 0; i < obstacles.Length; i++)
for(int i = 0; i < obstacles.Length; i++)
{
if(Random.Range(0,3) == 0)
{
obstacles[i].SetActive(true);
}
else
{
obstacles[i].SetActive(false);
}
obstacles[i].SetActive(false);
}
if(obstacles.Length == 0)
{
return;
}
// 낮은 장애물과 높은 장애물이 동시에 켜져 불가능한 배치가 되지 않도록 하나만 선택한다.
int patternIndex = Random.Range(0, obstacles.Length + emptyPatternWeight);
if(patternIndex < obstacles.Length)
{
obstacles[patternIndex].SetActive(true);
}
}
@@ -32,4 +39,4 @@ public class Platform : MonoBehaviour {
GameManager.instance.AddScore(1);
}
}
}
}
+137 -67
View File
@@ -1,49 +1,58 @@
using UnityEngine;
using System.Collections;
using UnityEngine;
// PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다.
public class PlayerController : MonoBehaviour {
public AudioClip deathClip; // 사망시 재생할 오디오 클립
public float jumpForce = 700f; // 점프 힘
public AudioClip deathClip; // 사망시 재생할 오디오 클립
public float jumpForce = 700f; // 점프 힘
public int maxJumpCount = 2; // 최대 점프 횟수
public float invincibleDuration = 1.5f; // 피해를 입은 뒤 무적 시간
private int jumpCount = 0; // 누적 점프 횟수
private bool isGrounded = false; // 바닥에 닿았는지 나타냄
private bool isDead = false; // 사망 상태
private int jumpCount = 0; // 누적 점프 횟수
private bool isGrounded = false; // 바닥에 닿았는지 나타냄
private bool isDead = false; // 사망 상태
private bool isSliding = false; // 슬라이딩 상태
private bool isInvincible = false; // 피해 후 무적 상태
private bool isSliding = false; // 슬라이딩 상태
private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트
private Animator animator; // 사용할 애니메이터 컴포넌트
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
private SpriteRenderer spriteRenderer; // 플레이어 스프라이트 표시 컴포넌트
private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트
private Animator animator; // 사용할 애니메이터 컴포넌트
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
private CapsuleCollider2D playerCollider; // 플레이어의 콜라이더 충돌 범위
private Vector2 originalColliderSize; // 기본 자세일 때 콜라이더 크기
private Vector2 originalColliderOffset; // 기본 자세일 때 콜라이더 중심 위치
private CapsuleDirection2D originalColliderDirection; // 기본 자세일 때 콜라이더 방향
public Vector2 slidingColliderSize; // 슬라이딩 자세일 때 콜라이더 크기
public Vector2 slidingColliderOffset; // 슬라이딩 자세일 때 콜라이더 중심 위치
public CapsuleDirection2D slidingColliderDirection = CapsuleDirection2D.Horizontal; // 슬라이딩 자세일 때 콜라이더 방향
private CapsuleCollider2D playerCollider; // 플레이어의 콜라이더 충돌 범위
private Vector2 originalColliderSize; // 기본 자세일 때 콜라이더 크기
private Vector2 originalColliderOffset; // 기본 자세일 때 콜라이더 중심 위치
public Vector2 slidingColliderSize; // 슬라이딩 자세일 때 콜라이더 크기
public Vector2 slidingColliderOffset; // 슬라이딩 자세일 때 콜라이더 중심 위치
private void Start() {
// 게임 오브젝트로부터 사용할 컴포넌트들을 가져와 변수에 할당
playerRigidbody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
spriteRenderer = GetComponent<SpriteRenderer>();
private void Start() {
// 게임 오브젝트로부터 사용할 컴포넌트들을 가져와 변수에 할당
playerRigidbody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
// 게임 콜라이더를 가져와서 현 상태를 저장
playerCollider = GetComponent<CapsuleCollider2D>();
originalColliderSize = playerCollider.size;
originalColliderOffset = playerCollider.offset;
// 게임 콜라이더를 가져와서 현 상태를 저장
playerCollider = GetComponent<CapsuleCollider2D>();
originalColliderSize = playerCollider.size;
originalColliderOffset = playerCollider.offset;
originalColliderDirection = playerCollider.direction;
}
private void Update() {
if(isDead)
private void Update() {
if(isDead)
{
return;
}
// 마우스 왼쪽 버튼을 눌렀으면 && 최대 점프 횟수(2)에 도달하지 않았다면
if(Input.GetMouseButtonDown(0) && jumpCount < 2)
// 슬라이딩 중이 아니라면 최대 점프 횟수까지 점프할 수 있다.
if(Input.GetMouseButtonDown(0) && jumpCount < maxJumpCount && !isSliding)
{
isGrounded = false;
jumpCount++;
// 점프 직전에 속도를 순간적으로 제로(0, 0)으로 변경
playerRigidbody.linearVelocity = Vector2.zero;
@@ -59,69 +68,130 @@ public class PlayerController : MonoBehaviour {
}
animator.SetBool("Grounded", isGrounded);
// 마우스 오른쪽 버튼을 누르고 땅에 있는 상태이며 슬라이딩이 아닌 상태면 슬라이딩 작동
if (Input.GetMouseButtonDown(1) && isGrounded && !isSliding)
// 달리는 중에 마우스 오른쪽 버튼을 누르고 있는 동안 슬라이딩 유지
if(Input.GetMouseButton(1) && isGrounded && !isSliding)
{
StartSlide();
}
else if (Input.GetMouseButtonUp(1) && isSliding)
else if((!Input.GetMouseButton(1) || !isGrounded) && isSliding)
{
EndSlide();
}
}
}
private void Die() {
// 애니메이터의 Die 트리거 파라미터를 셋
animator.SetTrigger("Die");
playerAudio.clip = deathClip;
playerAudio.Play();
playerRigidbody.linearVelocity = Vector2.zero;
isDead = true;
GameManager.instance.OnPlayerDead();
}
private void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Dead" && !isDead)
private void Die() {
if(isDead)
{
Die();
return;
}
}
private void OnCollisionEnter2D(Collision2D collision) {
// 어떤 콜라이더와 닿았으며, 충돌 표면이 위쪽을 보고 있으면
if(collision.contacts[0].normal.y > 0.7f)
if(isSliding)
{
EndSlide();
}
StopAllCoroutines();
spriteRenderer.color = Color.white;
// 애니메이터의 Die 트리거 파라미터를 셋
animator.SetTrigger("Die");
playerAudio.clip = deathClip;
playerAudio.Play();
playerRigidbody.linearVelocity = Vector2.zero;
isDead = true;
GameManager.instance.OnPlayerDead();
}
private void OnTriggerEnter2D(Collider2D other) {
if(other.CompareTag("Dead") && !isDead)
{
if(other.gameObject.name == "DeadZone")
{
Die();
return;
}
TakeDamage();
}
}
private void OnCollisionEnter2D(Collision2D collision) {
// 어떤 콜라이더와 닿았으며, 충돌 표면이 위쪽을 보고 있으면
if(collision.contacts[0].normal.y > 0.7f)
{
isGrounded = true;
jumpCount = 0;
}
}
}
private void OnCollisionExit2D(Collision2D collision) {
// 바닥에서 벗어났음을 감지하는 처리
isGrounded = false;
}
private void OnCollisionExit2D(Collision2D collision) {
// 바닥에서 벗어났음을 감지하는 처리
isGrounded = false;
private void StartSlide()
{
if(isSliding)
{
EndSlide();
}
}
private void StartSlide() {
isSliding = true;
animator.SetBool("Sliding", true);
playerCollider.size = slidingColliderSize;
playerCollider.offset = slidingColliderOffset;
playerCollider.direction = slidingColliderDirection;
}
private void EndSlide()
{
private void EndSlide() {
isSliding = false;
animator.SetBool("Sliding", false);
playerCollider.size = originalColliderSize;
playerCollider.offset = originalColliderOffset;
playerCollider.direction = originalColliderDirection;
}
}
private void TakeDamage() {
if(isInvincible)
{
return;
}
bool shouldDie = GameManager.instance.TakeDamage();
if(shouldDie)
{
Die();
return;
}
StartCoroutine(InvincibleRoutine());
}
private IEnumerator InvincibleRoutine() {
isInvincible = true;
float elapsedTime = 0f;
float blinkInterval = 0.1f;
while(elapsedTime < invincibleDuration)
{
spriteRenderer.color = new Color(1f, 1f, 1f, 0.35f);
yield return new WaitForSeconds(blinkInterval);
elapsedTime += blinkInterval;
spriteRenderer.color = Color.white;
yield return new WaitForSeconds(blinkInterval);
elapsedTime += blinkInterval;
}
spriteRenderer.color = Color.white;
isInvincible = false;
}
}
+81
View File
@@ -0,0 +1,81 @@
using UnityEngine;
// 슬라이딩 장애물 패턴이 켜질 때마다 매달린 장애물 위치를 섞는다.
public class SlideObstaclePattern : MonoBehaviour
{
public Transform[] obstacles; // 위치를 바꿀 고공 장애물들
public float[] xPositions = { -1.65f, -0.55f, 0.55f, 1.65f }; // 선택 가능한 로컬 x 위치
public float minSpacing = 1.5f; // 두 장애물 사이의 최소 간격
private void OnEnable()
{
if(obstacles == null || obstacles.Length == 0 || xPositions == null || xPositions.Length == 0)
{
return;
}
int firstIndex = Random.Range(0, xPositions.Length);
if(obstacles.Length == 1 || xPositions.Length == 1)
{
PlaceObstacle(obstacles[0], xPositions[firstIndex]);
return;
}
int secondIndex = PickSecondIndex(firstIndex);
float leftX = xPositions[firstIndex];
float rightX = xPositions[secondIndex];
if(leftX > rightX)
{
float temp = leftX;
leftX = rightX;
rightX = temp;
}
PlaceObstacle(obstacles[0], leftX);
PlaceObstacle(obstacles[1], rightX);
}
private int PickSecondIndex(int firstIndex)
{
for(int i = 0; i < 12; i++)
{
int candidateIndex = Random.Range(0, xPositions.Length);
if(candidateIndex != firstIndex && Mathf.Abs(xPositions[candidateIndex] - xPositions[firstIndex]) >= minSpacing)
{
return candidateIndex;
}
}
int bestIndex = firstIndex;
float bestDistance = 0f;
for(int i = 0; i < xPositions.Length; i++)
{
float distance = Mathf.Abs(xPositions[i] - xPositions[firstIndex]);
if(distance > bestDistance)
{
bestDistance = distance;
bestIndex = i;
}
}
return bestIndex;
}
private void PlaceObstacle(Transform obstacle, float xPosition)
{
if(obstacle == null)
{
return;
}
Vector3 localPosition = obstacle.localPosition;
localPosition.x = xPosition;
obstacle.localPosition = localPosition;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9dcd5b5fbb7b40d4b86fa136c439d1b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: