Unity | ๋Œ€ํฌ์˜๊ธฐ๐Ÿš€ (์˜ค๋ธŒ์ ํŠธ ํ’€๋ง)

Cleanยท2025๋…„ 4์›” 15์ผ

Unity

๋ชฉ๋ก ๋ณด๊ธฐ
4/24
post-thumbnail

์˜ค๋Š˜์€ ํƒฑํฌ๋ฅผ ์›€์ง์ด๊ณ  ๋Œ€ํฌ๋ฅผ ์˜๋Š” ๊ธฐ๋Šฅ์„

์ง์ ‘ ๊ตฌํ˜„ํ•œ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง๊ณผ ๋‚ด์žฅ๋œ ObjectPool<T> ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ด๋ณผ ์ƒ๊ฐ์ด๋‹ค.


์ด๋™

์šฐ์„  ํƒฑํฌ๊ฐ€ ์›€์ง์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋‘ ๋ฐ”ํ€ด์— Mesh Collider ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

Mesh Collider์˜ Convex๋ฅผ ์ฒดํฌํ•˜๋ฉด 3D ๋ชจ๋ธ์˜ ํ˜•ํƒœ๋Œ€๋กœ ์ฝœ๋ผ์ด๋”๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๋ฐ,

์„ฑ๋Šฅ ๋ถ€๋‹ด์ด ํฌ๋ฏ€๋กœ ์ƒ๊ฐํ•˜๊ณ  ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.โ—

๋‹ค๋งŒ, ์ด๋ฒˆ์—๋Š” ์›€์ง์ด๋Š” ์˜ค๋ธŒ์ ํŠธ๋Š” ์ด ํƒฑํฌ๋ฟ์ด๋ผ ๊ถ๊ธˆํ•ด์„œ ์ ์šฉํ•ด๋ดค๋‹ค.


์ด๋™์ฝ”๋“œ

public float moveSpeed = 5f;
public float turnSpeed = 100f;

void Update()
{
	// ํšŒ์ „
float bodyH = 0f;
if (Input.GetKey(KeyCode.LeftArrow))
	bodyH = -1f;
else if (Input.GetKey(KeyCode.RightArrow))
	bodyH = 1f;

transform.Rotate(Vector3.up, bodyH * turnSpeed * Time.deltaTime);

// ์ด๋™
float bodyV = 0f;
if (Input.GetKey(KeyCode.UpArrow))
	bodyV = 1f;
else if (Input.GetKey(KeyCode.DownArrow))
	bodyV = -1f;

transform.Translate(Vector3.forward * bodyV * moveSpeed * Time.deltaTime);
}

// ๋จธ๋ฆฌ ํšŒ์ „
float headH = 0f;
if (Input.GetKey(KeyCode.A))
	headH = -1f;
else if (Input.GetKey(KeyCode.D))
	headH = 1f;

head.transform.Rotate(Vector3.up, headH * turnSpeed / 2 * Time.deltaTime);

// ๋จธ๋ฆฌ ๊ฐ๋„ x๊ฐ’๋งŒ ์กฐ์ ˆ
float headV = 0f;
if (Input.GetKey(KeyCode.W))
	headV = -1f;
else if (Input.GetKey(KeyCode.S))
	headV = 1f;

head.transform.Rotate(Vector3.right, headV * turnSpeed / 2 * Time.deltaTime);

์ด ์ฝ”๋“œ๋Š” Update() ํ•จ์ˆ˜์—์„œ ํ˜ธ์ถœ๋œ๋‹ค.

WASD๋กœ ๋จธ๋ฆฌ ๋ฐฉํ–ฅ์„ ์กฐ์ ˆํ•˜๊ณ  ๋ฐฉํ–ฅํ‚ค๋กœ๋Š” ํƒฑํฌ๋ฅผ ์›€์ง์ด๋Š” ๋ฐฉ์‹์œผ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.

Rotate๋Š” ํ˜„์žฌ ํšŒ์ „๊ฐ’์— ๊ณ„์† ๋ˆ„์ ์‹œํ‚ค๋Š” ํ•จ์ˆ˜๋ผ ๋จธ๋ฆฌ๊ฐ€ ์ž…๋ ฅํ•œ ๋ฐฉํ–ฅ์ชฝ์œผ๋กœ ์„œ์„œํžˆ ๋ˆ๋‹ค.

