브레인멜트다운은 플랫포머 게임으로,
이번 포스팅에서는 플랫포머 게임에서 가장 중요한 캐릭터의 이동과 조작에 대해서 다루겠습니다.
이 게임은 두 개의 캐릭터를 한명이 컨트롤하는 게임으로,
이동의 알고리즘은 똑같지만 입력값이 다르다는 것이 특징입니다.
따라서 player라는 상위 추상클래스를 구현하고, 각각 캐릭터의 클래스가 이를 상속하게끔 설계했습니다.
(오브젝트 풀은 캐릭터가 착지할 때 생기는 랜딩 이펙트 관리에 사용했습니다)
위와 같이 구성했습니다.
전체 코드는 더보기로 확인해주세요.▼
상위클래스
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public abstract class Player : MonoBehaviour
{
[Header("State")]
[SerializeField] private float speed;
[SerializeField] private float jumpForce;
protected bool isSit;
protected bool isRun;
protected bool isUp;
protected bool isDown;
protected bool isGrounded;
private float stateUpDownNum;
[Header("Landing Effect")]
[SerializeField] ObjectPool objectPool;
[SerializeField] Transform footPos;
[Header("Die Effect")]
[SerializeField] GameObject dieEffect;
protected SpriteRenderer dieEffectSR;
protected Animator dieEffectAnim;
protected Dictionary<string, Color> colorDic;
protected Rigidbody2D rigid2d;
protected SpriteRenderer spriteRenderer;
protected Animator animator;
protected CapsuleCollider2D collider;
protected CapsuleCollider2D childCollider;
//세이브포인트
protected GameObject currentSavePoint;
protected Vector3 currentSavePointPos;
protected int audioNum;
public void Start()
{
rigid2d = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
animator = GetComponent<Animator>();
collider = GetComponent<CapsuleCollider2D>();
childCollider = transform.GetChild(0).gameObject.GetComponent<CapsuleCollider2D>();
dieEffectAnim = dieEffect.GetComponent<Animator>();
//state 초기화
speed = 8;
jumpForce = 17;
stateUpDownNum = 1.5f;
isSit = false;
isRun = false;
isUp = false;
isDown = false;
isGrounded = true;
//세이브포인트
currentSavePoint = GameObject.Find("savePoint" + GameManager.Instance.savePointNow.ToString());
currentSavePointPos = currentSavePoint.transform.position;
//컬러 딕셔너리 초기화 및 추가
colorDic = new Dictionary<string, Color>();
colorDic.Add("purple", new Color32(150, 93, 198, 255));
colorDic.Add("yellow", new Color32(255, 192, 99, 255));
}
//isUp과 isDown을 관리
protected void StateUpDown()
{
if (rigid2d.velocity.y > stateUpDownNum) { isUp = true; isDown = false; }
else if (rigid2d.velocity.y < -stateUpDownNum) { isUp = false; isDown = true; }
else { isUp = false; isDown = false; }
}
//애니메이션 관리
protected void MoveAnim()
{
animator.SetBool("isSit", isSit);
if (!isUp && !isDown) animator.SetBool("isRun", isRun); //점프중에는 달리는 애니메이션X
else animator.SetBool("isRun", false);
animator.SetBool("isUp", isUp);
animator.SetBool("isDown", isDown);
}
//랜딩사운드는 각 플레이어마다 오디오소스를 다르게 관리해서 추상함수로 구현
protected abstract void LandingSoundAudioSetting();
//이동 관리 함수
protected void Move(float moveX)
{
//이동
rigid2d.velocity = new Vector2(moveX * speed, rigid2d.velocity.y);
//x축 이동은 x*speed로 y축 이동은 기존의 속력값(현재는 중력)
//미끄러짐 방지
if (moveX == 0) rigid2d.constraints = RigidbodyConstraints2D.FreezePositionX | RigidbodyConstraints2D.FreezeRotation;
else rigid2d.constraints = RigidbodyConstraints2D.FreezeRotation;
//anim 좌우방향설정
if (moveX < 0) spriteRenderer.flipX = false;
else if (moveX > 0) spriteRenderer.flipX = true;
//상태 설정
if (moveX != 0) isRun = true;
else isRun = false;
}
//발소리 사운드관리
protected void MoveSound(int audioSource)
{
if (isRun && !AudioManager.Instance.audioSources[audioSource].isPlaying
&& !isSit && isGrounded)
{
if (SceneManager.GetActiveScene().name == "stage1")
AudioManager.Instance.PlaySE("s1Footstep", audioSource);
else if (SceneManager.GetActiveScene().name == "stage2")
AudioManager.Instance.PlaySE("s2Footstep", audioSource);
}
}
//죽을때 이펙트 관리
protected void DieEffect()
{
GameObject dieEffect_=Instantiate(dieEffect, transform.position, transform.rotation);
dieEffect_.SetActive(true);
dieEffectSR = dieEffect_.GetComponent<SpriteRenderer>();
DieEffectColorSetting(); //하위클래스별로 차이가 나는 부분
gameObject.SetActive(false);
}
//죽을때 이펙트가 캐릭터별로 달라 추상클래스로 구현
protected abstract void DieEffectColorSetting();
protected void Jump(bool key)
{
if (key && isGrounded)
{
isGrounded = false;
rigid2d.velocity = Vector2.up * jumpForce;
}
}
//앉을때 콜라이더 관리
protected void Sit(bool key)
{
if (key)
{
isSit = true;
collider.enabled = false;
childCollider.enabled = true;
}
else
{
isSit = false;
collider.enabled = true;
childCollider.enabled = false;
}
}
protected void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Obstacle") //몬스터와 충돌하면 죽게 처리
{
GameManager.Instance.isDead = true;
GameManager.Instance.GameOver();
DieEffect();
}
else if (collision.gameObject.tag == "Ground") //이중점프 방지를 위한 땅과의 충돌처리
{
isGrounded = true;
LandingEffect();
}
}
//착지 이펙트관리 - 오브젝트풀로 관리
private void LandingEffect()
{
GameObject landingEffect = objectPool.UsePrefab();
landingEffect.transform.position = footPos.position;
StartCoroutine(GetBackPrefabCo(landingEffect));
LandingSoundAudioSetting();
PlayLandingSound(audioNum);
}
//착지사운드 관리
private void PlayLandingSound(int audioSource)
{
if (SceneManager.GetActiveScene().name == "stage1")
AudioManager.Instance.PlaySE("s1Landing", audioSource);
else if (SceneManager.GetActiveScene().name == "stage2")
AudioManager.Instance.PlaySE("s2Landing", audioSource);
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
isGrounded = false;
}
}
//오브젝트풀에 다시 오브젝트 넣기
IEnumerator GetBackPrefabCo(GameObject gameObject)
{
yield return new WaitForSeconds(0.6f);
objectPool.GetBackPrefab(gameObject);
}
}
하위클래스(각 플레이어) - 하나만 작성해두었고 나머지 Player_yellow 클래스도 같은 방식으로 동작합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player_purple : Player
{
void Start()
{
base.Start();
gameObject.transform.position = currentSavePointPos + new Vector3(-1, 0.5f, 0);
}
void Update()
{
if (!GameManager.Instance.isPaused
&& !GameManager.Instance.isClear
&& !GameManager.Instance.isDead)
{
Move(Input.GetAxisRaw("Horizontal2"));
Jump(Input.GetKeyDown(KeyCode.W));
Sit(Input.GetKey(KeyCode.S));
StateUpDown();
MoveAnim();
MoveSound(constant.PLAYER_P_AUDIO_SOURCE);
}
}
protected override void DieEffectColorSetting()
{
dieEffectSR.color = colorDic["purple"];
}
protected override void LandingSoundAudioSetting()
{
audioNum = constant.PLAYER_P_AUDIO_SOURCE;
}
}
이동 관리 함수
매개변수로 인풋 값을 받습니다.
상위클래스▼
protected void Move(float moveX)
{
//이동
rigid2d.velocity = new Vector2(moveX * speed, rigid2d.velocity.y);
//x축 이동은 x*speed로 y축 이동은 기존의 속력값(현재는 중력)
//미끄러짐 방지
if (moveX == 0) rigid2d.constraints = RigidbodyConstraints2D.FreezePositionX | RigidbodyConstraints2D.FreezeRotation;
else rigid2d.constraints = RigidbodyConstraints2D.FreezeRotation;
//anim 좌우방향설정
if (moveX < 0) spriteRenderer.flipX = false;
else if (moveX > 0) spriteRenderer.flipX = true;
//상태 설정
if (moveX != 0) isRun = true;
else isRun = false;
}
이동
rigidbody의 velocity를 사용해서 캐릭터의 이동을 구현했습니다.
미끄러짐 방지
유니티에서 벽과의 충돌을 방지하기 위해 Physics Material의 friction의 값을 0으로 조절해 마찰을 없앴더니,
경사면에서 미끄러지는 문제가 발생해 인풋값이 없을 때에는 rigidbody의 constraints의 값을 고정시켜두었습니다.
anim좌우방향설정
spriteRendere의 flipX를 사용해 구현했습니다.
하위클래스▼
위의 함수를 구현하는 부분만 가져왔습니다.
Input.GetAxisRaw("Horizontal2") 입력값을 매개변수로 받아옵니다.
그 외에 점프/ 앉기 관리 함수도 인풋값을 매개변수로 받습니다.
각 캐릭터별로 인풋값이 다르기 때문에 하위클래스에서 각각 구현해줍니다.
void Update()
{
if (!GameManager.Instance.isPaused
&& !GameManager.Instance.isClear
&& !GameManager.Instance.isDead)
{
Move(Input.GetAxisRaw("Horizontal2")); //여기서 입력을 받습니다.
Jump(Input.GetKeyDown(KeyCode.W));
Sit(Input.GetKey(KeyCode.S));
StateUpDown();
MoveAnim();
MoveSound(constant.PLAYER_P_AUDIO_SOURCE);
}
}
점프 관련 함수
역시 rigidbody의 velocity를 통해 구현했다.
이중점프방지
isGrounded라는 bool변수와 충돌을 통해 제어했습니다.
protected void Jump(bool key)
{
if (key && isGrounded)
{
isGrounded = false;
rigid2d.velocity = Vector2.up * jumpForce;
}
}
protected void OnCollisionEnter2D(Collision2D collision)
{
//죽을 때 처리
if (collision.gameObject.tag == "Obstacle")
{
GameManager.Instance.isDead = true;
GameManager.Instance.GameOver();
DieEffect();
}
//땅과 충돌하면 isGrounded 켜줌
else if (collision.gameObject.tag == "Ground")
{
isGrounded = true;
LandingEffect();
}
}
//땅과 충돌끝나면 isGrounded 꺼줌
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
isGrounded = false;
}
}
'Unity 포트폴리오 > 플젝3. 플랫포머 게임[브레인 멜트다운 모작]' 카테고리의 다른 글
컬러블록 구현 (Unity Project / BrainMeltdown 모작 / 플랫포머 2d게임) (0) | 2022.07.24 |
---|---|
플레이어의 벽면 충돌 방지_ Physics Material 2D(Unity 프로젝트 / 플랫포머 2d 게임) (0) | 2022.07.17 |
일방충돌 구현 / Platform Effector /윗 방향으로는 통과하고 아랫방향으로는 막히는 충돌(Unity Project / BrainMeltdown 모작 / 플랫포머 게임) (0) | 2022.07.14 |
개인 포트폴리오 - 브레인 멜트다운 모작 기획서 (0) | 2022.07.06 |