본문 바로가기
대학생활/수업

게임프로그래밍고급 13주차 - 군집 시뮬레이션

by se.jeon 2023. 5. 30.
728x90
반응형

군집 시뮬레이션

- 개체들의 행동 패턴 활용

 

Boids 알고리즘

군집을 이루는 각각의 개체가 시각이나 청각 등의 감각을 이용해 주변 개체들의 움직임에 따라 자신의 움직임을 수정함으로써, 군집의 복잡한 움직임을 만들어 내는 알고리즘.

각각의 개체는 세 가지 간단한 규칙을 진행한다.

- 분리 (Seperation)
- 정렬 (Alignment)

- 결합 (Cohesion)

 

Boid

- 대상 : 무리에 있는 단일 개체

- 변수 : 위치와 속도

- 방식 : 행동 규칙 3개를 이용해서 가속도 계산

- 가속도는 현재 속도에 영향을 미치고, 속도에 의해 다음 위치가 결정됨.

- 너무 빨라지지 않도록 최고 속도를 정해놓고, 그보다 높아지지 않도록 조종이 필요.

 

Boids 알고리즘 - Alignment (정렬)

- 무리에 있는 개체들의 평균적인 방향으로 방향을 이동시킨다.

 

Boids 알고리즘 - Cohesion (결합)

- 무리에 있는 개체들을 평균적인 위치로 이동시킨다.

 

- FlockMgr : Boids 알고리즘을 위한 관련 파라미터 설정

- Boid : Boid에 해당하는 클래스.

- BoidBehavior : 행동을 정의하는 추상 클래스 (abstract class)

- BoidBehaviorController : 개별 행동들에 대한 제어 클래스

- BoidSeparation : Separation 행동에 대한 클래스

- BoidAlignment : Alignment 행동에 대한 클래스

- BoidCohesion : Cohesion 행동에 대한 클래스

 

Boid Script 구현

 

[RequireComponent(typeof(Collider2D))]
public class Boid : MonoBehaviour
{
    FlockMgr agentFlock;
    public FlockMgr AgentFlock { get { return agentFlock; } }
    Collider2D agentCollider;
    public Collider2D AgentCollider { get { return agentCollider; } }

    void Start()
    {
        agentCollider = GetComponent<Collider2D>();
    }

    public void Initialize(FlockMgr flock)
    {
        agentFlock = flock;
    }

    public void Move(Vector2 velocity)
    {
        transform.up = velocity;
        transform.position += (Vector3)velocity *
        Time.deltaTime;
    }
}

 

[RequireComponent(typeof(컴포넌트 이름))]
- Unity Script에서 특정 컴포넌트에 대해 자동으로 추가 요청 진행.
- 추가 특정 컴포넌트 필요할 경우, 자동으로 해당 컴포넌트를 추가
- 해당 컴포넌트를 추가하지 않는 오류가 발생하지 않음.
- RequireComponent를 관련 컴포넌트들 추가 가능
- 충돌체크 확인을 위해서 Collider2D를 타입을 추가

 

유니티에서의 Boids 구현 실습

- Boid 객체 생성을 위해서 Create Empty를 통해 GameObject 생성

- Boid 객체에 Sprite Renderer 컴포넌트 추가

- Sprite객체에 객체로 사용할 이미지 추가

- Circle Collider 2D 추가 , Boid Script 추가

- 최종적으로 만든 Boid Object를 Asset폴더로 옮겨서 Prefab으로 사용

- Prefab을 이용해서 Boid 중복 생성시에 이용

 

Prefab (프리팹)

Unity에서 GameObject를 이용해서 특정 컴포넌트를 추가하고

GameObject를 여러 곳에 사용하려면 GameObject를 중복해서 생성 진행하게 되는데,

이렇게 중복 생성한 GameObject의 속성들을 모두 수정하려면 어떻게 해야 하는가?

- 생성한 GameObject들을 모두 선택해서 속성값을 수정해야 한다.

- 중복 작업이 생기는 문제가 있다.

> GameObject와 해당 컴포넌트, 프로퍼티를 Asset에 저장하는 Prefab을 통해서 한꺼번에 수정 할 수 있다.

 

FlockMgr 구현

