낑깡의 게임 프로그래밍 도전기

C# 상태머신 상태패턴 본문

카테고리 없음

C# 상태머신 상태패턴

낑깡겜플밍 2023. 11. 14. 21:13
반응형
SMALL

상태머신

현재상태를 저장할수 있어야하며 전이가 될수 있어야한다.

현재 상태는 하나만 가져야한다.

 

상태패턴

상태머신을 객체지향적 패턴으로 만든것

 

고로 상태머신이라고 상태패턴은 아니며 상태패턴은 곧 상태머신이 된다

 

상태패턴은 다 다르지만

전략패턴은 행위의 파생느낌이다. 공격내 여러가지 공격이 있는것 처럼

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AI;
using static Unity.VisualScripting.Dependencies.Sqlite.SQLite3;

public enum ENEMY_STATE_TYPE
{
    IDLE,
    TRACE,
    ATTACK
}

public class State
{
    public Enemy enemy;
    public State(Enemy enemy)
    {
        this.enemy = enemy;
    }
    public virtual void Update()
    {

    }
}

public class IdleState : State
{
    public IdleState(Enemy enemy) : base(enemy)
    {
    }

    public override void Update()
    {
        if (enemy.isDetectiveTarget)
            enemy.curState = new TraceState(enemy);
    }
}
public class TraceState : State
{
    public TraceState(Enemy enemy) : base(enemy)
    {
    }

    public override void Update()
    {
        if (enemy.isDetectiveTarget == false)
            enemy.curState = new IdleState(enemy);
        if (enemy.isAttackable)
            enemy.curState = enemy.attackStrategy;
        enemy.SetTarget();
    }
}
public class AttackState : State
{
    public AttackState(Enemy enemy) : base(enemy)
    {
    }

    public override void Update()
    {
        if (enemy.isAttackable == false)
            enemy.curState = new TraceState(enemy);
        Debug.Log("공격!");
    }
}

public class MeleeAttackState : AttackState
{
    public MeleeAttackState(Enemy enemy) : base(enemy)
    {
    }

    public override void Update()
    {
        if (enemy.isAttackable == false)
            enemy.curState = new TraceState(enemy);
        Debug.Log("근거리 공격!");
    }
}

public class RanageAttackState : AttackState
{
    public RanageAttackState(Enemy enemy) : base(enemy)
    {
    }

    public override void Update()
    {
        if (enemy.isAttackable == false)
            enemy.curState = new TraceState(enemy);
        Debug.Log("원거리 공격!");
    }
}

public enum ATTACK_TYPE
{
    Melee,
    Range
}
public class Enemy : MonoBehaviour
{
    // public ENEMY_STATE_TYPE curType;
    public State curState;

    public ATTACK_TYPE atkType;
    public LayerMask targetMask;
    public float detectiveRange;
    public float attackableRange;
    public AttackState attackStrategy = null;

    public Transform target;
    public NavMeshAgent agent;
    // Start is called before the first frame update
    void Start()
    {
        if (atkType == ATTACK_TYPE.Melee)
        {
            attackStrategy = new MeleeAttackState(this);
        }
        else
        {
            attackStrategy = new RanageAttackState(this);
        }

        curState = new IdleState(this);
        agent = GetComponent<NavMeshAgent>();
    }

    public bool isDetectiveTarget;
    public bool isAttackable;

    private void Update()
    {
        isDetectiveTarget = Physics.OverlapSphere(transform.position, detectiveRange, targetMask).Length > 0;
        isAttackable = Physics.OverlapSphere(transform.position, attackableRange, targetMask).Length > 0;
        curState.Update();
    }

    public void SetTarget()
    {
        agent.SetDestination(target.position);
    }

    public void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        Gizmos.DrawWireSphere(transform.position, detectiveRange);
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, attackableRange);
    }
}

using System.Collections;
using System.Collections.Generic;
using System.Data;
using UnityEditor.Build;
using UnityEngine;
using static UnityEditor.Experimental.GraphView.Port;
using System;

namespace CustomCollections
{
    public class List
    {
        const int defaultCapacity = 4;//기존꺼는 바뀌면안되서 콘스트로 ㄷ만든다
        int[] items;
        int size = 0;

