오랜만의 글 작성입니다!
EPICGAMES에서 새롭게 출시한 언어인 Verse를 이용하여 UEFN 환경에서 제공된 에셋을 이용하여 맵을 구축하고 EPICGAMES에서 제공하는 게임 메카닉 가이드라인을 참고하여 나만의 커스텀 파쿠르 프로젝트를 만들어 보았기에 소개하고자 합니다.
공백의 4개월 동안의 근황은 잠시 각설하고.. 오늘은 바로 본론부터 들어가보도록 하겠습니다😀
해당 프로젝트는 아래의 사이트를 참고하여 작성한 프로젝트 입니다!
1. EPICGAMES- Verse 파쿠르 템플릿
2. EPICGAMES- 게임 메카닉 배우기
완성된 프로젝트의 결과물 동영상은 다음 유튜브 사이트에서 확인하실 수 있습니다.
결과물
Creative Device는 사용자가 Verse 프로그래밍을 이용하여 나만의 장치를 만들수 있는것을 말합니다!
제가 이번 게임에서 기용한 Creative Device는 4개가 있습니다.
차근차근 코드와 함께 소개해보도록 하겠습니다.
#Verse로 작성된 코드
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
button_interact := class(creative_device):
#편집 가능 필드
@editable
MyButton : button_device = button_device{}
#message type은 이펙트 localizes지정자가 필요
TextForMyUI<localizes>: message = "Up to Sky"
var MaybeMyUIPerPlayer : [player]?canvas = map{}
OnBegin<override>()<suspends>:void=
MyButton.InteractedWithEvent.Subscribe(HandleButtonInteraction)
HandleButtonInteraction(Agent:agent):void=
if(InPlayer:=player[Agent],PlayerUI:=GetPlayerUI[InPlayer]):
if(MyUI:=MaybeMyUIPerPlayer[InPlayer]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[InPlayer]=false){}
else:
NewUI:=CreateMyUI()
#위젯을 추가시 실행인자를 추가해 플레이어가 캔버스에서 커서를 사용할 수 있게해준다.
PlayerUI.AddWidget(NewUI,player_ui_slot{InputMode:=ui_input_mode.All})
if(set MaybeMyUIPerPlayer[InPlayer]=option{NewUI}){}
CreateMyUI():canvas=
MyUIButton: button_loud = button_loud{DefaultText := TextForMyUI}
MyUIButton.OnClick().Subscribe(HandleSelectedUIButton)
MyInteractableButtons:canvas = canvas:
Slots:=array:
canvas_slot:
Anchors := anchors{Minimum := vector2{X := 0.25, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := vector2{X := 0.5, Y := 0.5}
SizeToContent := true
Widget := MyUIButton
return MyInteractableButtons
HandleSelectedUIButton(Message : widget_message):void=
if(PlayerUI:= GetPlayerUI[Message.Player],MyUI:=MaybeMyUIPerPlayer[Message.Player]?,SelectButton:=text_button_base[Message.Source]):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Message.Player]=false){}
기본적인 구조는 다음과 같습니다.
#Verse로 작성된 코드
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }
using { /Verse.org/Native }
log_platform_series := class(log_channel){}
platform_series := class(creative_device):
Logger: log = log{Channel := log_platform_series}
@editable
HeadStart : float = 2.45
@editable
MinAppearDelay : float = 0.5
@editable
MaxAppearDelay : float = 1.5
@editable
MinDisappearDelay : float = 0.5
@editable
MaxDisappearDelay : float = 1.5
@editable
Platforms : []color_changing_tiles_device = array{}
OnBegin<override>()<suspends>:void=
loop:
sync:
ShowAllPlatforms()
block:
Sleep(HeadStart)
HideAllPlatforms()
HideAllPlatforms()<suspends>: void =
for(PlatformNumber->Platform : Platforms):
Platform.Hide()
Sleep(GetRandomFloat(MinDisappearDelay,MaxDisappearDelay))
ShowAllPlatforms()<suspends>:void =
for(PlatformNumber->Platform : Platforms):
Platform.Show()
Sleep(GetRandomFloat(MinAppearDelay,MaxAppearDelay))
해당 장치의 구조는 다음과 같습니다.
1. 편집가능(@editable) 변수들을 이용하여 주기를 조절할 수 있습니다.
2. 게임 시작시 loop를 이용하여 반복적으로 함수를 실행하고, sync를 이용해 ShowAllPlatforms()함수와
block을 동시에 실행합니다. sync를 이용해 사라짐과 생성이 자연스레 나타나는 효과를 줄 수 있습니다.
3. block은 절차적으로 실행하므로 Sleep(HeadStart)에 의해 HeadStart만큼의 시간 후 HideAllPlatforms() 함수를 호출합니다.
4. ShowAllPlatforms()와 HideAllPlatforms() 함수는 각각 color_changing_tiles_device의 노출과 숨김을 담당하는 함수입니다.
5. 참고로 color_changing_tiles_device는 Hide()와 Show() 내장함수를 갖고있기에 이를 이용하여 노출과 숨김을 이용할 수 있습니다.
#Verse로 작성된 코드
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Random }
using { /UnrealEngine.com/Temporary/Diagnostics }
disappear_on_touch_platform := class(creative_device):
@editable
DisappearDelay : float = 1.0
@editable
DelayMin : float = 3.0
@editable
DelayMax : float = 4.5
@editable
Platform : color_changing_tiles_device = color_changing_tiles_device{}
OnBegin<override>()<suspends>:void=
Platform.ActivatedEvent.Subscribe(OnPlayerTouch)
OnPlayerTouch(InPlayer : agent) : void=
spawn{RecyclePlatform()}
RecyclePlatform()<suspends>:void=
Sleep(DisappearDelay)
Platform.Hide()
Platform.Reset()
Sleep(GetRandomFloat(DelayMin,DelayMax))
Platform.Show()
해당 장치의 구조는 다음과 같습니다.
1.Platform의 활성화 이벤트를 이용하여 플레이어 접촉시 OnPlayerTouch를 호출합니다.
2. OnPlayerTouch는 RecyclePlatform()를 spawn시킵니다. 이는, Platform.ActivatedEvent.Subscribe에 등록될 함수는 를 가질수 없기에 RecyclePlatform()를 호출하는 형태로 사용합니다.
3. RecyclePlatform()가 를 갖는 이유는, Sleep()함수를 호출하기 위해선 해당 함수가 를 가져야 하기 때문입니다.
4. 결과적으로 플레이어가 발판에 닿을 시 사라졌다가 초기화되고 보여집니다. 이것이 걸리는 시간은 편집가능 변수의 범위에서 랜덤한 값을 가져와 실행됩니다.
하지만 유감스럽게도 동기화된 사라지는 플랫폼 장치와 접촉시 사라지는 플랫폼 장치는 제대로 작동하지 않습니다! 가이드라인에서 제시한 대로 Verse프로그래밍과 보일때만의 물리 설정을 했음에도 불구하고 발판이 투명해졌을때 플레이어가 다시 접촉을 한다면 떨어지는 것이 아닌 투명한 발판 위에 올라서게 됩니다. Platform.Reset()를 제거해도 마찬가지였습니다. 이를 해결하고자 여러 고민들을 해보았으나 결국 해결하지 못하였습니다. 아쉽게도 말이죠..
#Verse로 작성된 코드
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation}
using { /UnrealEngine.com/Temporary/Diagnostics }
log_parkour_jun := class(log_channel){}
all_device := class(creative_device):
Logger : log = log{Channel:=log_parkour_jun}
@editable
PlayerSpawnDevice : player_spawner_device = player_spawner_device{}
@editable
EndGameVictoryDevice : end_game_device = end_game_device{}
@editable
HUDMessageBattery : hud_message_device = hud_message_device{}
@editable
BatteryItemSpawners : []item_spawner_device = array{}
@editable
SecretBatteryItemSpawner : item_spawner_device = item_spawner_device{}
var BatteriesCollected : int = 0
BatteryCollectedMessage<localizes>(Amount:int, Plural:string) : message = "You collected {Amount} batter{Plural} of 8"
AllBatteriesCollectedMessage<localizes> : message = "모든 배터리 수집 완료"
SecretBatteryCollectedMessage<localizes> : message = "숨겨진 배터리 획득, 통로 개방"
OnBegin<override>()<suspends>:void=
for (BatterySpawner : BatteryItemSpawners):
BatterySpawner.ItemPickedUpEvent.Subscribe(HandleBatteryPickedUp)
SecretBatteryItemSpawner.ItemPickedUpEvent.Subscribe(HandleSecretBatteryPickedUp)
HandleBatteryPickedUp(Agent:agent):void=
set BatteriesCollected = BatteriesCollected + 1
Logger.Print("Number of batteries picked up: {BatteriesCollected}")
if:
BatteriesCollected >= BatteryItemSpawners.Length
then:
spawn { EndGame(Agent) }
else:
if:
BatteriesCollected = 1
then:
HUDMessageBattery.SetText(BatteryCollectedMessage(BatteriesCollected,"y"))
HUDMessageBattery.Show(Agent)
else:
HUDMessageBattery.SetText(BatteryCollectedMessage(BatteriesCollected,"ies"))
HUDMessageBattery.Show(Agent)
if:
NextBatterySpawner := BatteryItemSpawners[BatteriesCollected]
then:
NextBatterySpawner.SpawnItem()
HandleSecretBatteryPickedUp(Agent:agent):void=
Logger.Print("Picked up secret battery")
HUDMessageBattery.SetText(SecretBatteryCollectedMessage)
HUDMessageBattery.Show(Agent)
EndGame(Agent:agent)<suspends>:void=
HUDMessageBattery.SetText(AllBatteriesCollectedMessage)
HUDMessageBattery.Show(Agent)
Sleep(3.0)
EndGameVictoryDevice.Activate(Agent)
OnEnd<override>():void=
Logger.Print("Verse device stopped!")