Boids 관련 파라미터 처리를 위한 FlockMgr 이름으로 Script를 생성한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlockMgr : MonoBehaviour
{   
    public Boid boidPrefab;
    List<Boid> boids = new List<Boid>();
    //public FlockBehavior behavior;
    public BoidBehavior behaviorController;  // 2021.05.24

    [Range(10, 500)]
    public int startingCount = 250;
    const float AgentDensity = 0.08f;

    [Range(1f, 100f)]
    public float driveFactor = 10f;
    [Range(1f, 100f)]
    public float maxSpeed = 5f;
    [Range(1f, 10f)]
    public float neighborRadius = 1.5f;
    [Range(0f, 1f)]
    public float avoidanceRadiusMultiplier = 0.5f;

    float squareMaxSpeed;
    float squareNeighborRadius;
    float squareAvoidanceRadius;
    public float SquareAvoidanceRadius { get { return squareAvoidanceRadius; } }

    // Start is called before the first frame update
    void Start()
    {
        squareMaxSpeed = maxSpeed * maxSpeed;
        squareNeighborRadius = neighborRadius * neighborRadius;
        squareAvoidanceRadius = squareNeighborRadius * avoidanceRadiusMultiplier * avoidanceRadiusMultiplier;

        for (int i = 0; i < startingCount; i++)
        {
            Boid newAgent = Instantiate(
                boidPrefab,
                Random.insideUnitCircle * startingCount * AgentDensity,
                Quaternion.Euler(Vector3.forward * Random.Range(0f, 360f)),
                transform
                );
            newAgent.name = "Agent " + i;
            newAgent.Initialize(this);
            boids.Add(newAgent);
        }
    }

    // Update is called once per frame
    void Update()
    {
        foreach (Boid boid in boids)
        {
            List<Transform> context = GetNearbyObjects(boid);

            //FOR DEMO ONLY
            //agent.GetComponentInChildren<SpriteRenderer>().color = Color.Lerp(Color.white, Color.red, context.Count / 6f);

            Vector2 move = behaviorController.CalculateMove(boid, context, this);
            move *= driveFactor;
            if (move.sqrMagnitude > squareMaxSpeed)
            {
                move = move.normalized * maxSpeed;
            }
            boid.Move(move);
        }
    }

    List<Transform> GetNearbyObjects(Boid boid)
    {
        List<Transform> context = new List<Transform>();
        Collider2D[] contextColliders = Physics2D.OverlapCircleAll(boid.transform.position, neighborRadius);
        foreach (Collider2D c in contextColliders)
        {
            if (c != boid.AgentCollider)
            {
                context.Add(c.transform);
            }
        }
        return context;
    }
}

 

Instantiate

기준 Object를 특정 위치와 회전을 가진 상태로 새롭게 생성 가능하다.

Prefab을 특정 위치, 회전 값을 가지게 오브젝트 생성하는 데에 많이 사용된다.

public static Object Instantiate(Object original, Vector3 position, Quaternion rotation);

https://docs.unity3d.com/ScriptReference/Object.Instantiate.html

 

Unity - Scripting API: Object.Instantiate