Translate๋Š” ๊ฒŒ์ž„ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํŠน์ • ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™์‹œํ‚ค๋Š” ํ•จ์ˆ˜๋ผ

Vector3.foward์„ ์‚ฌ์šฉํ•ด ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ๋ฐ”๋ผ๋ณด๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™์‹œํ‚จ๋‹ค.


ํฌํƒ„ ํ”„๋ฆฌํŽฉ

ํฌํƒ„์„ ๋ฐœ์‚ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ•„์š”ํ•œ ๊ฒƒ์€ ๋ฌด์—‡์ผ๊นŒ?

๊ทธ์•ผ ๋‹น์—ฐํžˆ ํฌํƒ„์ด๋‹ค.

ํฌํƒ„์€ ์ปค์Šคํ…€ ํ’€๋ง์— ์‚ฌ์šฉํ•  Bullet๊ณผ ๋‚ด์žฅ ํ’€๋ง์— ์‚ฌ์šฉํ•  NewBullet์„ ๋งŒ๋“ค์—ˆ๋‹ค.

Bullet ์ฝ”๋“œ

public class Bullet : MonoBehaviour
{
	public Tank tank;
	Rigidbody rigid;

	void Awake()
	{
		rigid = GetComponent<Rigidbody>();
	}

	void OnEnable()
	{
		// 3์ดˆ ์‚ญ์ œ
		Invoke("SetTimerOff", 3f);
	}

	void SetTimerOff()
	{
		gameObject.SetActive(false);
		rigid.velocity = Vector3.zero;
	}

	void OnTriggerEnter(Collider other)
	{
		gameObject.SetActive(false);
		rigid.velocity = Vector3.zero;

		GameObject effect = tank.GetEffect();

		if (effect != null)
		{
			effect.transform.position = transform.position;
			effect.transform.rotation = Quaternion.identity;
			effect.SetActive(true);
		}
	}
}

NewBullet ์ฝ”๋“œ

public class NewBullet : MonoBehaviour
{
	public ObjectPool<NewBullet> objPool;

	void OnTriggerEnter(Collider other)
	{
		// ์ถฉ๋Œ์‹œ ๋ฆด๋ฆฌ์ฆˆ ๋‹ค์‹œ ํ’€๋กœ
		objPool.Release(this);
	}
}

ํฌํƒ„ ํ”„๋ฆฌํŽฉ๋“ค์„ ๋งŒ๋“ค๊ณ  ์œ„์˜ ์ฝ”๋“œ๋“ค์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

๋‚ด์žฅ ํ’€๋ง์ด ๋ฒŒ์จ ์ฝ”๋“œ ๊ธธ์ด๊ฐ€ ํ™• ๋‚œ๋‹ค. ๐Ÿ˜ฎ


์ปค์Šคํ…€ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง

์ปค์Šคํ…€ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง ์ฝ”๋“œ

// ์ด์•Œ, ์ดํŽ™ํŠธ
public GameObject bullet;
public GameObject bulletEffect;
// ์ด์•Œ ๋ฐฐ์—ด, ์ดํŽ™ํŠธ ๋ฐฐ์—ด
public GameObject[] bullets;
public GameObject[] bulletEffects;


// ์ปค์Šคํ…€ ํ’€
// Awake()
bullets = new GameObject[5];
for (int i = 0; i < bullets.Length; i++)
{
	bullets[i] = Instantiate(bullet, bulletBox.transform);
	bullets[i].SetActive(false);
}

bulletEffects = new GameObject[15];
for (int i = 0; i < bulletEffects.Length; i++)
{
	bulletEffects[i] = Instantiate(bulletEffect, effectBox.transform);
	bulletEffects[i].SetActive(false);
}
// ๋ฐ˜ํ™˜ ํ•จ์ˆ˜
GameObject GetBullet()
{
	for (int i = 0; i < bullets.Length; i++)
	{
		if (!bullets[i].activeSelf)
			return bullets[i];
	}
	return null;
}
public GameObject GetEffect()
{
	for (int i = 0; i < bulletEffects.Length; i++)
	{
		if (!bulletEffects[i].activeSelf)
			return bulletEffects[i];
	}
	return null;
}

