내가 현재 공부할 겸(?) 개발하고 있는 게임은 Dodge기반 게임이다. 그래서 오늘은 사방에서 플레이어를 향해 날라오는 총알을 구현할 것이다.
총알을 발사하는 방법은 여러가지가 있을 수 있는데 오늘은 2가지 방법을 소개한다.
첫번째 방법은 플레이어와 총알을 발사하는 위치에 따라 속력이 변하는 스크립트이다.
// Bullet.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
Vector3 playerPos;
Vector3 bulletPos;
Vector3 newPos;
public float speed = 0.01f;
void Start()
{
playerPos = GameObject.Find("Player").transform.position;
bulletPos = transform.position;
newPos = (playerPos - bulletPos) * speed;
Destroy(gameObject, 5f);
}
void FixedUpdate()
{
transform.position = transform.position + newPos;
}
}
간단하게 설명하면 플레이어 위치와 총알 위치를 통해 이동간격을 정하고 매 프레임마다 조금씩 움직이는 로직이다. speed가 0.01f로 설정되어 있으면 매프레임 총알이 플레이어를 향해 1%씩 움직인다는 뜻이다.
이 로직은 장점이자 단점이 거리에 상관없이 총알이 플레이어에 닿는 시간이 일정하다는 것이다. 따라서 총알이 발사되는 위치가 플레이어로부터 멀수록 총알의 속도가 빠르다는 것이다.
이 게임의 특성상 화면 밖에서 총알이 날라오는 경우도 있는데, 위와 같은 방법으로 구현하면 너무 빨라서 피하기 힘들다는 이유로 이 방법이 아닌 다른 방법을 통해 구현하려고 한다.
두번째 방법은 훨씬 간단한 방법이다. 플레이어가 있는 방향에 일정한 속력으로 총알을 발사하는 방법이다.
// Bullet.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
public float speed;
void Start()
{
GetComponent<Rigidbody2D>().AddForce(transform.right * speed, ForceMode2D.Impulse);
Destroy(gameObject, 5f);
}
}
두번째 방법에서 Bullet 스크립트에선 총알이 생성될 때 총알에 힘을 가해주는 것뿐이다. 이 방법에선 AddForce를 사용해야하므로 Bullet 오브젝트에 반드시 <Rigidbody2D>컴포넌트가 존재해야한다.
이 스크립트만 봤을 때 AddForce의 방향이 right가 아니라 플레이어방향으로 설정해야 하는거 아니야? 라는 의문이 들 수 있다. 이제 이 궁금증을 해결하기 위해 총알을 생성하는 스크립트를 살펴보자.
// BulletSpawner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyController : MonoBehaviour
{
private Transform target;
void Start()
{
target = GameObject.FindWithTag("Player").gameObject.transform;
}
void Update()
{
TargetRotaion();
BulletSpawn();
}
void TargetRotaion()
{
Vector3 dir = target.transform.position - transform.position;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
void BulletSpawn()
{
//총알 생성
}
}
위 스크립트는 총알을 생성하는 오브젝트에 컴포넌트로 들어가있는 스크립트이다. TargetRotation() 함수가 총알을 생성하는 오브젝트가 항상 플레이어를 바라보게하는 함수이다. 조금 더 세부적으로 설명하면 플레이어와 총알의 위치를 토대로 기울기를 구하고, 기울기로 바라보는 방향의 각도를 구해서 그 각도만큼 회전시키는 방법이다.
이를 통해 총알을 생성하는 오브젝트는 항상 플레이어를 바라보는 방향으로 회전하기 때문에 총알을 생성하기만 하면 플레이어를 향해 날라가는 것이다.
총알을 발사하는 것을 설명하는 과정에서 BulletSpawner.cs 스크립트를 보여줬는데, 이 스크립트에는 당연히 총알을 생성하는 함수가 존재한다. BulletSpawn() 함수를 어떻게 구현할지 고민해봤는데 적당히 랜덤한 주기마다 총알을 발사하면 좋을 것 같다고 생각했다. 그래서 아래와 BulletSpawner.cs를 완성하게 되었ㄷ.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyController : MonoBehaviour
{
public GameObject bulletPrefab;
public float spawnRateMin;
public float spawnRateMax;
private float spawnRate;
private float timeAfterSpawn;
private Transform target;
void Start()
{
target = GameObject.FindWithTag("Player").gameObject.transform;
timeAfterSpawn = 0f;
spawnRate = Random.Range(spawnRateMin, spawnRateMax);
}
void Update()
{
TargetRotaion();
BulletSpawn();
}
void TargetRotaion()
{
Vector3 dir = target.transform.position - transform.position;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
void BulletSpawn()
{
timeAfterSpawn += Time.deltaTime;
if (timeAfterSpawn >= spawnRate)
{
timeAfterSpawn = 0f;
GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
spawnRate = Random.Range(spawnRateMin, spawnRateMax);
}
}
}
구현 방식에 대해 설명하자면 Time.deltaTime을 이용해서 실시간으로 시간을 체크해서 총알을 스폰할 시간이 되면 총알을 발사하는 방식이다. 총알을 스폰할 시간은 Random.Range를 사용해서 지정한 최소, 최대값 내의 랜덤한 시간으로 설정했다.
위에서 구현한 모든 스크립트를 게임에 적용해 완성한 모습이다.