๐ŸซงArt_028 Mesh Spawner Manager (ํด๋ฆญ&ํญ๋ฐœ๊นŒ์ง€)

BamgasiJMยท2026๋…„ 4์›” 18์ผ

Unity GenArt

๋ชฉ๋ก ๋ณด๊ธฐ
39/41
post-thumbnail

1. ๋™์ž‘ ์›๋ฆฌ ๋ฐ ๊ตฌํ˜„

1.1 ์‹œ์Šคํ…œ ์ „์ฒด ๊ตฌ์„ฑ

[์ƒ์„ฑ ๋ชจ๋“ˆ] -> [๋ฌผ๋ฆฌ ์„ค์ • ๋ชจ๋“ˆ] -> [์ž…๋ ฅ ๊ฐ์ง€ ๋ชจ๋“ˆ] -> [ํญ๋ฐœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ชจ๋“ˆ]

1. ์ƒ์„ฑ ๋ชจ๋“ˆ (Spawning)

  • spawnRate ์— ๋”ฐ๋ผ ์ •ํ•ด์ง„ ๋นˆ๋„๋กœ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ.
  • spawnArea ๋ฒ”์œ„ ๋‚ด์—์„œ ๋žœ๋คํ•œ X, Z ์ขŒํ‘œ๋ฅผ ์„ ํƒํ•˜๊ณ , ๊ณ ์ •๋œ spawnHeight๋ฅผ Y ์ขŒํ‘œ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต์ค‘์—์„œ ๋‚™ํ•˜.
  • spawnPrefabs ๋ฐฐ์—ด์—์„œ ๋žœ๋ค ์ธ๋ฑ์Šค๋ฅผ ์„ ํƒํ•˜์—ฌ ๋‹ค์–‘ํ•œ ํ”„๋ฆฌํŒน์ด ํ˜ผํ•ฉ๋˜์–ด ์ƒ์„ฑ.

2. ๋ฌผ๋ฆฌ ์„ค์ • ๋ชจ๋“ˆ (Physics Setup)

  • ์ƒ์„ฑ๋œ ์ธ์Šคํ„ด์Šค์— Rigidbody์™€ Collider๊ฐ€ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ์ถ”๊ฐ€.
  • ContinuousDynamic ์ถฉ๋Œ ๊ฐ์ง€ ๋ชจ๋“œ๋ฅผ ์„ค์ •ํ•˜์—ฌ ๊ณ ์† ์ด๋™์‹œ ํŠ•๊น€ ํ˜„์ƒ (ํ„ฐ๋„๋ง)์„ ๋ฐฉ์ง€.
  • linearDamping, angularDamping์„ ์กฐ์ •ํ•˜์—ฌ ๊ณต๊ธฐ ์ €ํ•ญ ๋“ฑ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ์‡  ํšจ๊ณผ ๋ถ€์—ฌ.

3. ์ž…๋ ฅ ๊ฐ์ง€ ๋ชจ๋“ˆ (Input Handling)

  • Unity New Input System (UnityEngine.InputSystem)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งˆ์šฐ์Šค ์™ผ์ชฝ ํด๋ฆญ์„ ๊ฐ์ง€.
  • Camera.main.ScreenPointToRay ๋กœ ๋งˆ์šฐ์Šค ์œ„์น˜์—์„œ 3D ๊ณต๊ฐ„์œผ๋กœ Ray๋ฅผ ๋ฐœ์‚ฌ.
  • Physics.RaycastAll ์„ ์‚ฌ์šฉํ•˜์—ฌ Ray ๊ฒฝ๋กœ์ƒ์˜ ๋ชจ๋“  ์ฝœ๋ผ์ดํ„ฐ๋ฅผ ๊ฒ€์‚ฌ. ๋‹จ์ผ Raycast์™€ ๋‹ฌ๋ฆฌ ๊ฒน์ณ์ง„ ์˜ค๋ธŒ์ ํŠธ ์ค‘ ๋’ค์— ์žˆ๋Š” "ํญ๋ฐœ ํŠธ๋ฆฌ๊ฑฐ"๋„ ๊ฐ์ง€.

4. ํญ๋ฐœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ชจ๋“ˆ (Explosion Physics)

  • ํด๋ฆญ๋œ ํŠธ๋ฆฌ๊ฑฐ ์œ„์น˜๋ฅผ ์›์ ์œผ๋กœ ํ•˜์—ฌ explosionRadius ๋‚ด์˜ ๋ชจ๋“  Rigidbody๋ฅผ ํƒ์ƒ‰.
  • ๊ฐ ์˜ค๋ธŒ์ ํŠธ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ proximity (0.0 ~ 1.0)์„ ๊ณ„์‚ฐํ•˜์—ฌ, ์ค‘์‹ฌ์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๊ฐ•ํ•œ ํž˜์ด ๊ฐ€ํ•ด์ง€๋„๋ก.
  • radialForce(์ˆ˜ํ‰ ํ™•์‚ฐ๋ ฅ)์™€ upwardForce(์ˆ˜์ง ์ƒ์Šน๋ ฅ) ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ํญ๋ฐœ ์‹œ ๋ฌผ์ฒด๊ฐ€ ์œ„๋กœ ์†Ÿ๊ตฌ์น˜๋ฉฐ ์‚ฌ๋ฐฉ์œผ๋กœ ํผ์ง€๋Š” ๋ฌผ๋ฆฌ ํšจ๊ณผ๋ฅผ ๊ตฌํ˜„.

