본문 바로가기
게임/Unity

[Unity 2D] Indicator

by 신인용 2020. 2. 17.
반응형

 오늘은 화면 밖에 있는 아이템, 적을 표시하는 화살표(Indicator)를 그려보겠습니다.

 

 

 

Indicator

 

 

 

결과 미리보기 (아래가 살짝 짤렸습니다 ㅠ)

  캐릭터가 상하좌우로 움직이면 화면에 있던 다른 오브젝트(적, 아이템 등)가 화면을 벗어납니다.

화면을 벗어난 오브젝트들을 표시해 주는 것이 바로 Indicator입니다.

 

 

 

 

0. 기본 셋팅

 Main Camera를 Player의 자식으로 넣어주고

 Player에게 Rigidbody2D를 추가하고 Gravity Scale을 0으로 만들어주고

 Indicator Canvas의 Render Mode를 Screen Space - Camera로 바꾸고 Main Camera를 넣어줍니다.

 그리고 Indicator 오브젝트는 Prefab으로 만들어 줍니다.

 

 

 

 

 

1. Indicator

 

지금부터 작성할 Indicator의 동작원리는 이러합니다.

(해당 각도들은 가운데 선을 기준으로, 우측 0˚ ~ 180˚, 좌측 0˚ ~ -180˚입니다.)

 

검은 네모가 화면의 크기. 즉 카메라의 범위입니다.

angleRU는 화면 우측상단과 0˚사이의 각도입니다.

 

 위의 각도에 따라 상태를 나누어 indicator가 그려질 위치를 정해주어야 합니다.

만약 타겟 오브젝트의 각도가

-angleRU˚ < 타겟 오브젝트 각도 <= angleRU˚  =>  UP 상태.

angleRU˚ < 타겟 오브젝트 각도 <= 180˚-angleRU  =>  Right 상태.

180˚-angleRU˚ < 타겟 오브젝트 각도 <= 180˚ || -180˚ < 타겟 오브젝트 각도 <= angleRU˚-180˚  =>  Down 상태.

angleRU˚-180˚ < 타겟 오브젝트 각도 <= -angleRU˚  =>  Left 상태.

 

 

 Up, Right, Down, Left 상태를 나눈 후, 각 상태마다 인디케이터가 그려질 위치를 지정합니다.

ex) Right 상태일 때 indicator x위치는 고정위치로. y위치만을 player와 타겟 사이의 위치로 설정.

 

Right 상태

 

 Right 상태로 예를 들어보겠습니다.

Right 상태에서 Indicator의 위치는 X는 화면 우측에 고정. Y는 y의 위치에 있습니다.

즉, y의 위치만 구해주면 Indicator가 그려질 위치를 구할 수 있습니다.

 

닮은비를 이용하여,

x : x' = y : y'

x'y = xy'

y = xy'/x'

이처럼 y의 값을 구할 수 있습니다.

 

 

 그런데, Right 상태에서도 두가지 상태로 나뉩니다.

바로 target이 Player 위 쪽에 있는 상태target이 Player 아래쪽에 있는 상태입니다.

Right 상태에서, 두 가지 상태를 구별할 수 있는 방법은 y'의 부호를 보는 것입니다.

y' = target.y - player.y 이기 때문에

y'이 양수면 target이 위쪽에 있는 상태,

y'이 음수면 target이 아래쪽에 있는 상태가 됩니다.

 

 

