[C#, 유니티] Unity3D_RPG 몬스터 (타입 & 제네릭 상태패턴)

2022. 9. 1. 15:46코딩 2막 <C#개념편>

728x90

나는 커피를 좋아한다

그래서 공부를 하러 가든 친구와 약속을 잡든 우리 주변의 널리고 널린 카페를 탐방하는것을 좋아한다

오늘은 아카데미 근처에 카페에 잠시 들렸다

그곳에서 신기한 놈을 마주했다

이제 사람이 아닌 로봇이 커피를 만들어 내준다 그것도 눈 앞에서 생생하게..

맛은 그저 그런 커피였지만 신기한 경험이었다

아무튼.. 서두가 길었다; 오늘은 어제에 이어서 몬스터에 대한 응용을 공부해보았다


제네릭(Generic)

다음과 같이 쓰게 될 때 C++에서는 가능했지만

C#에서는 적용이 되지않음을 알 수 있다

예시) C#에서는 불가능함

이것은 완벽주의적 객체지향을 하는 C#이기때문에 오류가 발생하는데 이를 해결 할 수 있다

탬플릿과 제네릭의 차이점
제네릭(Generic)의 기본 방향은 제네릭이 있으면 "모든 자료형에 모두 대응할 수 있다"이다

C++에서 템플릿을 썼듯이 C#에서는 제네릭이 있다(사용 용도가 똑같은데?)
그렇다면 굳이 왜 C#에서는 템플릿이라 안부르고 이름을 다르게 했을까?
그것은 바로 동작방식이 다르기 때문

C++의 템플릿은 컴파일 당시 필요한 자료형을 생성하고 사용하는 방식이다
반면 C#은 완벽한 객체지향언어이기때문에, 제네릭의 경우 컴파일에 필요한 자료형을 만들고 쓴다는 방식이 허용되지 않기때문에
모든 자료형에 모두 대응할 수 있다(새로 생성X)

원거리 wolf 만들기

원거리울프 상태패턴 구상도

코드리뷰


스크립트

1. State.cs

1
2
3
4
5
6
7
8
9
10
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public abstract class State<T> where T : MonoBehaviour
{
    public abstract void Enter(T Owner);
    public abstract void Update(T Owner);
    public abstract void Exit(T Owner);
}
cs

2. StateMachine.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
// T1 : 상태 열거형 타입
// T2 : 상태타입에 맞는 상태
public class StateMachine<T1, T2> where T2 : MonoBehaviour
{
    private T2 Owner;
    private State<T2> curState;
    private Dictionary<T1, State<T2>> states;
 
    public StateMachine(T2 Owner)
    {
        this.Owner = Owner;
        curState = null;
        states = new Dictionary<T1, State<T2>>();
    }
 
    public void Update()
    {
        curState.Update(Owner);
    }
 
    public void AddState(T1 type, State<T2> state)
    {
        states.Add(type, state);
    }
 
    public void ChangeState(T1 type)
    {
        if (curState != null)
            curState.Exit(Owner);
        curState = states[type];
        curState.Enter(Owner);
    }
}
cs

3. RangeWolf.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class RangeWolf : MonoBehaviour
{
    public enum State { Idle, Trace, Attack, RunAway, Hit, Die }
    private StateMachine<State, RangeWolf> stateMachine;
 
    [SerializeField]
    private LayerMask _targetLayerMask;
    public LayerMask targetLayerMask { get { return _targetLayerMask; } }
    [SerializeField]
    private float _attackRange;
    public float attackRange { get { return _attackRange; } }
    [SerializeField]
    private float _moveSpeed;
    public float moveSpeed { get { return _moveSpeed; } }
    [SerializeField]
    private float hp = 10f;
    
 
    private Animator _animator;
    public Animator animator { get { return _animator; } }
    private ViewDetector _viewDetector;
    public ViewDetector viewDetector { get { return _viewDetector; } }
    private CharacterController _characterController;
    public CharacterController characterController { get { return _characterController; } }
 
    private void Awake()
    {
        _animator = GetComponent<Animator>();
        _viewDetector = GetComponent<ViewDetector>();
        _characterController = GetComponent<CharacterController>();
 
        stateMachine = new StateMachine<State, RangeWolf>(this);
 
        stateMachine.AddState(State.Idle, new RangeWolfStates.IdleState());
        stateMachine.AddState(State.Trace, new RangeWolfStates.TraceState());
        stateMachine.AddState(State.Attack, new RangeWolfStates.AttackState());
        stateMachine.AddState(State.RunAway, new RangeWolfStates.RunAwayState());
        stateMachine.AddState(State.Hit, new RangeWolfStates.HitState());
        stateMachine.AddState(State.Die, new RangeWolfStates.DieState());
 
        stateMachine.ChangeState(State.Idle);
    }
 
    private void Update()
    {
        stateMachine.Update();
    }
 
 
    public void ChangeState(State nextState)
    {
        stateMachine.ChangeState(nextState);
    }
}
cs

