본문 바로가기
Unity/2D

[Unity]2D게임만들기 #6_몬스터 AI구현

by Meaning_ 2021. 9. 26.
728x90
반응형

#1_준비하기


몬스터도 animator를 이용해서 idle과 Walk를 만들어준다.

이렇게 구현해주고 parameter로 isWalking이라는 불타입 변수를 만들어준다.

그다음에 해야하는거 3개! 

1. HasExitTime끄기

2. 겹치는 구간 없애기

3. isWalking이면 true 아니면 false Conditions에 설정해주기!

 

EnemyMove라는 새로운 C# 스크립트도 생성해준다! 

 

#2_기본이동


몬스터는 스스로 이동해야하기 때문에

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class EnemyMove : MonoBehaviour
{
    Rigidbody2D rigid; 
    // Start is called before the first frame update
    void Start()
    {
        rigid = GetComponent<Rigidbody2D>();
    }
 
    // Update is called once per frame
    void FixedUpdate()
    {
        //한 방향으로만 알아서 움직이게
        rigid.velocity = new Vector2(-1, rigid.velocity.y);//왼쪽으로 가니까 -1, y축은 0을 넣으면 큰일남!
    }
}
 
cs

 

 

왼쪽으로만 이동하게 설정해준다. -> 왼쪽으로 가니까 x축은 -1

 

#3_ 몬스터 행동설정


몬스터가 한쪽으로만 이동하니까 뭔가 재미가 없다. 몬스터가 왼쪽으로도 갔다가 오른쪽으로도 갔다가 멈추려면 어떻게 해야할까?

 

행동지표를 설정해주는 변수와 함수를 만들어준다.

 

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class EnemyMove : MonoBehaviour
{
    Rigidbody2D rigid;
    public int nextMove;//행동지표를 결정할 변수
 
    // Start is called before the first frame update
    void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        Think();
    }
 
    // Update is called once per frame
    void FixedUpdate()
    {
        //한 방향으로만 알아서 움직이게
        rigid.velocity = new Vector2(nextMove, rigid.velocity.y);//왼쪽으로 가니까 -1, y축은 0을 넣으면 큰일남!
    }
    //행동지표를 바꿔줄 함수 생각 --> 랜덤클래스 활용 
 
    void Think()
    {
        nextMove = Random.Range(-12); //-1이면 왼쪽, 0이면 멈추기,1이면 오른쪽으로이동
    }
}
 
cs

 

여기서 중요한건 Random클래스를 사용해서 왼쪽/멈추기/오른쪽을 랜덤하게 지정한다는 것이다!

 

하지만 Awake 메서드에 넣으면 처음부터 play버튼을 끌 때까지 똑같은 동작으로만 이동한다.

그렇다고 FixedUpdate에 넣으면 1초에 60번정도를 동작을 바꾸게 되므로 과부하에 걸린다. 

 

이때 생각해볼수 있는게

재귀함수를 쓰는것이다!

하지만 딜레이 없이 재귀를 쓰는것은 매우 위험하므로  시간지연 기능이 있는 Invoke함수를 이용해준다!

Invoke()

: 주어진 시간이 지난 뒤 지정된 함수를 실행하는 함수 

 

Invoke("매서드 이름",지연시키고 싶은 시간 +f)

 

<참고자료>

https://chameleonstudio.tistory.com/37

 

유니티 인보크 Invoke 사용법의 모든 것

해당 티스토리 페이지는 필자가 유니티 C# 개발을 하면서 학습한 내용들을 기록하고 공유하는 페이지입니다 ! - 틀린 부분이 있거나, 수정된 부분이 있다면 댓글로 알려주세요 ! - 해당 내용을 공

chameleonstudio.tistory.com

 

5초를 딜레이 시간으로 둘 것이고, 5초 마다 동작이 바뀌게된다.

 

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class EnemyMove : MonoBehaviour
{
    Rigidbody2D rigid;
    public int nextMove;//행동지표를 결정할 변수
 
    // Start is called before the first frame update
    void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        
        Invoke("Think",5);
    }
 
    // Update is called once per frame
    void FixedUpdate()
    {
        //한 방향으로만 알아서 움직이게
        rigid.velocity = new Vector2(nextMove, rigid.velocity.y);//왼쪽으로 가니까 -1, y축은 0을 넣으면 큰일남!
    }
    //행동지표를 바꿔줄 함수 생각 --> 랜덤클래스 활용 
 
    void Think()
    {
        nextMove = Random.Range(-12); //-1이면 왼쪽, 0이면 멈추기,1이면 오른쪽으로이동
 
        Invoke("Think"5);//재귀
    }
}
cs

 

Think 매서드 안에 Invoke를 넣어서 5초마다 Think 메서드에 다시 들어가는게 (재귀) 핵심이다!

 

 

#4_지능 높이기


낭떨어지에 떨어지지 않게 하려면 어떻게 할까?

플레이어 점프 뛸 때 바닥을 체크 하는 법이 RayHit였는데 이번에도 RayHit을 이용해줄것이다. 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 void FixedUpdate()
    {
        //한 방향으로만 알아서 움직이게
        rigid.velocity = new Vector2(nextMove, rigid.velocity.y);//왼쪽으로 가니까 -1, y축은 0을 넣으면 큰일남!
 
 
        //플랫폼 체크 
        //몬스터는 앞을 체크해야 
        Vector2 frontVec = new Vector2(rigid.position.x + nextMove,rigid.position.y);
        Debug.DrawRay(frontVec, Vector3.down, new Color(010)); 
        // 시작,방향 색깔
         
        RaycastHit2D rayHit = Physics2D.Raycast(frontVec, Vector3.down, 1, LayerMask.GetMask("Platform"));
 
        
        if (rayHit.collider == null)
        {
 
            Debug.Log("경고! 이 앞 낭떨어지다!");
 
        }
    }