This function makes a copy of an object in a similar way to the Duplicate command in the editor. If you are cloning a GameObject you can specify its position and rotation (these default to the original GameObject's position and rotation otherwise). If you

docs.unity3d.com

 

OverlapCircleAll

이웃된 GameObject들을 찾는다.

 

FlockMgr 구현

- Create Empty를 통해서 FlockMgr 객체 생성

- Script FlockMgr 구성요소 추가, FlockMgr의 Boid Prefab 설정

 

Boid Behavior 구현

- 추상클래스를 통해서 Boid의 BoidBehavior 구현

- BoidBehavior 클래스를 통해서 3가지 행동 클래스 구현

- Separation, Alignment, Cohesion 처리

 

CalculateMove

무리의 개체들의 움직임을 알기 위해서는 Transform값 확인 필수

 

Separation (분리) 구현

무리에 있는 개체들이 뭉치지 않도록 방향을 바꾸고 멀어지게 처리.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoidSeparation : BoidBehavior
{
    public override Vector2 CalculateMove(Boid agent, List<Transform> context, FlockMgr flockMgr)
    {
        // 주변이 없으면 특별한 변화가 없게 처리 
        if (context.Count == 0)
            return Vector2.zero;

        Vector2 avoidanceMove = Vector2.zero;
        int nAvoid = 0;
        
        // 주변 객체들의 Transform을 확인한다. 
        List<Transform> filteredContext = context;
        
        foreach (Transform item in filteredContext)
        {
            // 다른 객체들과의 거리를 구한다. 
            if (Vector2.SqrMagnitude(item.position - agent.transform.position) < flockMgr.SquareAvoidanceRadius)
            {
                nAvoid++;
                avoidanceMove += (Vector2)(agent.transform.position - item.position);
            }
        }
        if (nAvoid > 0)
            avoidanceMove /= nAvoid;

        return avoidanceMove;
    }
}

 

Alignment (정렬)

무리에 있는 개체들의 평균적인 방향으로 방향 이동시킨다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoidAlignment : BoidBehavior
{ 
    public override Vector2 CalculateMove(Boid agent, List<Transform> context, FlockMgr flockMgr)
    {
        //if no neighbors, maintain current alignment
        if (context.Count == 0)
            return agent.transform.up;

        //add all points together and average
        Vector2 alignmentMove = Vector2.zero;
        
        // 2021.05.24
        //List<Transform> filteredContext = (filter == null) ? context : filter.Filter(agent, context);
        List<Transform> filteredContext = context;

        foreach (Transform item in filteredContext)
        {
            alignmentMove += (Vector2)item.transform.up;
        }
        alignmentMove /= context.Count;

        return alignmentMove;
    }
}

 

Cohesion (결합)

무리에 있는 개체들을 평균적인 위치로 이동시킨다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoidCohesion : BoidBehavior
{
    public override Vector2 CalculateMove(Boid agent, List<Transform> context, FlockMgr flockMgr)
    {        
        if (context.Count == 0)
            return Vector2.zero;
             
        Vector2 cohesionMove = Vector2.zero;
                
        List<Transform> filteredContext = context;
        
        foreach (Transform item in filteredContext)
        {
            cohesionMove += (Vector2)item.position;
        }
        cohesionMove /= context.Count;
                
        cohesionMove -= (Vector2)agent.transform.position;
        return cohesionMove;
    }
}

 

행동 Object 구현

- 3가지 행동 클래스에 대해서 GameObject를 생성해서 Prefab생성

- Separation GameObject 생성, BoidSepartion 스크립트 추가

- Alignment GameObject 생성 , BoidAlignment 스크립트 추가

- Cohesion GameObject 생성 , BoidCohesion 스크립트 추가

- 생성한 3개의 GameObject를 Prefab 처리 : SBehavior , ABehavior, CBehavior

 

BoidBehaviorController 구현

- BoidBehaviorController 이름으로 Script생성

- 여러 행동들에 대해서 제어하기 위한 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoidBehaviorController : BoidBehavior
{ 
    public BoidBehavior[] behaviors;
    public float[] weights;

    public override Vector2 CalculateMove(Boid agent, List<Transform> context, FlockMgr flockMgr)
    {
        //handle data mismatch
        if (weights.Length != behaviors.Length)
        {
            Debug.LogError("Data mismatch in " + name, this);
            return Vector2.zero;
        }

        //set up move
        Vector2 move = Vector2.zero;

        //iterate through behaviors
        for (int i = 0; i < behaviors.Length; i++)
        {
            Vector2 partialMove = behaviors[i].CalculateMove(agent, context, flockMgr) * weights[i];

            if (partialMove != Vector2.zero)
            {
                if (partialMove.sqrMagnitude > weights[i] * weights[i])
                {
                    partialMove.Normalize();
                    partialMove *= weights[i];
                }

                move += partialMove;

            }
        }

        return move;
    }
}

- BoidBehaviorController 이름으로 GameObject생성

- BoidBehaviorController 스크립트 컴포넌트 추가

- Behaviors, Weight의 값을 3으로 설정

- Behaviors에 SBehavior , ABehavior, CBehavior Prefab 설정

- Weights에 각 행동에 대한 가중치 설정

- FlockMgr 게임 오브젝트의 Behavior Controller에 BoidBehaviorController GameObject 설정

 

BoidSteeredCohesion 구현

Cohesion의 응집력에 대한 부분을 자연스럽게 구현

Cohesion과 거의 유사, 부드럽게 처리

public class BoidSteeredCohesion : BoidBehavior
{
    Vector2 currentVelocity;
    public float agentSmoothTime = 0.5f;
    
    public override Vector2 CalculateMove(Boid agent, List<Transform> context, FlockMgr flockMgr)
    {
        if (context.Count == 0) return Vector2.zero;
        
        Vector2 cohesionMove = Vector2.zero;
        List<Transform> filteredContext = context;
        
        foreach (Transform item in filteredContext)
        {
       	 cohesionMove += (Vector2)item.position;
    	}
        
        cohesionMove /= context.Count;
        cohesionMove -= (Vector2)agent.transform.position;
        cohesionMove = Vector2.SmoothDamp(agent.transform.up, cohesionMove, ref currentVelocity, agentSmoothTime);
        return cohesionMove;
    }
}

BoidInRadius 구현

Boid의 움직임을 원 안에서 처리

public class BoidInRadius : BoidBehavior
{
    public Vector2 center;
    public float radius = 15f;
    
    public override Vector2 CalculateMove(Boid agent, List<Transform> context, FlockMgr flockMgr)
    {
        Vector2 centerOffset = center - (Vector2)agent.transform.position;
        float t = centerOffset.magnitude / radius;
        
        if (t < 0.9f)
        {
        	return Vector2.zero;
        }
    	return centerOffset * t * t;
    }
}

728x90
반응형