
๊ฒ์ ์์คํ ์ ์ฌ๋ฌ ํด๋์ค์์ ๊ฐํธํ ์ ๊ทผํ ์ ์๊ฒํ๋ค.
- ์ธ๋ถ ํด๋์ค์์ Player์ PoolManager์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
 - ์ฑ๊ธํค ํจํด์ ์ด์ฉํด ๊ตฌํํ์ฌ, ํน์ฌ๋ GameMangerํด๋์ค๊ฐ 2๊ฐ ์์ฑ๋๋ ๊ฒ์ ๋ง๋๋ค.
 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public PoolManager pool;
    public Player player;
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            if(instance != null)
                Destroy(this.gameObject);
        }
    }
}
์ค๋ธ์ ํธ ํ๋ง์ ์ํ ์คํฌ๋ฆฝํธ
- ๊ฒ์ ์ค ์ค๋ธ์ ํธ๋ฅผ ์์ฑ ๋ฐ ํ๊ดด๋ฅผ ํ๊ฒ๋๋ฉด ์ฑ๋ฅ์ ์ ํ๊ฐ ๋ฐ์ํ ์ ์๋ค.
 - SetActive๋ฅผ ์ฌ์ฉํด ์ค๋ธ์ ํธ์ ํ์ฑํ๋ก ์ฌํ์ฉํ์ฌ ํด๊ฒฐํ๋ค.
 - ๋ณธ ๊ฒ์์ ๋ง์ ์  ๋ฐ ๊ณต๊ฒฉ ํํ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํ์๋ถ๊ฐ๊ฒฐํ๋ค.
 - pools์ ๋ด๊ธด ์ค๋ธ์ ํธ๊ฐ ๋น์ด์๊ฑฐ๋ ๋ชจ๋ ํ์ฑํ ์ํ์ผ ๊ฒฝ์ฐ Instantiate์ผ๋ก ์๋ก ์์ฑํ๋ค.
 - ๋นํ์ฑํ๋ ์ค๋ธ์ ํธ๊ฐ ์๋ค๋ฉด, ํ์ฑํ ์์ผ์ค๋ค.
 
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
    public GameObject[] prefabs;
    private List<GameObject>[] pools;
    private void Awake()
    {
        pools = new List<GameObject>[prefabs.Length];
        for (int i = 0; i < pools.Length; i++)
        {
            pools[i] = new List<GameObject>();
        }
    }
    public GameObject Get(int index)
    {
        GameObject select = null;
        foreach (GameObject item in pools[index])
        {
            if (item.activeSelf == false)
            {
                select = item;
                select.SetActive(true);
                break;
            }
        }
        if (select == null)
        {
            select = Instantiate(prefabs[index], transform);
            pools[index].Add(select);
        }
        return select;
    }
}
์  ๊ฐ์ง๋ฅผ ์ํ ์คํฌ๋ฆฝํธ
- Raycast๋ฅผ ์ด์ฉํด ์ ์ ํ์งํ๋ค.
 - CircleCastAll : ํน์  ๋ฒ์ ๋ฐ ์์น์ ์ค๋ธ์ ํธ๋ค์ ๊ฐ์งํ๋ค.
 
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Scanner : MonoBehaviour
{
    public float scanRange;
    public LayerMask targerLayer;
    public RaycastHit2D[] targets;
    public Transform nearestTarget;
    private void FixedUpdate()
    {
        targets = Physics2D.CircleCastAll(transform.position, scanRange, Vector3.zero, 0, targerLayer);
        nearestTarget = GetNearest();
    }
    Transform GetNearest()
    {
        Transform result = null;
        float diff = 100;
        
        foreach (RaycastHit2D target in targets)
        {
            Vector3 myPos = transform.position;
            Vector3 targetPos = target.transform.position;
            float curDiff = Vector3.Distance(myPos, targetPos);
            if (curDiff < diff)
            {
                diff = curDiff;
                result = target.transform;
            }
        }
        return result;
    }
}
์  ์์ฑ ์คํฌ๋
- ์ ์ ์ํํ๋ ์คํฌ๋ฆฝํธ
 - SpawnData ํด๋์ค ์ฌ์ฉ
 
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
[System.Serializable]
public class SpawnData
{
    public float spawnTime;
    public int spriteType;
    public int health;
    public float speed;
}
public class Spawner : MonoBehaviour
{
    //public Transform[] spawnPoint; // ์คํฐ์ง์ ์ด ๋ค์์ผ ๋
    public Transform spawnPoint;
    public SpawnData[] spawnDatas;
    
        
    private int level;
    private float timer;
    private void Awake()
    {
        //spawnPoint = GetComponentsInChildren<Transform>();
    }
    void Update()
    {
        timer += Time.deltaTime;
        // level = Mathf.Min(Mathf.FloorToInt(GameManager.instance.gameTime / 10f), spawnDatas.Length - 1);
        // Mathf.FloorToInt : ์์์  ์๋๋ ๋ฒ๋ฆฌ๊ณ  Intํ์ผ๋ก ๋ฐ๊พธ๋ ํจ์ ex) 3.4 -> 3
        // Mathf.CeilToInt : ์์์  ์๋๋ฅผ ์ฌ๋ฆฌ๊ณ  Intํ์ผ๋ก ๋ฐ๊พธ๋ ํจ์ ex) 3.4 -> 4
        
        if (timer > spawnDatas[level].spawnTime)
        {
            timer = 0f;
            Spawn();
        }
    }
    void Spawn()
    {
        GameObject enemy = GameManager.instance.pool.Get(1);
        enemy.transform.position = spawnPoint.position;
        enemy.GetComponent<Enemy>().Init(spawnDatas[level]);
    }
}
์ ์ ์๋, ์ฒด๋ ฅ, ์ด๋ ๋ฑ์ ๊ด๋ฆฌํ๋ ์คํฌ๋ฆฝํธ
- Dead() ํจ์๋ ์ ๋๋ฉ์ด์  ์ด๋ฒคํธ๋ฅผ ์ด์ฉํ์ฌ ์  ์ฌ๋ง์ 1์ด ๋ค์ ์์ฒด๊ฐ ์ฌ๋ผ์ง๋ค.
 
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
    public float speed;
    public float health; // ํ์ฌ ์ฒด๋ ฅ
    public float maxHealth;
    public RuntimeAnimatorController[] animCon;
    public Rigidbody2D target;
    private bool isLive;
    private Rigidbody2D rigid;
    private Collider2D coll;
    private Animator anim;
    private SpriteRenderer spriter;
    private WaitForFixedUpdate wait;
    
    void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        spriter = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        wait = new WaitForFixedUpdate();
        coll = GetComponent<Collider2D>();
    }
    void FixedUpdate()
    {
        // GetCurrentAnimatorStateInfo : ํ์ฌ ์ ๋๋ฉ์ดํฐ์ ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์
        if(!isLive || anim.GetCurrentAnimatorStateInfo(0).IsName("Hit"))
            return;
        
        // 1. ํ๋ ์ด์ด๊ฐ ์์นํ ๋ฐฉํฅ ๊ตฌํ๊ธฐ
        Vector2 dirVec = target.position - rigid.position;
        // 2. ํ๋ ์ด์ด๊ฐ ์์นํ ๊ณณ์ผ๋ก ์ด๋ป๊ฒ ์์ง์ผ์ง ๊ณ์ฐ
        Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime;
        // 3. MovePosition์ผ๋ก ํ์ฌ ์์น์์ ๋ค์ ์์น๋ก ์ด๋ ์ํ
        rigid.MovePosition(rigid.position + nextVec);
        // 4. ๋ฆฌ์ง๋๋ฐ๋์ ๋ฌผ๋ฆฌ ์๋๊ฐ ์ด๋์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ๋ณด์ํ๊ธฐ
        rigid.velocity = Vector2.zero;
    }
    private void LateUpdate()
    {
        if(!isLive)
            return;
        spriter.flipX = target.position.x < rigid.position.x;
    }
    private void OnEnable()
    {
        target = GameManager.instance.player.GetComponent<Rigidbody2D>();
        isLive = true;
        coll.enabled = true;
        rigid.simulated = true; // ๋ฆฌ์ง๋๋ฐ๋ ์ปดํฌ๋ํธ ๋นํ์ฑํ๋ simulated์์ ๊ด๋ฆฌ
        spriter.sortingOrder = 2;
        anim.SetBool("Dead", false);
        health = maxHealth;
    }
    public void Init(SpawnData data)
    {
        anim.runtimeAnimatorController = animCon[data.spriteType];
        speed = data.speed;
        maxHealth = data.health;
        health = data.health;
    }
    void Dead()
    {
        gameObject.SetActive(false);
    }
    IEnumerator KnockBack()
    {
        yield return wait; // ํ๋์ ๋ฌผ๋ฆฌํ๋ ์์ ๋๋ ์ด
        Vector3 playerPos = GameManager.instance.player.transform.position;
        Vector3 dirVec = transform.position - playerPos;
        rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
    }
    
    private void OnTriggerEnter2D(Collider2D col)
    {
        if (!col.CompareTag("Bullet") || !isLive)
            return;
        
        health -= col.GetComponent<Bullet>().damage;
        StartCoroutine("KnockBack");
        
        if (health > 0)
        {
            // .. Live, Hit Action
            anim.SetTrigger("Hit");
        }
        else
        {
            // .. Die
            isLive = false;
            coll.enabled = false;
            rigid.simulated = false; // ๋ฆฌ์ง๋๋ฐ๋ ์ปดํฌ๋ํธ ๋นํ์ฑํ๋ simulated์์ ๊ด๋ฆฌ
            spriter.sortingOrder = 1;
            anim.SetBool("Dead", true);
        }
    }
}
์ ์ ๊ณต๊ฒฉํ๋ ํ์ ์คํฌ๋ฆฝํธ
- Scanner์คํฌ๋ฆฝํธ์์ ๋ฐ๊ฒฌํ ๊ฐ์ฅ ๊ฐ๊น์ด ์ ์ ๊ณต๊ฒฉํ๋ค.
 
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tower : MonoBehaviour
{
    public int towerType; // ํ์ ์ ํ
    public int damage; // ํ์ ๋ฐ๋ฏธ์ง
    public float firePeriod; // ๋ฐ์ฌ ์ฃผ๊ธฐ
    public float speed; // ๋ฐ์ฌ ์๋
    
    private float timer;
    private Scanner scanner;
    private Quaternion towerLook;
    private void Awake()
    {
        scanner = GetComponent<Scanner>();
    }
    
    void Update()
    {
        timer += Time.deltaTime;
        if (timer > firePeriod)
        {
            timer = 0f;
            Fire();
        }
    }
    void Fire()
    {
        if (!scanner.nearestTarget)
            return;
        Debug.Log("๋ฐ์ฌ์ค : " + scanner.nearestTarget);
        Vector3 targetPos = scanner.nearestTarget.position;
        Vector3 dir = targetPos - transform.position;
        dir = dir.normalized;
        Transform bullet = GameManager.instance.pool.Get(towerType).transform;
        bullet.position = transform.position;
        bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir);
        bullet.GetComponent<Bullet>().Init(0, damage, speed, dir);
    }
}
ํ์์์ ๋ฐ์ฌ๋ ์ด์ ์คํฌ๋ฆฝํธ
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
    public float damage;
    public int towerType;
    private Rigidbody2D rigid;
    
    private void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
    }
    public void Init(int towerType, float damage, float speed, Vector3 dir)
    {
        this.damage = damage;
        if (towerType == 0) // normal Tower
        {
            rigid.velocity = speed * dir;
        }
    }
    
    private void OnTriggerEnter2D(Collider2D col)
    {
        if (!col.CompareTag("Enemy"))
        {
            return;
        }
        rigid.velocity = Vector2.zero;
        gameObject.SetActive(false);
    }
    
    private void OnTriggerExit2D(Collider2D col)
    {
        if (col.CompareTag("Border"))
        {
            rigid.velocity = Vector2.zero;
            gameObject.SetActive(false);
        }
        
    }
}