1.2 ํ•„์ˆ˜ ์‚ฌ์ „ ์„ค์ •

์ฒดํฌ 1. ์‹ ๊ทœ ํƒœ๊ทธ ๋“ฑ๋ก

์Šคํฌ๋ฆฝํŠธ ๋‚ด๋ถ€์—์„œ instance.tag = "ExplosionTrigger"๋ฅผ ํ• ๋‹นํ•˜๋ฏ€๋กœ, ํ•ด๋‹น ํƒœ๊ทธ๊ฐ€ ํ”„๋กœ์ ํŠธ์— ๋“ฑ๋ก๋˜์–ด ์žˆ์–ด์•ผ ํ•จ.
1. ์ƒ๋‹จ ๋ฉ”๋‰ด์—์„œ Edit > Project Settings > Tags & Layers ์„ ํƒ
2. Tags ๋ฐฐ์—ด์˜ ๋นˆ ์Šฌ๋กฏ์„ ๋”๋ธ”ํด๋ฆญ
3. ExplosionTrigger๋ฅผ ์ž…๋ ฅ ํ›„ ์—”ํ„ฐ
4. ์„ค์ • ์ฐฝ ๋‹ซ๊ธฐ

  • โš ๏ธ ํƒœ๊ทธ๊ฐ€ ๋“ฑ๋ก๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜๋ฉด
    UnityException: Tag: ExplosionTrigger is not defined ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ.

์ฒดํฌ 2. ์นด๋ฉ”๋ผ ํƒœ๊ทธ ํ™•์ธ

Camera.main ์ด ์ •์ƒ ์ž‘๋™ํ•˜๋ ค๋ฉด ๋ฉ”์ธ ์นด๋ฉ”๋ผ์— MainCamera ํƒœ๊ทธ๊ฐ€ ํ• ๋‹น๋˜์–ด ์žˆ์–ด์•ผ ํ•จ.
1. Hierarchy ์—์„œ ๋ฉ”์ธ ์นด๋ฉ”๋ผ ์„ ํƒ
2. Inspector ์ƒ๋‹จ Tag ๋“œ๋กญ๋‹ค์šด์—์„œ MainCamera ์„ ํƒ (์—†์œผ๋ฉด Add Tag ๋กœ ๋“ฑ๋ก)

์ฒดํฌ 3. ํ”„๋ฆฌํŒน ์ค€๋น„ ๋ฐ ํ• ๋‹น

  1. ๋ฌผ๋ฆฌ ํšจ๊ณผ๋ฅผ ์ ์šฉํ•  3D ๋ชจ๋ธ (๋˜๋Š” ํ”„๋ฆฌํŒน) ์„ ์ค€๋น„.
  2. ํ”„๋ฆฌํŒน์— Mesh Renderer ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ.
  3. ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ฒจ๋ถ€๋œ ๋นˆ ์˜ค๋ธŒ์ ํŠธ์˜ Inspector ์—์„œ Spawn Prefabs ๋ฐฐ์—ด์— ํ”„๋ฆฌํŒน์„ ๋“œ๋ž˜๊ทธ&๋“œ๋กญ.
    • ์ธ๋ฑ์Šค 0 ๋ฒˆ ์Šฌ๋กฏ์— ํ• ๋‹น๋œ ํ”„๋ฆฌํŒน๋งŒ์ด ํญ๋ฐœ ํŠธ๋ฆฌ๊ฑฐ ์—ญํ• ์„ ์ˆ˜ํ–‰.
    • ๋‚˜๋จธ์ง€ ์Šฌ๋กฏ (1~4) ์€ ์ผ๋ฐ˜ ๋ฌผ์ฒด๋กœ ์ƒ์„ฑ.

์ฒดํฌ 4: ๋ฌผ๋ฆฌ ์ปดํฌ๋„ŒํŠธ ์„ค์ • (์„ ํƒ ์‚ฌํ•ญ)

์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋Ÿฐํƒ€์ž„์— Rigidbody ์™€ Collider ๋ฅผ ์ž๋™ ์ถ”๊ฐ€ํ•˜์ง€๋งŒ, ํ”„๋ฆฌํŒน ๋‹จ๊ณ„์—์„œ ๋ฏธ๋ฆฌ ์„ค์ •ํ•ด ๋‘๋ฉด ๋ถˆํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ ๋กœ์ง์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ”„๋ฆฌํŒน์— Rigidbody ์ถ”๊ฐ€: Use Gravity ์ฒดํฌ, Is Kinematic ํ•ด์ œ
  • ํ”„๋ฆฌํŒน์— Collider ์ถ”๊ฐ€: BoxCollider ๋˜๋Š” MeshCollider (Convex ์ฒดํฌ)

