공부/Unity

[Unity] 갤러그 만들기 - 1 (Coroutine, ScriptableObject, Instantiate, Destroy)

굴러다니다니 2023. 4. 5. 16:31
728x90

오늘은 갤러그 만들기!

구현 내용: 배경화면 무한 스크롤, 플레이어 키 입력으로 이동, Coroutine을 이용한 총알 생성, 플레이어 맵 내에서만 이동

 

2D로 시작하고, 10:16 사이즈로 해상도 설정을 해줍니다.

 

background 두 개를 unirun에서 했던 방식처럼 이어 붙여서 나중에 Loop 스크립트를 짜서 붙여주면 될거고요

Player를 저렇게 넣어주기 위해서

3개가 붙어있는 Player를 multiple로 바꿔서 Sprite Editor에서 잘라줍시다 컷컷

 

곱게 자르기

 

움직이는 모든 친구들을 위해 스크립트를 짜줍시다.

Movement2D로 작성

 

public class Movement2D : MonoBehaviour
{
    public float moveSpeed = 5f;
    [SerializeField] private Vector3 moveDirection = Vector3.zero;

    void Update()
    {
        transform.position += moveDirection * moveSpeed * Time.deltaTime;
    }
    public void MoveTo(Vector3 direction)
    {
        moveDirection = direction;
    }
}

moveSpeed는 5f로 초기값을 대충 잡아줬습니다 추후에 수정 가능하게 public으로,

Vector3의 움직이는 방향 초기에는 Vector3.zero로 해서 0, 0, 0 움직이지 않습니다

Update문을 통해 현재 position에서 moveDirection방향으로 speed만큼 움직입니다.

MoveTo 함수는 Direction 변수를 받아 moveDirection으로 연결합니다.

 

배경화면 Background도 이동해야하니 y축으로 -1 방향으로 5 스피드로 이동하게 하면 알아서 잘 돌아갑니다 얍삐

플레이어도 이를 사용할거라 movement 2D 넣어주고 방향은 정해주지 않으면 됩니다.

 

씬의 크기를 받고 이를 제어하기 위한 스크립트를 작성해봅시다.

Stage_data

 

[CreateAssetMenu] //에셋으로 만들기 위한 방법
public class Stage_Data : ScriptableObject
{
    [SerializeField] private Vector2 limitMin;
    [SerializeField] private Vector2 limitMax;

    //public Vector2 LimitMin => limitMin;
    //public Vector2 LimitMax => limitMax;
    public Vector2 LimitMin
    {
        get
        {
            return limitMin;
        }
    }
    public Vector2 LimitMax
    {
        get
        {
            return limitMax;
        }
    }
}

Stage data를 Asset으로 만들기 위해 맨 위에 CreateAssetMenu 키워드를 붙여주고 상속받던 Monobehavior을 지우고 ScriptableObject로 써줍시다.

Vector2로 limitMin, limitMax를 선언해서 limitmin값을 받아서 넣고, limitmax를 받아서 넣는 뭐 그런 간단한 함수를 씁니다.

참고로 저 긴 줄들의 코드를 public Vector2 LimitMin => limitMin으로 줄여쓸 수 있다고 합니다.

 

이제 Create를 누르면 Stage_Data라고 새로 뜨는데, 이 친구를 건들여봅시다.

 

 

요상하게 생긴 아이콘을 달고 나왔는데, 맵의 왼쪽 아래 꼭짓점의 x, y값을 Min으로

오른쪽 상단의 꼭짓점의 좌표를 Max값으로 넣어줍시다.

 

총알이 발사되는 시간을 주고 제어해봅시다.

Weapon이라는 script를 만들어 이렇게 설정해줍니다.

공격하는 시간과 불렛을 선언해줍니다.

 

public class Weapon : MonoBehaviour
{
    [SerializeField] private GameObject Player_bullet;
    [SerializeField] private float Attack_Rate = 0.1f;

    public void TryAttack()
    {
        Instantiate(Player_bullet, transform.position, Quaternion.identity);
    }
}

공격을 시도하면 Bullet을 player가 있는 위치에 만들고, 회전은 안하니깐 Quaternion.identity로 설정하면되는데

이걸 발사하고 플레이어를 이동시키기 위해 Player Control 스크립트를 만들어서 내용을 다음과 같이 넣어봅시다.

 

public class PlayerControl : MonoBehaviour
{
    private Movement2D movement2D;
    [SerializeField] private Stage_Data stagedata;
    [SerializeField] private Weapon weapon;

    private void Awake()
    {
        movement2D = transform.GetComponent<Movement2D>();
        weapon = transform.GetComponent<Weapon>();
    }

    void Start()
    {
        if (movement2D.moveSpeed <= 0f)
        {
            movement2D.moveSpeed = 5f;
        }
    }

    void Update()
    {
        float x = Input.GetAxisRaw("Horizontal");
        float y = Input.GetAxisRaw("Vertical");
        movement2D.MoveTo(new Vector3(x, y, 0f));

        if (Input.GetKeyDown(KeyCode.Space))
        {
            weapon.tryAttack();
        }
    }
    private void LateUpdate()
    {
        transform.position = new Vector3(
            Mathf.Clamp(transform.position.x, stagedata.LimitMin.x, stagedata.LimitMax.x),
            Mathf.Clamp(transform.position.y, stagedata.LimitMin.y, stagedata.LimitMax.y),
            0f
            );

    }

}

