본문 바로가기
프로젝트

[1인 게임 개발]Cannon vs Cannon

by 블로그별명 2024. 10. 7.

Cannon vs Cannon은 제가 두번째로 개발한 게임입니다. 전작 개발중 느낀 아쉬웠던 부분 이번게임에서 개선하려 노력했습니다.

  • 게임 전체적으로 이벤트를 적극적으로 활용했습니다.
  • 원씬으로 구성했습니다.
  • 게임디자인을 전작보다 세련되게 만드려고 노력했습니다.
  • 게임 점수에 따라 얻을수있는 콜렉션을 만들어 추가적인 콘텐츠를 만들었습니다.

추가적으로 외부 라이브러리도 이게임을 만들며 처음 써봤습니다(Dotween). 이번 게임도 전작과 같은 하이퍼 캐쥬얼 게임입니다. 프로젝트는 8월 20일에 시작되어 9월 13에 끝났습니다.

 

 

코드: https://github.com/hangilzzang/Canon-VS-Cannon

게임링크(플레이 스토어): https://play.google.com/store/apps/details?id=com.cannonvscannon.gilgames

 

Cannon vs Cannon - Google Play 앱

적의 포탄을 격추하세요

play.google.com

 


 

실제 게임 제작에 들어가기전에 기획단계에서 게임 방식, 각 장면의 콘셉트 아트 등등을 미리 정했습니다. 물론 개발 도중에 수정되거나 추가된 내용도 있었지만 전체적인 흐름 기획단계에서 미리 정했기때문에 개발 시간 단축에 도움이 되었던것같습니다. 아래는 기획 내용입니다. 


게임 

  • 게임이름: Cannon VS Cannon
  • 화면방향: Landscape
  • 장르: 하이퍼 캐쥬얼
  • 그래픽: 2D
  • 플랫폼: 모바일

 

성을 향해 날아오는 적의 대포알을 향해 알맞은 타이밍에 대포알을 쏴 격추시키는게임.

격추하면 1점 추가, 격추하지못하면 플레이어의 성이 파괴되며 게임 오버.

상대방의 대포알이 날아올때, 격추기회는 단 한번뿐이다.

 

 

디자인

다자인 스타일은 플랫디자인으로 결정했습니다. 제가 게임 디자인과 ui에 있어서 많이 참고하는 Ketchapp 게임사의 디자인 스타일이기도 합니다. 단색의 각진 도형으로 구성된 디자인이지만 깔끔한 느낌을 줍니다. 그림은 Inkscape을 이용해 그렸습니다.

 

격추

격추시 두 포탄 물리충돌 ⇒ 격추시 두포탄 모두 사라짐

처음에는 두 포탄이 물리적으로 충돌하도록하여, 플레이어가 정밀하게 타격하지않으면, 대포알이 계속 성으로 날아가는 가능성이 있도록 기획했습니다.

그러나 직접 프로토타입을 만들어 플레이해보니, 이는 오히려 부정적인 경험을 주었고, 뿐만아니라 충돌후 천천히 떨어져, 뒤이어 날아오는 공격을 방해하기도했습니다.

그래서 두포탄이 충돌후 빠르게 떨어지도록 중력 스케일을 조절했지만 여전히 문제는 해결되지않았습니다.

그래서 포탄이 충돌하면 둘다 사라지도록 바꿨더니, 플레이 경험히 훨씬 개선되었습니다.

 

대포

중앙에 배치, 지속적으로 변하는 발사각 ⇒ 왼쪽에 배치, 90도로 고정된 발사각도

지속적으로 변하는 발사각이 생동감 넘치는 게임을 만들어준다고 생각했으나 실제로 프로토타입을 만들어 플레이해보니, 상황에 따라 주어진 반응시간이 천차만별이었고 이는 부정적 경험으로 이어졌습니다.

따라서 왼쪽에 대포를 배치후 발사각도를 90도로 고정하여 불필요한 운적인 요소를 없앴습니다.

 

발사각도가 변하는 대포

 

발사각도가 고정된 대포

 

