Unity Chapter 6-2. 어메이징 볼링 게임 만들기 : 프롭, 데미지 시스템
카테고리: Unity Lesson 1
태그: Unity Game Engine
인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!
Chapter 6. 어메이징 볼링 게임 만들기
프롭들 만들기
- 프롭 :
Ball
오브젝트 주변에 분포 되어있는 큐브들!Ball
이 떨어지는 곳 주변의 프롭들 또한 같이 파괴된다.- 그리고 물리적 영향을 받아 튕겨져 나가기도 할 것.
- 크기를 다양하게 할 것이다.
- Cube 오브젝트 만들기
- 이름은
Small Prop
- Material로 색도 입혀주자.
- 이름은
- Rigid body 컴포넌트를 붙여준다.
Ball
이 떨어지는 곳 주변 프롭들은 튕겨져 나가야 하는 등등 물리적 영향을 받아야 하므로!
- 📜Prop.cs 스크립트를 만들어 붙여주자
📜 Prop.cs 의 역할
- 체력을 가지고 있어서 데미지를 받아 체력이 다 하면 파괴되야 함.
- 프롭의 파티클 오브젝트를 실시간으로 생성한다
- 나중에 만들 GameManager 에게 자신이 파괴 됐다는 것을 알려 점수를 계산하게끔 해야함.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Prop : MonoBehaviour
{
public int score = 5; // 자기 자신이 파괴될 때마다 올라갈 점수 = 5점
public ParticleSystem explosionParticle; // 프롭도 파괴되면 파티클 효과를 재생할 것. Ball과 다르게 파티클 프리팹을 실시간으로 찍어낼 것.
public float hp = 10f; // 자기 자신(Small Prop)의 체력
public void TakeDamage(float damage) // 프롭이 스스로 발동하는게 아니라 외부에서 실행시켜 줄 함수. 그래서 public 함수로 만듬.
{
hp -= damage;
if (hp <= 0)
{
ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation); // 파티클 시스템 생성
Destroy(instance.gameObject, instance.duration); // 해당 파티클이 수명 다한 후 파괴되게끔
gameObject.SetActive(false); // 프롭은 정말 많기 때문에 렉걸릴 수 있으므로 터진 프롭은 꺼주기. 프롭은 오브젝트 자체를 파괴하지 말고 그냥 꺼주기만.
}
}
}
- 변수
- score
- 자기 자신(
Small Prop
)이 파괴될 때마다 올라갈 점수 = 5점
- 자기 자신(
- explosionParticle
- 프롭이 터질 때 재생할 Particle System 컴포넌트를 가진 오브젝트를 이 슬롯에 드래그 앤 드롭 해야한다.
- 즉 대충
파티클 오브젝트
. Ball
과 다르게 게임 도중파티클 오브젝트
가 실시간으로 생성되게끔 할 것이다.
- hp
- 자기 자신(
Small Prop
)의 체력
- 자기 자신(
- score
- 함수
- public void TakeDamage(float damage)
- 자기 자신(
Small Prop
)이 실행하는 함수가 아니라 외부에서 실행시킬 함수.- 따라서 public으로 선언해준다.
- 외부에서
Small Prop
에게 데미지를 줄 때 외부에서 실행될 것.
- 자기 자신(
Small Prop
)의 체력을 깎고 (hp -= damage;) - 체력이 다하면 (if (hp <= 0))
- ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation);
- 게임 도중 실시간으로 explosionParicle 변수가 참조하고 있는 파티클 시스템 오브젝트를 복사하여 생성한다.
- 프롭의 파티클은 실시간으로 생성하는 것.
Ball
은 그냥 파티클 오브젝트를 처음부터 자식으로 데리고 있었는데 그 와 다르게 프롭의 파티클 오브젝트는 게임 도중 생성되게 해주자- 자기 자신(
Small Prop
)의 위치, 회전값 에서 생성
- 게임 도중 실시간으로 explosionParicle 변수가 참조하고 있는 파티클 시스템 오브젝트를 복사하여 생성한다.
- Destroy(instance.gameObject, instance.duration)
- instance.gameObject : 즉 Particle System 이 붙어있는 오브젝트를 파괴
- instance.duration : 파티클 오브젝트의 런타임이 지나고 난 후 파괴
- gameObject.SetActive(false);
Ball
과 다르게Small Prop
은 터져도 오브젝트 자체를 Destroy 하지 않는다.- 그냥 안보이게 꺼주자.
Small Prop
오브젝트는 정말 많아서 다 Destroy 해주다 보면 렉 걸릴 수 있어서
- ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation);
- 자기 자신(
- public void TakeDamage(float damage)
프롭의 파티클 효과 & 효과음
- Import 해온 파티클 프리팹들 중
SmallExplosion
을 Hierarchy 창으로 옮겨주어 오브젝트화 하자. - 포탄 파티클 효과 때와 마찬가지로
- Particle System 컴포넌트 에서
- looping을 자식들까지 모두 해제해주고
- Play on Awake 또한 자식들까지 모두 꺼주자
- Audio Source 컴포넌트도 붙여주자.
- Audio Clip엔 Import 받은 적당한 효과음을 넣어주자.
- Play on Awake 는 꺼준다.
- Particle System 컴포넌트 에서
- 수정한 이
SmallExplosion
오브젝트를 📁Prefabs 폴더로 옮겨 나만의 프리팹으로 만들어주자. Small Prop
도 프리팹으로 만들어주자- Hierarchy 창에 있는 기존
SmallExplosion
오브젝트는 지워 주도록 하자. 게임 도중에 실시간으로 찍어내어 생성할거니까 ! - ✨✨
Small Prop
의 explosion Particle 슬롯에SmallExplosion
프리팹을 드래그 앤 드롭 해주자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Prop : MonoBehaviour
{
public int score = 5;
public ParticleSystem explosionParticle;
public float hp = 10f;
public void TakeDamage(float damage)
{
hp -= damage;
if (hp <= 0)
{
ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation);
AudioSource explosionAudio = instance.GetComponent<AudioSource>();
explosionAudio.Play();
Destroy(instance.gameObject, instance.duration);
gameObject.SetActive(false);
}
}
}
- *AudioSource explosionAudio = instance.GetComponent
();* - 생성한 오브젝트(explosionParticle이 참조 중인 오브젝트의 사본)의 AudioSource 컴포넌트를 가져온다.
데미지 시스템
Ball
이 떨어지는 곳 폭발 반경 내에 있는Prop
들에게 데미지를 주어야 한다.(*TakeDamage함수*)Ball
의 폭발 반경을 보이지 않는 가상의 구로 그리고 구에 속하는 모든 `Prop`들을 가져와서 TakeDamage 함수를 발동할 것이다.- (구의 중심 + 구의 반지름) 거리 안에 위치한
Prop
들은 폭발 반경 안에 있다고 할 수 있다.
- (구의 중심 + 구의 반지름) 거리 안에 위치한
Layer를 사용하여 폭발 반경안에 있는 Prop들 구별
- 유니티에선
Tag
와Layer
로 오브젝트들을 구별할 수 있다.Tag
와Layer
의 차이Tag
- 일반적인 경우에 사용
- 1:1 비교만 가능
- 한 오브젝트는 하나의 태그만 가질 수 있다.
Layer
- 주로 물리 처리시 사용
- 한번에 여러개 필터링 가능
- 한 오브젝트는 여러개의 레이어를 가질 수 있다. 다중선택 가능.
- Layer - Add Layer - “Prop” 추가
Small Prop
의 Layer를 “Prop”으로 해준다.- Apply all 해주기.
📜 Ball.cs : 데미지 계산
프롭의 데미지를 깎는건 Ball
이 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour
{
public LayerMask whatIsProp;
public ParticleSystem explosionParticle;
public AudioSource explosionAudio;
public float maxDamage = 100f;
public float explosionForce = 1000f;
public float lifeTime = 10f;
public float explosionRadius = 20f;
void Start()
{
Destroy(gameObject, lifeTime);
}
private void OnTriggerEnter(Collider other)
{
Collider [] colliders = Physics.OverlapSphere(transform.position, explosionRadius, whatIsProp);
for (int i = 0; i < colliders.Length; i++)
{
Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
targetRigidbody.AddExplosionForce(explosionForce, transform.position, explosionRadius); // 알아서 스스로 튕겨나감
Prop targetProp = colliders[i].GetComponent<Prop>();
float damage = CalculateDamage(colliders[i].transform.position);
targetProp.TakeDamage(damage);
}
explosionParticle.transform.parent = null;
explosionParticle.Play();
explosionAudio.Play();
Destroy(explosionParticle.gameObject, explosionParticle.duration);
Destroy(gameObject);
}
private float CalculateDamage(Vector3 targetPosition)
{
Vector3 explosionToTarget = targetPosition - transform.position;
float distance = explosionToTarget.magnitude;
float edgeToCenterDistance = explosionRadius - distance;
float percentage = edgeToCenterDistance/explosionRadius;
float damage = maxDamage * percentage;
damage = Mathf.Max(0, damage);
return damage;
}
}
- 🚍 변수
- public LayerMask whatIsProp
- public이므로 유니티에서 슬롯이 열리는데 드롭다운으로 열린다..
- 유니티 슬롯에서 "Prop"을 선택해주자.
- 이제 whatIsProp 변수는 “Prop” 레이어를 가진 오브젝트를 참조하게 된다.
- “Prop” 레이어를 가진 오브젝트만 가져올 것이다.
- 구의 반경에 있는 오브젝트들 중 Prop 이 아닌 오브젝트들까지 다 가져오면 너무 성능 낭비기 때문이다.
- “Prop” 레이어를 가진 오브젝트만 가져올 것이다.
- public LayerMask whatIsProp
- 🚍 함수
- OnTriggerEnter
Collider [] colliders = Physics.OverlapShere(transform.position, explosionRadius, whatIsProp);
- OverlapShere
- 구의 중심 위치와 구의 반지름을 넘기면 그 가상의 구 반경 안에 있는 모든 Collider들 中 해당 레이어에 해당하는 것들만 colliders 배열에 담아 리턴해준다.
- 구의 중심
- transform.position
- 즉
Ball
자기 자신의 위치
- 구의 반지름
- explosionRadius
- 지정해준 폭발 반경
- 레이어
- whatIsProp
- 드롭다운 슬롯에서 지정한 Layer에 해당하는 것들만.
- 즉 레이어가 “Prop”인 Collider들
- whatIsProp
- 구의 중심
- 구의 중심 위치와 구의 반지름을 넘기면 그 가상의 구 반경 안에 있는 모든 Collider들 中 해당 레이어에 해당하는 것들만 colliders 배열에 담아 리턴해준다.
- for문
- Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
- colliders 배열 원소로부터 Rigidbody 를 갖져온다
- colliders 배열 원소 👉
Ball
폭발 구 반경 내에 있는Prop
들.
- colliders 배열 원소 👉
- Rigidbody를 가져오는 이유는 바로 아래에서
Prop
들의 폭발에 대한 물리 처리를 하기 위하여- 이제
Prop
들이 폭발에 의하여 튕겨져 나가거나 하는 물리적 모션을 취하게 된다.
- 이제
- colliders 배열 원소로부터 Rigidbody 를 갖져온다
- targetRigidbody.AddExplosionForce
- Rigidbody가 지원 하는 함수로 폭발에 대한 물리 처리를 해준다.
- 폭발력, 폭발위치, 폭발반경 이 3개의 매개변수를 넘겨주면 알아서 계산하여 물리 처리를 해준다.
- Prop targetProp = colliders[i].GetComponent<Prop>();
- 폭발 구 반경에 있는 각각의 프롭들에게 TakeDamage 함수를 사용하여 데미지를 주기 위해
Prop
컴포넌트(스크립트)를 참조한다.- TakeDamage는
Prop
의 함수기 때문에..
- TakeDamage는
- 폭발 구 반경에 있는 각각의 프롭들에게 TakeDamage 함수를 사용하여 데미지를 주기 위해
- targetProp.TakeDamage(damage);
- 데미지 주기.
- Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
- OverlapShere
- float CalculateDamage(Vector3 targetPosition)
- 자기 자신(
Ball
)의 위치(transform.position)과 매개변수로 받은 targetPosition 사이의 벡터를 구해서 그에 따라 데미지를 얼마나 줄 지 계산하고 그 데미지를 리턴함.- 거리에 따라 차등으로 데미지를 부여할 것.
- 거리 벡터 = 목적지 좌표 - 현재위치 좌표
- 벡터의 뺄셈
- 거리 벡터 = 목적지 좌표 - 현재위치 좌표
- explosionToTarget = (targetPosition - transform.position).magnitude
- (반경 안에 있는 Collider 프롭 위치벡터) - (구의 중심이자 Ball의 위치벡터) 의 길이 와 같다.
- 구의 중심에 가까울 수록 1.0f (100%)
- 구의 테두리에 가까울 수록 0.0f (0%)
- \[{explosionRadius - explosionToTarget}\over{explosionRadius}\]
- 위 값이 1.0 에 가까울수록 targetPosition이 구의 중심(Ball의 위치)에 가까운 것이고 값이 0.0에 가까울 수록 targetPosition이 구의 중심(Ball의 위치)에서 먼 것이다. (구의 반경에는 있되 구의 테두리에 가까움)
- distance = 0 이면, 즉 프롭이
Ball
의 위치와 일치하면 - edgeToCenterDistance는 1 이 되고 percentage = 1.0f
- distance = 1 이면, 즉 프롭이 구의 반경에 위치하되
Ball
의 위치와 가장 멀면 - edgeToCenterDistance는 0 이 되고 percentage = 0.0f
- damage = Mathf.Max(0, damage);
- 만약 구의 테두리에 걸치는 바람에 중심은 바깥에 있는데 몸의 한쪽은 구의 반경에 들어와 억울하게 Collider 배열에 들어오게 된 Collider가 있다면 문제가 생긴다. 이런 Collider들은 damage 값이 음수가 되기 때문.
- 중심이 구의 바깥에 있다는건 explosionRadius < distance 이기 때문에 결과적으로 최종 데미지 dagame 값이 음수가 된다.
- damage가 음수라는것은 곧 오히려 체력이 차오른다는 의미…
- 따라서 damage가 음수이면 안되므로 음수인 damage는 0으로 처리될 수 있게 Max 함수를 사용
- 만약 구의 테두리에 걸치는 바람에 중심은 바깥에 있는데 몸의 한쪽은 구의 반경에 들어와 억울하게 Collider 배열에 들어오게 된 Collider가 있다면 문제가 생긴다. 이런 Collider들은 damage 값이 음수가 되기 때문.
- 리턴할 최종 데미지 👉 damage = maxDamage * percentage;
- 거리에 따라 차등으로 데미지를 부여할 것.
- 자기 자신(
- OnTriggerEnter
그림 정정 : 중심은 반경 外라서 edgoeToCenterDistance
가 음수가 나오는 경우를 대비하여
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기