2. ์Šคํฌ๋ฆฝํŠธ

using UnityEngine;
using UnityEngine.InputSystem;     // Unity New Input System ๋„ค์ž„์ŠคํŽ˜์ด์Šค (๋งˆ์šฐ์Šค ์ž…๋ ฅ ์ฒ˜๋ฆฌ์šฉ)
using System.Collections.Generic;  // List<T> ์ œ๋„ค๋ฆญ ์ปฌ๋ ‰์…˜ ์‚ฌ์šฉ

public class MeshSpawnerManager : MonoBehaviour
{
    // =========================================================
    // [Inspector ๋…ธ์ถœ ๋ณ€์ˆ˜: ์ƒ์„ฑ ์„ค์ •]
    // =========================================================
    [Header("๐Ÿ“ฆ ์ƒ์„ฑ ์„ค์ •")]
    
    [Tooltip("์ด ์ƒ์„ฑํ•  ์˜ค๋ธŒ์ ํŠธ ๊ฐœ์ˆ˜")]
    public int maxSpawnCount = 500;       // ์‹œ์Šคํ…œ์ด ์ƒ์„ฑํ•  ์ตœ๋Œ€ ์˜ค๋ธŒ์ ํŠธ ์ˆ˜ (ํ’€๋ง ํ•œ๋„)

    [Tooltip("์ดˆ๋‹น ์ƒ์„ฑ ๊ฐœ์ˆ˜ (๋นˆ๋„)")]
    [Range(1f, 50f)]                      // Inspector ์—์„œ 1~50 ๋ฒ”์œ„ ์Šฌ๋ผ์ด๋” ์ œ๊ณต
    public float spawnRate = 10f;         // ์ดˆ๋‹น ์ƒ์„ฑํ•  ์˜ค๋ธŒ์ ํŠธ ๊ฐœ์ˆ˜ (์˜ˆ: 10 = 0.1 ์ดˆ๋‹น 1 ๊ฐœ ์ƒ์„ฑ)

    [Tooltip("์ƒ์„ฑ ์œ„์น˜์˜ Y ์ถ• ๋†’์ด")]
    public float spawnHeight = 5f;        // ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋  ๋†’์ด (๊ณต์ค‘์—์„œ ๋‚™ํ•˜ ์‹œ์ž‘)

    [Tooltip("์ƒ์„ฑ ๋ฒ”์œ„ (X, Z ํฌ๊ธฐ)")]
    public Vector2 spawnArea = new Vector2(5f, 5f);  // X, Z ์ถ• ๋žœ๋ค ์ƒ์„ฑ ์˜์—ญ์˜ ๋„ˆ๋น„/๊ธธ์ด

    [Tooltip("์ƒ์„ฑํ•  ํ”„๋ฆฌํŒน ๋ฐฐ์—ด โ€” Index 0 ์ด ํญ๋ฐœ ํŠธ๋ฆฌ๊ฑฐ ๋Œ€์ƒ")]
    public GameObject[] spawnPrefabs = new GameObject[5];  // ์ตœ๋Œ€ 5 ์ข… ํ”„๋ฆฌํŒน ๋“ฑ๋ก ๊ฐ€๋Šฅ (์ธ๋ฑ์Šค 0 ์€ ํŠน์ˆ˜ ์—ญํ• )

    // =========================================================
    // [Inspector ๋…ธ์ถœ ๋ณ€์ˆ˜: ํญ๋ฐœ ์„ค์ •]
    // =========================================================
    [Header("๐Ÿ’ฅ ํญ๋ฐœ ์„ค์ •")]
    
    [Tooltip("ํญ๋ฐœ ์˜ํ–ฅ ๋ฐ˜๊ฒฝ")]
    public float explosionRadius = 1f;    // ํญ๋ฐœ ์›์ ์œผ๋กœ๋ถ€ํ„ฐ ํž˜์ด ์ž‘์šฉํ•˜๋Š” ์ตœ๋Œ€ ๊ฑฐ๋ฆฌ

    [Tooltip("์œ„์ชฝ์œผ๋กœ ๋‚ ์•„์˜ค๋ฅด๋Š” ํž˜")]
    public float upwardForce = 50f;       // ํญ๋ฐœ ์‹œ Y ์ถ• ์–‘์˜ ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€ํ•ด์ง€๋Š” ํž˜ (์ƒ์Šน๋ ฅ)

    [Tooltip("์˜†์œผ๋กœ ํผ์ง€๋Š” ํž˜")]
    public float radialForce = 30f;       // ํญ๋ฐœ ์›์ ์—์„œ ๋ฐ”๊นฅ ๋ฐฉํ–ฅ (์ˆ˜ํ‰) ์œผ๋กœ ๊ฐ€ํ•ด์ง€๋Š” ํž˜ (ํ™•์‚ฐ๋ ฅ)