메인화면

 

  • 우측상단 최고기록 표시 ⇒ 중앙중단 최고기록 표시(UI 정렬위함)
  • tap to start: 깜빡거리는 애니메이션
  • 천천히 이동하는 구름 애니메이션
  • 평화로운 bgm 재생
  • 성은 적 대포알에 맞으면 부셔진다.
  • 메인화면 전에 로고씬 추가

게임화면

메인화면 ⇒ 게임화면 전환시

  • 빨간 깃발이 올라감
  • 대포가 직각으로 세워짐
  • 전쟁 나팔 소리 1회 재생

게임화면

  • 일시정지 ui 팝업
  • 대포알 날라가는 이펙트 Trail Renderer로 구현
  • 대포 발사시 도넛 구름 이펙트
  • 천천히 이동하는 구름 애니메이션
  • 전쟁 bgm 재생
  • 대포 격추시 이펙트(미정)
  • 격추시 점수가 1증가
  • 게임중 최고기록 달성시 음성으로 알려줌(new high score!)

 

게임오버

게임화면 ⇒ 게임오버화면 전환시

  • 성쪽으로 화면이동후 성 부셔지는 이펙트(상세구현 미정)

 

게임오버 화면

  • 게이지가 0이 될때까지 부활하지 않을시 자동으로 메인화면으로 이동
  • 전쟁 bgm 점점 작아짐
  • 광고 UI 커졌다 작아졌다하며 터치 유도
  • 광고 시청시 부셔진 성이 다시 결합되는(마치 시간을 되감는듯한 이펙트)가 연출된뒤 이어서 시작

 

비지니스 모델

  • 전면광고 매판이 끝나고 일정확률로 팝업
  • 리워드 광고 광고시청시 1회 부활, 게임오버를 무효화하고 게임 추가 진행가능

 

 

메인씬 구현

 

 

깜박거리는 텍스트

딱히 어려운점은 없었습니다. WaitForSeconds 사용법과 StartCoroutine 사용법을 익혔습니다. 텍스트의 깜박이는 주기는 모바일 게임 Mr gun의 주기를 참고했습니다.

 

 

떠다니는 구름

딱히 어려운점은 없었습니다. 구름이 계속 동쪽으로 이동하되, 오른쪽 시야 밖으로 사라지게 되면 왼쪽 시야 밖으로 포지션을 옮겨주는 식으로 구현했습니다.

 

 

날아오는 적의 대포알을 맞았을때 충격에 의해 자연스럽게 부셔지는 느낌을 원했습니다. 따라서 성을 처음부터 작은 블럭 여러개로 구성하여 충격에 쉽게 부셔지도록 할 예정입니다.

처음에는 단순히 성을 모두 같은크기의 조각으로 slice후 쌓았습니다.(모두 수작업으로 쌓았습니다 이때는 에디터 스크립트의 존재를 몰랐습니다.) 그랬더니 성의 양쪽끝부분이 중력의 영향을 받아 기울었습니다. 성을 벽돌형태로 자른후 엇갈리게 배치했더니 문제가 해결되었을뿐만아니라, 보기에도 훨씬 좋아졌습니다.

 

 

 

평화로운 bgm

bgm을 작곡했는데 컴퓨터로 들었을때의 소리와 제 핸드폰에서 들었을때의 소리가 달랐습니다.

모바일기기에서는 최고음량으로 설정했을때 드럼소리는 너무 크게 들려 거슬렸고 이에 불구하고 낮은 음역대의 멜로디라인은 거의 들리지않았죠.

모바일 기기 스피커의 문제라고 확신하게된 계기는 모바일기기에서 블루투스 헤드셋을통해 bgm을 들었을때는 멜로디라인이 선명하게 들렸기때문입니다. 즉 컴퓨터에서 모바일기기로의 빌드가 잘못된것이 아니라는것이지요.

그래도 최종적으로 드럼소리를 약간 줄이는것으로 평화로운 bgm작업을 마무리했습니다.

 

 

게임 장면

 

 

Touch To Start

  1. 클릭(터치)이벤트를 만들었습니다.
  2. 게임 상태를 명시하는 열거형을 만들었습니다.
  3. GameStart가 클릭 이벤트를 구독합니다.

