Flock(Boids) 알고리즘
:: 생물의 집단 행동 (새의 무리 이동 등)에서 아이디어를 얻어 만들어졌다.
모든 새는 3가지 규칙을 따른다.
1. cohesion : 모든 boid의 평균 위치에 더 근접하게 한다.
2. separation : 이웃한 boid와 충돌하지 않도록 한다
3. alignment : 각 보이드는 주변의 보이드와 같은 방향을 향하려는 특성을 가진다.
장점 : 구현과 사용이 쉽다.
단점 : 성능 문제, 완전한 컨트롤의 어려움, 대부분의 행동이 예기치 않을것
전체 코드(World of Zero님)
더보기
더보기
//속력 방향 (velocity)을 관리한다.
public class Boid : MonoBehaviour
{
public Vector3 velocity;
public float maxVelocity;
// Start is called before the first frame update
void Start()
{
velocity = transform.forward * maxVelocity;
}
// Update is called once per frame
void Update()
{
if (velocity.magnitude > maxVelocity) {
velocity = velocity.normalized * maxVelocity;
}
this.transform.position += velocity * Time.deltaTime;
transform.rotation = Quaternion.LookRotation(velocity);
}
}
//radius 거리 안에 있는 Boid에게로 방향을 선형보간해 이동한다
[RequireComponent(typeof(Boid))]
public class Boid_Alignment : MonoBehaviour
{
private Boid boid;
[InspectorName("another boid Search")]
public float radius;
// Start is called before the first frame update
void Start()
{
boid = GetComponent<Boid>();
}
// Update is called once per frame
void Update()
{
//모든 보이드들의 배열 가져오기
Boid[] boids = FindObjectsOfType<Boid>();
Vector3 average = Vector3.zero;
float found = 0;
//linq where :: System.Linq 사용
//boids.Where(b => b != boid)
//where : 자기 자신을 제외한 boids배열 내에서 범위기반 반목문(foreach) 수행
foreach (Boid boid in boids.Where(b => b != boid))
{
Vector3 diff = boid.transform.position - transform.position;
if (diff.magnitude < radius)
{
average += boid.velocity;
found += 1;
}
}
if (found > 0)
{
average /= found;
boid.velocity += Vector3.Lerp(boid.velocity, average, Time.deltaTime);
}
}
}
//타 Boid와 너무 가까히 있으면 선형보간해 역방향으로 이동한다
[RequireComponent(typeof(Boid))]
public class Boid_Inverse : MonoBehaviour
{
private Boid boid;
[InspectorName("another boid Search")]
public float radius;
public float repulsionForce;
// Start is called before the first frame update
void Start()
{
boid = GetComponent<Boid>();
}
// Update is called once per frame
void Update()
{
//모든 보이드들의 배열 가져오기
Boid[] boids = FindObjectsOfType<Boid>();
Vector3 average = Vector3.zero;
float found = 0;
//linq where ::
foreach (Boid boid in boids.Where(b => b != boid)) {
Vector3 diff = boid.transform.position - transform.position;
if (diff.magnitude < radius)
{
average += diff;
found += 1;
}
}
if (found > 0) {
average /= found;
boid.velocity -= Vector3.Lerp(Vector3.zero,
average, average.magnitude /radius) * repulsionForce;
}
}
}
//boid를 선형보간으로 뭉치게 한다
[RequireComponent(typeof(Boid))]
public class Boid_Cohesion : MonoBehaviour
{
private Boid boid;
[InspectorName("another boid Search")]
public float radius;
// Start is called before the first frame update
void Start()
{
boid = GetComponent<Boid>();
}
// Update is called once per frame
void Update()
{
//모든 보이드들의 배열 가져오기
Boid[] boids = FindObjectsOfType<Boid>();
Vector3 average = Vector3.zero;
float found = 0;
//linq where ::
foreach (Boid boid in boids.Where(b => b != boid)) {
Vector3 diff = boid.transform.position - transform.position;
if (diff.magnitude < radius)
{
average += diff;
found += 1;
}
}
if (found > 0) {
average /= found;
boid.velocity += Vector3.Lerp(Vector3.zero,
average, average.magnitude /radius);
}
}
}
//Boid를 Radius 반경의 구 내의 랜덤한 pos에 스폰해주는 역할
public class BoidSpawner : MonoBehaviour
{
public GameObject prefab;
public float radius;
public int number;
void Start()
{
for (int i = 0; i < number; i++) {
Instantiate(prefab, transform.position +
Random.insideUnitSphere * radius, Random.rotation);
}
}
}
//boid가 Radius 반경을 가진 구 내에서 활동하도록 제한한다.
[RequireComponent(typeof(Boid))]
public class Boid_Container : MonoBehaviour
{
private Boid boid;
public float radius;
public float boundaryForce;
// Start is called before the first frame update
void Start()
{
boid = GetComponent<Boid>();
}
// Update is called once per frame
void Update()
{
if (boid.transform.position.magnitude > radius)
{
boid.velocity += transform.position.normalized *
(radius - boid.transform.position.magnitude) *
boundaryForce * Time.deltaTime;
}
}
}
코드 리뷰 : 성능 이슈
1. 매 프레임마다 FindObjects사용
void Update(){
//모든 보이드들의 배열 가져오기
Boid[] boids = FindObjectsOfType<Boid>();
//...
}
게임매니저나 BoidSpawner에서 모든 boid를 참조해 관리해주면
매 프레임마다 boid를 새로 찾을 일이 없게 된다.
2. 매 프레임마다 Linq의 사용
자기 자신을 제외한 모든 boid 참조를 받기 위해 Linq.Where() 등 사용하는데,
1번과 마찬가지로 게임매니저나 BoidSpawner에서 모든 boid를 List로 관리해주고
List함수로 자기 자신 제외를 한번 하게 하면 성능 향상 가능
3. boid간 거리 비교에서의 Vector.magnitude (제곱근)의 사용
원 충돌은 제곱으로도 가능함
3개의 간단한 룰이 복잡한 새 무리 이동 시뮬레이션을 만든다는게 신기했네요.
생명게임이 생각나기도 했어요.
'프로그래밍 > 기타' 카테고리의 다른 글
[HKU대학 강의]강의 마무리, 감사 메일 빌드본 (1) | 2020.11.13 |
---|---|
[HKU] 렌더링 기법 RayTracing, RayMarching, PathTracing 장점과 단점 비교 (0) | 2020.10.22 |