    // =========================================================
    // [Private ์ƒ์ˆ˜ ๋ฐ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜]
    // =========================================================
    
    // Element 0 ์ธ์Šคํ„ด์Šค๋ฅผ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•œ ํƒœ๊ทธ
    // โš ๏ธ Unity ๋ฉ”๋‰ด Edit > Project Settings > Tags & Layers ์—์„œ "ExplosionTrigger" ํƒœ๊ทธ๋ฅผ 
    //    ๋ฏธ๋ฆฌ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—†์œผ๋ฉด ๋Ÿฐํƒ€์ž„์— ํƒœ๊ทธ ํ• ๋‹น ์‹œ ์—๋Ÿฌ ๋ฐœ์ƒ
    private const string TRIGGER_TAG = "ExplosionTrigger";

    private int currentSpawnCount = 0;    // ํ˜„์žฌ๊นŒ์ง€ ์ƒ์„ฑ๋œ ์˜ค๋ธŒ์ ํŠธ ์นด์šดํŠธ
    private float spawnTimer = 0f;        // ๋‹ค์Œ ์ƒ์„ฑ๊นŒ์ง€์˜ ์‹œ๊ฐ„ ๋ˆ„์  ํƒ€์ด๋จธ
    private List<Rigidbody> spawnedObjects = new List<Rigidbody>();  // ์ƒ์„ฑ๋œ ๋ชจ๋“  Rigidbody ์ฐธ์กฐ ์ €์žฅ (ํญ๋ฐœ ์‹œ ํƒ์ƒ‰์šฉ)

    // =========================================================
    // [Unity LifeCycle: Start]
    // =========================================================
    void Start()
    {
        // ๋ฐ”๋‹ฅ ํ‰๋ฉด ์ž๋™ ์ƒ์„ฑ (ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ•์šฉ)
        InitializeFloor();
        
        // ํ”„๋ฆฌํŒน ๋ฐฐ์—ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (๋„ ์š”์†Œ ๊ฒฝ๊ณ  ์ถœ๋ ฅ)
        ValidatePrefabs();
    }

    // =========================================================
    // [Unity LifeCycle: Update]
    // =========================================================
    void Update()
    {
        // -----------------------------------------------------
        // [1. ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ ๋กœ์ง]
        // -----------------------------------------------------
        // ์ตœ๋Œ€ ์ƒ์„ฑ ๊ฐœ์ˆ˜์— ๋„๋‹ฌํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋งŒ ์ƒ์„ฑ ์ง„ํ–‰
        if (currentSpawnCount < maxSpawnCount)
        {
            spawnTimer += Time.deltaTime;  // ํ”„๋ ˆ์ž„ ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ๋ˆ„์ 
            
            // ์„ค์ •๋œ ์ƒ์„ฑ ์ฃผ๊ธฐ (1/spawnRate) ์— ๋„๋‹ฌํ•˜๋ฉด
            if (spawnTimer >= 1f / spawnRate)
            {
                spawnTimer = 0f;  // ํƒ€์ด๋จธ ๋ฆฌ์…‹
                SpawnObject();    // ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ ํ•จ์ˆ˜ ํ˜ธ์ถœ
            }
        }

        // -----------------------------------------------------
        // [2. ๋งˆ์šฐ์Šค ํด๋ฆญ ๊ฐ์ง€ ๋กœ์ง (New Input System)]
        // -----------------------------------------------------
        // Mouse.current ๊ฐ€ ์œ ํšจํ•˜๊ณ  (๋ฐ์Šคํฌํ†ฑ ํ”Œ๋žซํผ), ์™ผ์ชฝ ๋ฒ„ํŠผ์ด ์ด๋ฒˆ ํ”„๋ ˆ์ž„์— ๋ˆŒ๋ ธ๋‹ค๋ฉด
        if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
        {
            HandleClickExplosion();  // ํญ๋ฐœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ํ˜ธ์ถœ
        }
    }

    // =========================================================
    // [Private Method: ๋ฐ”๋‹ฅ ํ‰๋ฉด ์ž๋™ ์ƒ์„ฑ]
    // =========================================================
    void InitializeFloor()
    {
        // PrimitiveType.Plane ์œผ๋กœ ๊ธฐ๋ณธ ํ‰๋ฉด ๋ฉ”์‹œ ์ƒ์„ฑ
        GameObject floor = GameObject.CreatePrimitive(PrimitiveType.Plane);
        floor.name = "Ground_Floor";  //Hierarchy ์—์„œ ์‹๋ณ„ํ•˜๊ธฐ ์‰ฌ์šด ์ด๋ฆ„ ์ง€์ •
        
        // ํ‰๋ฉด ํฌ๊ธฐ ์กฐ์ ˆ (๊ธฐ๋ณธ 10x10 ์„ 100x100 ์œผ๋กœ ์Šค์ผ€์ผ์—…)
        floor.transform.localScale = new Vector3(10f, 1f, 10f);
        // ๋ฐ”๋‹ฅ์„ ์›์  (0,0,0) ์— ์œ„์น˜์‹œํ‚ด
        floor.transform.position = Vector3.zero;
    }