๋ฐœ์‚ฌํ•˜๊ธฐ ์ „, Tank ํด๋ž˜์Šค์— ์ด์•Œ๊ณผ ์ดํŽ™ํŠธ๋ฅผ ๋ณด๊ด€, ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•„๋“œ์™€ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

  • bullet ํฌํƒ„ ํ”„๋ฆฌํŽฉ

  • bullets ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•  ํฌํƒ„ ์˜ค๋ธŒ์ ํŠธ๋“ค์„ ๋‹ด์„ ๋ฐฐ์—ด

  • bulletEffect ํฌํƒ„ ํ„ฐ์ง€๋Š” ์ดํŽ™ํŠธ

  • bulletEffects ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•  ํฌํƒ„ ์ดํŽ™ํŠธ ์˜ค๋ธŒ์ ํŠธ๋“ค์„ ๋‹ด์„ ๋ฐฐ์—ด

  • GetBullet()์€ ์‚ฌ์šฉ์ค‘์ด์ง€ ์•Š์€ ํฌํƒ„ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฐ˜ํ™˜

  • GetEffect()๋Š” ์‚ฌ์šฉ์ค‘์ด์ง€ ์•Š์€ ์ดํŽ™ํŠธ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฐ˜ํ™˜

Awake() ํ•จ์ˆ˜์—์„œ ์ดˆ๊ธฐํ™”๋  ๋•Œ Instantiate() ํ•จ์ˆ˜๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•œ ํ›„ ๋ณด์ด์ง€ ์•Š๊ฒŒ ๋น„ํ™œ์„ฑํ™” ํ•ด๋’€๋‹ค.


๋‚ด์žฅ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง

๋‚ด์žฅ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง ์ฝ”๋“œ

// ๋‚ด์žฅ ํ’€
newBulletPool = new ObjectPool<NewBullet>(
	// Func < T > createFunc
	() => Instantiate(newBullet, bulletStart.transform.position, bulletStart.transform.rotation),

	// Action<T> actionOnGet
	bullet => {
		// ์ด์•Œ์˜ ์˜ค๋ธŒ์ ํŠธ
		GameObject newBulletObject = bullet.gameObject;
		// NewBullet ์ปดํฌ๋„ŒํŠธ
		NewBullet newBulletLogic = bullet.GetComponent<NewBullet>();

		// ์œ„์น˜ ์ง€์ •
		newBulletObject.transform.position = bulletStart.transform.position;
		newBulletObject.transform.rotation = bulletStart.transform.rotation;

		// NewBullet ํ•„๋“œ์— ํƒฑํฌ์™€ Release๋ฅผ ์œ„ํ•œ ํ’€ ์ „๋‹ฌ(๊ฒŒ์ž„๋งค๋‹ˆ์ €๊ฐ€์—†์–ด์„œ)
		newBulletLogic.objPool = newBulletPool;

		// ์ด์•Œ ํ™œ์„ฑํ™”
		newBulletObject.SetActive(true);

		// 30f ํŒŒ์›Œ๋กœ ๋ฐœ์‚ฌ
		Rigidbody bulletRigid = newBulletObject.GetComponent<Rigidbody>();
		bulletRigid.AddForce(newBulletObject.transform.forward * 30f, ForceMode.Impulse);
	},

	// Action<T> actionOnRelease
	bullet => {
		// ์†๋„ 0๋กœ ๋‹ค์Œ์— ํ™œ์„ฑํ™”๋  ๋•Œ ์ค‘์ฒฉ๋จ
		bullet.GetComponent<Rigidbody>().velocity = Vector3.zero;
		// ์ดํŽ™ํŠธ
		GameObject effect = GetEffect();

		if (effect != null)
		{
			effect.transform.position = bullet.transform.position;
			effect.transform.rotation = bullet.transform.rotation;
			effect.SetActive(true); // ์ดํŽ™ํŠธ์—์„œ ์ž๋™์œผ๋กœ ๋น„ํ™œ์„ฑํ™”
		}
		// ์ด์•Œ ๋น„ํ™œ์„ฑํ™”
		bullet.gameObject.SetActive(false);
	},

	// Action<T> actionOnDestroy
	bullet => Destroy(bullet.gameObject),

	// bool Collection Check ์–ด๋””์— ์“ฐ์ง€?
	true,

	// int defaultCapacity, int maxSize
	5, 5
);