Right상태에서 target이 위쪽에 있을 때 ( y' > 0 )

 위쪽에 있을 때는 player.y 에 y를 더하면 indicator의 y위치를 구할 수 있습니다.

Right상태에서 target이 아래쪽에 있을 때 ( y' < 0 )

 아래쪽에 있을 때는 player.y 에 y를 빼면 indicator의 y위치를 구할 수 있습니다.

 

 

 

 

 Right 외에 Up, Down, Left도 똑같은 원리로 작성하면 되겠습니다.

 

 

 

Indicator 소스코드

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
164
165
166
167
168
169
170
171
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
public class Indicator : MonoBehaviour
{
    public GameObject player; // player 오브젝트
 
    private Vector2 playerVec; // Player 벡터
    private Vector2 playerScreenVec; // Screen 상의 Player 벡터
    
    private float angleRU; // 우측상단 대각선 각도
 
    private float screenHalfHeight = 0.5f; // 화면 높이 절반
    private float screenHalfWidth = 0.5f; // 화면 폭 절반
 
    void Start()
    {
        playerScreenVec = Camera.main.WorldToScreenPoint(player.transform.position);
        playerVec = Camera.main.WorldToViewportPoint(player.transform.position); // 0f ~ 1f
 
        Vector2 vecRU = new Vector2(Screen.width, Screen.height) - playerScreenVec;
        vecRU = vecRU.normalized;
        angleRU = Vector2.Angle(vecRU, Vector2.up);
    }
    
    public void DrawIndicator(GameObject obj, GameObject indicatorObj)
    {
        Image indicator = indicatorObj.GetComponent<Image>();
 
        Vector2 objScreenVec = Camera.main.WorldToScreenPoint(obj.transform.position);
        Vector2 objVec = Camera.main.WorldToViewportPoint(obj.transform.position); // 0f ~ 1f
 
        Vector2 targetVec = objScreenVec - playerScreenVec;
        targetVec = targetVec.normalized;
 
        float targetAngle = Vector2.Angle(targetVec, Vector2.up); // 0 ~ 180
        int sign = Vector3.Cross(targetVec, Vector2.up).z < 0 ? -1 : 1;
        targetAngle *= sign; // -180 ~ 180
 
        float xPrime = objVec.x - playerVec.x;
        float yPrime = objVec.y - playerVec.y;
 
        float anchorMinX;
        float anchorMinY;
        float anchorMaxX;
        float anchorMaxY;
 
        if (-angleRU < targetAngle && angleRU >= targetAngle) // UP 쪽에 있을 때
        {
            anchorMinY = 0.94f;
            anchorMaxY = 0.94f;
            // y anchor 지정
 
            float posX = (Mathf.Abs(xPrime) * screenHalfHeight) / yPrime;
 
            if (xPrime > 0// Right
            {
                anchorMinX = screenHalfWidth + posX;
                anchorMaxX = screenHalfWidth + posX;
 
                if (anchorMinX > 0.965f) anchorMinX = 0.965f;
                if (anchorMaxX > 0.965f) anchorMaxX = 0.965f;
                // 이미지가 넘어가는 걸 방지
            }
            else // Left
            {
                anchorMinX = screenHalfWidth - posX;
                anchorMaxX = screenHalfWidth - posX;
 
                if (anchorMinX < 0.035f) anchorMinX = 0.035f;
                if (anchorMaxX < 0.035f) anchorMaxX = 0.035f;
                // 이미지가 넘어가는 걸 방지
            }
 
            indicator.rectTransform.anchorMin = new Vector2(anchorMinX, anchorMinY);
            indicator.rectTransform.anchorMax = new Vector2(anchorMaxX, anchorMaxY);
            // indicator의 anchor 지정
        }
        else if(angleRU < targetAngle && 180 - angleRU >= targetAngle) // RIGHT 쪽에 있을 떄
        {
            anchorMinX = 0.965f;
            anchorMaxX = 0.965f;
            // x anchor 지정
 
            float posY = (screenHalfWidth * Mathf.Abs(yPrime)) / xPrime;
 
            if (yPrime > 0// Up
            {
                anchorMinY = screenHalfHeight + posY;
                anchorMaxY = screenHalfHeight + posY;
 
                if (anchorMinY > 0.94f) anchorMinY = 0.94f;
                if (anchorMaxY > 0.94f) anchorMaxY = 0.94f;
                // 이미지가 넘어가는 걸 방지
            }
            else // Down
            {
                anchorMinY = screenHalfHeight - posY;
                anchorMaxY = screenHalfHeight - posY;
 
                if (anchorMinY < 0.04f) anchorMinY = 0.04f;
                if (anchorMaxY < 0.04f) anchorMaxY = 0.04f;
                // 이미지가 넘어가는 걸 방지
            }
 
            indicator.rectTransform.anchorMin = new Vector2(anchorMinX, anchorMinY);
            indicator.rectTransform.anchorMax = new Vector2(anchorMaxX, anchorMaxY);
            // indicator의 anchor 지정
        }
        else if((180 - angleRU < targetAngle && 180 > targetAngle) 
            || (-180 <= targetAngle && angleRU - 180 >= targetAngle)) // DOWN 쪽에 있을 때
        {
            anchorMinY = 0.04f;
            anchorMaxY = 0.04f;
 
            float posX = (Mathf.Abs(xPrime) * screenHalfHeight) / -yPrime;
 
            if (xPrime > 0// Right
            {
                anchorMinX = screenHalfWidth + posX;
                anchorMaxX = screenHalfWidth + posX;
 
                if (anchorMinX > 0.965f) anchorMinX = 0.965f;
                if (anchorMaxX > 0.965f) anchorMaxX = 0.965f;
            }
            else // Left
            {
                anchorMinX = screenHalfWidth - posX;
                anchorMaxX = screenHalfWidth - posX;
 
                if (anchorMinX < 0.035f) anchorMinX = 0.035f;
                if (anchorMaxX < 0.035f) anchorMaxX = 0.035f;
            }
 
            indicator.rectTransform.anchorMin = new Vector2(anchorMinX, anchorMinY);
            indicator.rectTransform.anchorMax = new Vector2(anchorMaxX, anchorMaxY);
        }
        else if(angleRU - 180 < targetAngle && -angleRU >= targetAngle) // LEFT 쪽에 있을 때
        {
            anchorMinX = 0.035f;
            anchorMaxX = 0.035f;
 
            float posY = (screenHalfWidth * Mathf.Abs(yPrime)) / -xPrime;
 
            if (yPrime > 0// Up
            {
                anchorMinY = screenHalfWidth + posY;
                anchorMaxY = screenHalfWidth + posY;
 
                if (anchorMinY > 0.94f) anchorMinY = 0.94f;
                if (anchorMaxY > 0.94f) anchorMaxY = 0.94f;
            }
            else // Down
            {
                anchorMinY = screenHalfWidth - posY;
                anchorMaxY = screenHalfWidth - posY;
 
                if (anchorMinY < 0.04f) anchorMinY = 0.04f;
                if (anchorMaxY < 0.04f) anchorMaxY = 0.04f;
            }
            indicator.rectTransform.anchorMin = new Vector2(anchorMinX, anchorMinY);
            indicator.rectTransform.anchorMax = new Vector2(anchorMaxX, anchorMaxY);
        }
 
        indicator.rectTransform.anchoredPosition = new Vector3(00);
        // 위에서 지정한 anchor로 이동
    }
}
 
cs

 

 

 

 

 

 

2. Target Object

 타겟이 되는 오브젝트 ( 여기선 Skeleton ) 에서는,

화면 밖에 있는지 검사,

검사 후 검사여부에 따라 Indicator 그려주기.

 이 두가지만 실행할 것입니다.

 

변수 선언 부분과 Inspector창.

 

 

Skeleton 소스 코드

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Skeleton : MonoBehaviour
{
    public Indicator indicator;
    public GameObject indicatorObj;
    private GameObject myIndicatorObj;
    public GameObject indicatorCanvas;
 
    private bool hasIndicator = false;
 
 
    void Update()
    {
        SetMyIndicator();
        if(hasIndicator)
        {
            indicator.DrawIndicator(gameObject, myIndicatorObj);
        }
    }
 
 
    private void SetMyIndicator()
    {
        if (IsOffScreen() && !hasIndicator) // 화면 밖인데 내 Indicator가 없으면
        {
            myIndicatorObj = Instantiate(indicatorObj); // 내 Indicator 생성
            myIndicatorObj.transform.SetParent(indicatorCanvas.transform);
            hasIndicator = true;
        }
        else if (!IsOffScreen() && hasIndicator) // 화면 안인데 내 Indicator가 있으면
        {
            Destroy(myIndicatorObj); // 내 Indicator를 파괴
            hasIndicator = false;
        }
    }
 
 
    private bool IsOffScreen()
    {
        Vector2 vec = Camera.main.WorldToViewportPoint(transform.position);
        if (vec.x <= 1 && vec.y <= 1 && vec.x >= 0 && vec.y >= 0// 화면 안쪽 범위
            return false;
        else
            return true;
    }
}
 
cs

 

 

 

 

 

 

(참고) Player 소스 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Player : MonoBehaviour
{
    private Rigidbody2D playerRigid;
    private float speed = 5f;
 
    void Start()
    {
        playerRigid = GetComponent<Rigidbody2D>();
    }
 
    void Update()
    {
        float inputX = Input.GetAxis("Horizontal");
        float inputY = Input.GetAxis("Vertical");
 
        Vector2 velocity = new Vector2(inputX, inputY);
        velocity *= speed;
        playerRigid.velocity = velocity;
    }
}
cs

 

 

 

 

 

 

3. 결론

결과물 (아래가 살짝 짤렸습니다 ㅠ)

 

반응형

댓글