cs

 

경고 문구가 계속 뜨는데 그 이유는 frontVec에 있다.

Vector2 frontVec = new Vector2(rigid.position.x + nextMove,rigid.position.y);

--> frontVec는 x축을 현재 몬스터의 위치+몬스터의 이동방향(-1,0,1) 을 해주었다.

즉, 낭떨어지보다 1만큼 덜 갔을 때부터 frontVec는 낭떨어지임을 인지하고 있게된다.

그래서 경고문구가 계속 뜨는 것이다.

--> 이렇게 몬스터보다 1만큼 앞에 빔이 나가고 있다.

이 코드를 추가해주면서 낭떨어지에 닿았을때 1만큼 후퇴하게 된다. 즉, 방향을 바꾼다. 

 

이 로직을 좀 더 안정적으로 짜려면 Cancel Invoke()를 사용해주면된다.

 

Cancel Invoke()

: 현재 작동중인 모든 Invoke함수를 멈추는 함수 

 

1
2
3
4
5
6
7
8
if (rayHit.collider == null)
        {
 
            nextMove = nextMove * (-1);
            CancelInvoke();
            Invoke("Think", 5);
 
        }
cs

낭떨어지에 닿으면 1보후퇴 -> 모든 Invoke함수를 멈추고 -> 다시 생각 

즉, 낭떨어지에 닿았을 때 Invoke 함수를 멈추지 않으면 1보 후퇴를 하더라도 5초가 끝나기 전에는 이전과 같은 방향을 향하고 있지면 CancelInvoke를 사용함으로써 5초 이전이라도 낭떨어지에 닿으면 바로 방향을 바꿔버릴 수 있다.

 

약간 아쉬운것은 낭떨어지보다 1만큼 이전에 방향을 돌려버리는 것인데 낭떨어지 거의 끝까지가서 방향을 돌리려면 어떻게 해야할까?

 Vector2 frontVec = new Vector2(rigid.position.x + nextMove,rigid.position.y);

 

--> 여기서 nextMove*0.2f 로 바꿔주면 된다!

 

 

#5_애니메이션 설정


 

기존의 bool형인 isWalking 파라미터를 지우고 int 형의 WalkSpeed 파라미터를 추가해준다.

 

idle ->walk 일때는

 

walk -> idle 일때는

 

이걸 코드에서 setInteger를 이용해서 컨트롤 할것이다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
void Think()
    {
        nextMove = Random.Range(-12); //-1이면 왼쪽, 0이면 멈추기,1이면 오른쪽으로이동
 
        float nextThinkTime = Random.Range(2f, 5f);//생각하는 시간도 랜덤으로 
 
        Invoke("Think", nextThinkTime);//재귀
 
        anim.SetInteger("WalkSpeed", nextMove);
 
 
    }
cs

 

낭떨어지일때 방향 flip이 안돼서 Turn이라는 함수를 새로 만들어줬다.

 

그래서 코드 완성본은

 

<EnemyMove.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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class EnemyMove : MonoBehaviour
{
    Rigidbody2D rigid;
    Animator anim;
    SpriteRenderer spriteRenderer;
    public int nextMove;//행동지표를 결정할 변수
 
    // Start is called before the first frame update
    void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        
        
        Invoke("Think",5);
    }
 
    // Update is called once per frame
    void FixedUpdate()
    {
        //한 방향으로만 알아서 움직이게
        rigid.velocity = new Vector2(nextMove, rigid.velocity.y);//왼쪽으로 가니까 -1, y축은 0을 넣으면 큰일남!
 
 
        //플랫폼 체크 
        //몬스터는 앞을 체크해야 
        Vector2 frontVec = new Vector2(rigid.position.x + nextMove*0.2f,rigid.position.y);
        Debug.DrawRay(frontVec, Vector3.down, new Color(010)); 
        // 시작,방향 색깔
         
        RaycastHit2D rayHit = Physics2D.Raycast(frontVec, Vector3.down, 1, LayerMask.GetMask("Platform"));
 
        
        if (rayHit.collider == null)
        {
 
            Turn();
 
        }
    }
    //행동지표를 바꿔줄 함수 생각 --> 랜덤클래스 활용 
 
    void Think()
    {
 
        //set next active
        nextMove = Random.Range(-12); //-1이면 왼쪽, 0이면 멈추기,1이면 오른쪽으로이동
 
        //sprite animation
        anim.SetInteger("WalkSpeed", nextMove);
 
        //방향 바꾸기 (0일 때는 굳이 바꿀 필요없기에 조건문 사용해준거)
        if (nextMove != 0)
        {
            spriteRenderer.flipX = (nextMove == 1); //nextMove가 1이면 방향바꾸기
        }
 
        //재귀 
        float nextThinkTime = Random.Range(2f, 5f);//생각하는 시간도 랜덤으로 
 
        Invoke("Think", nextThinkTime);//재귀
 
 
 
 
    }
 
    void Turn()
    {
        nextMove = nextMove * (-1);
        spriteRenderer.flipX = (nextMove == 1); //nextMove가 1이면 방향바꾸기
 
 
        CancelInvoke();
        Invoke("Think"2);
    }
}
cs

 

+) 유니티 Awake와 Start의 차이점

 

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=rwans0397&logNo=220813631095 

 

[유니티] Awake Start 차이점

Awake,Start 유니티에서 지정한 함수로서 초기화시 사용되는 함수들이다. 단 한번만 호출된다. 1. Awake...

blog.naver.com

 

728x90
반응형

댓글