๐ŸŽฎ Unity ํƒ€์›Œ ๋””ํŽœ์Šค ๊ฐœ๋ฐœ๊ธฐ - ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง - ๋ฐœ์‚ฌ์ฒด ์„ฑ๋Šฅ ์ตœ์ ํ™” ๐Ÿ›

5P2RS5ยท2026๋…„ 2์›” 10์ผ

์ปดํ“จํ„ฐ ์‚ด๋ฆฌ๊ธฐ

๋ชฉ๋ก ๋ณด๊ธฐ
7/8
post-thumbnail

2D ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ํƒ€์›Œ ๋””ํŽœ์Šค ๊ฒŒ์ž„์˜ ํ•ต์‹ฌ ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ ๊ณผ์ •
(์ง€๊ธˆ๊นŒ์ง€์˜ ์ž‘์—…๋ฌผ์„ AI๊ฐ€ ์ž‘์„ฑํ•ด์ค€ ๊ฐœ๋ฐœ์ผ์ง€)

๋ฌธ์ œ ๋ฐœ๊ฒฌ

ํƒ€์›Œ๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๊ฑด์„คํ•˜๊ณ  ์ ์ด ๋ชฐ๋ ค์˜ค๋ฉด ๋ˆˆ์— ๋„๋Š” ๋ ‰์ด ๋ฐœ์ƒํ–ˆ๋‹ค.
์›์ธ์€ ๋ช…ํ™•ํ–ˆ๋‹ค. BasicTower๊ฐ€ ๊ณต๊ฒฉํ•  ๋•Œ๋งˆ๋‹ค ๋ฐœ์‚ฌ์ฒด๋ฅผ Instantiate()๋กœ ์ƒ์„ฑํ•˜๊ณ , ๋ช…์ค‘ํ•˜๋ฉด Destroy()๋กœ ํŒŒ๊ดดํ•˜๋Š” ๊ตฌ์กฐ์˜€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

// ๊ธฐ์กด ์ฝ”๋“œ - ๋งค ๊ณต๊ฒฉ๋งˆ๋‹ค ์ƒ์„ฑ/ํŒŒ๊ดด ๋ฐ˜๋ณต
public override void Attack()
{
    GameObject projectileObj = Instantiate(projectilePrefab, spawnPos, Quaternion.identity);
    ...
}

void HitTarget()
{
    enemy.TakeDamage(damage);
    Destroy(gameObject); // ๋งค๋ฒˆ ํŒŒ๊ดด
}

ํƒ€์›Œ 10๊ฐœ๊ฐ€ ์ดˆ๋‹น 1๋ฐœ์”ฉ ์˜๋ฉด, ๋งค ์ดˆ๋งˆ๋‹ค 10๋ฒˆ์˜ Instantiate + 10๋ฒˆ์˜ Destroy๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
์ด ๊ณผ์ •์—์„œ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น โ†’ GC(๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜) ๋ฐœ๋™ โ†’ ํ”„๋ ˆ์ž„ ๋“œ๋ž์ด ์ผ์–ด๋‚œ๋‹ค.

ํ•ด๊ฒฐ: ์˜ค๋ธŒ์ ํŠธ ํ’€๋ง

์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์˜ ํ•ต์‹ฌ์€ ๊ฐ„๋‹จํ•˜๋‹ค.

ํŒŒ๊ดดํ•˜์ง€ ๋ง๊ณ  ์ˆจ๊ธฐ๊ณ , ์ƒ์„ฑํ•˜์ง€ ๋ง๊ณ  ๊บผ๋‚ด ์“ด๋‹ค.

๊ฒŒ์ž„ ์‹œ์ž‘ ์‹œ ๋ฐœ์‚ฌ์ฒด๋ฅผ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋†“๊ณ , ํ•„์š”ํ•  ๋•Œ ํ™œ์„ฑํ™”ํ•ด์„œ ์“ฐ๊ณ , ๋‹ค ์“ฐ๋ฉด ๋น„ํ™œ์„ฑํ™”ํ•ด์„œ ํ’€์— ๋Œ๋ ค๋†“๋Š”๋‹ค.

๊ธฐ์กด ๋ฐฉ์‹:
Attack โ†’ Instantiate() โ†’ ์ด๋™ โ†’ ๋ช…์ค‘ โ†’ Destroy() โ†’ GC ๋ถ€ํ•˜

ํ’€๋ง ๋ฐฉ์‹:
๊ฒŒ์ž„ ์‹œ์ž‘ โ†’ ๋ฏธ๋ฆฌ 20๊ฐœ ์ƒ์„ฑ (๋น„ํ™œ์„ฑํ™”)
    โ†“
Attack โ†’ Pool.Get() (ํ™œ์„ฑํ™”) โ†’ ์ด๋™ โ†’ ๋ช…์ค‘ โ†’ Pool.Return() (๋น„ํ™œ์„ฑํ™”)
    โ†“