    // =========================================================
    // [Private Method: ํ”„๋ฆฌํŒน ๋ฐฐ์—ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ]
    // =========================================================
    void ValidatePrefabs()
    {
        // ๋ฐฐ์—ด์ด ์™„์ „ํžˆ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ ๋กœ๊ทธ ์ถœ๋ ฅ
        if (spawnPrefabs.Length == 0)
        {
            Debug.LogError("spawnPrefabs ๋ฐฐ์—ด์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.");
            return;
        }
        
        // ๊ฐ ์Šฌ๋กฏ์„ ์ˆœํšŒํ•˜๋ฉฐ ๋„ (null) ์ฐธ์กฐ๊ฐ€ ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌ
        for (int i = 0; i < spawnPrefabs.Length; i++)
        {
            if (spawnPrefabs[i] == null)
                Debug.LogWarning($"์ธ๋ฑ์Šค {i}์˜ ํ”„๋ฆฌํŒน์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.");
        }
    }

    // =========================================================
    // [Private Method: ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ ๋ฐ ๋ฌผ๋ฆฌ ์„ค์ •]
    // =========================================================
    void SpawnObject()
    {
        // ํ”„๋ฆฌํŒน ๋ฐฐ์—ด์ด ๋น„์–ด์žˆ์œผ๋ฉด ์ƒ์„ฑ ์ค‘๋‹จ
        if (spawnPrefabs.Length == 0) return;

        // 0 ๋ถ€ํ„ฐ ๋ฐฐ์—ด ๊ธธ์ด๊นŒ์ง€ ๋žœ๋ค ์ธ๋ฑ์Šค ์„ ํƒ (๊ท ๋“ฑ ํ™•๋ฅ )
        int index = Random.Range(0, spawnPrefabs.Length);
        GameObject prefab = spawnPrefabs[index];
        
        // ์„ ํƒ๋œ ํ”„๋ฆฌํŒน์ด ๋„์ด๋ฉด ์Šคํ‚ต
        if (prefab == null) return;

        // -----------------------------------------------------
        // [๋žœ๋ค ์ƒ์„ฑ ์œ„์น˜ ๊ณ„์‚ฐ]
        // -----------------------------------------------------
        // X, Z ๋Š” spawnArea ๋ฒ”์œ„ ๋‚ด์—์„œ ๋žœ๋ค, Y ๋Š” ๊ณ ์ •๋œ spawnHeight
        Vector3 spawnPos = new Vector3(
            Random.Range(-spawnArea.x / 2f, spawnArea.x / 2f),  // X: -area/2 ~ +area/2
            spawnHeight,                                         // Y: ๊ณ ์ • ๋†’์ด
            Random.Range(-spawnArea.y / 2f, spawnArea.y / 2f)   // Z: -area/2 ~ +area/2
        );

        // ํ”„๋ฆฌํŒน ์ธ์Šคํ„ด์Šคํ™” (์œ„์น˜: spawnPos, ํšŒ์ „: ๋žœ๋ค)
        GameObject instance = Instantiate(prefab, spawnPos, Random.rotation);

        // -----------------------------------------------------
        // [Rigidbody ์ปดํฌ๋„ŒํŠธ ์„ค์ •]
        // -----------------------------------------------------
        Rigidbody rb = instance.GetComponent<Rigidbody>();
        // Rigidbody ๊ฐ€ ์—†์œผ๋ฉด ๋™์ ์œผ๋กœ ์ถ”๊ฐ€
        if (rb == null) rb = instance.AddComponent<Rigidbody>();
        
        // ๋ฌผ๋ฆฌ ์†์„ฑ ์„ค์ •
        rb.isKinematic = false;                    // ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์— ์˜ํ•ด ์ œ์–ด๋˜๋„๋ก ์„ค์ •
        rb.mass = 1f;                              // ์งˆ๋Ÿ‰ 1kg (๊ธฐ๋ณธ๊ฐ’)
        rb.linearDamping = 0.1f;                   // ์„ ํ˜• ์†๋„ ๊ฐ์‡  (๊ณต๊ธฐ ์ €ํ•ญ ํšจ๊ณผ)
        rb.angularDamping = 0.05f;                 // ๊ฐ์†๋„ ๊ฐ์‡  (ํšŒ์ „ ์ €ํ•ญ ํšจ๊ณผ)
        rb.useGravity = true;                      // ์ค‘๋ ฅ ์ ์šฉ (๋‚™ํ•˜ ํšจ๊ณผ)
        // ๊ณ ์† ์ด๋™ ์‹œ ์ถฉ๋Œ์„ ๋†“์น˜์ง€ ์•Š๋„๋ก ์ •๋ฐ€ํ•œ ๊ฐ์ง€ ๋ชจ๋“œ ์„ค์ •
        rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

        // -----------------------------------------------------
        // [Collider ์ปดํฌ๋„ŒํŠธ ์„ค์ •]
        // -----------------------------------------------------
        Collider col = instance.GetComponent<Collider>();
        // Collider ๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ ๋ฐ•์Šค ์ฝœ๋ผ์ด๋” ์ถ”๊ฐ€
        if (col == null) col = instance.AddComponent<BoxCollider>();

        // -----------------------------------------------------
        // [ํญ๋ฐœ ํŠธ๋ฆฌ๊ฑฐ ํƒœ๊ทธ ํ• ๋‹น]
        // -----------------------------------------------------
        // ์ธ๋ฑ์Šค 0 ๋ฒˆ ํ”„๋ฆฌํŒน (๋ฐฐ์—ด์˜ ์ฒซ ๋ฒˆ์งธ ์š”์†Œ) ์—๋งŒ ํŠน์ˆ˜ ํƒœ๊ทธ ๋ถ€์—ฌ
        // ์ด ํƒœ๊ทธ๊ฐ€ ๋ถ™์€ ์˜ค๋ธŒ์ ํŠธ๋งŒ ๋งˆ์šฐ์Šค ํด๋ฆญ ์‹œ ํญ๋ฐœ์„ ํŠธ๋ฆฌ๊ฑฐํ•จ
        if (index == 0)
        {
            instance.tag = TRIGGER_TAG;
        }

        // ์ƒ์„ฑ๋œ Rigidbody ๋ฅผ ๊ด€๋ฆฌ ๋ฆฌ์ŠคํŠธ์— ๋“ฑ๋ก (ํญ๋ฐœ ์‹œ ํƒ์ƒ‰ ๋Œ€์ƒ)
        spawnedObjects.Add(rb);
        // ์ƒ์„ฑ ์นด์šดํŠธ ์ฆ๊ฐ€
        currentSpawnCount++;
    }

