[멋쟁이사자처럼 부트캠프 TIL회고] 38일차 : 폭발통 만들기
폭발물 제작
화살에 맞으면 터지는 물품인 폭발물을 제작하려고 한다.
스스로 했던 게임들의 '폭발'하는 물체들을 찾아보자면,
1. 스타크래프트 : 리버의 스캐럽, 핵 , 벌쳐의 마인, 인페스티드 테란
스타크래프트에 있는 요소들은 대부분 터지는 순간 범위로 데미지가 들어간다.
리버 같은 경우, 스플래쉬 범위에 따라 데미지가 다르게 들어간다.
마찬가지로 마인도 그렇다.
2. 배틀그라운드 : 감자그라운드라고 불렸던 시절이 있었을만큼 폭발물인 수류탄의 중요성이 크다.
엎드리거나, 거리가 멀어지면 데미지가 줄어든다.
3. 발로란트 : 레이즈의 폭발 봇, 폭발 팩, 페인트 탄, 대미 장식(궁극기) 모두 폭발 데미지가 있다.
모든 스킬이 거리별 데미지가 있다. 그리고 추가적으로 페인트 탄은 터지는 즉시 데미지가 있고 더해서
파편이 생기고 그 파편마다 데미지가 있는 것이다.
스파이크 : 터지는 순간 일정 범위 내에 데미지가 들어 오는 것이 아닌, 점차 커지면서 범위 안에 있는
적들에게 순차적으로 데미지를 준다.
폭발물 구현 계획
1. 새로운 Object를 생성하고, Collider와 layer를 설정해 Object에 닿은 물체들에 일괄적으로 데미지를 적용.
처음으로 생각나는 방법이자, 가장 간단하다고 생각하는 방법이다.
2. 이펙트에 콜라이더를 적용해 데미지 적용
3. 오브젝트 안에 파편들을 비활성화 상태로 놓고, 폭발 시 활성화 시키면서 랜덤한 방향으로 힘을 줌
2번과 3번 방법은 특별한 상황이 오면 구현해볼 것이고, 우선은 1번 방법으로 구현해보려고 한다.
구현
폭발물로 쓸 이미지와 사운드를 구했다.
스크립트를 만들어 DestroyableObject를 상속시켜준다( HP가 있고, HP가 0이 되면 파괴, 화살 접촉 시 HP-1)
폭발을 하자마자 펑 하고 사라지는 것이 아닌, 일정 시간이 지나고 사라지게 하기 위하여 코루틴을 사용할 것이다.
의도하는 바는, HP가 0보다 작아지면, 폭발물을 생성하면서 코루틴이 시작되고
폭발물은 주변 폭발 범위에 데미지를 주고, 설정한 시간이 지나면 사라진다.
코루틴으로 제작 중 문제 발생
아래는 왜 안되는지 알겠다, 결국 HP<= 0 이라면 , 밑의 네줄짜리 코드는 모두 같은 프레임에 실행된다.
코루틴 실행 도중, 담고 있는 오브젝트가 사라져버려 작동이 안되는 것.
코루틴에는 소유권이라는 개념이 있는데, 소유권을 가진 객체가 비활성화되거나 파괴되면 해당 객체가 소유한 모든 코루틴이 중단돼요.
위의 코드는 충돌을 하지 않는 문제가 있었는데
base를 안써서 그랬었다... 바보가 맞다.
원래 있던 폭발물을 사라지게 만들고 싶었다.
setAcitve false하니까 원래처럼 안됨. (코루틴이 시작이 안된다)
Destroy와 같은 원리 !
그래서 rigidbody만 없에버렸다.
콜라이더만 남겨두고 physics2D 만져서 땅이랑 충돌 안하게 해주었따.
정상 작동 !
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bomb2 : DestroyableObject
{
[SerializeField]
private GameObject explosion2;
private AudioSource audioSource;
private Vector3 boomScale;
void Start()
{
this.maxHP = 2;
this.HP = 2;
audioSource = this.gameObject.GetComponent<AudioSource>();
//boomScale = explosion2.GetComponent<Transform>().localScale;
}
void Update()
{
base.Update();
}
public void OnCollisionEnter2D(Collision2D other)
{
base.OnCollisionEnter2D(other);
if (other.gameObject.layer == 3)
{
Debug.Log("온콜리전까지 들어왔다 !");
if (HP <= 1)
{
var rb = gameObject.GetComponent<Collider2D>();
rb.enabled = false;
GameObject boom = Instantiate(explosion2, transform.position, Quaternion.identity);
audioSource.Play();
StartCoroutine(boomEffect2(boom));
}
}
}
IEnumerator boomEffect2(GameObject boom)
{
boomScale = boom.transform.localScale;
boom.transform.localScale = Vector3.Lerp(new Vector3(1,1,1),new Vector3(3,3,3), 1.0f);
yield return new WaitForSeconds(1.0f);
Destroy(boom);
Destroy(this.gameObject);
yield return null;
}
}
비슷하게 짰는데, Lerp 부분이 문제가 있던건지 작동을 안함.
내 생각대로라면 스케일이 Lerp하며 커지는 1동안 따로 커지고, 그리고 부서지는... 이상적인 그림이었는데..
Lerp의 기능을 잘 몰라서 발생했던 문제였다.
IEnumerator boomEffect2(GameObject boom)
{
boomScale = boom.transform.localScale;
float duration = 1.0f;
float elapsedTime = 0.0f;
while (elapsedTime < duration)
{
float t = elapsedTime / duration;
elapsedTime += Time.deltaTime;
boom.transform.localScale = Vector3.Lerp(new Vector3(1,1,1),new Vector3(5,5,5), t);
yield return null;
}
Destroy(boom);
Destroy(this.gameObject);
yield return null;
}
이런 방식으로 고쳤다.
if (other.gameObject.layer == 3 || other.gameObject.layer == 8)
연쇄 충돌을 고안하려고 했는데 실패함, 분명 충돌 레이어를 설정했는데.. ?
성공함, 왜 안되었던 거냐 ! Transform처럼 순간이동 판정이기 때문에.. 둘 중 하나는 움직어야 충돌 (Oncollision)이 발생함
그런데 움직이는게 아니라 스케일을 하나하나 확장시키는 것이었다.
그래서 OntriggerEnter를 추가했다.
public class Bomb : DestroyableObject
{
[SerializeField]
private GameObject explosion;
private AudioSource audioSource1;
void Start()
{
this.maxHP = 2;
this.HP = 2;
audioSource1 = this.gameObject.GetComponent<AudioSource>();
}
void Update()
{
base.Update();
}
IEnumerator boomEffect(GameObject boom)
{
yield return new WaitForSeconds(1.0f);
Destroy(boom);
Destroy(this.gameObject);
yield return null;
}
public void OnCollisionEnter2D(Collision2D other)
{
base.OnCollisionEnter2D(other);
if (other.gameObject.layer == 3 )
{
if (HP <= 1)
{
Boom();
}
}
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.layer == 8)
{
Boom();
}
}
public void Boom()
{
var rb = gameObject.GetComponent<Collider2D>();
rb.enabled = false;
GameObject boom = Instantiate(explosion, transform.position, Quaternion.identity);
audioSource1.Play();
StartCoroutine(boomEffect(boom));
}
}
중간 중간 바디 타입 문제일수도 있다고 생각해서 바디 타입도 고쳐줬다.
코드를 고치니 바디 타임 세 가지는 모두 정상 작동했다.
중간에 연쇄 충돌을 하고 싶어서 하다가 코드 문제가 아니라 Rigidbody가 둘 다 없었기 때문에 안되었던 것 !
Rigidbody를 추가하고 Static으로 바꿔 주었다.
연쇄 폭발은 잘 되어가는데 슬라임 넌 왜 안죽니.. ?
++ 아 Trigger로만작동하는구나 이제... 그럼 원래 계획과 달라진다. 다른 방법으로 폭발을 구현하는 것이 현명할 듯 하다.
다른 방법은 없나 ?
범위 공격인 '폭발'에서 이런 방법은 같은 데미지를 주는 것에는 효율적이고 쉬운 방법일 수 있겠지만,
스플레쉬 데미지, 거리를 계산해서 다른 데미지를 보는 방식에는 적합하지 않은 것 같다.
물론 여러개의 오브젝트로 콜라이더를 여러개 생성해서 하는 방법도 있겠지만 비효율적이다.
그래서 다른 분은 어떻게 했나.. 봤더니 Raycast를 사용해서 거리를 측정하고, 거리별 데미지를 주었다.
이런 방식으로 구현하는 것이 범위별 데미지에는 더욱 좋은것 같다.
https://docs.unity3d.com/kr/530/ScriptReference/Rigidbody.AddExplosionForce.html
Rigidbody-AddExplosionForce - Unity 스크립팅 API
Applies a force to a rigidbody that simulates explosion effects.
docs.unity3d.com
3D에는 이런 함수도 있다.
참조할만한 유튜브
https://www.youtube.com/watch?v=snh2HrpV4No
https://www.youtube.com/watch?v=4Ggi_l-ijaA
이분들도 overlapshpere를 사용하고, AddExplosionForce 를 사용해서 한다.
3D 환경에서도 구현해보면서 감을 익혀야겠다.
다음 목표 : 디자인패턴 예습, 3개로 분해되는 화살 만들기
* 폭발물이 0이 되면 부서지니, 1이 되었을 때 터지는 로직을 만들었다. 그러나 이는 0이 되었을 때 사라져서 폭발물이 폭발하지 않는 버그가 발생할 수 있다. 이를 수정*보완해야 한다.