๋‹ค์Œ Attack โ†’ ๊ฐ™์€ ์˜ค๋ธŒ์ ํŠธ ์žฌ์‚ฌ์šฉ

๊ตฌํ˜„

1. ProjectilePool.cs - ํ’€ ๋งค๋‹ˆ์ €

public class ProjectilePool : MonoBehaviour
{
    public static ProjectilePool Instance;

    [Header("Pool Settings")]
    public GameObject projectilePrefab;
    public int initialPoolSize = 20;

    private Queue<GameObject> pool = new Queue<GameObject>();

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializePool();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    void InitializePool()
    {
        for (int i = 0; i < initialPoolSize; i++)
        {
            CreateNewProjectile();
        }
    }

    void CreateNewProjectile()
    {
        GameObject obj = Instantiate(projectilePrefab, transform);
        obj.SetActive(false);
        pool.Enqueue(obj);
    }

    public GameObject Get(Vector3 position, Quaternion rotation)
    {
        if (pool.Count == 0)
        {
            CreateNewProjectile();
        }

        GameObject obj = pool.Dequeue();

        if (obj == null)
        {
            CreateNewProjectile();
            obj = pool.Dequeue();
        }

        obj.transform.SetParent(null);
        obj.transform.position = position;
        obj.transform.rotation = rotation;
        obj.SetActive(true);

        return obj;
    }

    public void Return(GameObject obj)
    {
        if (obj == null) return;

        obj.SetActive(false);
        obj.transform.SetParent(transform);
        pool.Enqueue(obj);
    }
}

Queue๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š” FIFO(์„ ์ž…์„ ์ถœ) ๊ตฌ์กฐ๋ผ์„œ ๊ฐ€์žฅ ์˜ค๋ž˜ ์‰ฐ ์˜ค๋ธŒ์ ํŠธ๋ถ€ํ„ฐ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. List๋กœ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ Queue๊ฐ€ ์˜๋ฏธ์ ์œผ๋กœ ๋” ๋งž๋‹ค.

ํ’€์ด ๋น„์—ˆ์„ ๋•Œ๋Š” CreateNewProjectile()๋กœ ์ž๋™ ํ™•์žฅ๋œ๋‹ค. ์ฒ˜์Œ์— 20๊ฐœ๋กœ ๋ถ€์กฑํ•˜๋ฉด ์•Œ์•„์„œ ๋Š˜์–ด๋‚˜๋‹ˆ๊นŒ ํ’€ ํฌ๊ธฐ๋ฅผ ์ •๋ฐ€ํ•˜๊ฒŒ ๊ณ„์‚ฐํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

2. Projectile.cs - ํ’€ ๋ฐ˜ํ™˜์œผ๋กœ ๋ณ€๊ฒฝ

public class Projectile : MonoBehaviour
{
    private Transform target;
    private float damage;
    private float speed;
    private bool isInitialized = false;
    private float lifetime = 7f;
    private float aliveTimer;
    private Vector3 lastDirection;

    public void Initialize(Transform target, float damage, float speed)
    {
        this.target = target;
        this.damage = damage;
        this.speed = speed;
        this.isInitialized = true;
        this.aliveTimer = 0f;
        this.lastDirection = Vector3.right;
    }

    void Update()
    {
        if (!isInitialized) return;

        // 7์ดˆ ์ดˆ๊ณผ ์‹œ ์ž๋™ ํšŒ์ˆ˜
        aliveTimer += Time.deltaTime;
        if (aliveTimer >= lifetime)
        {
            ReturnToPool();
            return;
        }

        // ํƒ€๊ฒŸ์ด ์žˆ์œผ๋ฉด ์ถ”์ , ์—†์œผ๋ฉด ๋งˆ์ง€๋ง‰ ๋ฐฉํ–ฅ์œผ๋กœ ์ง์ง„
        Vector3 direction;
        if (target != null)
        {
            direction = (target.position - transform.position).normalized;
            lastDirection = direction;
        }
        else
        {
            direction = lastDirection;
        }

        transform.position += direction * speed * Time.deltaTime;

        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        transform.rotation = Quaternion.Euler(0, 0, angle);

        if (target == null) return;
        float distanceToTarget = Vector3.Distance(transform.position, target.position);
        if (distanceToTarget < 0.2f)
        {
            HitTarget();
        }
    }

    void HitTarget()
    {
        Enemy enemy = target.GetComponent<Enemy>();
        if (enemy != null)
        {
            enemy.TakeDamage(damage);
        }

        ReturnToPool();
    }