    // =========================================================
    // [Private Method: ๋งˆ์šฐ์Šค ํด๋ฆญ ํญ๋ฐœ ์ฒ˜๋ฆฌ]
    // =========================================================
    
    // Physics.RaycastAll ์‚ฌ์šฉ ์ด์œ :
    // - ํ”„๋ฆฌํŒน์ด ๊ฒน์ณ ์Œ“์ธ ์ƒํƒœ์—์„œ Physics.Raycast(๋‹จ์ผ) ๋Š” ๋งจ ์•ž ์˜ค๋ธŒ์ ํŠธ๋งŒ ํžˆํŠธํ•จ
    // - RaycastAll ์€ ๋ ˆ์ด ๊ฒฝ๋กœ ์œ„์˜ ๋ชจ๋“  ์ฝœ๋ผ์ด๋”๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ
    // - ๊ฐ€๋ ค์ง„ ์œ„์น˜์˜ Element 0(ํŠธ๋ฆฌ๊ฑฐ) ๋„ ๊ฐ์ง€ ๊ฐ€๋Šฅ
    void HandleClickExplosion()
    {
        // ๋ฉ”์ธ ์นด๋ฉ”๋ผ๊ฐ€ ์—†์œผ๋ฉด ์—๋Ÿฌ ๋กœ๊ทธ ์ถœ๋ ฅ ํ›„ ์ค‘๋‹จ
        if (Camera.main == null)
        {
            Debug.LogError("MainCamera ํƒœ๊ทธ๊ฐ€ ์„ค์ •๋œ ์นด๋ฉ”๋ผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.");
            return;
        }

        // ํ˜„์žฌ ๋งˆ์šฐ์Šค ํฌ์ง€์…˜ ์ฝ๊ธฐ (New Input System ๋ฐฉ์‹)
        Vector2 mousePos = Mouse.current.position.ReadValue();
        
        // ์นด๋ฉ”๋ผ์—์„œ ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ์ง€๋‚˜๊ฐ€๋Š” 3D ๋ ˆ์ด ์ƒ์„ฑ
        Ray ray = Camera.main.ScreenPointToRay(mousePos);
        
        // ๋ ˆ์ด์™€ ๊ต์ฐจํ•˜๋Š” ๋ชจ๋“  ์ฝœ๋ผ์ด๋” ํƒ์ƒ‰ (๊ฑฐ๋ฆฌ ์ œํ•œ ์—†์Œ: Mathf.Infinity)
        RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);

