두 번째 미니게임 블록 깨기 개발 일지 4

- 총 개발 시간 20시간 25분
- 목숨 시스템
- 목숨 깎일 때 새로운 공 추가 기능
- 게임 종료와 다시하기 기능
- 공, 하트 아이템 기능
- 단단, 투명 블록 기능
목숨 시스템
현재 존재하는 공이 없다면 목숨이 깎이고 새로운 공이 추가되고, 모든 목숨을 소모하는 경우 게임 오버가 된다.
게임 화면 하단에 Finish 벽을 새로 만들고 공이 이 벽과 만나면 over되는 것으로 간주한다.
// BallController.cs
// 공이 아래로 떨어졌을 때 이벤트
[HideInInspector] public UnityEvent OnBallOvered;
// 아래 벽과 부딪혔을 때 이벤트가 실행되고 공 삭제
else if (collision.gameObject.CompareTag("Finish"))
{
OnBallOvered.Invoke();
direction = Vector2.zero;
Destroy(gameObject);
}
// GameManager.cs
// 현재 목숨과 공의 수
private int currentLife = 3;
private int currentBalls = 1;
// 공이 아래로 떨어졌을 때 이벤트 연결
private void Awake()
{
BaseBallController.OnBallOvered.AddListener(() => CheckGameOver());
}
private void CheckGameOver()
{
if(--currentBalls <= 0)
{
UpdateLifeUI(--currentLife);
if (currentLife < 0)
{
GameOver();
}
else
{
AddBaseBall();
}
}
}
새로운 공은 패들 가운데에 생성하고 이벤트를 연결해준다.
또한 공끼리는 충돌을 피하기 위해 Ball Layer를 추가 후 Layer Collision Matrix에서 서로 충돌 체크를 해제하였다.
private void AddBaseBall()
{
currentBalls++;
GameObject ball = Instantiate(Ball, new Vector3(PaddleController.GetPaddleXPosition(), -6.861f, 0), Quaternion.identity, BallContainer);
BallController ballController = ball.GetComponent<BallController>();
ballController.OnPointScored.AddListener(() => UpdateUI());
ballController.OnBallOvered.AddListener(() => CheckGameOver());
}
게임 종료와 다시하기 기능
게임 종료의 경우 UI가 등장하고 패들의 움직임을 막기 위해 PaddleController에서 bool 변수 CanMove를 추가해주었다.
다시하는 경우 UI가 사라지고, 블록과 공, 게임에서 사용하는 변수들을 초기화 해준다.
private void GameOver()
{
GameOverUI.SetActive(true);
StopAllCoroutines();
PaddleController.CanMove = false;
TotalScoreText.text = score.ToString();
}
private void Retry()
{
GameOverUI.SetActive(false);
InitializeBricks(initialSpawnY);
PaddleController.CanMove = true;
activeBrickCount = rowMaxCount * columnMaxCount;
currentLife = 3;
currentBalls = 0;
score = 0;
CurrentScoreText.text = score.ToString();
UpdateLifeUI(currentLife);
AddBaseBall();
}
공, 하트 아이템 기능
공과 하트 아이템의 경우 GameManager에서 적용되어야 하기 때문에 PaddleController에서 이벤트를 추가했다.
// PaddleController.cs
[HideInInspector] public UnityEvent OnHeartGained;
[HideInInspector] public UnityEvent OnBallGained;
private void OnTriggerEnter2D(Collider2D collision)
{
var Item = collision.gameObject.GetComponent<Item>();
if (Item != null)
{
switch (Item.ItemType)
{
case ItemType.Ball:
OnBallGained.Invoke();
break;
case ItemType.Heart:
OnHeartGained.Invoke();
break;
// 이하 생략
}
공 아이템을 얻을 때 이벤트는 새로운 공 추가 함수와 연결하고, 하트 아이템을 얻을 때 이벤트는 목숨을 추가하고 UI를 업데이트 해준다.
// GameManager.cs
private void Awake()
{
PaddleController.OnHeartGained.AddListener(() => UpdateLifeUI(++currentLife));
PaddleController.OnBallGained.AddListener(() => AddBaseBall());
}
private void UpdateLifeUI(int life)
{
if(life > 3)
{
life = 3;
}
if(life < 0)
{
life = 0;
}
for (int i=0; i<life; i++)
{
Lifes[i].SetActive(true);
}
for(int i=life; i<Lifes.Capacity; i++)
{
Lifes[i].SetActive(false);
}
}
단단, 투명 블록 기능
단단 블록과 투명 블록은 아이템 블록과 구분이 필요할거 같아 새로운 class를 만들었다.
아래는 간단하게 Brick class에 대한 UML 다이어그램이다.
SpecialBrick은 공의 충돌이 두 번 이상 필요한 블록이다.
HardBrick의 경우 공이 처음 충돌하면 껍데기가 벗겨지고, 두번 째 충돌해야 블록이 사라진다.
TransparentBrick의 경우 공이 처음 통과하면 블록이 생긴 후 충돌해야 블록이 사라진다.
Brick의 OnBrickDestroyed()는 점수를 얻을 수 있는 상태고, 블록이 사라지도록 함수를 작성해두었다.
SpecialBrick에서 OnBrickDestroyed()가 실행될 때 처음 충돌인지 나타내는 IsFirst를 통해 충돌 횟수를 구분한다.
true인 경우(첫 충돌), IsFirst를 false로 변경하고 SetSecondLevel()함수를 실행시켜 첫 번째 충돌 후 변화를 적용한다.
false인 경우(두번째 충돌), Brick의 OnBrickDestroyed()를 실행시킨다.
public class Brick : MonoBehaviour
{
[HideInInspector] public bool CanPointed = false;
public virtual void OnBrickDestroyed()
{
CanPointed = true;
Destroy(transform.gameObject);
}
}
public class SpecialBrick : Brick
{
[HideInInspector] public bool IsFirst = true;
public virtual void SetSecondLevel() { }
public override void OnBrickDestroyed()
{
if(IsFirst)
{
IsFirst = false;
SetSecondLevel();
}
else
{
base.OnBrickDestroyed();
}
}
}
그러면 공이 블록과 충돌했을 때 Brick의 OnBrickDestroyed를 실행시키고 점수를 얻을 수 있는 상태일 때만 점수를 올린다.
SpecialBrick의 경우 점수를 얻을 수 있는 상태는 두번째 충돌임을 의미하게 된다.
// BallController.cs
if (collision.gameObject.CompareTag("Brick"))
{
var brickObject = collision.gameObject;
var brick = brickObject.GetComponent<Brick>();
if(brick != null)
{
brick.OnBrickDestroyed();
if (brick.CanPointed)
{
OnPointScored.Invoke();
}
}
}
HarkBrick은 첫 충돌 후 단순히 껍데기 object만 비활성화해주면 된다.
public class HardBrick : SpecialBrick
{
public override void SetSecondLevel()
{
subSprite.gameObject.SetActive(false);
}
}
TransparentBrick은 공이 지나갈 수 있도록 해야하기 때문에 초기에 collider의 isTrigger가 true로 되어있다.
그래서 첫 충돌 후에는 isTrigger를 false로 하고 투명 블록 이미지를 기본 블록 이미지로 변경시켜준다.
또한 첫 출돌 후 실행되어야 하기 때문에 OnTriggerExit2D에서 SetSecondLevel를 실행시켜준다.
public class TransparentBrick : SpecialBrick
{
public Sprite BrickImage;
public Collider2D BrickCollider;
public override void SetSecondLevel()
{
mainSprite.sprite = BrickImage;
BrickCollider.isTrigger = false;
IsFirst = false;
}
private void OnTriggerExit2D(Collider2D collision)
{
SetSecondLevel();
}
}
두번 째 게임 개발 완료 후기
드디어 두번째 게임을 완성했다! 확실히 저번 게임보다 개발할 것이 더 많아서 개발 시간도 더 걸렸다.
기획과 설계를 하지 않고 기능 하나하나 붙이면서 개발하다 보니까 코드들이 깔끔하지 않은거 같다.
GameManager와 Controller의 역할을 제대로 나누어진건지 잘 모르겠다.
그래서 특히나 디자인 패턴의 필요성을 여실히 느끼게 되었다....!
디자인 패턴에 대해 공부하고 나서 적용할 수 있는 부분은 수정해야겠다.
2D 게임을 두 개 만들었으니 다음 게임은 3D로 디자인 패턴을 적용하여 설계 후 개발해봐야겠다.
