Verse를 이용한 커스텀 파쿠르 수집맵 만들기

gcoco·2023년 9월 3일
0
post-thumbnail

안녕하세요! GCOCO입니다.


잡설 한 COOKIE🍪

오랜만의 글 작성입니다!

EPICGAMES에서 새롭게 출시한 언어인 Verse를 이용하여 UEFN 환경에서 제공된 에셋을 이용하여 맵을 구축하고 EPICGAMES에서 제공하는 게임 메카닉 가이드라인을 참고하여 나만의 커스텀 파쿠르 프로젝트를 만들어 보았기에 소개하고자 합니다.

공백의 4개월 동안의 근황은 잠시 각설하고.. 오늘은 바로 본론부터 들어가보도록 하겠습니다😀


해당 프로젝트는 아래의 사이트를 참고하여 작성한 프로젝트 입니다!
1. EPICGAMES- Verse 파쿠르 템플릿
2. EPICGAMES- 게임 메카닉 배우기

완성된 프로젝트의 결과물 동영상은 다음 유튜브 사이트에서 확인하실 수 있습니다.
결과물


사용된 Creative Device

Creative Device는 사용자가 Verse 프로그래밍을 이용하여 나만의 장치를 만들수 있는것을 말합니다!
제가 이번 게임에서 기용한 Creative Device는 4개가 있습니다.

  1. 클릭이 가능한 Interactive-Button UI 장치
  2. 동기화된 사라지는 플랫폼 장치
  3. 접촉시 사라지는 플랫폼 장치
  4. 수집한 배터리의 수를 카운트하고 HUD 장치를 이용해 유저에게 표시하는 장치

차근차근 코드와 함께 소개해보도록 하겠습니다.


클릭이 가능한 Interactive-Button UI 장치

참고 페이지

#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){}

기본적인 구조는 다음과 같습니다.

  1. 게임이 실행되면 버튼에 상호작용시 상호작용 이벤트에 등록된 함수 HandleButtonInteraction를 호출합니다.
  2. 플레이어에게 이미 위젯이 활성화 되있는 상태면 한 번 더 상호작용시 위젯을 제거하고, 그렇지 않으면 위젯을 표시합니다.
  3. CreateMyUI 함수는 UEFN에서 제시한 캔버스를 이용해 위젯을 띄우는 방법입니다.
  4. 또한 HandleSelectedUIButton를 이용해 플레이어가 위젯 클릭시 위젯을 제거합니다.


동기화된 사라지는 플랫폼 장치

#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()를 제거해도 마찬가지였습니다. 이를 해결하고자 여러 고민들을 해보았으나 결국 해결하지 못하였습니다. 아쉽게도 말이죠..


수집한 배터리의 수를 카운트하고 HUD 장치를 이용해 유저에게 표시하는 장치

#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!")

profile
그코코 입니다.

0개의 댓글