        // ํžˆํŠธ๋œ ๋ชจ๋“  ์ฝœ๋ผ์ด๋” ์ˆœํšŒ
        foreach (RaycastHit hit in hits)
        {
            // ํžˆํŠธ๋œ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ "ExplosionTrigger" ํƒœ๊ทธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด
            if (hit.collider.CompareTag(TRIGGER_TAG))
            {
                Debug.Log($"โœ… Element 0 ํด๋ฆญ ๊ฐ์ง€: {hit.collider.gameObject.name}");
                
                // ํžˆํŠธ ์ง€์ ์˜ ์›”๋“œ ์ขŒํ‘œ๋ฅผ ์›์ ์œผ๋กœ ํญ๋ฐœ ํŠธ๋ฆฌ๊ฑฐ ์‹คํ–‰
                TriggerExplosionAt(hit.collider.transform.position);
                
                // ์ฒซ ๋ฒˆ์งธ ํŠธ๋ฆฌ๊ฑฐ๋งŒ ๋ฐ˜์‘ํ•˜๋„๋ก ๋ฃจํ”„ ์ข…๋ฃŒ (return)
                return;
            }
        }
    }

    // =========================================================
    // [Private Method: ํญ๋ฐœ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜]
    // =========================================================
    
    // ํญ๋ฐœ ์›์  ๊ธฐ์ค€ ๋ฐ˜๊ฒฝ ๋‚ด ๋ชจ๋“  Rigidbody ์— ํž˜ ์ ์šฉ
    void TriggerExplosionAt(Vector3 origin)
    {
        // ๊ด€๋ฆฌ ๋ฆฌ์ŠคํŠธ์—์„œ ์ด๋ฏธ ํŒŒ๊ดด๋œ (null) Rigidbody ์ œ๊ฑฐ (๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ)
        spawnedObjects.RemoveAll(rb => rb == null);

        // ๋ชจ๋“  ๋“ฑ๋ก๋œ Rigidbody ์ˆœํšŒ
        foreach (Rigidbody rb in spawnedObjects)
        {
            // ํ˜„์žฌ ์˜ค๋ธŒ์ ํŠธ์™€ ํญ๋ฐœ ์›์  ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
            float dist = Vector3.Distance(rb.position, origin);
            
            // ํญ๋ฐœ ๋ฐ˜๊ฒฝ์„ ๋ฒ—์–ด๋‚œ ์˜ค๋ธŒ์ ํŠธ๋Š” ๋ฌด์‹œ
            if (dist > explosionRadius) continue;

            // ํญ๋ฐœ ์›์ ์—์„œ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํ–ฅํ•˜๋Š” ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
            Vector3 dir = rb.position - origin;
            
            // ๋ฐฉํ–ฅ ๋ฒกํ„ฐ๊ฐ€ 0 ์— ๋งค์šฐ ๊ฐ€๊นŒ์šฐ๋ฉด (์ •ํ™•ํžˆ ์ค‘์‹ฌ์— ์žˆ๋Š” ๊ฒฝ์šฐ)
            // ๋‚˜๋ˆ„๊ธฐ ์˜ค๋ฅ˜ ๋ฐฉ์ง€ ๋ฐ ์ž„์˜ ๋ฐฉํ–ฅ์œผ๋กœ ํž˜์„ ์ฃผ๊ธฐ ์œ„ํ•ด ๋žœ๋ค ๋‹จ์œ„ ๋ฒกํ„ฐ ์‚ฌ์šฉ
            if (dir.sqrMagnitude < 0.001f)
                dir = Random.onUnitSphere;

            // ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ์ •๊ทœํ™” (๊ธธ์ด 1 ๋กœ ๋งŒ๋“ค์–ด ํž˜์˜ ๋ฐฉํ–ฅ๋งŒ ์ถ”์ถœ)
            dir.Normalize();
            
            // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ํž˜์˜ ์„ธ๊ธฐ ๋ณด์ • (proximity)
            // dist/explosionRadius: 0(์ค‘์‹ฌ) ~ 1(๊ฒฝ๊ณ„)
            // 1 - clamp(...): 1(์ค‘์‹ฌ, ๊ฐ•ํ•จ) ~ 0(๊ฒฝ๊ณ„, ์•ฝํ•จ)
            float proximity = 1f - Mathf.Clamp01(dist / explosionRadius);
            
            // ์ตœ์ข… ํž˜ ๋ฒกํ„ฐ ๊ณ„์‚ฐ: (์ˆ˜ํ‰ ํ™•์‚ฐ๋ ฅ + ์ˆ˜์ง ์ƒ์Šน๋ ฅ) * ๊ฑฐ๋ฆฌ ๋ณด์ •๊ณ„์ˆ˜
            Vector3 force = (dir * radialForce + Vector3.up * upwardForce) * proximity;
            
            // Rigidbody ์— ์ˆœ๊ฐ„์ ์ธ ํž˜ (Impulse) ์ ์šฉ
            // ForceMode.Impulse: ์งˆ๋Ÿ‰์„ ๊ณ ๋ คํ•œ ์ˆœ๊ฐ„ ๊ฐ€์†๋„ (ํญ๋ฐœ ๊ฐ™์€ ์ผํšŒ์„ฑ ํž˜์— ์ ํ•ฉ)
            rb.AddForce(force, ForceMode.Impulse);
        }

        Debug.Log($"๐Ÿ’ฅ ํญ๋ฐœ ์™„๋ฃŒ. ์›์ : {origin}");
    }
}