        public int this[int index]//디스라는 것은 객체하나 이것을 쓰면 At을 안쓰고 출력가능
        {
            get { return items[index]; }
            set { items[index] = value; }
        }
        public List()
        {
            items = new int[defaultCapacity];
        }
        public void Add(int item) 
        {
            if (size >= items.Length)
                Grow();
            items[size] = item;
            size++;
        }
        /*
        public int At(int index) 
        { return items[index]; }*/
        public void Grow()
        {
            //Capacity를 2배늘려주고
            //새로운 배열 Capacity만큼 할당하고
            //기존 배열속 원소들은 새로운 배열에 복사해서 넣는 과정
            int newCapacity = items.Length*2;
            int[] newItems = new int[newCapacity];

            Array.Copy(items,0, newItems, 0, size);//(복사위치, 위치의 어디부터, 복사대상, ,복사대상의 시작점, 끝점)
            /*
            for(int i = 0; i < items.Length; i++)
            {
                newItems[i] = items[i];
            }*/
            items = newItems;
        }
    }

    public class GameManager : MonoBehaviour
    {
        //List<T> 가변배열 : 크기가 변할 수 있는 배열
        //Array   정적배열 : 크기가 변하지 않는 배열

        int[] array;
        int capacity = 5;

        List list;
        void Start()
        {
            list = new List();
            list.Add(10);

            /*
            int[] array = new int[capacity];
            array[0] = 10;
            array[1] = 20;
            array[2] = 30;
            array[3] = 40;
            array[4] = 50;
            capacity = capacity * 2;
            int[] temp = array;//얕은 복사가 일어남
            Debug.Log(temp[0]);//10나옴
            array = new int[capacity];
            //array = new int[30]; //위의 배열이 삭제후 30개로 새로 생성
            for (int i = 0; i < capacity; i++)
            {
                array[i] = temp[i];
            }

            Debug.Log(temp[0]);//10나옴
            Debug.Log(array[0]); //새로 만들면서 주소가 달라져서 0번이 출력이 안된다.*/


            Debug.Log(list[1]);
        }
    }

}
using System.Collections;
using System.Collections.Generic;
using System.Data;
using UnityEditor.Build;
using UnityEngine;
using static UnityEditor.Experimental.GraphView.Port;
using System;
using System.Runtime.ExceptionServices;

namespace CustomCollections
{
    public class List
    {
        const int defaultCapacity = 4;//기존꺼는 바뀌면안되서 콘스트로 ㄷ만든다
        int[] items;
        int size = 0;
        public int Capacity { get { return items.Length; } }//전체길이
        public int Count { get { return size; } }//안에 몇개가 있는지 세는거

        public int this[int index]//디스라는 것은 객체하나 이것을 쓰면 At을 안쓰고 출력가능
        {
            get { return items[index]; }
            set { items[index] = value; }
        }
        public List()
        {
            items = new int[defaultCapacity];
        }
        public void Add(int item)
        {
            if (size >= items.Length)
                Grow();
            items[size] = item;
            size++;
        }
        public void RemoveAt(int index)// 그 번째를 지움
        {
            for (int i = index; i < items.Length - 1; i++)
            {
                items[i] = items[i + 1];
            }
            size--;
        }
        public int indexOf(int item)
        {

            return Array.IndexOf(items, item, 0, size);
        }
        public void Remove(int item)//그값을 지우는거고
        {
            int index = indexOf(item);
            if (index >= 0)
            {
                RemoveAt(index);
            }
        }

        public void Clear() //배열 초기화
        {
            items = new int[defaultCapacity];
            size = 0;
        }

        /*
        public int At(int index) 
        { return items[index]; }*/
        public void Grow()
        {
            //Capacity를 2배늘려주고
            //새로운 배열 Capacity만큼 할당하고
            //기존 배열속 원소들은 새로운 배열에 복사해서 넣는 과정
            int newCapacity = items.Length * 2;
            int[] newItems = new int[newCapacity];

            Array.Copy(items, 0, newItems, 0, size);//(복사위치, 위치의 어디부터, 복사대상, ,복사대상의 시작점, 끝점)
            /*
            for(int i = 0; i < items.Length; i++)
            {
                newItems[i] = items[i];
            }*/
            items = newItems;
        }
    }

    public class GameManager : MonoBehaviour
    {
        //List<T> 가변배열 : 크기가 변할 수 있는 배열
        //Array   정적배열 : 크기가 변하지 않는 배열

        int[] array;
        int capacity = 5;

