일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- COSMO
- 애니메이션
- 서포터즈
- Pixelart
- 개발
- 반환원정대
- 스마일게이트
- 모작
- Aseprite
- 픽셀아트
- pixel art
- 장학팀
- photoshop
- TOOL
- 도트
- 도트공부
- 멋쟁이사자처럼
- 드로잉 연습
- menu
- 연습
- 인디게임 개발
- 자원순환보증금관리센터
- 노하우
- 포토샵
- 드로잉
- 에이세프라이트
- 기초
- 픽셀 아트
- layer
- 채색
- Today
- Total
소소한 나의 하루들
2d 종스크롤 슈팅(4) - 적 전투와 피격 이벤트 만들기 본문
출처: https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2&feature=shared
이번에는 플레이어가 맞아보도록 구현해본다.
#1. 적 등장 강화
위에서만 말고, 옆에서도 등장하도록 해본다. 스폰 포인트를 복사해서 좌우로 배치시킨다. 그리고 생성위치가 늘어났으니, GameManager에서 SpawnPoints 값을 수정해서 새로운 좌표를 적용시킨다. 여기에 맞춰서 적 생성좌표 스크립트 로직도 수정해준다.
//Enemy
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
//rigid = GetComponent<Rigidbody2D>();
//rigid.velocity = Vector3.down * speed;
}
이제 생각해야할 것은 지금까지 적 비행기는 생성되면 무조건 아래로만 내려갔었는데, 만약 생성위치가 오른쪽 또는 왼쪽에서 생성이 된다면, 화면에서 보이지 않을 것이기 때문에 왼쪽 생성 시 오른쪽으로 날라와야하고, 오른쪽 생성 시 왼쪽으로 날라와야한다.
따라서 해당 로직은 삭제하고 적 비행기 속도를 GameManager가 관리하도록 수정한다. 이제 Enemy 스크립트에서 Rigidbody2D도 이제 필요없으니 삭제한다.
//GameManager
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3);
int ranPoint = Random.Range(0, 9);
GameObject enemy = Instantiate(ememyObjs[ranEnemy], spawnPoints[ranPoint].position, spawnPoints[ranPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
if (ranPoint == 5 || ranPoint == 6) //왼쪽
{
rigid.velocity = new Vector2(enemyLogic.speed * 1, -1);
}
else if (ranPoint == 7 || ranPoint == 8) //오른쪽
{
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else
{
rigid.velocity = new Vector2(0, enemyLogic.speed * -1);
}
}
velocity는 사실 속도 개념이다. 속력은 스칼라지만, 속도는 방향까지 가지고 있기 때문이다. 생성 위치에 따라 속도를 다르게 설정한다.
이제 또 고려해야하는 점은 왼쪽, 오른쪽에서 적 비행기 생성 시 총알 경계박스와 닿지 않도록 조절하는 것이다. (닿으면 사라지므로) 직접 실행해보면서 사이드에서 생성되는 적 비행기가 바로 사라지지 않는지/정상적으로 이동하는지 확인한다.
※근데 살짝 이상한게 사이드에서 생성되는 적들이 바라보는 방향은 아래방향 고정인 상황에서 대각선 아래방향으로 움직이니까 부자연스럽게 움직인다.
따라서 사이드에서 오는 적 비행기에 한해서 속도 방향에 따라 적 비행기 회전을 적용시킨다.
if (ranPoint == 5 || ranPoint == 6) //왼쪽
{
enemy.transform.Rotate(Vector3.back * 90);
rigid.velocity = new Vector2(enemyLogic.speed * 1, -1);
}
else if (ranPoint == 7 || ranPoint == 8) //오른쪽
{
enemy.transform.Rotate(Vector3.forward * 90);
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else
{
rigid.velocity = new Vector2(0, enemyLogic.speed * -1);
}
방향을 돌리는 함수는 Rotate()이다. 2D에서 회전을 가할 수 있는 축은 z축이다. 그리고 z축의 단위벡터는 Vector3.forward 또는 Vector3.back이다.
*왼쪽에서 회전을 forward로 넣었다면, 그와 대칭 방향으로 회전을 오른쪽에서 가하려면 회전을 back으로 넣어야한다.
(어떻게 됐든 양쪽을 대칭으로 넣어주려면 한쪽을 forward로 넣었다면 반대쪽은 back으로 [혹은 그 반대]로 넣어야한다) forward로 넣어서 실행해봤더니 방향이 반대라면 back으로 수정해주면 된다.
#2. 적 공격
이번에는 적들이 사용할 총알을 이용해서 적이 플레이어를 공격하도록 해본다.
우선 이전에 만들어뒀던 플레이어 총알 프리펩 파일 하나를 Project창 Prefabs 폴더 내에서 ctrl+D해서 복사-붙여넣기한다. 이걸 갖고 적 총알 프리펩으로 만들어주기만 하면 되는데 문제가 프리펩 파일 이름을 수정할 수 없게 고정되어있다.
그래서 해당 프리펩 파일의 Inspector창에서 Open 버튼을 클릭하면 Scene에 프리펩 오브젝트가 표시된다.
: Scene 장면을 수정할 수 있게 바뀐 것이 아니라, 프리펩 자체에서만 수정할 수 있게 Scene에 배치가 된다.
그러면 이제 이름도 수정하고 sprite도 적 총알 이미지로 바꿔주고, Box Collider 2D size도 적 총알 이미지에 맞게 수정해준다. + 서로 충돌하지 않도록 Is Trigger 체크. 수정이 다 끝나면 다시 Scene을 눌러 프리펩 설정화면에서 나가준다.
마지막으로 태그까지 EnemyBullet으로 바꿔준다.
: 프리펩 수정
//Enemy
public class Enemy : MonoBehaviour
{
public float speed;
public int health;
public Sprite[] sprites;
public float maxShotDelay;
public float curShotDelay;
SpriteRenderer spriteRenderer;
public GameObject bulletObjA;
public GameObject bulletObjB;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Update()
{
Fire();
Reload();
}
void Fire()
{
if (curShotDelay < maxShotDelay)
return;
curShotDelay = 0;
}
void Reload()
{
curShotDelay += Time.deltaTime;
}
이제 적 비행기들도 플레이어처럼 총알을 쏘기위해 플레이어의 총알 쏘는 로직을 재활용한다. 다만, 플레이어처럼 버튼을 눌렀을 때 총알을 쏘는 것이 아니고, 알아서 자동으로 쏘도록 한다.
GetButton()부분은 지우고, 오직 딜레이를 구현하는 부분만 갖고오면 된다.
적 비행기 중 중간 사이즈의 빠른 비행기(Enemy M)는 총알을 쏘지 않도록 한다. 그래서 오브젝트의 이름을 지정해야하는데, 변수 name은 Object Name 내부에 들어있어서 enemyName으로 변수를 생성한다. [특정 enemyName을 가진 적 비행기는 특정 공격을 할 수 있거나 공격을 하지 않도록 제어함]
//GameManager
public GameObject player;
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3);
int ranPoint = Random.Range(0, 9);
GameObject enemy = Instantiate(ememyObjs[ranEnemy], spawnPoints[ranPoint].position, spawnPoints[ranPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
enemyLogic.player = player;
//Enemy
public GameObject player;
이제 총알을 플레이어에게 쏘기위해 플레이어 변수가 필요하다.
※프리펩은 Scene에 올라온 오브젝트에 접근 불가능하다. (프리펩은 아직 Scene에 올라오지 않은 오브젝트들)
그러면 어떻게 해야할까 프리펩이 Scene에 생성되었을 때 플레이어변수를 넣으면 될 것이다.
우선 GameManager가 Player 오브젝트를 들고있어야한다. 그리고 Enemy 스크립트에도 똑같이 player GameObject 변수를 만들어주고, GameManager에서 적 프리펩 생성 후에 Enemy의 player 변수에 player 오브젝트를 담아준다.
: 적 생성 직후에 플레이어 변수를 넘겨주는 것으로 해결
else if (enemyName == "S")
{
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position;
rigid.AddForce(dirVec * 10, ForceMode2D.Impulse);
}
목표물로 방향 설정하는 법은 목표물 위치에서 자신의 위치를 빼면 된다. *벡터의 뺄셈 개념
[목표물로 방향 = 목표물 위치 - 자신의 위치]
큰 적 비행기는 큰 총알을 2발 쏘도록 하겠다.
if (enemyName == "L")
{
//GameObject bulletL = Instantiate(bulletObjB, transform.position, transform.rotation);
//GameObject bulletR = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);
GameObject bulletR = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
//Vector3 dirVecL = player.transform.position - transform.position;
//Vector3 dirVecR = player.transform.position - transform.position;
Vector3 dirVecL = player.transform.position - transform.position + Vector3.left * 0.3f;
Vector3 dirVecR = player.transform.position - transform.position + Vector3.right * 0.3f;
rigidL.AddForce(dirVecL * 10, ForceMode2D.Impulse);
rigidR.AddForce(dirVecR * 10, ForceMode2D.Impulse);
}
그런데 주석처럼 작성하게되면 한 곳에서 2발을 쏘게되어 하나로 보이게 될 것이다. (bulletL, bulletR의 생성위치와 방향이 같으므로)
이제 실행시키기 전에 GameManager에 Player 오브젝트를 드래그하여 적용시켜준다.
그리고 적 비행기 프리펩의 Enemy 스크립트 컴포넌트에는 Scene의 Player 오브젝트를 적용시킬 수 없다. 대신 실행시키면 GameManager의 Player 오브젝트 값이 적용될 것이다. 그러니 냅둬도 괜찮다. 하지만 적 총알 Bullet 프리펩은 적용시킬 수 있으니 적용시켜야한다.
: 프리펩이 프리펩 사용은 가능
또 적 비행기 프리펩의 Enemy Name은 채울 수 있으니 크기 순대로 L, M, S로 입력해주고, 적 프리펩의 maxShotDelay 값도 설정해준다. (총알 딜레이) 총알 프리펩은 서로 물리충돌을 일으키면 안되니까 Is Trigger를 체크해줘야한다.
※실행시키면 총알이 굉장히 빠르게 순간이동하는 것처럼 정신없이 발사되는 것을 확인할 수 있다.
dirVec, dirVecR, dirVecL 값이 플레이어의 transform.position, 그리고 적 비행기의 transform.position [좌표값]이기 때문에 보통 값이 1을 넘어갈 것이다.
→그래서 해야하는 것은 단위벡터화시켜야한다.
단위벡터로 만드는 방법 참고
Vector3.Normalize()
Vector3.Normalized
normalized(또는 Normalize())를 사용한다. →방향은 유지가 되고 크기는 1인 단위벡터가 된다.
*normalized는 변수이고, normalize()는 함수이다. [normalized : 벡터가 단위값 1로 변환된 변수]
이제 적도 총알을 쏠 수 있게 되었다.
#3. 피격 이벤트
그러면 이제 플레이어가 총알을 맞을 차례이다. Player의 OnTriggerEnter2D 안에 적에 대한 로직을 추가한다. 플레이어는 적의 총알을 맞거나, 적과 직접 닿으면 피격되면서 체력이 닳거나 죽어야 하지만 플레이어는 Destroy()로 파괴하면 안된다. 다시 생성해야하기 때문에 죽더라도 일정시간 뒤 다시 생성해야한다. 따라서 플레이어를 SetActive()로 비활성화한다.
//Player
public void OnTriggerEnter2D(Collider2D collision)
{
...
else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
{
gameObject.SetActive(false);
Destroy(collision.gameObject);
}
}
죽어서 비활성화되었다가도 다시 일정시간 뒤 부활해야하기 때문에 활성화시켜줘야하는데 OnTriggerEnter2D의 else if문 내에서 Invoke()로직을 실행하기에는 이미 Player 스크립트를 가진 Player 오브젝트가 비활성화되었기 때문에 로직을 실행하기 어려울 것이다. 하지만 플레이어와 피격된 적(또는 총알)은 파괴시킨다.
따라서 GameManager에서 Player 오브젝트가 비활성화되었는지를 판단해서 플레이어를 복귀시키는 로직은 매니저가 관리한다. (이미 GameManager에서 Player를 변수화해서 갖고있기 때문에)
//GameManager
public void RespawnPlayer()
{
Invoke("RespawnPlayerExe", 2.0f);
}
void RespawnPlayerExe()
{
player.transform.position = Vector3.down * 3.5f;
player.SetActive(true);
}
//Plyaer
else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
{
gameManager.RespawnPlayer();
gameObject.SetActive(false);
Destroy(collision.gameObject);
}
우선 플레이어가 맨 처음 시작하는 좌표를 기억해야한다. 그리고 해당 좌표에 Player 오브젝트를 활성화시킨다.
실행하기 전에 Player 오브젝트에 GameManager를 드래그 적용시켜놓고 실행한다.
실행이 잘 되는 것을 확인했다면, 이제는 플레이어/적 비행기의 체력, 속도와 총알의 속도, 데미지 등을 조절해서 밸런스에 맞게 게임 난이도를 조절하면 된다.
'개발 > 유니티' 카테고리의 다른 글
2d 종스크롤 슈팅(6) - 아이템과 필살기 구현하기 (0) | 2024.02.20 |
---|---|
2d 종스크롤 슈팅(5) - UI 간단하게 완성하기 (0) | 2024.02.18 |
2d 종스크롤 슈팅(3) - 적 비행기 만들기 (0) | 2024.02.16 |
2d 종스크롤 슈팅(2) - 총알발사 구현하기 (0) | 2024.02.13 |
2d 종스크롤 슈팅(1) - 플레이어 이동 구현하기 (0) | 2024.02.12 |