GameStart매서드는 메인화면에서 클릭입력이 들어왔을때 작동하게 됩니다.

// TouchToStart.cs
public class TouchToStart : MonoBehaviour
{
    void Start()
    {
        EventManager.instance.MouseDownEvent += GameStart; // 이벤트 등록
    }

    void OnDisable()
    {
        EventManager.instance.MouseDownEvent -= GameStart; // 이벤트 구독 해제 (필수)
    }

    void GameStart()
    {
        if (GameManager.instance.gameState == GameManager.GameState.Main)
        {
            GameManager.instance.gameState = GameManager.GameState.Ready; // 상태변경
            EventManager.instance.TriggerStateChanged(); // 상태변경 이벤트 트리거
            gameObject.SetActive(false); // 메인화면 ui 제거
        }
    }
}

 

 

게임 준비 단계 동작

Ready로 상태변화가 일어난후 이벤트 핸들러인 **GameReady_**가 실행됩니다.

GameReady_는 터치 입력 발생이후 실행해야할 동작의 리스트를 StartCoroutine을 통해 순서대로 이행합니다.

// PlayerCannon.cs
void Start()    // 이벤트 등록
{
    EventManager.instance.StateChangeEvent += GameReady_;
}
void OnDisable() // 이벤트 해제
{
    EventManager.instance.StateChangeEvent -= GameReady_;
}

void GameReady_()
{
    if (GameManager.instance.gameState == GameManager.GameState.Ready)
    {
        StartCoroutine(GameReady());
    }
}

IEnumerator GameReady()
{
    if (GameManager.instance.gameState == GameManager.GameState.Ready)
    {
        bgm.Pause();
        bgm.clip = warBGM;
        warHorn.Play(); // 전쟁 나팔 소리 재생
        flag.SetActive(true); // 깃발 떨어짐
        yield return new WaitWhile(() => warHorn.isPlaying); // 소리 재생이 끝날 때까지 대기
        bgm.Play(); // 전쟁 bgm 재생
        cannonFrame.Play(); // 발사각도 조절 효과음
        yield return StartCoroutine(RotateCannon()); // 대포 각도조절
        cannonFrame.Pause();
        GameManager.instance.gameState = GameManager.GameState.Game; // 상태변경
    }
}

 

 

대포발사

  • 대포발사시 대포 소리
  • 대포발사시 연기 효과(연기 투명도, 위치, 스케일 조절)
  • tail render를 이용한 대포알에 궤적추가
  • 대포 발사시 반동 효과

대포 발사시 위의 4가지 효과를 추가적으로 구현했습니다. 반동효과는 원래 기획에는 없던것이나, 대포발사효과가 밋밋한것같은 느낌이 들어 추가해주었습니다.

tail renderer 컴포넌트를 통해 궤적효과를 추가하는 부분에서 약간의 문제가 있었습니다. 게임실행시 궤적이 보이지않는 문제였습니다. 구글링을 통해 문제를 해결했습니다.

대포를 연속으로 발사하지못하도록 제한했습니다. 대포의 재장전 시간에 대포를 클릭했을때의 틱틱 효과음을 추가했습니다.(생각보다 별로라 제거됨)

 

 

적의 공격

적 공격시 대포 소리를 출력합니다.

 

 

다양한 충돌 궤적

  • 씬 윗쪽에 트리거 콜라이더를 만들어 씬 밖으로 빠져나가는 대포알을 삭제하도록 했습니다.
  • 대포알의 Angular Drag 수치를 올려 땅에 떨어진공의 움직임을 제한했습니다.

충돌 궤적같은경우

  • 궤적이 게임화면을 벗어나지않는다
  • 플레이어가 격추하지못할시 성을 공격한다
  • 플레이어 대포가 격추할수있는 위치로 날아온다.

총 3가지 조건을 만족 하는 궤적을 직접 인게임에서 실험해보며 찾았습니다.

