

https://assetstore.unity.com/packages/vfx/particles/particle-pack-127325


컴포넌트의 각 속성들이 어떤 역할인지만 잘 알고 있자.



충돌 발생 (Collision Event) 조건

Trigger Event 발생 조건

게임 오브젝트의 문자 속성을 가져오는 코드 gameObject.tag는 해당 문자열의 복사본을 생성해서 메모리에 할당한다. 즉, 가비지 컬렉션(Garbage Collection)의 대상이 된다. 가비지 컬렉션은 메모리에 자동으로 할당된 저장 공간을 일정 시간이 지난 후 사용하지 않으면 자동으로 해제하는 동작을 말한다. 따라서 가비지 컬렌션 처리해야 할 대상이 많아질수록 끊김 현상이 발생한다. 그렇기 때문에 CompareTag 함수를 사용해서 가비지 컬렉션의 발생을 방지하자.
using UnityEngine;
public class MyGizmos : MonoBehaviour
{
public Color _color = Color.yellow;
public float _radius = 0.1f;
void OnDrawGizmos() {
Gizmos.color = _color;
Gizmos.DrawSphere(transform.position, _radius);
}
}
위와 같은 스크립트를 생성해서 오브젝트 컴포넌트에 추가해줌으로서 해당 오브젝트에 기즈모를 그릴 수 있다.

오브젝트를 따라가는 궤적 효과를 구현하는 방식은 여러가지 있지만 대부분 오브젝트가 이동함과 동시에 동적으로 메시를 만들고 일정 시간이 흐른 다음 생성된 메시를 제거해 나가는 방식이 흔히 접할 수 있는 기법이다.
유니티에서는 Trail Renderer 컴포넌트로 메시의 동적 생성을 쉽게 처리해준다.

오브젝트를 움직이니 Train Renderer로 인해 메시가 동적으로 생성된 모습을 확인할 수 있다. Material을 적절히 수정해줘서 아래와 같은 효과를 구현할 수 있다.

Min Vertex Distance : 버텍스 간의 최솟값을 설정하는 것으로 값이 작을수록 오밀조밀한 폴리곤을 생성한다. 물체가 직선으로만 이동한다면 값을 크게해서 동적으로 생성되는 폴리곤의 값을 줄이고, 포물선 운동처럼 곡선 이동한다면 값을 작세 수정해 부드러운 궤적으로 표현될 수 있게 적절히 조절하자.
색상과 투명도를 좀 손봐주면 아래와 같이 궤적 효과를 지닌 총알 구현이 완료된다.

충돌 지점의 정보를 가져오기 위해서는 GetContact (모든 지점의 정보가 필요하다면 GetContacts 함수)를 사용한다.
GetContactPoint 함수의 반환 타입은 ContatcPoint 구조체 타입으로 아래와 같이 정의돼 있다.

위의 CompareTag와 비슷한데 충돌 지점의 접점 정보는 Collision.Contacts 속성으로도 알 수 있지만 contacts 속성은 가비지 컬렉션을 발생시킨다. 따라서 GetContact, GetContacts를 권장한다.
일단 폭발을 영향을 줄 사물들끼리 같은 레이어에 위치시킨다. (여기서는 BARREL 레이어에 모두 위치시킴) 그 다음에 Physics.OverlapSphere로 겹치는 오브젝트 콜라이더를 모두 가져온 다음에 폭발 영향을 준다.