        List list;
        void Start()
        {
            list = new List();
            list.Add(10);
            /*
            int[] array = new int[capacity];
            array[0] = 10;
            array[1] = 20;
            array[2] = 30;
            array[3] = 40;
            array[4] = 50;
            capacity = capacity * 2;
            int[] temp = array;//얕은 복사가 일어남
            Debug.Log(temp[0]);//10나옴
            array = new int[capacity];
            //array = new int[30]; //위의 배열이 삭제후 30개로 새로 생성
            for (int i = 0; i < capacity; i++)
            {
                array[i] = temp[i];
            }
            Debug.Log(temp[0]);//10나옴
            Debug.Log(array[0]); //새로 만들면서 주소가 달라져서 0번이 출력이 안된다.*/

            Debug.Log(list[1]);
        }
    }
}

 

구조는 같고 데이터 타입만 다른 것에 제네릭화 하기 좋다 <T>

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;

public delegate void CustomDel();

public delegate bool BoolDel(int x);


namespace CustomCollections
{
    public class List<T>
    {
        const int defaultCapacity = 4;
        T[] items;
        int size = 0;
        public int Capacity {  get { return items.Length; } }
        public int Count { get { return size; } }

        public T this[int index]
        {
            get
            {
                return items[index];
            }
            set
            {
                items[index] = value;
            }
        }

        public List()
        {
            items = new T[defaultCapacity];
        }

        public int FindIndex(Predicate<T> match)
        {
            for(int i=0;i<items.Length; i++)
            {
                if (match(items[i]))
                {
                    return i;
                }
            }
            return -1;
           
            //특정한 조건에 만족하는 원소의 인덱스를
            //가져오는 함수.
        }

        public void Add(T item)
        {
            if (size >= items.Length)
                Grow();

            items[size] = item;
            size++;
        }
        
        public int indexOf(T item)
        {
            return Array.IndexOf(items, item, 0, size);
        }
        //+)
        public void RemoveAt(int index)
        {
            for(int i=index;i<items.Length-1; i++)
            {
                items[i] = items[i + 1];
            }
            size--;
        }
        //++)
        public void Remove(T item)
        {
            int index = indexOf(item);
            if(index >= 0)
            {
                RemoveAt(index);
            }
        }

        public void Clear()
        {
            items = new T[defaultCapacity];
            size = 0;
        }

        public void Grow()
        {
            int newCapacity = items.Length * 2;
            T[] newItems = new T[newCapacity];

            Array.Copy(items, 0, newItems, 0, size);
            /*
            for(int i=0;i<items.Length;i++)
            {
                newItems[i] = items[i];
            }
            */
            items = newItems;
        }
    }

    public class GameManager : MonoBehaviour
    {
        //List<T> 가변배열 : 크기가변할 수 있는 배열
        //Array   정적배열 : 크기가 변하지 않는 배열

        List<int> list;

        public bool IsUpperThanTen(int value)
        {
            return value > 10;
        }
        void Start()
        {
            list = new List<int>();
            list.Add(10);
            list.Add(20);
            list.Add(30);
            list.Add(40);
            list.Add(50);
            list.Add(60);

            for(int i=0;i<list.Count;i++)
            {
                Debug.Log(list[i]);
            }

            Debug.Log(list.FindIndex((int value) => { return value == 50; }))
        }
    }

}

유니티는 throw를 던져주기만 해도 알아서 잡는다.

 public class Stack
    {
        const int defaultCapacity = 4;
        int[] array;
        int size = 0;

        public Stack()
        {
            array = new int[defaultCapacity];
        }
        public void Clear()
        {
            array = new int[defaultCapacity];
            size = 0;
        }
        public void Push(int item)
        {
            if (size >= array.Length)
                Grow();

            array[size] = item;
            size++;
        }
        public int Pop()
        {
            size--;
            return array[size-- -1];
        }
        public bool IsEmpty()
        {
            return size == 0;
        }
        public bool IsFull()
        {
            return size == array.Length;
        }

        public int Peek()
        {
            return array[size];
        }

        public bool TryPop(out int result)
        {
            if (IsEmpty())
            {
                result = default(int);
                return false;
            }
            else
            {
                result = array[size];
                return true;
            }
        }
        public void Grow() 
        { 
            int newCapacity = array.Length * 2;
            int[] newArray = new int[newCapacity];
            Array.Copy(array, 0, newArray, 0, size);
            array = newArray;
        }
    }

처음에 클래스에 where T : ICompareble을 해주면 애초에 비교대상만 들어갈 수 있게되고 주석의 것을 안쓰고 아래 처럼 쓸 수 있다.

반응형
LIST