    void ReturnToPool()
    {
        isInitialized = false;
        target = null;

        if (ProjectilePool.Instance != null)
        {
            ProjectilePool.Instance.Return(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

๋ณ€๊ฒฝ ํฌ์ธํŠธ ๋‘ ๊ฐ€์ง€:

Destroy โ†’ ReturnToPool: Destroy(gameObject) ๋Œ€์‹  ProjectilePool.Instance.Return()์„ ํ˜ธ์ถœํ•œ๋‹ค. ์ƒํƒœ(isInitialized, target)๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋น„ํ™œ์„ฑํ™”๋งŒ ํ•˜๋ฉด ๋.

ํƒ€๊ฒŸ ์†Œ์‹ค ์ฒ˜๋ฆฌ ๊ฐœ์„ : ๊ธฐ์กด์—๋Š” ํƒ€๊ฒŸ์ด ์‚ฌ๋ผ์ง€๋ฉด ๋ฐœ์‚ฌ์ฒด๊ฐ€ ๋ฐ”๋กœ ์—†์–ด์กŒ๋‹ค. ์ด๊ฑด ์‹œ๊ฐ์ ์œผ๋กœ ์–ด์ƒ‰ํ•˜๋‹ค. ์ ์ด ์ฃฝ์—ˆ๋Š”๋ฐ ๋‚ ์•„๊ฐ€๋˜ ํƒ„์•Œ์ด ๊ฐ‘์ž๊ธฐ ์‚ฌ๋ผ์ง€๋‹ˆ๊นŒ. ์ด์ œ๋Š” lastDirection์„ ์ €์žฅํ•ด์„œ ๋งˆ์ง€๋ง‰ ๋ฐฉํ–ฅ์œผ๋กœ ๊ณ„์† ์ง์ง„ํ•˜๋‹ค๊ฐ€ 7์ดˆ ํ›„ ์ž๋™ ํšŒ์ˆ˜๋œ๋‹ค.

ํƒ€๊ฒŸ ์‚ด์•„์žˆ์Œ โ†’ ์ถ”์  ์ด๋™
ํƒ€๊ฒŸ ์‚ฌ๋ผ์ง   โ†’ ๋งˆ์ง€๋ง‰ ๋ฐฉํ–ฅ์œผ๋กœ ์ง์ง„
7์ดˆ ๊ฒฝ๊ณผ      โ†’ ์ž๋™ ํ’€ ๋ฐ˜ํ™˜

3. BasicTower.cs - ํ’€์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ

public override void Attack()
{
    if (currentTarget == null) return;
    if (projectilePrefab == null) return;

    Vector3 spawnPos = firePoint != null ? firePoint.position : transform.position;
    GameObject projectileObj = null;

    if (ProjectilePool.Instance != null)
    {
        projectileObj = ProjectilePool.Instance.Get(spawnPos, Quaternion.identity);
    }
    else
    {
        // ํ’€์ด ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹ (ํด๋ฐฑ)
        projectileObj = Instantiate(projectilePrefab, spawnPos, Quaternion.identity);
    }

    Projectile projectile = projectileObj.GetComponent<Projectile>();
    if (projectile != null)
    {
        projectile.Initialize(currentTarget, damage, projectileSpeed);
    }

    OnAttackExecuted();
}

ProjectilePool.Instance๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด Instantiate ๋ฐฉ์‹์œผ๋กœ ํด๋ฐฑํ•œ๋‹ค. ํ’€์„ ์•ˆ ๋งŒ๋“ค์–ด๋„ ๊ฒŒ์ž„์ด ๋Œ์•„๊ฐ„๋‹ค.

์ •๋ฆฌ

ํ•ญ๋ชฉ๊ธฐ์กดํ’€๋ง ์ ์šฉ ํ›„
์ƒ์„ฑ๋งค ๊ณต๊ฒฉ๋งˆ๋‹ค Instantiate()๊ฒŒ์ž„ ์‹œ์ž‘ ์‹œ 1ํšŒ
์ œ๊ฑฐ๋งค ๋ช…์ค‘๋งˆ๋‹ค Destroy()SetActive(false)
GC ๋ฐœ์ƒ๋งค ํ”„๋ ˆ์ž„๊ฑฐ์˜ ์—†์Œ
ํƒ€๊ฒŸ ์†Œ์‹ค์ฆ‰์‹œ ํŒŒ๊ดด (์–ด์ƒ‰ํ•จ)์ง์ง„ ํ›„ 7์ดˆ ๋’ค ํšŒ์ˆ˜

์˜ค๋ธŒ์ ํŠธ ํ’€๋ง์€ Unity์—์„œ ๋ฐ˜๋ณต ์ƒ์„ฑ/ํŒŒ๊ดด๋˜๋Š” ์˜ค๋ธŒ์ ํŠธ(๋ฐœ์‚ฌ์ฒด, ์ดํŽ™ํŠธ, ์  ๋“ฑ)์— ๊ฑฐ์˜ ํ•„์ˆ˜์ ์œผ๋กœ ์ ์šฉํ•˜๋Š” ํŒจํ„ด์ด๋‹ค. ๊ตฌํ˜„ ์ž์ฒด๋Š” Queue ํ•˜๋‚˜๋กœ ๊ฐ„๋‹จํ•œ๋ฐ, ์„ฑ๋Šฅ ์ฐจ์ด๋Š” ํฌ๋‹ค.

profile
โ˜€๏ธ Infra_Architecture ๐Ÿ” ๐ŸŒ” Game_Dev

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