public class EmemyAttackTest : MonoBehaviour
{
    public GameObject cannonballPrefab;
    Rigidbody2D rb;
    public Vector2 launchPoint = new Vector2(15,-3f);
    public float forceAmount = 16f;
    public float fireAngle = 50f; // 왼쪽방향 기준

    void Update()
    {
        if (Input.GetMouseButtonDown(1))
        {
            LaunchEnemyCannonBall();
        }
    }


    void LaunchEnemyCannonBall()
    {
        float angle = (-(fireAngle - 90) + 90f) * Mathf.Deg2Rad;
        Vector2 forceDirection = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));

        GameObject cannonball = Instantiate(cannonballPrefab, launchPoint, Quaternion.identity);
        rb = cannonball.GetComponent<Rigidbody2D>();
        rb.AddForce(forceDirection * forceAmount, ForceMode2D.Impulse);
    }
}

 

이제 이 궤적을 인게임에 어떻게 활용할것인지 결정해야 합니다

  • 모든 궤적중에 하나의 궤적이 랜덤하게 발사되는것
  • 게임이 지속될수록 점점 난이도(대포알 속도기준)가 높은 궤적들이 나오는것

첫번째 방식과 두번째 방식을 모두 구현해 테스트해본결과 점점 난이도가 높아지는 방식이 더 좋아 채택했습니다.

 

 

게임오버

  1. 게임오버 이벤트를 만들었습니다.
  2. 대포알이 성벽돌 콜라이더에 닿으면 게임오버 이벤트를 트리거합니다.
  3. 게임오버 이벤트가 실행되면 적의 대포는 더이상 발사되지않습니다. 그리고 GameOver_ 메서드가 실행됩니다
// GameOver.cs
  void GameOver_()
  {
      GameManager.instance.gameState = GameManager.GameState.NoRevive; // 상태변경
      rockBreak.Play();
      // 카메라 이동
      StartCoroutine(MoveCameraToPosition());
      
      if (canRevive) // 부활 ui팝업
      {       
          reviveUI.SetActive(true);
          canRevive = false;
      }
      else // 게임종료
      {
          EventManager.instance.TriggerStateChanged();
      }
  }

 

부활은 1회만 가능합니다. 부활할수없다면 일반적인 게임 종료 화면이 재생됩니다.

 

 

부활UI

// ReviveUI.cs
void Start()
{
    // 타임스케일 설정
    Time.timeScale = 0.2f;
    Time.fixedDeltaTime = 0.01f * Time.timeScale;

    Color imageColor = image.color;
    image.color = new Color(imageColor.r, imageColor.g, imageColor.b, 0.83f); 

    StartCoroutine(DecreaseSlider());
    StartCoroutine(ScaleUpAndDown());
}

IEnumerator DecreaseSlider()
{
    while (slider.value > 0)
    {
        // Time.unscaledDeltaTime을 사용해 타임스케일의 영향을 받지 않도록 설정
        slider.value -= decreaseSpeed * Time.unscaledDeltaTime;
        yield return null;
    }

    GameManager.instance.gameState = GameManager.GameState.NoRevive; // 상태변경
    EventManager.instance.TriggerStateChanged();
}
  • 살짝 느려진 시간 부활 ui가 팝업되는 동안 게임시간이 느리게 흘러가도록 만들어 현재 잠시 현실에서 벗어나 특별한 선택중이라는 느낌을 주고자 했습니다.
  • 약간 어두워진 화면 게임화면은 살짝 어두워져 부활 UI에 시선이 집중될수있도록 했습니다.
  • 슬라이더 애니메이션 남은 광고 ui팝업시간을 점점줄어드는 슬라이더를 통해 사용자가 확인할수있게하여 플레이어의 부정적 경험을 해소하고자했습니다.
  • 커졌다 작아졌다하는 광고 ui 광고아이콘은 커졌다 작아졌다를 반복하며 시선을 유도합니다.

 

부활X

부활 ui에서 제한시간동안 광고시청을 선택하지않은경우, 그리고 일반적으로 게임오버됐을때의 동작입니다.

  • 밝아진 화면
  • 부활 ui 제거
  • 내려왔다 올라가는 점수 UI
  • 페이드아웃하며 씬 재시작

