๐ŸซงArt_029 NPC Distributor

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

Unity GenArt

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

  • ํ”„๋ฆฌํŒน ์ดˆ๊ธฐ Y ์œ„์น˜: spawnCenter.y๋ฅผ ์ง€๋ฉด ๋†’์ด์™€ ์ •ํ™•ํžˆ ๋งž์ถ”์„ธ์š”. ์•ฝ๊ฐ„๋งŒ ๋œจ๊ฑฐ๋‚˜ ๋ฐ•ํ˜€๋„ FreezePositionY๊ฐ€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ฐ€์–ด๋‚ด๋Š” ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • BoxCollider ํฌ๊ธฐ: ํ”„๋ฆฌํŒน์˜ ์ฝœ๋ผ์ด๋”๊ฐ€ ์‹ค์ œ ๋ฉ”์‰ฌ๋ณด๋‹ค ๋„ˆ๋ฌด ํฌ๋ฉด ์ƒ์„ฑ ์‹œ ๊ฒน์ณ์„œ ์„œ๋กœ ๋ฐ€์–ด๋‚ด๋Š” ํ˜„์ƒ(XZ)์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”์‹œ Collider.size๋ฅผ ์‹ค์ œ ๋ชจ๋ธ ํฌ๊ธฐ์˜ 90~95%๋กœ ์ค„์ด์„ธ์š”.
  • MovePosition์€ FreezePositionY ์„ค์ •์„ ์ž๋™์œผ๋กœ ์กด์ค‘ํ•˜๋ฏ€๋กœ ์ฝ”๋“œ๋ฅผ ๋” ์ˆ˜์ •ํ•  ํ•„์š” ์—†์ด Y์ถ•์ด ์™„์ „ํžˆ ๊ณ ์ •๋ฉ๋‹ˆ๋‹ค.
using UnityEngine;
using System.Collections.Generic;

public class NPCController : MonoBehaviour
{
    [Header("๐ŸŽญ ํ”„๋ฆฌํŒน ์Šฌ๋กฏ (๋“œ๋ž˜๊ทธ & ๋“œ๋กญ)")]
    public GameObject[] npcPrefabs = new GameObject[5];

    [Header("๐Ÿ“ฆ ์Šคํฐ ๋ฒ”์œ„ ์„ค์ •")]
    public Vector3 spawnCenter = Vector3.zero;
    public Vector2 spawnSize = new Vector2(10f, 10f);
    public int countPerPrefab = 5;

    [Header("โš™๏ธ ์ด๋™ ๋ฐ ํšŒ์ „ ๊ณตํ†ต ์„ค์ •")]
    public float moveSpeed = 2f;
    public float rotationSmoothness = 5f;   // Slerp ํšŒ์ „ ์†๋„
    public float bounceRandomAngle = 60f;   // ์ถฉ๋Œ ์‹œ ์ถ”๊ฐ€ ๋žœ๋ค ๊ฐ๋„ ๋ฒ”์œ„

    private List<NPCAgent> agents = new List<NPCAgent>();

    void Start() => SpawnNPCs();

    void SpawnNPCs()
    {
        foreach (var prefab in npcPrefabs)
        {
            if (prefab == null) continue;

            for (int i = 0; i < countPerPrefab; i++)
            {
                float xPos = spawnCenter.x + Random.Range(-spawnSize.x / 2f, spawnSize.x / 2f);
                float zPos = spawnCenter.z + Random.Range(-spawnSize.y / 2f, spawnSize.y / 2f);
                Vector3 spawnPos = new Vector3(xPos, spawnCenter.y, zPos);

                GameObject npc = Instantiate(prefab, spawnPos, Quaternion.identity, transform);

                // โœ… ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ˜ ์ถฉ๋Œ ๊ฐ์ง€ + Y์ถ• ์ด๋™ ์™„์ „ ์ž ๊ธˆ
                Rigidbody rb = npc.AddComponent<Rigidbody>();
                rb.isKinematic = false;
                rb.useGravity = false;
                rb.constraints = RigidbodyConstraints.FreezePositionY | 
                                 RigidbodyConstraints.FreezeRotationX | 
                                 RigidbodyConstraints.FreezeRotationZ;
                rb.collisionDetectionMode = CollisionDetectionMode.Continuous;

                if (npc.GetComponent<Collider>() == null)
                    npc.AddComponent<BoxCollider>();

                NPCAgent agent = npc.AddComponent<NPCAgent>();
                agent.Init(moveSpeed, rotationSmoothness, bounceRandomAngle, rb);
                agents.Add(agent);
            }
        }
    }
}

public class NPCAgent : MonoBehaviour
{
    private Rigidbody rb;
    private float speed;
    private float rotSpeed;
    private float maxBounceAngle;
    private Quaternion targetRotation;
    private Vector3 moveDirection;
    private float collisionCooldown = 0f;

    public void Init(float s, float r, float b, Rigidbody rigidbody)
    {
        rb = rigidbody;
        speed = s;
        rotSpeed = r;
        maxBounceAngle = b;

        float randY = Random.Range(0f, 360f);
        targetRotation = Quaternion.Euler(0f, randY, 0f);
        transform.rotation = targetRotation;
        moveDirection = transform.forward;
    }

    void Update()
    {
        if (collisionCooldown > 0f) collisionCooldown -= Time.deltaTime;

        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotSpeed * Time.deltaTime);

        // ๐Ÿ“ Y์ถ•๋งŒ ํšŒ์ „ ํ—ˆ์šฉ (X, Z ๊ฐ•์ œ 0์œผ๋กœ ์ง๋ฆฝ ์œ ์ง€)
        Vector3 euler = transform.eulerAngles;
        euler.x = 0f;
        euler.z = 0f;
        transform.eulerAngles = euler;
    }

    void FixedUpdate()
    {
        // ๐Ÿƒ ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ˜ ์ด๋™ (Y์ถ•์€ FreezePositionY๋กœ ์ž๋™ ์ฐจ๋‹จ๋จ)
        moveDirection = new Vector3(transform.forward.x, 0f, transform.forward.z).normalized;
        rb.MovePosition(rb.position + moveDirection * speed * Time.fixedDeltaTime);
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collisionCooldown > 0f) return;
        collisionCooldown = 0.25f;

        ContactPoint contact = collision.GetContact(0);
        Vector3 normal = contact.normal;
        Vector3 flatNormal = new Vector3(normal.x, 0f, normal.z);

        if (flatNormal.sqrMagnitude > 0.001f)
        {
            flatNormal.Normalize();
            Vector3 flatDir = new Vector3(moveDirection.x, 0f, moveDirection.z).normalized;
            Vector3 reflectDir = Vector3.Reflect(flatDir, flatNormal);

            float randomOffset = Random.Range(-maxBounceAngle, maxBounceAngle);
            Vector3 finalDir = Quaternion.Euler(0f, randomOffset, 0f) * reflectDir;
            finalDir.y = 0f;
            finalDir.Normalize();

            float targetY = Mathf.Atan2(finalDir.x, finalDir.z) * Mathf.Rad2Deg;
            targetRotation = Quaternion.Euler(0f, targetY, 0f);
        }
        else
        {
            targetRotation = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
        }
    }
}

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

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