Unity - Week 05

이응민·2025년 2월 1일

Unity

목록 보기
5/12

종스크롤 2D 슈팅 게임 만들기(1)

먼저 배경화면 이미지를 만든다. 무한으로 맵의 배경화면이 계속해서 이동하는 것처럼 구현하기위해 이어지는 배경화면 두 개를 배치하고 기존에 보여지는 배경화면 1이 아래로 다 내려가면 다시 배경화면 2 위에 붙여서 계속 반복되도록해 맵이 무한으로 반복되는 것처럼 보이도록 한다.

그리고 배경화면이 아래로 내려가는 것을 구현하기 위해 스크립트를 작성한다.

배경화면을 스크롤하기 위한 스크립트 BackgroundScroll.cs 스크립트이다. 그리고 해당 스크립트 내에서 IBackgroundScroller 인터페이스를 선언한다. 이 인터페이스는 배경화면을 아래로 스크롤하는 Scroll, 배경화면을 다시 위로 붙이는 ResetPosition, 스크롤의 스피드를 정하는 SetScrollSpeed를 선언한다. 그리고 BackGroundScroll 클래스에서는 StartPos를 정해놓고 배경화면을 아래로 내리다가 화면 밖으로 벗어나면 다시 StartPos로 배경화면의 위치를 조정하는
함수를 선언한다. 그리고 ScrollManager 스크립트를 작성해서 스크롤할 배경화면을 지정해주고 배경화면들의 속도를 정해주고 스크롤을 한다.

먼저 List로 IBackgroundScroller 형식의 리스트를 선언해주고 IBackgroundScroller를 갖는 배경화면 두 개를 지정해준다. 그런데 유니티에서는 인터페이스를 찾는 함수가 없기 때문에 인터페이스를 찾아서 가져오는 InterfaceFinder를 정의한다.

여기서는 FindObjectdByType으로 MonoBehaviour타입의 오브젝트들을 찾는다. MonoBehaviour 은 모든 오브젝트가 갖고있기 때문에 사실상 모든 오브젝트들을 찾아서 배열에 넣는 것이다. 그리고 List를 템플릿으로 선언해서 인터페이스를 담을 수 있는 리스트를 만드는 것이다. 그래서 FindObjectOfInterface는 모든 오브젝트 중에서 원하는 인터페이스를 갖는 오브젝트를 찾아서 리스트에 넣어두는 것이다. 그래서 배경화면 오브젝트에 BackgroundScroll 스크립트를 넣어두면 IBackgrounInterface가 있기 때문에 ScrollManager에서 인터페이스로 가져와서 배경이미지를 스크롤한다.

스프라이트의 기본 이미지를 나눌 수 있다. Type을 선택해서 Automatic이나 Column 혹은 Row의 수로 나눌 수 있다. 그리고 나눈 스프라이트를 이용해서 애니메이션을 만든다.

메뉴에서 edit에서 project setting에서 Physics 2D 항목에서 Layer Collision Matrix를 통해서 오브젝트의 Layer를 설정해서 오브젝트 간의 충돌을 결정할 수 있다. Player와 Enemy의 Character Layer, 투사체의 Projectile Layer, 아이템의 PickupItems Layer를 정의해주고 서로 어떤 Layer들이 충돌하는지 설정해준다.

Character Layer는 Projectile Layer, Character Layer와 서로 충돌할 수 있게 해주고 PickupItems Layer는 PickupItems Layer끼리만 충돌하도록 설정한다. 그리고 Player의 Inspector에서 Layer를 Character로 설정한다.

그리고 Player character의 이동(movement)과 투사체 발사를 위한 무기(weapon)을 다루는 PlayerController 스크립트를 만든다. PlayerController에는 캐릭터 움직임을 대한 인터페이스 IMovement와 캐릭터 무기에 대한 인터페이스 IWeapon를 참조한다. 그래서 먼저 IMovement 인터페이스와 IWeapon 인터페이스를 만든다.

IMovement 인터페이스에서는 오브젝트가 움직이는지 여부를 결정하는 함수 SetEnable과 움직이는 방향을 정하는 함수 Move를 정의한다.

