2021년 2월 1일 월요일

UE4 - Skookum script good example code : Death Of Tick

2015 년 6 월 26 일부터 2016 년 1 월 16 일까지 ~ 6 개월 동안 총 4,660 개의 컴파일을 컴파일하는 데 44.98 시간을 보냈습니다. 이것은 평균 34.75 초 / 컴파일이지만, UE4를 다시로드하고 핫 리로드가 실패했을 때 (약 25 % 확률) 포인터를 확인하는 것을 잊었을 때 작업했던 작업으로 돌아가는 데 걸리는 시간을 고려하지 않습니다. 언리얼 엔진 4의 C ++ 반복 시간에 대한이 엄청난 좌절감이 생겼었습니다.


제가 원했던 것은 UE4에서 사용할 수있는 간단한 스크립팅 언어로 C ++ 반복 시간을 줄이는 것이 었습니다.


SkookumScript에 대해 이야기하기 전에 Tick에 대해 논의해야합니다. 

게임 개발자로서 우리는 Tick에 살고 죽습니다. 

Tick은 우리가 게임에서하는 모든 일에 명백한 필수 요소이지만 틱이 얼마나 끔찍한 지 생각해 본 적이 있습니까?

Tick이 구현을 제어하는 ​​방법을 설명하기 위해 간단한 알고리즘을 살펴봅시다.


C++ - Tick 을 사용할 경우


유도 미사일이 발사되었을 때 다음 알고리즘을 사용하는 상상해보십시오. 여기에서 존재의 3 단계로 구분됩니다.


스테이지 1

  • 전방 방향으로 임펄스 적용

  • 0.5 초 동안 기다려

2 단계

  • 목표 획득

  • 타겟의 홈

3 단계

  • 목표물이 맞지 않으면 10 초 후 자살


틱 때문에 의사 코드는 다음과 같이 보일 수 있습니다.

Begin()
{
  Stage = 1
  FireImpulse()
  SetTimer Stage1Over for 0.5 seconds //Stage1Over에 0.5초 타이머 입력.
  SetTimer Stage2Over for 10 seconds  //Stage2Over에 10초 타이머 입력.
}

Tick()
{
  if Stage == 2
  {
    HomeToTarget()
  }
}

Stage1Over()
{
  Stage = 2
  AcquireTarget()
}

Stage2Over()
{
  DestroySelf()
}


알고리즘의 의도를 파악하는 것은 복잡성이 증가함에 따라 지루해질 수 있습니다.

우리는 이러한 복잡성과 상태 전환을 더 잘 구조화하는 데 도움이되는 패턴을 가지고 있지만,

결국 틱은 우리가 쉽게 이해할 수있는 알고리즘을 가져 와서 일부 표현주의 예술가가 캔버스에 페인트를 칠하는 것처럼 코드베이스에 흩뿌 리도록 강제합니다.



게임 로직 작성에 대한 전체 관점을 바꾸는 더 좋은 방법이 있습니다. 이 알고리즘에 대한 실제 SkookumScript (Sk) 코드는 다음과 같습니다.


()
[
  fire_forward_impulse
  _wait ( 0.5 ) //0.5초 딜레이.
  acquire_target

  race
  [
    _home_to_target
    _wait ( 9.5 ) // 위에서 이미 0.5초를 기다렸습니다.
  ]

  destroy_self
]

평범한 영어처럼 읽히기에 대충 무엇을 의미하는지 알 수 있겠지만, race [] 가 무엇인지 궁금하겠지요?


race → 말 그대로 경주하다에서 따온 명령.[                                       ] 사이의 명령어들 중 하나가 완료되는 즉시 다음 줄의 코드로 이동.


위 코드에서 race 함수의 결과 ex)

[

  race

  [

    _home_to_target

    _wait ( 9.5 ) // 위에서 이미 0.5초를 기다렸습니다.

  ]

  destroy_self

]


↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 위 코드에 대한 출력 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

_home _to_target 실행중...

_wait ( 9. 5 ) 실행중...

만약 타겟에 9.5초가 지났어도 도달하지 못했다면 ...  // 이부분에서 _home _to_target 이게 먼저 성공했는지 _wait ( 9. 5 ) 가 먼저 성공했는지  둘중 하나 먼저 성공한게 있다면 바로 빠져나와서 아래 코드 실행.
destroy_self ... 


발사 루프 예

다음 예제에는 _fire코 루틴 수정이 포함됩니다 . 이 코 루틴은 다음과 같이 무한 루프에서 호출됩니다.

loop
[
  if @is_firing? // @ indicates a member variable while ? indicates a boolean
  [
    _fire
  ]
  else [_wait] // wait 1 frame
]