ObjectPool API

๋‚ด์žฅ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์€ ํ’€๋ง์„ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ๋Œ€๋ถ€๋ถ„์˜ ์ƒํ™ฉ? ์„ ์ •ํ•ด๋‘๋Š” ๋А๋‚Œ์ธ ๊ฒƒ ๊ฐ™๋‹ค.

  • Func T createFunc : ํ’€์— ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์—†์„ ๋•Œ

  • Action T actionOnGet : Get()์œผ๋กœ ๊บผ๋‚ผ ๋•Œ

  • Action T actionOnRelease : Release()๋กœ ๋ฐ˜ํ™˜ํ•  ๋•Œ

  • Action T actionOnDestroy : ์นด์šดํŠธ๋ฅผ ์ดˆ๊ณผํ•˜์—ฌ ํŒŒ๊ดดํ•  ๋•Œ

  • collectionCheck : ๋””๋ฒ„๊น…์šฉ์ด๋ผ๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ์“ฐ๋Š”์ง„ ๋ชจ๋ฅด๊ฒ ๋‹ค..

  • defaultCapacity : ์ตœ์†Œ ๊ฐœ์ˆ˜

  • maxSize : ์ตœ๋Œ€ ๊ฐœ์ˆ˜


๋ฐœ์‚ฌ (์ปค์Šคํ…€ ํ’€๋ง)

๋ฐœ์‚ฌ (์ปค์Šคํ…€ ํ’€๋ง) ์ฝ”๋“œ

// Update()
// ์ปค์Šคํ…€ ํ’€ ํฌํƒ„ ๋ฐœ์‚ฌ
if (Input.GetKey(KeyCode.Space))
{
	// ์ฟจ์ผ๋•Œ๋Š” ํŒŒ์›Œ ๊ณ„์† ์ดˆ๊ธฐํ™” = ์žฅ์ „๋ชปํ•ด๋‘๊ฒŒ
	if (curCool < maxCool)
		curPower = 0;
	if (curPower < maxPower)
		curPower += Time.deltaTime * 30f; // ๋ˆ„๋ฅด๋Š”๋™์•ˆ ํŒŒ์›Œ์ƒ์Šน
}

if (Input.GetKeyUp(KeyCode.Space))
{
	if (curCool < maxCool)
	{
		curPower = 0;
		return;
	}

	// ์ตœ์†Œ ํŒŒ์›Œ 20
	if (curPower < 20f)
		curPower = 20f;

	// ํฌํƒ„ ๋ฐœ์‚ฌ
	GameObject newbullet = GetBullet();
	if (newbullet != null)
	{
		Bullet bulletLogic = newbullet.GetComponent<Bullet>();
		newbullet.transform.position = bulletStart.transform.position;
		newbullet.transform.rotation = bulletStart.transform.rotation;
		newbullet.SetActive(true);
		bulletLogic.tank = this;

		Rigidbody bulletRigid = newbullet.GetComponent<Rigidbody>();
		bulletRigid.AddForce(newbullet.transform.forward * curPower, ForceMode.Impulse);
	}

	// ํŒŒ์›Œ ์ฟจ ์ดˆ๊ธฐํ™”
	curPower = 0;
	curCool = 0;
}
ํƒฑํฌ TankTurret์˜ ์ž์‹์œผ๋กœ ๋นˆ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ƒ์„ฑ, ์ด ์œ„์น˜์— ํฌํƒ„์„ ์ƒ์„ฑํ•œ๋‹ค.

์ŠคํŽ˜์ด์Šค๋ฐ”๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์œผ๋ฉด ์ตœ๋Œ€ 50f ๊นŒ์ง€ ํŒŒ์›Œ๊ฐ€ ์Œ“์ด๊ณ  ํ‚ค๋ฅผ ๋–ผ๋ฉด ๋ฐœ์‚ฌํ•˜๋Š” ์ฐจ์ง• ๊ธฐ๋Šฅ๊ณผ