내려왔다 올라가는 점수 UI의 경우 Rider의 연출을 따라했습니다. 해당 연출의 핵심은 물체가 가속도 운동을한다는것이었습니다. 등속도 운동을 구현하는것은 간단하지만 가속도 운동을 구현은 어떻게 해야할지 감이 잘 안잡혔습니다.

처음에는 점수 ui에 rigidbody 컴포넌트 할당후 add force를통해 중력에 의해 자연스럽게 아래로 내려온뒤 위로 마저 올라가는 모습을 구현하려 했지만 ui오브젝트에 addforce가 적용되지않았습니다.

구글링을 해본결과 DOTween이라는 라이브러리를 사용하면 ui의 가속도 운동을 구현할수있다는것을 알게되었고 이를통해 구현할수있었습니다.

 

 

부활

광고아이콘의 버튼 컴포넌트 클릭시 부활합니다.

  1. 맵에 존재하는 모든 대포알을 없앱니다(이벤트 이용)
  2. 부셔진 기존 성 게임오브젝트를 비활성화하고 스페어 성을 활성화시킵니다.
  3. 부셔진 성을 향해 있던 메인카메라를 다시 원래 자래로 복귀시킵니다.
  4. 다시 적대포와 플레이어 대포를 활성화 시킵니다.(이벤트 이용)

 

 

추가 구현

초기 기획한것들을 모두 구현한이후에 더욱더 완성도 있는 게임을 위해 몇가지 추가작업을 진행했습니다. 

 

 

점수관련

  • ui를 변경하였습니다 이제 현재기록과 최고기록이 함께 표시됩니다.
  • 최고기록 달성시 new record 텍스트가 커졌다 작아졌다를 반복합니다.

 

수집시스템

  • 플레이어 대포에서 날아가는 대포알을 커스텀 할수있습니다. 대포를 터치하면 내 수집품을 확인할수있습니다.
  • 대포알 꾸미기 아이템은 특정 점수 이상을 달성할때 해금됩니다.
  • 수집품에서 선택한 항목은 게임을 재시작해도 유지되도록 PlayerPrefs를 활용해 데이터로 저장했습니다.
  • 공을 선택하는 소리와 콜렉션 창에서 나가고 들어오는 효과음을 적용했습니다.

 

튜토리얼

 

이전작에서 실제 사용자로부터 튜토리얼이 있었으면 좋겠다는 피드백을 받은적이 있었습니다. 간단한게임이지만 처음사용자(최고기록이 0점인경우)의 게임 이해를 돕기위해 튜토리얼을 추가했습니다.

 

 

광고삽입

이제 출시전 마지막으로 광고를 넣어보려합니다.

확장성과 테스트의 용이성을 고려해 기존 버전과 광고가 적용된 버전간의 전환이 자유롭게 될수있도록 구현했습니다.(확장성을 위해)

 

리워드광고

부활광고의 로드는 씬이 새로 시작할때마다 한번씩 이루어집니다. 만약 이미 로드된 광고가 있다면 새롭게 광고를 로드하지 않습니다.

부활 UI팝업조건

부활 UI팝업조건이 조금더 복잡해졌습니다.

  • 팝업조건
    1. 첫번째로 죽었으며, 광고X 버전인경우
    2. 첫번째로 죽었으며, 광고O 버전이면서 광고가 로드된경우
  • 팝업되지않는 조건
    1. 두번째로 죽은경우
    2. 첫번째로 죽었으며, 광고O 버전이지만 광고가 로드되지않은경우

광고 아이콘 터치X

광고 아이콘을 클릭하지않은경우 그대로 게임오버가 진행됩니다

광고 아이콘 터치O

  • 광고X 버전 광고 없이 바로 부활
  • 리워드 광고 시청완료 부활
  • 리워드 광고 시청완료X 게임 오버 진행

전면광고

  • 광고X 버전 기존과 동일
  • 광고O 버전 1/3의 확률로 게임종료시 광고 팝업

'프로젝트' 카테고리의 다른 글

[1인 게임 개발]Baseball Swing!  (0) 2024.10.07

댓글