fire_projectile 1.0 // 1.0은 발사체의 크기입니다.
_wait 0.15 )





즉시 vs 지연

Sk는 즉각적인 진술과 지연된 진술의 개념을 가지고 있습니다. 

즉각적인 문은 (지금까지 사용한 모든 함수와 마찬가지로) 동일한 프레임에서 완료되는 반면 지연된 문은 완료하는 데 두 개 이상의 프레임이 걸릴 수 있습니다. 

지연된 문을 코 루틴이라고하며 밑줄로 시작합니다. 

_wait지정된 시간 동안 대기하는 내장 코 루틴입니다.


_wait // 1 프레임 대기
_wait // 1 초 기다립니다


메서드 또는 코 루틴이 인수를 사용하지 않는 경우 (또는 기본 인수가 필요한 경우) 괄호가 필요하지 않습니다. 

위의 기본 예에서 즉시 문과 fire_projectile(1.0)지연 문이 모두 표시 _wait(0.15)됩니다.

_wait메서드 내에서 호출하려고 fire_projectile하면 지연된 문을 즉시 내에서 호출 할 수 없기 때문에 컴파일 오류가 발생합니다. 

그러나 _fire_projectile대기가 허용 되는 코 루틴 을 쉽게 만들 수 있습니다. 또는 branch즉시 내에서 새 코 루틴을 해제 할 수 있습니다


재 장전 지연이있는 3개의 미사일

// 기다릴 수있는 능력으로 무언가를 3 번 ​​수행
// 즉각적인 버전도 있습니다.
3. _do
[
	fire_projectile ( 1.0 )
	_wait ( 0.05 )
]
_wait ( 0.5 )




하다(do)

do 키워드는 말 그대로 n번 수행을 의미합니다.


5. do [ println ( "HI" )] // HI를 5 번 인쇄합니다.
3. do [ println ( idx )] // 012 출력

// 각각 1 초 간격으로 HI를 3 번 ​​인쇄합니다.
3. _do
[
	println ( "HI" )
	_wait ( 1 )
]


위 코드 중 3. do [ println ( idx )] 에서 idx란.

변수를 사용하여 반복 횟수를 지정할 수도 있습니다. .NET idxFramework에서 현재 인덱스를 제공하는 기본 제공 변수입니다.

variable.do[println(idx)] // prints 0 1 2 ... variable - 1


스프레드 샷

// 총 소켓의 회전을 가져옵니다.
//! 새 개체를 구성하는 데 사용됩니다.
// : 변수를 객체에 바인딩
// @는 개체의 멤버 변수에 액세스하는 데 사용되며 @rob_skeleton은
// Pawn 클래스의 일부인 스켈 레탈 메시 컴포넌트입니다.
// "LeftGunSocket".Name은 문자열을 내장 된 FName으로 변환합니다.
// socket_rotation 메소드가 필요합니다.

! rot : @rob_skeleton. socket_rotation ( "LeftGunSocket" .Name )

// 발사체의 회전 및 배율 전달
// rot. @ yaw는 RotationAngles 객체의 yaw 변수에 액세스합니다.

fire_projectile_degrees ( rot. @ yaw, 1.0 )
fire_projectile_degrees ( rot. @ yaw + 5.0 , 1.0 )
fire_projectile_degrees ( rot. @ yaw- 5.0 , 1.0 )

_wait ( 0.5 )




버스트 + 그랜드 피날레

3._do
[
  fire_projectile ( 1.0 )
  _wait ( 0.05 )
]

_wait( 0.3 )

100.do
[
  // idx >>는 idx (Integer)를 Real로 변환합니다. idx >> Real이라고 쓸 수 있지만 Sk는
  // 컨텍스트에 따라 올바른 유형으로 변환 할 수있을만큼 똑똑합니다.
  fire_projectile_degrees([360.0/100.0] * idx >>, 1.0 ) 
]

_wait( 1.0 )



버스트 + 서클 + 피날레


3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]

50._do
[
  fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
  _wait(0.05)
]

_wait(0.3)

100.do
[
  fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0)
]

_wait(1.0)



버스트 + 싱크 [서클 서클] + 피날레

이것은 뭔가 새로운 것이 있습니다. 당신이 그것을 발견하는지보십시오.


3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]

sync
[  
  50._do
  [
    fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
    _wait(0.05)
  ]
  75._do_reverse
  [
    fire_projectile_degrees([360.0 / 75.0] * idx>>, 0.8, green)
    _wait(0.05)
  ]
]

_wait(0.3)

100.do
[
  fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0)
]

_wait(1.0)




위의 코드 블록은 sync라는 기능을 사용중입니다.

sync는 병렬로 실행할 코루틴 블록을 지정할 수 있습니다. 실행은 sync블록의 모든  코루틴이 완료된 후에 만 블록을 넘어 계속 됩니다.

sync
[
   _routine1
   _routine2
   _routine3
]
// 루틴 1-3이 완료 될 때까지이 라인에 도달하지 않습니다.


제 경우에는 _do 2개의 코 루틴을 정의하는 데 사용 하고 있습니다. 

내가 좋아하는 패턴을 정하면 이것을 리팩토링 할 수 있으므로 _circular_pattern수량, 색상, 지연 및 스케일을 취하는 코루틴을 만들 수 있습니다 .

// 프로덕션 코드는 다음과 비슷합니다.
_burst_shot

sync
[
_circular_pattern(50, blue, 0.050.8)
_circular_pattern(75, green, 0.050.8)
]

_grand_finale


버스트 + 레이스 [서클 서클 대기] + 피날레

3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]

race
[  
  50._do
  [
    fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
    _wait(0.05)
  ]
  
  75._do_reverse
  [
    fire_projectile_degrees([360.0 / 75.0] * idx>>, 0.8, green)
    _wait(0.05)
  ]

  _wait(1)
]
  
_wait(0.3)

100.do
[
  fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0)
]

_wait(1.0)




경주

race병렬로 실행할 코 루틴 블록을 지정할 수 있습니다. 

때 어떤 이 코 루틴 출구에서, 다른 모든 코 루틴 race블록이 종료되고 실행은지나 계속 race차단합니다. 

말 그대로 모든 코 루틴이 가장 먼저 나가기 위해 경쟁하는 경주와 같습니다. 

예를 들어, 나는 같은 코드를 촬영 한 sync예와 race'는 대조를 거라고 _wait(1)

예상되는 것은 _wait(1)원형 발사 패턴 이전에 끝나므로 전체 레이스 블록은 1 초 후에 종료됩니다.

 race에 대한 또 다른 용도가 있습니다. 무엇을하는지 추측 해보세요.

race
[
   _wait_not_firing
   _fire
]

받았어? _fire사용자가 발사 버튼을 놓으면 전체 코 루틴을 중단 합니다.

어디 _wait_not_firing다음과 같습니다

()
[
   loop
   [
       [exit] when not @is_firing?
      _wait
   ]
]


분기(brach)

이 마지막 예제는 branch.  branch백그라운드에서 코 루틴을 실행하고 즉시 다음 문으로 계속 진행합니다. 

여기서는 그랜드 피날레 발사체에 몇 가지 추가 동작을 연결하는 데 사용합니다. 또한 sync문 에서 실행 패턴을 변경 하고 중첩을 허용하도록 기본 클로저 변수 이름을 재정의하는 예를 추가했습니다 _do(직접 조사 할 수 있음). 

색상을 가이드로 사용하여 각 문장이 무엇을하는지 확인하십시오.

3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]
  
sync
[  
  50._do
  [
    fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
    _wait(0.05)
  ]
 
  250._do_reverse
  [
    fire_projectile_degrees([[5.0 * 360.0] / 250.0] * idx>>, 0.8, green)
    _wait(0.01)
  ]
   
  4._do
  [
    5.do (Integer idx2)
    [
      fire_projectile_degrees([[90.0 / 5.0] * idx2>>] + [90.0 * idx>>], 2.0, purple)        
    ]
    _wait(0.8)
  ]
]
  
_wait(0.3)
100.do
[
  !p : fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0, purple)
  branch
  [
    _wait(0.2)
    if p.not_nil?
    [
      p._seek_actor(this) // this is the pawn that is firing these projectiles
    ]      
  ]
]

_wait(1.0)




마무리

: 그건 내가 Sk를의 기둥을 고려 무엇에 충돌 과정이었다 racesyncbranch와 _wait. 그러나 저는 언어에 포함 된 기능의 극히 일부만을 다루었습니다. 

바라건대, Tick을 제거하면 매우 표현적이고 간단한 알고리즘을 작성할 수있는 방법을 보셨을 것입니다.

나는 Sk가 반복하는 속도가 얼마나 빠른지 또는 강력한 명령 프롬프트로서 얼마나 유용한 지 보여주지 못했습니다. 

이 모든 발사 패턴은 라이브 러닝 게임에서 만들어졌으며 핫 리 컴파일에 문자 그대로 1 초도 걸리지 않았습니다. 

솔직히, 각 프로토 타입 사이에서 ESC를 반사적으로 치는 습관을 깨는 것은 언어 자체를 배우는 것보다 어렵습니다.


 출처 : https://error454.com/2017/03/09/the-death-of-tick-ue4-the-future-of-programming/

댓글 없음:

댓글 쓰기