그리고 IMovement 인터페이스를 상속받는 PlayerMove 스크립트를 구현해서 Player 캐릭터의 이동을 구현한다. 그리고 PlayerMove 스크립트를 Player 오브젝트에 넣어준다. 이동 속도와 이동 가능한 범위를 설정하고 Move 함수를 구현한다. 그리고 PlayerController 스크립트에서 IMovement를 찾아서 movement에 저장하고 CustomUpdate 함수에서 Move 함수를 호출한다. 여기서 CustomUpdate 함수는 PlayerController를 제어하는 스크립트의 Update에서 호출해서 제어한다. 그 PlayerController 함수를 제어하는 스크립트가 GameManager 스크립트이다. GameManager 스크립트는 게임의 전반적인 흐름을 관리한다. 게임의 시작, 정지, 종료와 사운드 관리, 씬 관리, 에셋 로딩 입력시스템 등등을 제어한다. 이 스크립트는 싱글턴으로 구현한다. 싱글턴을 구현하기 위해서 Singleton 스크립트를 구현한다. Singleton 스크립트를 통해 싱글턴 패턴을 사용하는 클래스에서 Singleton 스크립트를 상속받아서 사용한다. Singleton은 씬이 변경되어도 인스턴스를 유지하는 싱글턴과 씬이 변경되면 인스턴스를 삭제하는 두 가지로 구현한다.

위 스크립트는 Singleton 스크립트로 이전에 예제에서 구현했던 것과 같다. 그리고 GameManager 클래스는 Singleton 클래스를 상속받는다. 그리고 GameManager는 싱글턴이므로 씬이 바뀌어도 그대로이다. 그래서 Awake를 이용해서 GameManager에서 필요한 오브젝트를 찾으려고하면 찾을 수 가 없다. 그래서 InitSceneLoad 함수에서 필요한 오브젝트들을 찾아서 씬이 바뀔때마다 호출해서 오브젝트를 찾는다. GameManager 함수에서는 GameStart라는 코루틴 함수를 정의해서 몇 초 동안 기다렸다가 Player 캐릭터가 움직이고 투사체를 발사하도록 한다. 이런 것들은 게임에서 많이 볼 수 있는데 예를 들어 레이싱 게임의 경우 게임이 시작되었을 때 3, 2, 1 카운트 다운을 하는 동안 이동을 못하게하면서 WaitForSeconds를 사용해서 기다렸다가 카운트 다운이 끝나면 움직임이 가능하도록 한다.

private PlayerController pc;

private void LoadSceneInit()
{
	pc = FindAnyObjectByType<PlayerController>();
}

IEnumerator GameStart() 
{
	yield return new WaitForSecond(1.0f);
    pc?.StartGame();
}

위 코드가 코루틴에서 1초 동안 기다렸다가 PlayerController의 StartGame 함수를 호출해서 Player 캐릭터가 움직일 수 있도록 한다. FindAnyObjectByType은 FindObjectsByType과 다르게 하나의 오브젝트만 찾는 것이다. 그리고 유저의 입력에 의해서 Player 캐릭터의 이동을 제어하기 위해서 입력을 받는 인터페이스 IInputHandle을 만들어준다.

public interface IInputHandle
{
    public Vector2 GetInput();
}

GetInput은 키보드로부터 입력을 받아서 Vector2로 저장한다. 이것은 KeyboardInputHandle 클래스에서 IInputHandle에서 상속받아 구현한다.

public class KeyboardInputHandle : MonoBehaviour, IInputHandle
{
    public Vector2 GetInput()
    {
        return new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
    }
}

그리고 KeyboardInputHandle 스크립트를 GameManager 오브젝트의 컴포넌트로 넣어주고 LoadInitScene에서 가져온다. 그리고 UPdate에서 PlayerController?.CustomUpdate(inputHandle.GetInput())으로 Player의 이동 방향을 바꿔준다. 그리고 플레이어의 투사체를 발사하는 무기를 구현하기 위해 인터페이스 IWeapon를 만들어준다.

