[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 9회차 미션 시작합니다.




C#의 Generic 문법을 이용하여 FSM을 구현하는 내용입니다.

상태를 나타내는 State 01~ 구현을 하고, 이 State를 관리하는 State Machine을 구현합니다.

초기화를 진행하면서 State Machine에 각 State 01~ 등록을 진행합니다.

게임 플레이 중에 "Current State", 즉 Transition이 State 간 변경되며 상태가 바뀌는 방식으로 구현합니다.

[Project View] - [Assets] - Scripts 폴더 내에서 "Create - C# Script"를 진행하여 "StateMachine_New"로 작성합니다.


StateMachine_New.cs

public abstract class State<T>
{
    protected StateMachine_New<T> stateMachine;
    protected T context;

    public State()
    {
    }

    internal void SetStateMachineAndContext(StateMachine<T> stateMachine, T context)
    {
        this.stateMachine = stateMachine;
        this.context = context;

        OnInitialized();
    }

    public virtual void OnInitialized()
    {
    }

    public virtual void OnEnter()
    {
    }

    public abstract virtual void Update(float deltaTime);

    public virtual void OnExit()
    {
    }
}

 


public sealed class StateMachine_New<T>
{
    private T context;

    private State<T> currentState;
    public State<T> CurrentState => currentState;

    private State<T> previousState;
    public State<T> PreviousState => previousState;

    private float elapsedTimeInState = 0.0f; // 상태가 변환 되었을 때 변환된 상태에서 얼마나 시간이 흘렀는가를 위한 변수.

    public float ElapsedTimeInState => elapsedTimeInState;

    private Dictionary<System.Type, State<T>> states = new Dictionary<System.Type, State<T>>();

    public StateMachine(T context, State<T> initialState)
    {
        this.context = context;

        // 초기 상태 설정
        AddState(initialState);
        currentState = initialState;
        currentState.OnEnter(); // 초기 State 실행
    }

    public void AddState(State<T> state)
    {
        state.SetMachineAndContext(this, context);
        states[state.GetType()] = state;
    }

    public void Update(float deltaTime)
    {
        elapsedTimeInState += deltaTime;

        currentState.Update(deltaTime);
    }

    public R ChangeState<R> where R : State<T>
    {
        var newType = typeof(R);
        if (currentState.GetType() == newType)
        {
            return currentState as R;
        }
    
        if (currentState != null)
        {
            currentState.OnExit();
        }

        previousState = currentState; // 이전상태 설정
        currentState = states[newType]; // 현재상태 설정
        currentState.OnEnter();
        elapsedTimeInState = 0.0f; // 초기화

        return currentState as R;
    }
}

초기화시 상태를 등록하는 것은 C#의 Dictionary 문법을 사용하여 구현합니다. Template class를 사용하여 State class를 구현하고 StateMachine class에서는 해당 상태 및 흘러간 시간을 체크할 수 있도록 하고, ChangeState() 함수를 통해 instance의 상태 전환을 하는 방법입니다.



이를 이용하여 캐릭터 AI 구현을 해보도록 하겠습니다.


적 GameObject를 구성한 모습입니다.

시간 관계상 대부분 작업된 상태로 준비를 해놓고 시작합니다. NavMesh, Animation 등 앞 시간에서 배운 내용들이기 때문에 다시 설명을 하지는 않습니다. 공격 State도 3가지를 가지고 있고, 연동된 변수도 미리 준비되어 있습니다.
실제로 멋진 적 캐릭터를 가지고 구현을 해보니 좋긴합니다.. 단지 배운 내용이 한번에 집성체로 나타나니 ㅋㅋ 가..감당하기가 어렵네요.. 머 그래도 괜찮습니다. 저희가 하려는 건 FSM이니까요



+ Idle State - 적을 찾아다니는 상태. 사정거리 내에 적이 있는지 체크. 사정거리 밖에 있고 적에게 이동해야 하는지 체크.
+ Attack State - 적이 사정거리내 있는 상태. 공격 애니메이션 구동.
+ Move State - 적에게 이동하는 상태. 이동 애니메이션 구동.

 



EnemyController_New.cs

public class EnemyController_New : MonoBehaviour
{
    protected StateMachine<EnemyController_New> stateMachine;

    private void Start()
    {
        stateMachine = new StateMachine<EnemyController_New>(this, new IdleState());
        stateMachine.AddState(new MoveState());
        stateMachine.AddState(new AttackState());
    }

    private void Update()
    {
        stateMachine.Update(Time.deltaTime);
    }
}


오늘은 완전 코드 레벨이네요. ㅋㅋ 유니티 화면을 거의 보지도 못했어요. ㅎㅎ
강의에서 설명되는 내용들을 코드로 작성하였지만, 제가 보니 설명에는 없지만 실제 코드화된 부분들이 있습니다.
하지만 실제 코드가 교육자료로 첨부되어 있기 때문에 공부하는데에는 지장이 있지는 않습니다. 자료는 첨부하지 않습니다. 그냥 저런 형태로 구현을 하는구나라고만 생각하고 보면 될 것 같습니다.



IdleState.cs

    public class IdleState : State<EnemyController_New>
    {
        private Animator animator;
        private CharacterController controller;

        protected int hasMove = Animator.StringToHash("Move");
        protected int hasMoveSpeed = Animator.StringToHash("MoveSpeed");

        public override void OnInitialized()
        {
            animator = context.GetComponent<Animator>();
            controller = context.GetComponent<EnemyController_New>();
        }

        public override void OnEnter()
        {
            animator?.SetBool(hasMove, false);
            animator?.SetFloat(hasMoveSpeed, 0);
            controller?.Move(Vector3.zero);
        }

        public override void Update(float deltaTime)
        {
            Transform enemy = context.SearchEnemy();
            if (enemy)
            {
                if (context.IsAvailableAttack)
                {
                    stateMachine.ChangeState<AttackState>();
                }
                else
                {
                    stateMachine.ChangeState<MoveState>();
                }
            }
        }

        public override void OnExit()
        {
        }
    }

 



코드 레벨로는 파일도 여러개이고, 직접 작성하여 컴파일도 해보아야 하는 부분이라 그림으로 마지막 정리를 해주시네요. ^^




IdleState 상태 기준으로의 설명입니다.

결국 초기화시에 OnInit으로 시작을 하고,
상태가 시작되면 OnEnter로 진입을 하게되고,
현 Idle 상태로 지속 OnUpdate가 호출되며 준비하다가,
적이 사정거리내에 있다면 OnExit를 호출하고,
AttackState로 전환이 이루어지도록 만든 소스코드인 것입니다.

ㅎㅎ 진짜 중요한 내용이 많은 코드인데.. 짧게 할수도 없고 너무 길게 할수도 없기에 어쩔수 없는 것이라고 봐야합니다. 이것과 관련해서는 C# 문법과 프로그래밍 스킬이 어느정도 있어야 이해가 가능하다고 할 수 있겠네요.

 

 

아쉬워서 유니티의 Enemy가 설정된 화면 스샷 하나 첨부합니다. ㅎㅎ

 

 



패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 8회차 미션 시작합니다.


환경 시스템중 네비게이션 시스템 알아보기와 적 캐릭터 AI 구현 모델에 대해서 공부하는 시간입니다.


일단 저번시간에 배운 Terrain 시스템 중 나무를 처리하는 부분중 괜찮은 유틸리티에 대한 소개입니다.
TreeIt: Tree Generator 라는 무료 유틸리티 소개 www.evolved-software.com/treeit/treeit

 

TreeIt | Tree Generator

 

www.evolved-software.com

 

 


네비게이션 시스템은 게임 월드에서 이동할 수 있는 길찾기 시스템입니다. 네비게이션 시스템의 단점은 NavMesh위에 항상 NavMeshAgnet가 존재해야 한다는 점입니다.

이 문제를 해결하려면 캐릭터 콘트롤의 navMesh를 사용하는 것이 아니라 단순히 길찾기만 사용하는 방법도 있습니다. CalculatePath() 함수에 대해 알아보시면 됩니다.

NavMesh는 캐릭터가 걸어다닐 수 있는 표면들을 Mesh로 표현한 것입니다.
NavMeshAgent는 GameObject가 원하는 목표로 지정했을 때 경로를 계산해주고 장해물과 또다른 Agent들끼리의 충돌을 피할 수 있도록 해줍니다.
NavMeshObstacle는 Agent가 경로를 탐색하는 동안 회피해야하는 움직이는 장해물들을 정의할 수 있습니다. 문이 열리고 닫히는 등의 작업에 사용할 수 있습니다.
OffMeshLink는 Agent가 연결되지 않은 내부 Mesh를 이동하는 방법에 대한 정의입니다. 특정 영역에 대해서 Agent가 Jump를 통해서만 이동할 수 있는 등의 특수한 경로라고 보면 될 것같습니다.



네비게이션 편집을 위해서 [Window] - [AI] - [Navigation] 메뉴로 진입합니다.
처음에는 아무것도 없는 Level이 표시됩니다. Navigation Mesh도 Baking 한다고 표현합니다.

[Object] 탭으로 진행하여 [Mesh Renderers] 필터를 클릭하고, Hierarchy 뷰에서 NavMesh를 정의할 GameObject들을 선택하고 "Navigation Static [v]"을 Check하여 주고, Navigation Area는 "Walkable"을 선택하여 줍니다. 예제에서는 Floor와 이동가능한 계단으로 설정된 Cube들을 선택하였습니다.
이렇게 Navigation Static [v]을 선택해야 NavMesh에서 사용되는 영역으로 Baking 됩니다.

Terrain이 있는 경우 [Terrains] 탭으로 진입하여 마찬가지로 "Navigation Static [v]"과 "Walkable"을 선택해주면 됩니다.


[Bake] 탭으로 이동하여 아래와 같이 각종 옵션들을 설정하고 [Bake]를 클릭하여 Baking을 시작합니다.

+ Agent Radius: 0.5
+ Agent Height: 2
+ Max Slope: 45
+ Step Height: 0.3

아래 2개의 값은 "Off Mesh Link"를 Baking 하게될 때 참고하는 값입니다. 여기서는 자동적으로 하지 않을 것이라서 0으로 처리합니다.

+ Drop Height: 0
+ Jump Distance: 0

Baking을 완료하면 GameObject가 이동할 수 있는 NavMesh가 생성된 것을 확인할 수 있습니다.


GameObject가 걸어 올라갈 수 없는 컨테이너, 박스 같은 오브젝트는 "Not Walkable"로 설정하는 것이 좀 더 좋겠지요.



Scene View를 보는 상태로 플레이를 진행해 봅니다. Game View에서 특정 위치를 클릭하면 자동으로 계산된 NavMesh를 보여주며 캐릭터가 이동하는 것을 볼 수 있습니다. 이를 활용하면 NavMesh와 NavMeshAgent가 정상동작하는지 등을 디버깅하는데 도움이 됩니다.





Off Mesh Link는 컴포넌트입니다.
Jump_Link GameObject에 "Off Mesh Link" 컴포넌트를 추가합니다.
Jump_Link GameObject의 자식으로 "Start", "End"라는 Sphere를 2개 구성해줍니다.
Start 구는 언덕 위에 배치하고, End 구는 Floor 위에 배치하여 줍니다.

[Off Mesh Link] 옵션을 설정합니다.
+ Start = Start (Transform) - 위에 설정한 Start GameObject 설정
+ End = End (Transform) - 위에 설정한 End GameObject 설정
+ Cost Override = -1 - 길찾기를 할 때 현재 Off Mesh Link가 얼마정도의 길찾기 비용이 들어가는가 설정
+ Bi Directional = [ ] - Check를 해주게 되면 양방향 이동이 가능하게 됩니다. 여기서는 언덕에서 내려오는 것이므로 Uncheck 합니다.
+ Activated = [v] - 현재 Off Mesh Link의 활성화 여부. 특정 아이템을 가진 경우에만 이동 가능과 같은 기능을 구현할 때 사용.
+ Auto Update Positions = [v] - Agent에 대한 위치를 자동화으로 업데이트
+ Navigation Area = Jump



실행하면 다음과 같이 구동되는 것을 볼 수 있습니다. "Bi Directional [v]"을 Check하면 End 점에서 Start 점으로 날아서 점프하는 멋진 모습을 볼 수 있네요 ㅋㅋㅋ



Off Mesh Link 자동 생성

[Bake] 탭 - [Generated Off Mesh Links]의 값에서
+ Drop Height = 5
+ Jump Distance = 5
정도로 설정을 해줍니다.

[Object] 탭 - Floor와 Cube로 구성된 Stair를 선택하고 "Generate OffMeshLinks [v]"를 체크한 후에 [Bake]를 클릭하여 Baking 하면 됩니다. 아래와 같이 생성된 Off Mesh Link를 확인할 수 있습니다.






동적 장애물에 대한 NavMeshObstacle 설정하기.

Cube GameObject를 하나 추가합니다.
컴포넌트 추가를 하여 "Nav Mesh Obstacle"을 검색하여 추가합니다.

설정값중.
Carve = [v] - Agent가 이 Object에 대한 회피값 설정
Move Threshold = 0.1 - 이동시 얼마나 회피할 것인가
Time To Stationary = 0.5 - 이 Object가 이동할 때 내부 Mesh를 얼마나 빨리 갱신할 것인가


구동을 해보면 실시간으로 이동가능한 경로가 생기거나 사라지게 하거나 하는 것이 가능합니다.




앞으로 강의에 사용하게 될 Map을 소개해 주시네요.


와우 멋집니다 ^^~ㅎㅎ 대신 저 정도의 퀄러티는 유료 에셋을 구매하여 구성한 것이라고 합니다.
유료 에셋은 게임에 어느정도 최적화된 에셋들이 에셋 스토어에 많기 때문에 포트폴리오 제작 등에 많이 도움이 될 것입니다.

해당 레벨은 총 3개의 방으로 구성되어 있습니다.
방의 연결 부분들은 문이나 함정 등을 설정하여 사용할 수 있도록 할 예정입니다.
방마다 조금씩 다른 Lighting System을 적용해 두었고, Light Probe도 적용되어 있습니다. 현재 촘촘하게 하여 Texture가 18장이라 메모리 용량이 커진 상태입니다.
위에서 공부한 NavMesh도 적용하여 이동가능한 위치 설정이 완료된 상태입니다.




문같은 테스트를 위해 Cube 오브젝트를 적당한 크기로 방과 방 사이에 배치해 주고 "Nav Mesh Obstacle"을 설정하여 캐릭터가 이동하지 못하게 바로 설정이 가능합니다.



포트폴리오를 위한 게임 레벨을 구성을 할때의 팁입니다.

+ Random dungeon generator watabou.itch.io/one-page-dungeon

 

One Page Dungeon by watabou

One page dungeon generator

watabou.itch.io

무작정 맵을 그리는 것이 아니라 미리 어느 정도 잘 구성된 맵을 구성하여 사용가능합니다.

+ 스냅핑 기능을 위한 ProGrids
 [Window] - Package Manager - Advanced - Show preview packages에서 "ProGrids"를 검색하여 설치합니다.
 오브젝트들의 배치들을 편하게 도와주는 기능이라고 보시면 됩니다.

 
 
 
 
 

  
게임 캐릭터의 AI 구현하기
 
AI 구현 모델들
+ Finit State Machine
+ Behavior Tree
+ 캐릭터 AI 구현
+ 캐릭터 시야 및 향상된 AI 구현 




좌측 Finit State Machine 유한 상태 머신은 캐릭터의 상태 Transition에 대한 제어를 직관적으로 할 수 있습니다.
우측 Behavior Tree는 캐릭터의 행동에 대한 기능과 제약을 직관적으로 할 수 있습니다.

FSM
한번에 하나의 상태만 가능합니다. 한 상태에서 다른 상태로의 이전은 Transition입니다.
사각형 박스는 Node, 화살표는 Transition이라고 보면 됩니다.

내용중 주황색으로 표시된 "player is near"를 보면 캐릭터가 돌아다니는 상태에서 주변에 다른 플레이어나 적이 나타나는 경우 "공격" state로 전환되는 transition이 발생하게 됩니다.

Behavior Tree의 Node 3가지 종류
+ Control node = Selector(특정 행동 선택), Sequence(자식 노드들을 순차적으로 수행) 등.
+ Decorator node = if문, loop문 등
+ Execution node = Action. 특정 행동을 수행.



예를 들어 위와 같은 특정 루틴을 수행해야 하는 시퀀스가 있다고 했을 때, 가진 돈이 200원 미만이 되면 "If Decorator" node가 failure를 부모 "Sequence"에 return하고, 결론적으로 전체 root가 Failure로 처리되어 특정 Sequence 동작 전체가 Failure 상태임을 처리할 수가 있게 됩니다.

성공 상황은 쉽겠지요 ^^.  하위 노드가 모두 수행되어 "Action 6"인 의자에 앉는다 까지 모두 Success로 성공하게 되면, 모든 Sequence 동작이 정상적으로 수행되었음을 "Success"로 처리되어 확인하게 됩니다.

이렇듯 Behavior Tree는 특정 흐름들을 제어할 수 있도록 도와주는 AI 모델이라고 보면 됩니다.


아직 구현은 하지 않았지만 벌써 상상이 가네요 ㅠ.ㅜ; Node Editor 같은 화면에서 함수들을 저렇게 Tree 구조로 배치하면 동적으로 조건들이 처리될테고.. 기존 if-else로 지저분하게 만들던 코드들을 깔끔하게 처리할 수 있겠네요. 정말 머리 좋습니다. 대단 대단.




패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 7회차 미션 시작합니다.


Lighting 시스템 알아보기 2




Light Probe는 Bake된 조명정보를 Scene에 저장하는 기법입니다.
Light Map은 표면에 대한 정보를 저장하고, Light Probe는 공간에 대한 정보를 저장하는 것입니다.

Light Probe는 Spherical Harmonic 기법을 사용합니다. 복잡한 계산을 처리합니다.
Dynamic object에 Global illumination 효과를 주기 위해서 사용합니다.

Reflection Probe는 Cube 맵의 주변 환경을 Textutre로 처리하는 것이라고 이해하면 됩니다.
상하좌우앞뒤를 카메라로 촬영한 이미지를 object에 입혔다고 보면 됩니다.

유니티에서는 실시한 reflection으로 처리가 가능하지만 성능의 문제가 발생할 수 있으므로, 덜 중요한 object들에는 reflection probe로 처리된 이미지를 object에 baking하여 사용하는 방식을 보통 취합니다.



Light Probe 설정 방법


Light Probe Group을 추가합니다.
Light Probe Group을 조밀하게 설정하는 것이 좋기는 하지만, Baking에 시간이 많이 소요되고 Light Map의 개수가 많이 늘어나서 필요한 메모리의 양이 많이 높아지기 때문에 적절한 설정을 해주어야 합니다.

"Edit Light Probes"를 선택한 이후에 Vertex들을 선택하고 Ctrl+C Ctrl+V로 복사하여 확장해 나아갑니다.

벽, 즉 object들 주위로는 조명 변화가 많이 일어나므로 조밀하게 설정하는 것이 좋습니다. 그리고 Generate로 Baking을 진행하면 됩니다.




Sphere를 추가하여 공간에서 이동하여 보면 영향을 받는 Light Probe들이 표시가 되어 확인이 가능합니다.

PlayerCharacter를 가지고 플레이를 시작해보면 캐릭터의 위치에 따라 조명의 영향을 받는 것을 볼 수 있습니다.

 





Reflection Probe 설정 방법



Light - Reflection Probe를 추가합니다.
Type - Baked로 진행합니다. "Bake"를 누르면 ReflectionProbe가 생성되는 것을 확인할 수 있습니다.

타겟 Platform에 맞는 옵션으로 지속적으로 최적화하는 노력이 필요합니다.






 

Terrain 시스템

 



Terrain 시스템에서는 공간배치, 지형 높이, 홀 배치, 나무와 풀 배치, Height Map 등을 할 수 있습니다.

나무는 Mesh object로 저정을 하지만 풀은 개수가 많기 때문에 billboard로 처리를 하게 됩니다. 물론 detail함을 위해 object로 처리할 수도 있습니다.
나무의 윈드존 설정 가능합니다. 바람이 불어 흔들리는 효과입니다.

시스템에 많은 영향을 주게 되므로 Terrain Object의 개수나 Texture의 크기 등을 고려하여 개발해주어야 합니다.





[Window] - TextMeshPro 를 선택하여 편집 설정 창이 나타납니다.

원하는 타일을 선택하여 Terrain을 지정한 후, Brush 툴을 선택하고 옵션 값들을 조정한 후, LMB 와 Shift + LMB 등을 활용하여 높이를 조절합니다.

Paint Texture 등을 이용하여 특정 Texture를 입히며 그릴 수도 있고, Terrain Hole 등으로 던전 입구를 표시한다거나 할 수가 있습니다. 다양한 기능과 옵션을 사용하여 구성해 봅니다.




Wind Settings for Grass (On Terrain Data)
풀이 얼마나 자동으로 흔들릴지를 설정하는 부분입니다.

Wind Zone 오브젝트를 추가하면 나무에 바람의 영향을 줄 수 있습니다.

Terrain 상태에서도 Global Illumination 설정과 Light Probe Group 등을 설정하여 테스트를 해보면 지형에 영향을 받는 조명 시스템을 구성할 수 있습니다.


 

캐릭터가 빨간 조명이나 던전 입구의 노란 조명에 영향을 받는 것을 확인할 수 있습니다.





패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts