SCM player skins Line Up: [EUD 강좌] 마법을 사용한 위치로 로케이션을 옮겨보자

3/08/2015

[EUD 강좌] 마법을 사용한 위치로 로케이션을 옮겨보자



결과 














요약 

  • 구조오프셋을 이용하여 유닛의 마법 시전을 인식.
  • 구조오프셋을 이용하여 유닛이 마법을 사용한 위치의 좌표값 알아내기.
  • 이진법을 이용하여 유닛이 마법을 사용한 위치의 좌표로 로케이션 옮기기.
  • Base +0x58, Order Coordinates(명령 좌표), 4byte, 좌표값이 (X + 65536*Y)로 저장됨.
  • 0x58DC60, Loaction Table(로케이션 크기), 4byte, +0x00: X1좌표, +0x04: Y1좌표, +0x08: X2좌표, +0x0C: Y2좌표










내용

일단 이 강좌의 원리는 http://cafe.naver.com/turel9907/book2995125/1975와 http://cafe.naver.com/edac/32709을 이용하였음을 밝힙니다.


마법을 사용한 위치로 로케이션을 옮기기 위해서는 다음과 같은 과정을 거칩니다.

  1. 마법의 시전 인식
  2. 마법의 좌표 인식 및 로케이션 옮기기

각 과정에 대한 내용을 나누어 설명하도록 하겠습니다.



 






1. 마법의 시전 인식
​다양한 방법이 있겠지만 http://cafe.naver.com/edac/32709에서 말한 것처럼 유닛의 마나소모량을 인식하여 마법의 시전을 인식하도록 합니다. 먼저 예제맵을 만들어 자신이 마법을 인식할 유닛을 하나 배치합시다. 그리고 그 유닛의 Index번호를 기억하도록 합니다. 저는 Infested Kerrigan을 Index = 0으로 두었습니다.










그러면 이제 EUD Gen2를 켜보도록 합시다. (카테고리: EUD-유틸리티 항목에 있을 겁니다.) 그리고 트리거를 하나 생성하고 컨디션에서 Unit-Unit Energy를 선택합니다. 그리고 Index ID에 자신이 맵상에 두었던 마법 시전을 인식할 유닛의 Index 번호를, Energy에 인식할 유닛의 마나량을 기입합니다. 저같은 경우 Infested Kerrigan의 Index번호는 0이고, 맵에서 트리거로 항상 이 유닛의 Energy를 100%로 설정할 것이기 때문에 한번이라도 마법을 사용하면 인식할 수 있도록 유닛의 에너지가 240이하가 되면 인식하도록 하였습니다.












그리고 OK버튼을 누르면 다음과 같이 트리거가 뜰것입니다. 이를 트리거로 집어넣도록 합시다.