void IndirectDamage(Vector3 pos) {
Collider[] colls = Physics.OverlapSphere(pos, radius, 1 << 3);
foreach (Collider coll in colls) {
// 폭발 범위에 포함된 드럼통의 Rigidbody 컴포너트 추출
rb = coll.GetComponent<Rigidbody>();
// 드럼통의 무게를 가볍게
rb.mass = 1.0f;
// FreezeRotation 제한값을 해제
rb.constraints = RigidbodyConstraints.None;
// 폭발력을 전달
rb.AddExplosionForce(1500.0f, pos, radius, 1200.0f);
}

OverlapSphereNonAlloc : 원래 OverlapSphere는 검출될 개수가 명확하지 않을 때만 사용해야 한다. 메모리 Garbage가 발생하기 때문인데 따라서 개수가 명확할 때는 Garbage가 발생하지 않는 OverlapSphereNonAlloc을 사용하자.
Collider[] colls = new Collider[10];
void IndirectDamage(Vector3 pos) {
//Collider[] colls = Physics.OverlapSphere(pos, radius, 1 << 3); //Garbage Collection
Physics.OverlapSphereNonAlloc(pos, radius, colls, 1 << 3);
}
오디오는 기본적으로 AudioListener, AudioSource 컴포넌트가 필요하다. 기본적으로 메인 카메라에 Listener가 들어있다.
사우드를 스테레오, 모노 어떤 것을 사용할지 잘 결정하자. 모바일 게임에서 스테레오 사운드는 용량과 성능 저하의 원인이 될 수 있다. 따라서 스테레오 음원으로 음향 효과를 극대화 할 것이 아니라면 모노 음원으로 변환하여 사용하는 것이 성능면에서 유리한다.

이 외에도 로드 옵션, 임포트 옵션은 대부분 크게 문제가 없지만 잘못 설정하면 성능 저하의 원인이 되므로 적절하게 설정하자.



Preload Audio Data : 씬이 로딩될 때 씬에서 사용하는 모든 오디오 클립이 미리 로딩 (기본값은 체크), 언체크 시 처음 오디오를 재생할 때 로딩되기 때문에 레이턴시가 발생


3D Sound Settings의 Min, Max Distance를 설정해주면 거리에 따른 소리 출력이 가능하다. Min Distance 내에서는 100%의 불륨 크기로, Max Distance의 경우에는 50%로 점차 작아지는 볼륨 크기로 들린다.

[RequireComponent(typeof(AudioSource))]
RequireComponent : 스크립트에서 반드시 함께 있어야만 하는 컴포넌트를 자동으로 추가하고, 실수로 의존성 있는 컴포넌트를 삭제하는 것을 방지하기 위한 어트리뷰트
private AudioClip fireSfx;
private new AudioSource audio;
void Start()
{
audio = GetComponent<AudioSource>();
}
void Fire() {
Instantiate(bullet, firePos.position, firePos.rotation);
audio.PlayOneShot(fireSfx, 1.0f);
}
void Fire() {
Instantiate(bullet, firePos.position, firePos.rotation);
audio.PlayOneShot(fireSfx, 1.0f);
}
계속해서 말하지만 문자열로 뭔가를 사용할 때는 거의 웬만하면 가비지 컬렉션이 발생한다. 함수의 원형을 넘기는 방식을 사용하도록 하자.
IEnumerator Start()
{
tr = GetComponent<Transform>();
anim = GetComponent<Animation>();
anim.Play("Idle");
turnSpeed = 0.0f;
yield return new WaitForSeconds(0.3f);
turnSpeed = 80.0f;
}
[RequireComponent(typeof(AudioSource))]
public class FireCtrl : MonoBehaviour
{
public GameObject bullet;
public Transform firePos;
public AudioClip fireSfx;
private new AudioSource audio;
private MeshRenderer muzzleFlash;
void Start()
{
audio = GetComponent<AudioSource>();
muzzleFlash = firePos.GetComponentInChildren<MeshRenderer>();
muzzleFlash.enabled = false;
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0)) {
Fire();
}
}
void Fire() {
Instantiate(bullet, firePos.position, firePos.rotation);
audio.PlayOneShot(fireSfx, 1.0f);
StartCoroutine(ShowMuzzleFlash());
}
IEnumerator ShowMuzzleFlash() {
Vector2 offset = new Vector2(Random.Range(0, 2), Random.Range(0,2)) * 0.5f;
muzzleFlash.material.mainTextureOffset = offset;
float angle = Random.Range(0, 360);
muzzleFlash.transform.localRotation = Quaternion.Euler(0, 0, angle);
float scale = Random.Range(1.0f, 2.0f);
muzzleFlash.transform.localScale = Vector3.one * scale;
muzzleFlash.enabled = true;
yield return new WaitForSeconds(0.2f);
muzzleFlash.enabled = false;
}
}
public class RemoveBullet : MonoBehaviour
{
public GameObject sparkEffect;
void OnCollisionEnter(Collision coll) {
if (coll.collider.CompareTag("BULLET")) {
ContactPoint cp = coll.GetContact(0);
Quaternion rot = Quaternion.LookRotation(-cp.normal);
GameObject spark = Instantiate(sparkEffect, cp.point, rot);
Destroy(spark, 0.5f);
Destroy(coll.gameObject);
}
}
}
public class BulletCtrl : MonoBehaviour
{
public float damage = 20.0f;
public float force = 1500.0f;
private Rigidbody rb;
void Start() {
rb = GetComponent<Rigidbody>();
rb.AddForce(transform.forward * force);
}
}
public class BarrelCtrl : MonoBehaviour
{
public GameObject expEffect;
public Texture[] textures;
public float radius = 10.0f;
private new MeshRenderer renderer;
private Transform tr;
private Rigidbody rb;
private int hitCount = 0;
void Start()
{
tr = GetComponent<Transform>();
rb = GetComponent<Rigidbody>();
renderer = GetComponentInChildren<MeshRenderer>();
int idx = Random.Range(0, textures.Length);
renderer.material.mainTexture = textures[idx];
}
void OnCollisionEnter(Collision coll) {
if (coll.collider.CompareTag("BULLET")) {
if (++hitCount == 3) {
ExpBarrel();
}
}
}
void ExpBarrel() {
GameObject exp = Instantiate(expEffect, tr.position, Quaternion.identity);
Destroy(exp, 5.0f);
//rb.mass = 1.0f;
//rb.AddForce(Vector3.up * 1500.0f);
IndirectDamage(tr.position);
Destroy(gameObject, 3.0f);
}
//Collider[] colls = new Collider[10];
void IndirectDamage(Vector3 pos) {
Collider[] colls = Physics.OverlapSphere(pos, radius, 1 << 3); //Garbage Collection
//Physics.OverlapSphereNonAlloc(pos, radius, colls, 1 << 3);
foreach (Collider coll in colls) {
rb = coll.GetComponent<Rigidbody>();
rb.mass = 1.0f;
rb.constraints = RigidbodyConstraints.None;
rb.AddExplosionForce(1500.0f, pos, radius, 1200.0f);
}
}
}