플랫포머 게임 - 3 : 장애물 변경
저는 플랫포머 게임에서 즉사 혹은 큰 대미지를 주는 가시를 아주 싫어합니다.
원작의 컨셉에 맞게 가시를 다른 것으로 변경 해보겠습니다.
1) 플랫폼 프리팹 복사
2) 복사한 플랫폼에서 가시를 하나 제외하고 전부 삭제
3) 가시 오브젝트로 2번 게시글에서 플레이어 캐릭터를 만들어 준 것과 동일하게 움직이는 병사 오브젝트 생성
(다른 설정들은 동일하게 가져가게끔)
4) 스케일, 위치를 플랫폼 위에 서 있는 것으로 조정
5) 사각형 트리거를 병사 크기와 비슷하게 조정
6) 병사들을 복사하여 각 위치에 배치
7) 모든 위치의 병사들을 한번에 복사
8) 복사한 병사들의 스켈레톤 데이터 에셋을 변경
9) 각 위치에 병사 하나만 랜덤 확률로 나타날 수 있도록 원본을 복제한 스크립트 수정
using UnityEngine;
// 발판으로서 필요한 동작을 담은 스크립트
public class Platform_rangrun : MonoBehaviour {
// 장애물 오브젝트들 > 각 위치의 병사들을 담을 여러 배열
public GameObject[] fighters;
public GameObject[] knights;
public GameObject[] Spearmans;
// 플레이어 캐릭터가 발판을 밟았었는가
private bool stepped = false;
// 발판에 병사가 몇명이 올라갈 수 있는가
const int num_soldiers_max = 5;
// 컴포넌트가 활성화될때 마다 매번 실행되는 메서드
private void OnEnable() {
// 발판을 리셋하는 처리
stepped = false;
// 발판 위 장애물의 최대 수만큼 루프
for (int i = 0; i < num_soldiers_max; i++)
{
// 해당 위치의 모든 병사 비활성화
fighters[i].SetActive(false);
knights[i].SetActive(false);
Spearmans[i].SetActive(false);
// 장애물 배치 위치가 늘어나면서 각 위치 등장 확률 조정
// 현재 순번의 장애물을 1/5 확률로 등장
if (Random.Range(0, 5) == 0)
{
// 어떤 종류의 병사가 나올지 랜덤
int branch = Random.Range(0, 3);
switch (branch)
{
case 0:
fighters[i].SetActive(true);
break;
case 1:
knights[i].SetActive(true);
break;
case 2:
Spearmans[i].SetActive(true);
break;
}
}
}
}
void OnCollisionEnter2D(Collision2D collision) {
// 플레이어 캐릭터가 자신을 밟았을때 점수를 추가하는 처리
if (collision.collider.CompareTag("Player") && !stepped)
{
// 점수가 중복으로 더해지지 않도록
stepped = true;
// 점수+1
GameManager.instance.AddScore(1);
}
}
}
하지만 지금 병사들은 모습만 바뀌었지 닿으면 죽는 가시와 같은 판정입니다.
이를 수정해 보겠습니다.
원작 게임에서는 보병 > 창병 > 기병 > 보병의 가위바위보 상성 관계를 가지고 있습니다.
이에 대응하여 3종류의 병사를 넣었습니다.
이 게임의 주인공은 기병입니다.
보병에 강하고, 창병에는 약한 상성을 가지고 있습니다.
기병에는 상성이 없습니다.
게임 시스템 상 억까도 있을 수 있어서
창병에 1번 닿을 때 끝나버리면 게임이 너무 시시해질 듯하니 변경 룰을 만들어보겠습니다.
라이프는 3
보병과 닿으면 추가 점수
창병과 닿으면 라이프 -2
기병과 닿으면 라이프 -1
플랫폼에서 떨어지면 라이프 -2 및 리스폰
10) 각 병종에 해당하는 태그를 생성 > 각각 알맞는 병사에 부여
플랫폼에 안착하지 못하고 떨어지는 것을 판정하는 트리거 오브젝트에 태그 부여
11) 체력 표시 ui 생성
12) 기획에 따라 플레이어가 트리거에 닿았을 때 처리 부분 스크립트 수정
using UnityEngine;
// 스파인 애니메이션을 사용하기 위해 네임스페이스를 스파인에서 쓰는 것과 동일하게
namespace Spine.Unity
{
// PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다.
public class PlayerController_rangrun : MonoBehaviour
{
public AudioClip deathClip; // 사망시 재생할 오디오 클립
public Vector2 jumpForce = new Vector2(0f, 700f); // 점프 힘
// hp 표시 ui들
public Transform[] hpMarks;
[SerializeField]
Soldier_Hit soldier_hit;
private int jumpCount = 0; // 누적 점프 횟수
const int jumpCountMax = 2;
private bool isDead = false; // 사망 상태
// 현재, 최대 hp;
int hp = 3;
const int hp_Max = 3;
float respawn_PosY = 3f;
const int respawn_jumpForce = 150;
private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트
//private Animator animator; // 사용할 애니메이터 컴포넌트
SkeletonAnimation skeletonAnimation;
private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
private void Start()
{
// 초기화
TryGetComponent(out playerRigidbody);
TryGetComponent(out skeletonAnimation);
TryGetComponent(out playerAudio);
}
private void Update()
{
if (isDead)
return;
// 점프(마우스 좌클릭 or 화면 터치) 버튼을 눌렀고, 점프 횟수가 최대에 도달하지 않았다면
if (Input.GetMouseButtonDown(0) && jumpCount < jumpCountMax)
{
// 점프 횟수 증가
jumpCount++;
// 점프 직전 순간적으로 속도를 0으로
// 빠르게 2번 입력하면 힘을 중첩해서 2번 빠르게 주기에 더 높은 점프가 가능
// 혹은 떨어지는 중 힘을 줘도 상쇄되어서 점프 높이가 낮아짐
// 따라서 초기화가 필요
playerRigidbody.velocity = Vector2.zero;
// 리지드바디에 위쪽으로 힘 주기
playerRigidbody.AddForce(jumpForce);
// 점프 사운드 재생
playerAudio.Play();
}
// 점프 버튼에서 손을 떼고, 위로 상승 중이었다면(낙하 도중에 속도가 낮춰진다는 건 이상하니까)
else if (Input.GetMouseButtonUp(0) && playerRigidbody.velocity.y > 0)
{
// 속도 절반? 왜?
// 점프 버튼을 꾹 누르고 있으면 더 높이 점프하는 것을 간략하게 구현
// 반대로 생각해서 점프 버튼에서 손을 떼는 순간 속도를 줄여 점프 높이를 줄이도록
playerRigidbody.velocity /= 2;
}
}
private void Die()
{
// 사망 애니메이션 재생
//animator.SetTrigger("Die");
skeletonAnimation.loop = false;
skeletonAnimation.AnimationName = "death";
// 사망 사운드로 클립 교체하고 재생
playerAudio.clip = deathClip;
playerAudio.Play();
// 속도를 0으로
playerRigidbody.velocity = Vector2.zero;
// 사망 처리
isDead = true;
// 게임매니저의 게임오버 처리
GameManager.instance.OnPlayerDead();
}
// 트리거를 가진 장애물과의 충돌을 감지
private void OnTriggerEnter2D(Collider2D other)
{
// 부딪힌 트리거의 오브젝트 태그가 Dead이고, 살아있는 상태일 때
//if (other.CompareTag("Dead") && !isDead)
//{
// Die();
//}
switch (other.tag)
{
// 임시: 모든 병사들은 상호작용 후 비활성화
// 보병 > 점수 획득
case "Fighter":
GameManager.instance.AddScore(1);
other.gameObject.SetActive(false);
soldier_hit.SummonHit(soldier_hit.Fighters);
break;
// 기병 > 체력 -1
case "Knight":
hpChange(-1);
other.gameObject.SetActive(false);
soldier_hit.SummonHit(soldier_hit.Knights);
break;
// 창병 > 체력 -2
case "Spearman":
hpChange(-2);
other.gameObject.SetActive(false);
soldier_hit.SummonHit(soldier_hit.Spearmans);
break;
case "Fall":
// 체력 -2
hpChange(-2);
// 사망하지 않았다면
if(!isDead)
{
// 리스폰 위치로 이동
transform.position = new Vector3(transform.position.x, respawn_PosY, 0);
// 속도를 초기화하고 리스폰 위치에서 위로 점프
playerRigidbody.velocity = Vector2.zero;
playerRigidbody.AddForce(Vector2.up * respawn_jumpForce);
// 연속 점프 횟수 초기화
jumpCount = 0;
}
break;
default:
break;
}
}
// 바닥에 닿았음을 감지하는 처리
private void OnCollisionEnter2D(Collision2D collision)
{
// 어떤 콜라이더와 닿았고 충돌면이 위를 보고 있을 때
// 바닥 수직 위로 캐릭터가 안착 > y축 1.0f
if (collision.contacts[0].normal.y > 0.7f)
{
jumpCount = 0;
}
}
// 매개변수만큼 체력 변화 및 ui 표시 변화 + 죽음 처리
void hpChange(int amount)
{
if(amount < 0)
{
amount = -amount;
for (int i = 0; i < amount; i++)
{
if (hp > 0)
hpMarks[--hp].gameObject.SetActive(false);
}
// hp가 0이 되었다면 죽음 처리
if (hp.Equals(0))
Die();
}
else
{
for (int i = 0; i < amount; i++)
{
if (hp < hp_Max)
hpMarks[hp++].gameObject.SetActive(true);
}
}
}
}
}
< 결과 >
아직은 다소 밋밋하지만 논리대로 동작하는 것을 확인했습니다.