저는 TrigEditPlus를 사용하였습니다. (조건절과 액션절 제한이 풀린 scmdraft2 : http://cafe.naver.com/edac/28858, TrigEditPlus 플러그인 : http://cafe.naver.com/edac/34966)


빨간색 네모상자에 있는 트리거가 해당 EUD 트리거입니다.










그럼 트리거가 정상적으로 작동하는지 한번 테스트해보도록 합시다. 유닛의 마나 변화를 인식하면 'Skill Used!'라는 텍스트 메세지가 뜨도록 만들어보았습니다. 이 때, 터보트리거를 적용하면 마법 시전 인식이 빠르게 일어날 것입니다.












2. 마법의 좌표 인식 및 로케이션 옮기기
유닛의 명령 좌표는 구조오프셋 Base +0x58(EUD DB)에 그 값이 저장되어 있습니다. 구조오프셋 계산기를 켜봅시다. (http://seokma.blogspot.com/2015/02/eud.html유닛번호는 유닛의 Index 번호, Base+?는 Base 값에서 더하는 값을 의미합니다. 저같은 경우 유닛의 Index 번호는 0이고, 명령 좌표를 인식할 것이므로 Base에서 0x58을 더할 것입니다. 계산을 하면 다음과 같이 0x59CD00이라는 주소값이 나옵니다. 즉, 이 주소에 유닛의 명령 좌표가 저장되어 있습니다.













여기서 의문이 들것입니다. 명령의 좌표값은 위 주소값에서 참조하여 알아낼 수 있을 것입니다. 하지만 이를 스타2처럼 어디 변수에 저장해 둘 수 있는 것도 아니고, 어떻게 이 좌표로 로케이션을 옮겨야 할까요? 답은 이진법을 이용하는 것입니다. 중학교 수학 시간에 다음과 같은 것을 배웠을 것입니다.


10진수 값을 1일 될때까지 계속 2로 나눕니다. 각각의 과정에서 나머지를 합하였을 때, 10진수 값을 2진수로 나타낼 수 있는 것이죠. 이를 이용해 다음과 같은 알고리즘을 생각해봅시다.

  1. 명령의 좌표값 A를 인식. 로케이션의 좌표값 X1, X2, Y1, Y2 모두 미리 0으로 셋팅.
  2. A를 2로 나눈 몫 = C. 로케이션의 좌표값 X1, X2, Y1, Y2 모두 +C, 그리고 A = A/2. A가 1이 아닐때 2를 반복. 
  3. A가 1일 때. X1, X2, Y1, Y2 모두 +1. 해당 로케이션은 명령한 곳으로 옮겨짐.

명령의 좌표값을 2로 나눈 몫만큼 로케이션의 좌표값에 계속해서 더합니다. 그러다보면 명령의 좌표값이 0이 되는 순간이 오는데, 그동안 더해진 좌표값이 그대로 로케이션의 좌표값으로 더해지므로 명령한 곳으로 로케이션이 옮겨지게 됩니다.


이를 트리거로 구현해봅시다. 트리거에서는 2로 나눌 수 없으니 범위를 정하여 일일이 2의 제곱수만큼의 값을 빼주어야 합니다. 스타크래프트의 최대 맵 크기는 256이고, 최소 그리드는 4, 픽셀 다위는 4이므로 256*4*4=4096. 따라서 X좌표, Y좌표 모두 1,2,4,8,16, ... ,4096까지의 2의 제곱수의 값들만 인식해주면 됩니다. 즉, 26개 트리거 노가다입니다(...) 이 때, 좌표값은 (X + 65536*Y)와 같이 저장되니까 Y좌표는 2868435456, 134217728, 67108864, ... , 65536까지의 값을 인식해주고, X좌표는 4096, 2048, 1024, ... ,1까지의 값을 인식해줍니다.










그 전에 로케이션의 크기 변경 오프셋에 대해 알아봅시다. 다음은 로케이션 크기 변경 오프셋입니다.

로케이션 크기

  • 오프셋 : 0x58DC60 + (4byte)*(5*(로케이션 Index))
  • X1좌표: +0x00, Y1좌표: +0x04, X2좌표: +0x08, Y2좌표: +0x0C
  • 값 : 좌표값 (단위 : 픽셀)

로케이션 Index는 로케이션이 놓여진 순서대로 0, 1, 2, 3, ... 입니다. 좌표값은 1그리드가 32픽셀입니다. 저는 맵 상에 로케이션 Index가 0인 로케이션을 하나 깔아놓았습니다.










그럼 이제 본격적으로 트리거를 짜봅시다. 먼저 위 '1. 마법의 시전 인식'에서 만든 트리거를 이용하여 유닛이 마법 시전을 하였을 때, 스위치를 열고, 로케이션의 위치를 초기화 해줍니다.

Trigger { 
players = {P1},
conditions = {
              Deaths(19065, AtMost, -251658241, "Terran Marine");
                Deaths(P4, AtLeast, 1, 1587);
      },
actions = {
              DisplayText("Skill Used!", 4);
                SetSwitch("Switch 1", Set);
              SetDeaths(P12, SetTo, 0, 303);     //X1좌표
              SetDeaths(P1, SetTo, 0, 304);      //Y1좌표
              SetDeaths(P2, SetTo, 0, 304);      //X2좌표
              SetDeaths(P3, SetTo, 0, 304);      //Y2좌표
              Comment("IF Unit #0 has at most 240 energy");
               PreserveTrigger();
     },
}
그리고 Y좌표부터 인식해주도록 합니다.

Trigger {
players = {P1},
conditions = {
             Deaths(P4, AtLeast, 268435456, 1587); 
             Switch("Switch 1", Set);
     },
actions = {
            SetDeaths(P4, Subtract, 268435456, 1587);     // 좌표값에 값을 빼줍니다.
            SetDeaths(P1, Add, 4096, 304);     // 로케이션의 Y1 좌표에 값을 더합니다.
            SetDeaths(P3, Add, 4096, 304);     // 로케이션의 Y2 좌표에 값을 더합니다.
            PreserveTrigger();
     },
}
다음과 같이 빨간색 부분은

  • 268435456
  • 134217728
  • 67108864
  • 33554432
  • 16777216
  • 8388608
  • 4194304
  • 2097152
  • 1048576
  • 524288
  • 262144
  • 131072
  • 65536

까지 인식을 해줍니다.







다음으로는 X좌표를 인식합니다.

Trigger {
players = {P1},
conditions = {
                Deaths(P4, AtMost, 65535, 1587);
              Deaths(P4, AtLeast, 4096, 1587); 
               Switch("Switch 1", Set);
       },
actions = {
             SetDeaths(P4, Subtract, 4096, 1587);     // 좌표값에 값을 빼줍니다.
             SetDeaths(P12, Add, 4096, 303);     // 로케이션의 X1 좌표에 값을 더합니다.
             SetDeaths(P1, Add, 4096, 304);     // 로케이션의 X2 좌표에 값을 더합니다.
             PreserveTrigger();
       },
}
다음과 같이 빨간색 부분은

  • 4096
  • 2048
  • 1024
  • 512
  • 256
  • 128
  • 64
  • 32
  • 16
  • 8
  • 4
  • 2
  • 1

까지 인식을 해줍니다. 





그리고 좌표값의 인식과 로케이션의 이동을 끝내면 다음과 같이 스위치를 꺼줍니다. 저는 로케이션이 잘 이동했는지 확인하기 위해 해당 로케이션에 마린을 생성하도록 해보았습니다.

Trigger {
 players = {P1},
conditions = {
            Deaths(P4, Exactly, 0, 1587);
            Switch("Switch 1", Set);
      },
actions = {
            SetSwitch("Switch 1", Clear);
            CreateUnit(1, "Terran Marine", "Location 0", P1);
            PreserveTrigger();
      },
}
단, 여기서 유의할 점은 트리거를 꼭 순서대로 작성해야 합니다. 트리거는 작동 순서가 있으므로 작동 순서에 맞지 않게 작성할 경우, 좌표값의 인식이 제대로 되지 않을 수 있습니다.










이제 스타크래프트에서 잘 적용됬는지 확인해봅시다. 다음과 같이 잘 적용됬음을 확인할 수 있습니다. 단, 한 가지 흠이라면 마법의 좌표를 계속해서 빼서 0으로 만들었기 때문에 항상 마법은 맵 상단 위쪽으로 향합니다. 이 강좌를 응용하려면 마법의 이펙트를 없애는 것을 추천합니다.




댓글 없음:

댓글 쓰기