일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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
- 장학팀
- 스마일게이트
- 개발
- 도트공부
- photoshop
- 자원순환보증금관리센터
- pixel art
- 드로잉 연습
- 픽셀 아트
- TOOL
- Aseprite
- 모작
- 도트
- 서포터즈
- 픽셀아트
- 드로잉
- 연습
- 포토샵
- 멋쟁이사자처럼
- layer
- 채색
- 기초
- 노하우
- 에이세프라이트
- 인디게임 개발
- menu
- Today
- Total
소소한 나의 하루들
2d 종스크롤 슈팅(2) - 총알발사 구현하기 본문
출처: https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2&feature=shared
이제는 플레이어가 총알을 발사하는 로직을 직접 구현해본다.
#1. 준비하기
플레이어가 총알을 쏘아야한다. 우선 주어진 에셋 sprite의 Pixels per Unit을 24로, Filter Mode는 Point (no filter)로, Compression은 None으로 설정한다.
Spirte Editor에서 이러한 다양한 크기의 총알 sprite같은 경우 Slice를 Automatic으로 설정하고 Slice하면 된다.
#2. 프리펩
주어진 에셋 아틀라스에서 첫번째 sprite를 Scene에 드래그한다. 그리고 이 총알 오브젝트(Player Bullet A)에 충돌 이벤트를 위한 콜라이더 컴포넌트와 Rigidbody 2D 컴포넌트를 생성한다.
총알은 나중에 AddForce()를 이용해서 날릴 예정이기 때문에 Rigidbody 2D 컴포넌트의 타입을 Dynamic으로 주는 것이 맞다. 대신 중력은 받지 않으므로 Gravity Scale은 0으로 설정한다.
이제 이 총알 오브젝트를 플레이어가 쏘도록 해야하기 때문에 총알 오브젝트 자체를 에셋으로 만들어야한다. [프리펩]
프리펩(Prefabs): 재활용을 위해 에셋으로 저장된 게임 오브젝트
따라서 Assets 아래에 Prefabs 폴더를 생성하고, 여기에서 프리펩을 관리한다. Hierarchy창에 있는 Player Bullet A 오브젝트를 그대로 Project창의 Prefabs폴더 안으로 드래그하여 프리펩을 생성한다.
프리펩이 만들어지면, Hierarchy창의 해당 오브젝트가 파란색으로 표시된다. : 프리펩에서 나온 게임 오브젝트임을 의미
이 프리펩 오브젝트(총알 오브젝트)를 그대로 복사+붙여넣기(Ctrl+D)한다. [Ctrl+D : 복사해서 바로 붙여넣기 단축키]
복붙한 프리펩 오브젝트에 새로운 총알 이미지를 적용시킨다. 새로운 이미지에 맞게 콜라이더 히트박스 크기도 조정해준다.
이것도 프리펩으로 저장한다. 그런데, 이 오브젝트는 프리펩 오브젝트를 수정한 오브젝트이기 때문에, 유니티에서 '새로운 프리펩으로 저장할 것인지' 한번 더 물어본다. (Original Prefabs: 새로운 프리펩으로 저장한다)
총알이 앞으로 나간다고 가정한다. 이 총알이 앞으로 나가서 어떠한 오브젝트에 맞고 사라져야한다. = 지워져야한다.
총알 오브젝트가 그대로 남아있으면 안된다. 그래서 총알을 지울 수 있는 경계선을 만들어본다. 참고
#3. 오브젝트 삭제
총알의 움직임을 관리해주는 총알 스크립트를 생성한다. (Bullet)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
{
Destroy(gameObject);
}
}
}
총알이 닿으면 사라지는 총알제거 경계를 위해 새로운 태그로 OnTriggerEnter2D()에 조건을 건다. Destory()로 매개변수 오브젝트를 삭제한다.
Destroy() : 매개변수 오브젝트를 삭제하는 함수
*MonoBehaviour 내에 들어있는 메소드 함수
총알제거 경계는 위에서 만들었던 Border 오브젝트를 그대로 Ctrl+D하여 복사, 붙여넣기한다. 더 큰 총알 제거 경계를 생성한다.
안쪽 경계선은 플레이어가 맵 밖을 벗어나지 않도록 하기위함이고, 바깥쪽 경계선은 총알이 닿으면 사라지도록 한다.
Prefabs 폴더에서 총알 프리펩 파일을 선택하고, Add Component해서 만들어줬던 Bullet을 검색해서 스크립트를 컴포넌트로 추가해준다. (프리펩 파일에 직접 추가)
: Scene에 추가한 프리펩 오브젝트에도 자동으로 Bullet 스크립트 컴포넌트가 추가된다.
실행해서 총알을 옮겨보면 해당 경계지점에 닿는 순간 총알이 제거된다. (총알이 누적되지 않도록 설정)
이제 총알 프리펩 오브젝트 2개는 지워준다.
#4. 오브젝트 생성
게임 시작할 때 처음에는 총알이 없다. 그래서 총알 만드는 로직을 작성한다. Player 스크립트에서 총알 프리펩을 저장할 변수를 생성한다. (bulletObjA, bulletObjB)
이제 이 프리펩을 Scene에 추가하는 것은 어떻게 할까?
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
지금부터는 Update()의 로직을 함수로 분리해서 캡슐화시킨다. 우리가 에셋 프리펩으로 되어있는 오브젝트를 Scene에 새로 만드는 함수는 Instrantiate()이다.
Instantiate() : 매개변수 오브젝트를 생성하는 함수
*첫번째 매개변수로 무조건 '프리펩 오브젝트'가 들어간다.
*Instantiate() 함수의 오버로드(인수에 따라 다르게 정의된 로직이 작성된 같은 이름의 함수 종류)가 많다.
여러 오버로드의 Instantiate() 함수에서 사용할 타입은 4번째이다. (프리펩 오브젝트, 생성될 위치, 생성된 오브젝트의 방향)
public class Player : MonoBehaviour
{
...
public GameObject bulletObjA;
public GameObject bulletObjB;
Animator anime;
private void Awake()
{
anime = GetComponent<Animator>();
}
void Update()
{
Move();
Fire();
}
void Fire()
{
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
}
void Move()
{
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anime.SetInteger("Input", (int)h);
}
...
}
플레이어로부터 총알이 나가는 것이니까 위치, 회전 매개변수는 플레이어의 transform을 사용한다. 이제 기존에 없던 오브젝트를 프리펩 오브젝트로 지정하여 생성했으니까 사용해야한다.
Rigidbody2D를 가져와서 Addforce()로 총알 발사 로직을 작성한다.
저장하고, Player 오브젝트의 Player 스크립트 컴포넌트에 적절한 프리펩 오브젝트 파일을 드래그하여 적용시킨다.
실행시키면 총알끼리 서로 충돌해서 사방으로 총알이 퍼져나가는 모습을 볼 수 있다.
※총알끼리의 충돌을 허용하면 안되기 때문에 총알 프리펩의 Box Collider 2D 컴포넌트에서 Is Trigger를 체크해야한다.
이제 총알이 더이상 쌓이지 않고 프리펩 오브젝트들이 생성되고있다. 총알 제거 경계에 맞은 총알들이 제거되면서 일정 개수 이하 프리펩 오브젝트들만 Scene에 나타나고 있다.
총알이 제대로 발사되는 것을 확인했으니 플레이어 조작에 의해 총알이 발사되도록 로직을 추가해준다.
Input.GetButtonDown() / Input.GetButton() / Input.GetButtonUp() 중 GetButton()을 사용해야한다.
*Down이나 Up은 1프레임, 찰나의 순간이다.
유니티에서는 'Fire1'이라는 기본 버튼도 제공하는데, Fire1 버튼을 눌렀을 때 총알이 나가도록 한다. (마우스 왼쪽버튼 또는 왼쪽 ctrl)
void Fire()
{
if (!Input.GetButton("Fire1"))
return;
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
}
여러 코딩 스타일이 있는데, if문을 통해서 총알발사 로직을 묶는 방법도 있고, 아니면 그 반대의 조건을 가정해서 return시키는 방법도 있다. 이러한 로직 스타일 작성법을 가정해서 본인에게 맞는 코드 작성을 하는 것이 좋다.
장전 시간 조절
public float speed;
public float maxShotDelay; //실제 딜레이
public float curShotDelay; //한발 쏜 다음 충전되기까지 딜레이
그리고 총알이 매 프레임마다 너무 빠르게 발사되기 때문에 발사 딜레이 로직을 위한 변수(최대, 현재)를 생성한다.
총알을 장전하는 함수를 만들어본다. (Reload())
void Update()
{
Move();
Fire();
Reload();
}
void Fire()
{
if (!Input.GetButton("Fire1"))
return;
if (curShotDelay < maxShotDelay)
return;
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
curShotDelay = 0;
}
void Reload()
{
curShotDelay += Time.deltaTime;
}
딜레이 변수에 Time.deltaTime을 계속 더하여 시간을 계산한다. 프레임이 진행될수록 프레임에 소요된 시간이 계속 curShotDelay에 가산된다. 참고
Fire()에도 조건을 추가해준다. 현재 curShotDelay가 (내가 설정해준) maxShotDelay를 넘지 않았다면 아직 장전이 되지 않은 것이므로 return해준다. 만약 curShotDelay < maxShotDelay 조건을 통과해서 총알을 쏜 다음에는 curShotDelay는 0으로 초기화해준다.
maxShotDelay 값을 주고, 실행시켜보면 curShotDelay 값이 계속 가산되는데 여기서 Fire1키(마우스 좌클릭or 왼쪽 ctrl)을 누르면 curShotDealy가 maxShotDelay보다 커지는 순간 총알이 발사되면서 curShotDelay가 0으로 초기화되는게 반복된다.
#5. 파워 모드
슈팅게임을 할 때 중간에 아이템이 떨어지는 것을 볼 수 있다. 그 아이템을 먹으면 총알이 더 많이 쏘아지게된다. (아니면 공속이나 데미지가 증가하거나 공격범위가 넓어진다)
switch (power)
{
case 1:
//Power One
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
이전에 작성한 이 총알발사 로직은 파워가 1일때의 파워모드이다. 그렇다면 switch-case문으로 파워모드를 관리해줘야겠다. 파워 2일때는 총알이 2개 발사되도록 한다.
*transform은 무조건 Vector3이다.
실행하고나서 Power 값을 1, 2, 3으로 변경해주면 Power 모드에 따라 설정한 공격모션이 나간다.
문제상황 - 피드백
부모 오브젝트 선택 시, 자식 오브젝트 일부(또는 모두)가 표시되지 않는 문제
간혹 부모 오브젝트를 선택했을 때 자식 오브젝트들이 모두 표시되지 않거나, 일부만 Scene에 나타나는 문제가 있다. 빈 오브젝트이기에 Sprite Renderer컴포넌트가 없어서 Order in Layer 순번 설정도 할수 없고, 해당 오브젝트를 삭제하고 새로 만들어도 그대로라서 재부팅까지 해보았지만 자식 오브젝트가 모두 표시되지 않는 문제는 해결되지 않았다.
부모 오브젝트를 체크해제(비활성화)했다 체크(활성화)하면 일시적으로 자식 오브젝트들이 모두 표시되지만 다시 부모 오브젝트 클릭 시 일부(또는 전체) 오브젝트가 안보이는 문제가 남아있어서 유니티 자체 문제라고 결론지었다.
이 경우 어쩔 수 없이 작업중인 현재 유니티 프로젝트에 해당 문제가 있음을 인지하고 수시로 체크해보면서 헷갈리지 않도록 작업해야겠다.
'개발 > 유니티' 카테고리의 다른 글
2d 종스크롤 슈팅(4) - 적 전투와 피격 이벤트 만들기 (0) | 2024.02.17 |
---|---|
2d 종스크롤 슈팅(3) - 적 비행기 만들기 (0) | 2024.02.16 |
2d 종스크롤 슈팅(1) - 플레이어 이동 구현하기 (0) | 2024.02.12 |
탑다운 2d RPG(최종) - 피드백 추가 및 개선사항 (0) | 2024.02.08 |
탑다운 2d RPG(8) - 모바일 UI & 안드로이드 빌드 (0) | 2024.02.05 |