
- ํ๋ฆฌํน ์ด๊ธฐ 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;
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);
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);
Vector3 euler = transform.eulerAngles;
euler.x = 0f;
euler.z = 0f;
transform.eulerAngles = euler;
}
void FixedUpdate()
{
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);
}
}
}