Awake로 값을 받아 초기화를 시켜주고, Start했을 때 혹시나 speed값이 초기화가 안 된 친구가 있다면 5f로 바꿔줍니다.

Update문을 통해서 이동을 시켜줄건데, key input을 이용하지 않고 Unity에서 기본 제공하는 Horizontal, Vertical 기능을 이용해 x, y값에 저장해두고 movement2D 스크립트에 쓴 MoveTo에 넘겨줘 이동해줍니다.

space바를 누른다면 tryAttack 함수를 실행하고, Update문이 끝난 후 LateUpdate문으로 맵 밖으로 이동이 불가능하게 만들어줍니다.

Mathf.Clamp(변수, 최소, 최대) 최소부터 최대 사이로 변수값을 지정한다.

 

이렇게 만들고 실행한다면 스페이스바를 꾹 누르든 한 번 누르든 딱 한번만 발사가 되는데, 이 상황을 코루틴을 사용해 해결해보려고 합니다.

아까 만든 Weapon 스크립트를 다시 뜯어 아래의 코드를 추가해줍시다.

 

    private IEnumerator TryAttack_co()
    {
        while (true)
        {
            Instantiate(Player_bullet, transform.position, Quaternion.identity);
            yield return new WaitForSeconds(Attack_Rate);
        }
    }

    public void startFire()
    {
        StartCoroutine("TryAttack_co");
    }
    
    public void stopFire()
    {
        StopCoroutine("TryAttack_co");
    }

Tryattack을 코루틴으로 만들어 실행하기 위한 코드입니다.

while(true)일 동안 반복해서 bullet을 생성하고, 유니티에게 다시 주도권을 넘겨줍니다. Rate만큼의 시간을 기다리면서!

 

또한 startFire로 코루틴 시작문을, stopFire로 코루틴 종료문을 만들어 다시 Player Control을 수정해줍시다.

 

 void Update()
    {
        float x = Input.GetAxisRaw("Horizontal");
        float y = Input.GetAxisRaw("Vertical");
        movement2D.MoveTo(new Vector3(x, y, 0f));

        if (Input.GetKeyDown(KeyCode.Space))
        {
            weapon.startFire();
        }
        if (Input.GetKeyUp(KeyCode.Space))
        {
            weapon.stopFire();
        }
    }

player Control의 update문을 위와 같이 수정한다면 누를때부터 뗄 때 까지 총알을 계속 0.1초 간격으로 생산하는 모습을 확인할 수 있습니다!

(bullet을 프리팹화해서 컴포넌트에 끌어서 연결해줘서 gameobject에 넣어주셔야됩니다)

 

히히 총알발사

하지만, 옆에 보면 생산된 총알이 계쏙 쌓이는데요,,, Destroy로 화면을 벗어난 총알을 삭제해봅시다!

 

Bullet Control 스크립트를 생성해 코드를 입력해줍시다.

 

public class BulletControl : MonoBehaviour
{
    [SerializeField] private Stage_Data stageData;
    private float destroyWeight = 2.0f; //삭제 여유공간

    private void LateUpdate()
    {
        if(transform.position.y < stageData.LimitMin.y - destroyWeight || transform.position.y > stageData.LimitMax.y + destroyWeight ||
            transform.position.x < stageData.LimitMin.x - destroyWeight || transform.position.x > stageData.LimitMax.x + destroyWeight)
        {
            Destroy(gameObject);
        }
    }
}

stage Data에서 limit min, max를 받아서 처리할 겁니다.

destroy Weight는 저 범위를 벗어났다고 바로 삭제하지 않고 조금 여유 공간을 두고 삭제하려고 만든 것이고,

총알의 위치가 min에서 여유공간 뺀거보다 작거나 뭐시기 크거나 그냥 화면을 벗어났다면 destroy하는 코드입니다.

잘 사라지는 총알들

총알도 잘 사라지네요 와하!

 

이제 지난시간 이용한 backgroundloop를 활용해 스크립트를 작성합니다.

 

public class BackgroundLoop : MonoBehaviour
{
    private float height;

    private void Awake()
    {
        height = transform.GetComponent<BoxCollider2D>().size.y;
    }

    void Update()
    {
        if (transform.position.y < -height)
        {
            Reposition();
        }
    }

    public void Reposition()
    {
        Vector2 offset = new Vector2(0, 2 * height);
        transform.position = (Vector2)transform.position + offset;
    }
}

background에 boxcollider를 넣어줘서 height를 저장해 반복실행했습니다 우하하

 

다음으로 총알과 Enemy가 부딪힌다면 둘 다 사라지게 만들게 하기 위해

Enemy Control 스크립트를 추가해 Tag로 이를 구별해 없애보겠습니다.

Enemy -> tag Enemy, Bullet -> tag Bullet 만들어서 넣어주기

 

public class EnemyControl : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Bullet")) 
        {
            Destroy(gameObject);
        }
    }
}

부딪힌게 총알이면 enemy 본인이 삭제되는 단순 코드입니다.

 

아까 만든 bullet control에도 단순하게 붙여봅시다.

 

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Enemy"))
        {
            Destroy(gameObject);
        }
    }

끝! 이 아니라

이렇게하면 둘이 부딪혀도 막 엇나가는데 이건 둘 다 collider를 가지고 있지만, 한 쪽이 rigidbody가 없어서 생긴 일이기 때문에 저는 enemy에게 rigidbody를 넣어주고 gravity scale을 0으로 만들어 중력의 작용을 받지 않게 해줬습니다.

 

이렇게 하면 일단 1일차 갤러그 끝!

728x90