์ฟจํƒ€์ž„ ์ค‘์—๋Š” ๋ฐœ์‚ฌ๋ฅผ ๋ชปํ•˜๊ฒŒ ํ•˜๋Š” ์ฟจํƒ€์ž„ ๊ธฐ๋Šฅ๋„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.


๊ธฐ๋ณธ์ ์ธ ๋ฐœ์‚ฌ ํ๋ฆ„์€

1. ์ŠคํŽ˜์ด์Šค๋ฐ” ํ‚ค๋ฅผ ๋ˆ„๋ฆ„

2. ํƒฑํฌ์˜ ํฌ์‹  ์•ž ํ—ˆ๊ณต์˜ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•ด์„œ ๋ณ€์ˆ˜๋กœ ์ €์žฅ spawnPos, spawnRot

3. Getbullet() ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉ์ค‘์ด์ง€ ์•Š์€ ํฌํƒ„ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฐ›์•„์˜ด

4. ํฌํƒ„ ์˜ค๋ธŒ์ ํŠธ์˜ ์œ„์น˜, ํšŒ์ „ ๋ณ€๊ฒฝ

5. ํฌํƒ„์˜ Rigidbody๋ฅผ ๊ฐ€์ ธ์™€ AddForce() ๋กœ ํž˜ ์ ์šฉ

6. ์Š~ ๐Ÿš€

์œ„์— ์ ์–ด๋‘” Bullet ํด๋ž˜์Šค์˜ ์ฝ”๋“œ์—์„œ

void OnTriggerEnter(Collider other)
{
	gameObject.SetActive(false);
	rigid.velocity = Vector3.zero;

	GameObject effect = tank.GetEffect();

	if (effect != null)
	{
		effect.transform.position = transform.position;
		effect.transform.rotation = Quaternion.identity;
		effect.SetActive(true);
	}
}

ํฌํƒ„์ด ๋‹ค๋ฅธ ์˜ค๋ธŒ์ ํŠธ์™€ ๋ถ€๋”ช์น˜๋ฉด ํฌํƒ„์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ , ์†๋„๋ฅผ 0์œผ๋กœ ๋งŒ๋“ ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ทธ ์œ„์น˜์— ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์—์„œ ์ดํŽ™ํŠธ๋ฅผ ๋ฐ˜ํ™˜๋ฐ›์•„ ํฌํƒ„์ด ํ„ฐ์ง€๋Š” ์ดํŽ™ํŠธ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.


๋ฐœ์‚ฌ (๋‚ด์žฅ ํ’€๋ง)

๋ฐœ์‚ฌ (๋‚ด์žฅ ํ’€๋ง) ์ฝ”๋“œ

// ๋‚ด์žฅ ํ’€ ๋ฐœ์‚ฌ
if (Input.GetKeyDown(KeyCode.LeftControl))
{
	// ์˜ค๋ธŒ์ ํŠธ ํ’€์—์„œ ๊ฐ€์ ธ์˜ด
	newBulletPool.Get();
}

๋‚ด์žฅ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์€ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ์ƒํ™ฉ์— ๋”ฐ๋ฅธ ์ฝ”๋“œ๋ฅผ ๋„ฃ์—ˆ๊ธฐ์—,

์ปค์Šคํ…€ ํ’€๋ง์— ๋น„ํ•˜๋ฉด ์—„์ฒญ๋‚˜๊ฒŒ ์งง๋‹ค.


์•„์ง ๋ถ€์กฑํ•ด ๋ณด์ด์ง€๋งŒ ์ปค์Šคํ…€ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง๊ณผ ๋‚ด์žฅ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์œผ๋กœ ๋ฐœ์‚ฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

ํ™•์‹คํžˆ ์„ค๊ณ„๋ฅผ ํ•˜๊ณ  ์ฝ”๋”ฉ์„ ํ•œ๋‹ค๋ฉด ๋ฏธ๋ฆฌ ์ƒํ™ฉ์— ๋”ฐ๋ฅธ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค๊ณ ,

๊ธฐ๋Šฅ ์ˆ˜์ •์‹œ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ์œ„์น˜์—์„œ ํ•˜๋ฉด๋˜๋‹ˆ ๋‚ด์žฅ ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์ด ํŽธํ•ด๋ณด์ธ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€