요마전의 개발 이슈 중 하나인 "유저가 맘대로 팬을 그은 궤적에 맞게 스킬이 발동되어야 한다." 를
구현해봅시다. 미리 조사해놓은 자료들에 따르면, Unity에서는 라인을 그리는 방법이 다양합니다. 크게 세가지로
나뉘는데 다음과 같은 방법이 있습니다.
- LineRenderer 를 통한 구현방법
- mesh Graphic 을 통한 구현방법
- GL Class 를 통한 구현방법
각
각의 방법은 장/단점이 존재하는데, LineRenderer 의 경우 구현은 쉬우나 퍼포먼스가 떨어지는 방식이라고 하고, 두번째인
mesh Graphic은 텍스쳐나 쉐이더 등을 계속 생성해서 구현하는 방식으로 코드상으로 복잡하지만 LineRenderer에 비해
퍼포먼스가 좋다고 합니다. 그리고 마지막 GL Class 방법은 오직 Unity Pro버전에서만 가능한 구현방법으로 코드의
가독성과 성능에 있어서 앞선 두방법 보다 앞선다고 합니다.
(참고 : http://www.everyday3d.com/blog/index.php/2010/03/15/3-ways-to-draw-3d-lines-in-unity3d/)
따라서 우리는 간편하기도 하고 퍼포먼스가 좋다는 GL Class방식을 사용해보려 시도해보려했으나 예제코드에 대한 이해가 부족해서 고민하던 찰나, "후르츠 닌자"라는 게임을 알게되었고 이 게임에서 사용되는 터치궤적에 대한 효과를 따라 구현해보게 되었습니다.
결국 LineRenderer를 통한 구현방법을 사용하게 된 셈입니다. 단, "후르츠 닌자"의 경우 2D카메라의 형태를 사용하고 있고, 저희 프로젝트에서는 3D카메라를 사용해야 했기에 아래 그림과 같이 구현하게 되었습니다.
구현방법을 보면 3인칭 카메라로 보이는 화면영역에서 캐릭터의 움직임과 스킬액션이 동시에 이루어져야 하는데, 이때 스킬을 시전할땐 화면에서 보이는 깊이(원근)가 달라도 모두 스킬이 적용될 수 있도록 해야합니다. 즉, 그리는 선은 평면이어도 깊이가 모두 반영이 될 수 있도록 Ray를 이용하여 구현하여야 했습니다.
그림에서 보이는 초록색과 빨간색 화살표는 Ray로 우선 먼저 스킬액션의 선을 저장하고, 그 선을 효과로 보여주는 배열이 필요합니다. 또한 스킬에 닿을 대상 오브젝트들을 효과를 보여줄때 동시에 Ray를 쏴서 효과를 적용시킬 배열이 필요합니다. 따라서 구현시에 2개의 배열을 사용하여 구현하였습니다.
기본적인 캐릭터 이동부분이 구현되어 있었기 때문에, 코드에서는 스킬버튼을 누를때 캐릭터의 움직임을 멈추고 스킬을 시전할 수 있도록 상태를 나누게 됩니다. 구현된 코드는 아래와 같으며, 정리되지 않은 코드이므로 참고만 하시길 바랍니다. :)
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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | using UnityEngine; using System.Collections; using System.Collections.Generic; public class SkillAction : MonoBehaviour { /* 변수 설명부분 * [GameObject] player : 플레이어 캐릭터 * [GameObject] gb : 불꽃이펙트 (Trail Renderer) * [GameObject] gb2 : 선 이펙트 (Trail Renderer) * [GameObject] g : 안쓰는 오브젝트1 * [Vector3 배열] lp : * [Vector3 배열] lp2 : * s : Vector3 형 변수 (초기값은 자동으로 0, 0, 0) * s2 : Vector3 형 변수 (초기값은 자동으로 0, 0, 0) * aa : 스킬버튼 눌렀을때(-1), 보통상태(0), 이펙트가움직이는동안(1) * time : 이펙트가 그려질때 사용되는 시간변수 * time2 : 초기화부분에서 사용되는 시간변수 * time3 : 불꽃이펙트의 시작에 사용되는 시간변수 * a : 배열 위치변수 * bb : 이펙트가 스킬을 그린지점까지 도착여부를 알리는 스테이트 */ public GameObject player; public GameObject gb; public GameObject gb2; public GameObject g; private Vector3[] lp; private Vector3[] lp2; private Vector3 s; private Vector3 s2; static public int aa = 0; float time = 0; float time2 = 0; float time3 = 0; int a = 0; int bb = 0; private bool monster_collider; void Start() { lp = new Vector3[0]; lp2 = new Vector3[0]; //Debug.Log(s); //Debug.Log(s2); } void Update() { // 스킬버튼을 눌렀을 시 if (aa == -1) { // 불꽃이펙트의 시작위치를 플레이어의 위치로 설정 gb.transform.position = player.transform.position; time3 += Time.deltaTime; // 버튼 누르고 그리기까지 0.5초 if (time3 > 0.5f) { time3 = 0; aa = 0; } } // 캐릭터의 움직임 스테이트가 정지상태일때 (스킬시전때문에) if (CharacterRayMove.cha_state == 1) { if (aa == 0) { // 마우스가 드래그 상태이면(버튼클릭이 유지되는 상태) if (Input.GetMouseButton(0)) { // 선 이펙트의 움직임을 인풋의 경로로 받아온다. gb2.transform.position = GetNewPoint(); // e를 카메라 좌표로 받아옴 Vector3 e = GetNewPoint(); // e2를 마우스 좌표로 받아옴 Vector3 e2 = GetNewPoint2(); // 받아온 s 좌표가 0이 아니면.. (처음이 아닌이상 대부분 거침..) if (s != Vector3.zero) { // lp 배열에 좌표추가(카메라) lp = AddLine(lp, s, e, false); } // 받아온 s2 좌표가 0이 아니면.. if (s2 != Vector3.zero) { // lp2 배열에 좌표추가(마우스) lp2 = AddLine(lp2, s2, e2, false); } // s2 변수에 e2 변수의 값(인풋포지션)을 대입 s2 = e2; // s 변수에 월드좌표로 변환한 e 변수의 값(*,*,10)을 대입 s = e; } else { s2 = Vector3.zero; s = Vector3.zero; } // 마우스 드래그가 끝나는 순간 = 마우스 버튼클릭이 끝나는 순간 if (Input.GetMouseButtonUp(0)) { // 상태를 이펙트가 움직이는 동안의 상태로 바꿈 aa = 1; } } // 이펙트가 움직이는 동안의 상태 if (aa == 1) { time += Time.deltaTime; // 이펙트랑 lp배열0번째 거리 비교해서 1.0 이상이면(거리가 있으면) 이펙트를 그려줌. if (Vector3.Distance(gb.transform.position, lp[0]) > 1.0f) { Vector3 vec = lp[0] - player.transform.position; // 이펙트를 움직이는 부분 gb.transform.Translate(vec * 0.08f); // Debug.Log(Vector3.Distance(gb.transform.position, lp[0])); } else { // 이펙트가 플레이어위치에서 스킬을 시전한 시작위치에 도달하면 상태를 바꿔줌 bb = 1; } // lp 0번배열에 도착했으니 이펙트를 그려라. if (bb == 1) { // 시간이 0.01초 지나면 if (time > 0.01f) { // lp배열의 크기가 a(??)보다 클 경우 if (lp.Length > a) { // 이펙트의 위치를 lp배열의 a 위치로 잡는다. gb.transform.position = lp[a]; // Ray ray2는 카메라에서 쏘는 것으로 Ray ray2 = Camera.main.ScreenPointToRay(lp2[a]); // RaycastHit 판별변수(hitInfo2) 선언 RaycastHit hitInfo2; // 화면상에 레이를 표시(Debug용) Debug.DrawRay(ray2.origin, ray2.direction * 100, Color.yellow); // ray2가 쏴진 정보가 hitInfo2에 저장 if (Physics.Raycast(ray2, out hitInfo2)) { Debug.Log(hitInfo2.collider.tag); // Ray를 충돌한 오브젝트 정보가 "MONSTER"이면 해당오브젝트를 소멸 if (hitInfo2.collider.tag == "MONSTER") { Debug.Log("!!!!!"); Destroy(hitInfo2.collider.transform.gameObject); } } time = 0; a++; } else { // 스킬을 다 그렸으므로 초기화상태로 돌림 bb = 0; aa = 2; } } } } // 스킬 이펙트 그려진 이후 초기화 부분 if (aa == 2) { time2 += Time.deltaTime; if (time2 > 1.5f) { lp = new Vector3[0]; lp2 = new Vector3[0]; aa = 0; a = 0; Debug.Log("change"); CharacterRayMove.cha_state = 0; } } } } /* 배열에 점을 추가하는 함수 (s = start, e = end, tmp = true/false) */ Vector3[] AddLine(Vector3[] l, Vector3 s, Vector3 e, bool tmp) { int vl = l.Length; if (!tmp || vl == 0) { l = resizeVertices(l, 2); } else { vl -= 2; } l[vl] = s; l[vl + 1] = e; return l; } /* 배열을 받아서 배열이 꽉 차면 늘리는 함수 */ Vector3[] resizeVertices(Vector3[] ovs, int ns) { Vector3[] nvs = new Vector3[ovs.Length + ns]; for (int i = 0; i < ovs.Length; i++) { nvs[i] = ovs[i]; } return nvs; } /* 인풋(마우스)좌표를 월드좌표로 변환해서 리턴하는 함수 */ Vector3 GetNewPoint() { return Camera.main.ScreenToWorldPoint( new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10)); } /* 인풋(마우스)좌표를 리턴하는 함수 */ Vector3 GetNewPoint2() { return Input.mousePosition; } } |
'프로그래밍 > 프로젝트' 카테고리의 다른 글
[요마전] 스킬액션 구현하기 #3 (0) | 2013.12.12 |
---|---|
[요마전] 스킬액션 구현하기 #2 (0) | 2013.12.10 |
[개발일지] 요마전 #1 (1) | 2013.12.03 |
[미니프로젝트] AIE 리뷰 - 코드 분석 (2) | 2013.10.11 |
[미니프로젝트] AIE 리뷰 - 변수 분석 (0) | 2013.10.08 |