3. ํ•ต์‹ฌ ๋กœ์ง

3.1 RaycastAll ์‚ฌ์šฉ ์ด์œ 

์ผ๋ฐ˜์ ์ธ ๊ฒŒ์ž„์—์„œ ๋งˆ์šฐ์Šค ํด๋ฆญ ํŒ์ •์€ Physics.Raycast (๋‹จ์ผ ํžˆํŠธ) ๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ ์ด ์•„ํŠธ์›Œํฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ˆ˜ํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒ.

  • ์ˆ˜๋ฐฑ ๊ฐœ์˜ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ค‘์ฒฉ๋˜์–ด ์Œ“์ž„
  • ํญ๋ฐœ ํŠธ๋ฆฌ๊ฑฐ (์ธ๋ฑ์Šค 0) ๊ฐ€ ๋‹ค๋ฅธ ์˜ค๋ธŒ์ ํŠธ ๋’ค์— ๊ฐ€๋ ค์งˆ ์ˆ˜ ์žˆ์Œ

Raycast ๋Š” ๋ ˆ์ด ๊ฒฝ๋กœ์ƒ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ฝœ๋ผ์ด๋” ํ•˜๋‚˜๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ๋’ค์— ์ˆจ์€ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•จ. ๋ฐ˜๋ฉด RaycastAll ์€ ๊ฒฝ๋กœ์ƒ์˜ ๋ชจ๋“  ์ฝœ๋ผ์ด๋”๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, foreach ๋ฃจํ”„์—์„œ ํƒœ๊ทธ๋ฅผ ๊ฒ€์‚ฌํ•˜์—ฌ ๊ฐ€๋ ค์ง„ ํŠธ๋ฆฌ๊ฑฐ๋„ ์ •ํ™•ํ•˜๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ.

๐Ÿ’ก ์„ฑ๋Šฅ ๊ณ ๋ ค์‚ฌํ•ญ: ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ๋งค์šฐ ๋งŽ์„ ๊ฒฝ์šฐ RaycastAll ์€ ๋น„์šฉ์ด ๋†’์Šต๋‹ˆ๋‹ค. ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” ์ตœ๋Œ€ 500 ๊ฐœ ์ •๋„์˜ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๊ฐ€์ •ํ•˜๊ณ  ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ๋” ๋งŽ์€ ์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ ˆ์ด์–ด ๋งˆ์Šคํฌ (layerMask) ๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ ๊ณต๊ฐ„ ๋ถ„ํ•  (QuadTree ๋“ฑ) ๊ธฐ๋ฒ•์„ ์ถ”๊ฐ€๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3.2 ํญ๋ฐœ ํž˜ ๊ณต์‹

Vector3 force = (dir * radialForce + Vector3.up * upwardForce) * proximity;
  1. dir * radialForce: ํญ๋ฐœ ์ค‘์‹ฌ์—์„œ ๋ฐ”๊นฅ์œผ๋กœ ํผ์ง€๋Š” ์ˆ˜ํ‰ ๋ฐฉํ–ฅ ํž˜.
  2. Vector3.up * upwardForce: ๋ฌด์กฐ๊ฑด ์œ„์ชฝ์œผ๋กœ ์ž‘์šฉํ•˜๋Š” ํž˜.
  3. * proximity: ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ํž˜์ด ์•ฝํ•ด์ง€๋„๋ก. ์ค‘์‹ฌ์— ๊ฐ€๊นŒ์šด ๋ฌผ์ฒด๋Š” ๊ฐ•ํ•˜๊ฒŒ, ๊ฐ€์žฅ์ž๋ฆฌ๋Š” ์•ฝํ•˜๊ฒŒ ๋‚ ์•„๊ฐ€๋ฏ€๋กœ ์ž์—ฐ์Šค๋Ÿฌ์šด ํญ๋ฐœ ํ™•์‚ฐ์„ ๊ตฌํ˜„.

3.3 Rigidbody & Collider ์ปดํฌ๋„ŒํŠธ ์ž๋™ ์„ค์ •

  • ๋งค๋ฒˆ ํ”„๋ฆฌํŒน์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์—†์Œ
  • ๋ฌผ๋ฆฌ ์†์„ฑ (damping, collisionDetectionMode ๋“ฑ) ์ด ์ฝ”๋“œ์—์„œ ์ผ๊ด€๋˜๊ฒŒ ์ ์šฉ๋˜์–ด ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์žฌํ˜„์„ฑ ๋ณด์žฅ
  • ๋‹จ, ๋Ÿฐํƒ€์ž„์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜๊ณผ ์ธ์Šคํ„ด์Šคํ™” ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ”„๋ฆฌํŒน์—์„œ ๋ฏธ๋ฆฌ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์„ฑ๋Šฅ์ƒ ์œ ๋ฆฌ.

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

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