4. RangeWolfStates.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
namespace RangeWolfStates
{
    public class BaseState : State<RangeWolf>
    {
        public override void Enter(RangeWolf Owner)
        {
        }
 
        public override void Update(RangeWolf Owner)
        {
        }
 
        public override void Exit(RangeWolf Owner)
        {
        }
    }
 
    public class IdleState : BaseState
    {
        public override void Enter(RangeWolf Owner)
        {
            Owner.animator.SetBool("Run Forward"false);
        }
 
        public override void Update(RangeWolf Owner)
        {
            Owner.viewDetector.FindTarget();
            GameObject target = Owner.viewDetector.target;
 
            if (target != null)
                Owner.ChangeState(RangeWolf.State.Trace);
        }
 
        public override void Exit(RangeWolf Owner)
        {
 
        }
    }
 
    public class TraceState : BaseState
    {
        public override void Enter(RangeWolf Owner)
        {
            Owner.animator.SetBool("Run Forward"true);
        }
 
        public override void Update(RangeWolf Owner)
        {
            Owner.viewDetector.FindTarget();
            GameObject traceTarget = Owner.viewDetector.target;
 
            if (traceTarget == null)
            {
                Owner.ChangeState(RangeWolf.State.Idle);
                return;
            }
 
            Vector3 moveDir = traceTarget.transform.position - Owner.transform.position;
            Owner.characterController.Move(moveDir.normalized * Time.deltaTime * Owner.moveSpeed);
            Owner.transform.LookAt(traceTarget.transform.position); 
 
            GameObject attackTarget;
            Collider[] targets = Physics.OverlapSphere(Owner.transform.position, Owner.attackRange, Owner.targetLayerMask);
            if (targets.Length > 0)
            {
                attackTarget = targets[0].gameObject;
                Owner.ChangeState(RangeWolf.State.Attack);
                return;
            }
            else
            {
                attackTarget = null;
            }
        }
 
        public override void Exit(RangeWolf Onwer)
        {
 
        }
    }
 
    public class AttackState : BaseState
    {
        public override void Enter(RangeWolf Owner)
        {
            Owner.animator.SetTrigger("Breath Attack");
            Owner.StartCoroutine(AttackTime(Owner));
        }
 
        public override void Update(RangeWolf Owner)
        {
 
        }
 
        public override void Exit(RangeWolf Onwer)
        {
 
        }
 
        IEnumerator AttackTime(RangeWolf Owner)
        {
            yield return new WaitForSeconds(1.0f);
            Owner.ChangeState(RangeWolf.State.Trace);
        }
    }
 
    public class RunAwayState : State<RangeWolf>
    {
        public override void Enter(RangeWolf Owner)
        {
            Owner.animator.SetBool("Walk Backward"true);
        }
 
        public override void Update(RangeWolf Owner)
        {
            
        }
 
        public override void Exit(RangeWolf Owner)
        {
 
        }
    }
 
    public class HitState : State<RangeWolf>
    {
        public override void Enter(RangeWolf Owner)
        {
        }
 
        public override void Update(RangeWolf Owner)
        {
 
        }
 
        public override void Exit(RangeWolf Owner)
        {
 
        }
    }
 
    public class DieState : State<RangeWolf>
    {
        public override void Enter(RangeWolf Owner)
        {
        }
 
        public override void Update(RangeWolf Owner)
        {
            Owner.animator.SetTrigger("Die");
            Debug.Log("죽습니다");
        }
 
        public override void Exit(RangeWolf Owner)
        {
 
        }
    }
}
cs

원거리 공격을 하는 몬스터 구현 결과물


참고

 

제네릭 및 템플릿(C++/CLI)

자세한 정보: 제네릭 및 템플릿(C++/CLI)

docs.microsoft.com


공감해주셔서 감사합니다

728x90