이 인터페이스는 무기의 주인을 설정해주는 SetOwner 함수, 발사하는 Fire 함수, 폭탄을 발사하는 LaunchBomb 함수, 무기의 활성화를 설정해주는 SetEnable 함수를 정의한다. 그리고 PlayerWeapon에서 함수들을 구현해준다. 먼저 투사체가 발사될 곳을 지정해주고 projectile prefabs를 지정해준다. 그리고 bomb prefab도 지정해준다.

이 예제에서는 자동으로 게임이 시작되면 발사체가 발사되도록 구현했다. 그것이 아래의 Fire 함수이다.

public void Fire()
{
    if (Time.time < nextFireTime)
    {
        return;
    }

    if (isFiring)
    {
        nextFireTime = Time.time + fireRate;

        startAngle = -spreadAngle * (numOfProjectiles - 1) / 2;

        for (int i = 0; i < numOfProjectiles; i++)
        {
            angle = startAngle + spreadAngle * i;

            fireRotation = firePoint.rotation * Quaternion.Euler(0.0f, 0.0f, angle);
            fireDir = fireRotation * Vector2.up;

            ProjectileManager.Instance.FireProjectile(currentType, firePoint.position, 
            fireDir, gameObject, 1, 10.0f);
        }
    }
}


처음 발사 각도를 -10.0으로 정하고 5.0만큼 각도를 증가시켜서 총 5발을 흩뿌리면서 발사한다. 발사체는 지정된 방향으로 정해진 속도를 갖고 이동한다. 발사시킨 객체(Owner)의 적과 부딪혔을때 상대방에게 데미지를 전달하는 역할이다. 데미지도 설정해준다. 먼저 데미지를 받는 것을 구현하기 위해 인터페이스인 IDamaged를 작성한다.

public interface IDamaged
{
    void TakeDamage(GameObject attacker, int damage);
}

인자로 공격하는 캐릭터 attacker와 공격의 데미지량 damage를 받는 함수 TakeDamage 함수를 정의한다. 이 함수는 피격을 다룬다. 그리고 발사체에 대한 스크립트 Projectile 스크립트를 만든다. Projectile 스크립트에서 OnTriggerEnter2D 유니티 메시지를 이용해서 충돌을 다룬다. 먼저 발사하는 오브젝트나 그 오브젝트와 같은 Tag에 있는 오브젝트와 부딪히면 return으로 함수를 탈출한다. 그리고 DestroyArea에 닿으면 Projectile이 사라지게 한다. 그리고 collision으로 Projectile과 충돌한 오브젝트에 대한 정보를 받아오는데 그 오브젝트에 IDamaged가 있다면 TakeDamaged 함수에 Projectile의 소유자(Owner)와 데미지량을 인자로 호출한다. Projectile의 Tag와 Layer를 Projectile을 변경한다. ProjectileManager로 씬이 바뀌면 싱글턴으로 만들어준다. ProjectileManager은 오브젝트 풀의 역할을 한다. 먼저 Projectile prefab을 지정해주고 그 Projectile들을 큐로 Projectile을 미리 만들어서 저장하는 풀을 만든다. 이전 오브젝트 풀링에서 했던 것처럼 Allocate으로 Instantiate으로 특정 개수만큼 미리 만들고 큐에 저장한다. 그리고 GetProjectileFromPool 함수로 큐에서 Projectile을 빼서 사용하고 ReturnProjectileToPool 함수로 사라진 Projectile을 비활성화하고 큐에 넣는다. 그리고 투사체를 발사하는 FireProjectile 함수를 선언한다.

이제 PlayerController에 IWeapon을 추가한다. 그리고 StartGame과 StopGame 함수를 선언해서 각각 게임이 시작되었을때 movement와 weapon의 Enable을 true로 설정하거나 게임이 종료되었을때 Enable을 false로 설정해 게임에서의 Player의 움직임과 무기를 제어한다.

  • Asset 출처 : 고박사의 유니티 노트

0개의 댓글