Skip to content

Getting Started With Arcanepad on Godot 4

Welcome to the Arcanepad Godot Tutorial! This guide will help you get started with creating Arcanepad games in Godot.

WARNING

IMPORTANT!

We are using Godot 4.3.rc3. Don't try with older versions as they won't work. You can download it here https://godotengine.org/article/release-candidate-godot-4-3-rc-3/#downloads

On the video tutorial using 4.3.dev4, but it had a sound bug that the new rc3 has fixed. Also in the video we explain how to install export templates from the provided zip, this is no longer necesary as rc3 can install the export templates automatically on the export window. You just need to point to the downloaded zip's. Reach us at arcanepad@gmail.com or discord if you need help.

Repo

https://github.com/imvenx/arcanepad-godot-sdk

php
extends Control

const PadScenePath = "res://Scenes/Pad/Pad.tscn"
const ViewScenePath = "res://Scenes/View/View.tscn"


func _ready():
    call_deferred("goToPropperScene")
    
# INFO: This is a trick we do to have automatically loaded the game pad in the web exports
# The way it works is: We set the export WebGL without the View scene, so when we load on our
# phone, it will go automatically to the Pad scene, but on the editor, since it has all the
# scenes it will load the View. Feel free to change it to fit your workflow.
func goToPropperScene():
    if ResourceLoader.exists(PadScenePath) and not ResourceLoader.exists(ViewScenePath):
        goToPadScene()
        return
        
    goToViewScene()


func goToPadScene():
    get_tree().change_scene_to_file(PadScenePath)


func goToViewScene():
    get_tree().change_scene_to_file(ViewScenePath)
extends Control

const PadScenePath = "res://Scenes/Pad/Pad.tscn"
const ViewScenePath = "res://Scenes/View/View.tscn"


func _ready():
    call_deferred("goToPropperScene")
    
# INFO: This is a trick we do to have automatically loaded the game pad in the web exports
# The way it works is: We set the export WebGL without the View scene, so when we load on our
# phone, it will go automatically to the Pad scene, but on the editor, since it has all the
# scenes it will load the View. Feel free to change it to fit your workflow.
func goToPropperScene():
    if ResourceLoader.exists(PadScenePath) and not ResourceLoader.exists(ViewScenePath):
        goToPadScene()
        return
        
    goToViewScene()


func goToPadScene():
    get_tree().change_scene_to_file(PadScenePath)


func goToViewScene():
    get_tree().change_scene_to_file(ViewScenePath)
php
extends Node

# INFO: This is an implementation of how you could handle your gamepads
# connection/disconection, but is not definitive, if your game requires
# a different implementation go ahead and change this. For example maybe
# you don't want to destroy players on gamepad disconnect, instead you may
# want to pause the game.


var players := [] 
var gameStarted := false
var isGamePaused := false
var playerScene = preload("res://Scenes/View/Player/Player.tscn")

func _ready():
    Arcane.init(self, { "deviceType": "view", "logLevel": "verbose" })
    
    Arcane.msg.on(AEventName.ArcaneClientInitialized, onArcaneClientInitialized)

func onArcaneClientInitialized(initialState: AModels.InitialState):
    
    # Create a player for each gamepad that existed before the view was created
    for pad in initialState.pads: createPlayer(pad)
        
    # Listen to gamepads connecting after the view was created and create them if no exist
    Arcane.msg.on(AEventName.IframePadConnect, onIframePadConnect)
    # Listen for gamepads disconnecting and destroy the player
    Arcane.msg.on(AEventName.IframePadDisconnect, onIframePadDisconnect)

    # Listen pause app    
    Arcane.msg.on(AEventName.PauseApp, onPauseApp)

    # Listen resume app    
    Arcane.msg.on(AEventName.ResumeApp, onResumeApp)
    

func onPauseApp():
    $PausePanel.show()
    
func onResumeApp():
    $PausePanel.hide()
        
    
# We create a player if it wasn't created before
func onIframePadConnect(e):
    var playerExists = players.any(func(player): return player.pad.iframeId == e.iframeId)
    if playerExists: return

    var pad = ArcanePad.new(e.deviceId, e.internalId, e.iframeId, true, e.user)
    createPlayer(pad)
    

func onIframePadDisconnect(event):
    var disconnectedPlayer = null
    for _player in players:
        if _player.pad.iframeId == event.iframeId:
            disconnectedPlayer = _player
            break
            
    if disconnectedPlayer == null:
        push_error("Player not found to remove on disconnect")
        return

    destroy_player(disconnectedPlayer)


func createPlayer(pad):
    if(AUtils.isNullOrEmpty(pad.iframeId)): return
    
    var newPlayer = playerScene.instantiate()
    print(newPlayer)
    newPlayer.initialize(pad)
    add_child(newPlayer)
    players.append(newPlayer)
    

func destroy_player(player):
    players.erase(player)
    player.queue_free()
extends Node

# INFO: This is an implementation of how you could handle your gamepads
# connection/disconection, but is not definitive, if your game requires
# a different implementation go ahead and change this. For example maybe
# you don't want to destroy players on gamepad disconnect, instead you may
# want to pause the game.


var players := [] 
var gameStarted := false
var isGamePaused := false
var playerScene = preload("res://Scenes/View/Player/Player.tscn")

func _ready():
    Arcane.init(self, { "deviceType": "view", "logLevel": "verbose" })
    
    Arcane.msg.on(AEventName.ArcaneClientInitialized, onArcaneClientInitialized)

func onArcaneClientInitialized(initialState: AModels.InitialState):
    
    # Create a player for each gamepad that existed before the view was created
    for pad in initialState.pads: createPlayer(pad)
        
    # Listen to gamepads connecting after the view was created and create them if no exist
    Arcane.msg.on(AEventName.IframePadConnect, onIframePadConnect)
    # Listen for gamepads disconnecting and destroy the player
    Arcane.msg.on(AEventName.IframePadDisconnect, onIframePadDisconnect)

    # Listen pause app    
    Arcane.msg.on(AEventName.PauseApp, onPauseApp)

    # Listen resume app    
    Arcane.msg.on(AEventName.ResumeApp, onResumeApp)
    

func onPauseApp():
    $PausePanel.show()
    
func onResumeApp():
    $PausePanel.hide()
        
    
# We create a player if it wasn't created before
func onIframePadConnect(e):
    var playerExists = players.any(func(player): return player.pad.iframeId == e.iframeId)
    if playerExists: return

    var pad = ArcanePad.new(e.deviceId, e.internalId, e.iframeId, true, e.user)
    createPlayer(pad)
    

func onIframePadDisconnect(event):
    var disconnectedPlayer = null
    for _player in players:
        if _player.pad.iframeId == event.iframeId:
            disconnectedPlayer = _player
            break
            
    if disconnectedPlayer == null:
        push_error("Player not found to remove on disconnect")
        return

    destroy_player(disconnectedPlayer)


func createPlayer(pad):
    if(AUtils.isNullOrEmpty(pad.iframeId)): return
    
    var newPlayer = playerScene.instantiate()
    print(newPlayer)
    newPlayer.initialize(pad)
    add_child(newPlayer)
    players.append(newPlayer)
    

func destroy_player(player):
    players.erase(player)
    player.queue_free()
php
extends Node


var pad:ArcanePad
var padQuaternion := Quaternion.IDENTITY

func initialize(_pad:ArcanePad) -> void:
    
    pad = _pad
    
     # Get User Name
    print("User Name: ", pad.user.name)
     # Get User Color
    print("User Color: #", pad.user.color)
    
    # Listen messages from the gamepad
    pad.on("Attack", onAttack)
    pad.on("CustomEvent", onCustomEvent)
    
    pad.on("SomeEvent", func(): print("something")) 
    # Stop Listening
    pad.off("SomeEvent") 
    
    # Send message from viewer to gamepad
    pad.emit(AEvents.ArcaneBaseEvent.new("HelloFromView")) 
    
    # Get rotation quaternion of the gamepad
    pad.startGetQuaternion()
    pad.onGetQuaternion(onGetQuaternion)
    # pad.stopGetQuaternion() 
    
    # Get pointer of the gamepad
    pad.startGetPointer()
    pad.onGetPointer(onGetPointer)
    # pad.stopGetPointer() 
    
    # Get linear acceleration of the gamepad
    #pad.startGetLinearAcceleration()
    #pad.onGetLinearAcceleration(onGetLinearAcceleration)
    #pad.stopGetLinearAcceleration() 
    
    
func _process(_delta):
    # Rotate player mesh with gamepad rotation
    $MeshInstance3D.transform.basis = Basis(padQuaternion)
    
    
func onAttack():
    
    # Send message to pad
    pad.emit(AEvents.ArcaneBaseEvent.new("HelloFromView")) 
    
    AUtils.writeToScreen(self, pad.user.name + " attacked")


func onCustomEvent(e):
    prints("Received custom event with val: ", e.someVal)
    

func onGetQuaternion(e):
    if(e.w == null || e.x == null || e.y == null || e.z == null): return
    
    # Set gamepad rotation to our padQuaternion variable, which will be used on _process
    # to rotate the mesh
    padQuaternion.x = -e.x
    padQuaternion.y = -e.y
    padQuaternion.z = e.z
    padQuaternion.w = e.w
    

func onGetPointer(e):
    if(e.x == null || e.y == null): return
    
    # Move the pointer
    var viewport_size = get_viewport().get_size()

    var new_x = viewport_size.x * (e.x / 100.0)
    var new_y = viewport_size.y * (e.y / 100.0)
    
    $Pointer.position = Vector2(new_x, new_y)
    
    
func onGetLinearAcceleration(e):
    prints("Linear Acceleration: ", e)
extends Node


var pad:ArcanePad
var padQuaternion := Quaternion.IDENTITY

func initialize(_pad:ArcanePad) -> void:
    
    pad = _pad
    
     # Get User Name
    print("User Name: ", pad.user.name)
     # Get User Color
    print("User Color: #", pad.user.color)
    
    # Listen messages from the gamepad
    pad.on("Attack", onAttack)
    pad.on("CustomEvent", onCustomEvent)
    
    pad.on("SomeEvent", func(): print("something")) 
    # Stop Listening
    pad.off("SomeEvent") 
    
    # Send message from viewer to gamepad
    pad.emit(AEvents.ArcaneBaseEvent.new("HelloFromView")) 
    
    # Get rotation quaternion of the gamepad
    pad.startGetQuaternion()
    pad.onGetQuaternion(onGetQuaternion)
    # pad.stopGetQuaternion() 
    
    # Get pointer of the gamepad
    pad.startGetPointer()
    pad.onGetPointer(onGetPointer)
    # pad.stopGetPointer() 
    
    # Get linear acceleration of the gamepad
    #pad.startGetLinearAcceleration()
    #pad.onGetLinearAcceleration(onGetLinearAcceleration)
    #pad.stopGetLinearAcceleration() 
    
    
func _process(_delta):
    # Rotate player mesh with gamepad rotation
    $MeshInstance3D.transform.basis = Basis(padQuaternion)
    
    
func onAttack():
    
    # Send message to pad
    pad.emit(AEvents.ArcaneBaseEvent.new("HelloFromView")) 
    
    AUtils.writeToScreen(self, pad.user.name + " attacked")


func onCustomEvent(e):
    prints("Received custom event with val: ", e.someVal)
    

func onGetQuaternion(e):
    if(e.w == null || e.x == null || e.y == null || e.z == null): return
    
    # Set gamepad rotation to our padQuaternion variable, which will be used on _process
    # to rotate the mesh
    padQuaternion.x = -e.x
    padQuaternion.y = -e.y
    padQuaternion.z = e.z
    padQuaternion.w = e.w
    

func onGetPointer(e):
    if(e.x == null || e.y == null): return
    
    # Move the pointer
    var viewport_size = get_viewport().get_size()

    var new_x = viewport_size.x * (e.x / 100.0)
    var new_y = viewport_size.y * (e.y / 100.0)
    
    $Pointer.position = Vector2(new_x, new_y)
    
    
func onGetLinearAcceleration(e):
    prints("Linear Acceleration: ", e)
php
extends Control

func _ready():
    Arcane.init(self, {"deviceType": "pad", "padOrientation": "portrait"})

    Arcane.msg.on(AEventName.ArcaneClientInitialized, onArcaneClientInitialized)
    
    # Listen event
    Arcane.msg.on("HelloFromView", onHelloFromView)
    
        
func onArcaneClientInitialized(initialState: AModels.InitialState):
    print(initialState.pads)
    AUtils.writeToScreen(self, "Arcane Client Initialized")
    
    # Send untyped Message from gamepad to viewer
    Arcane.msg.emitToViews(AEvents.ArcaneBaseEvent.new("HelloFromPad"))
    # Send typed event from gamepad to the viewer
    Arcane.msg.emitToViews(MyCustomEvent.new(5))
    
func onHelloFromView():
    AUtils.writeToScreen(self, "View Says Hello")
    Arcane.pad.vibrate(200)


# INFO: Your custom typed events inherit from ArcaneBaseEvent.
# This could be an attack event or jump or move, etc.
class MyCustomEvent extends AEvents.ArcaneBaseEvent:
    var someVal:int
    
    func _init(_someVal:int):
        super._init("MyCustomEvent")
        someVal = _someVal
extends Control

func _ready():
    Arcane.init(self, {"deviceType": "pad", "padOrientation": "portrait"})

    Arcane.msg.on(AEventName.ArcaneClientInitialized, onArcaneClientInitialized)
    
    # Listen event
    Arcane.msg.on("HelloFromView", onHelloFromView)
    
        
func onArcaneClientInitialized(initialState: AModels.InitialState):
    print(initialState.pads)
    AUtils.writeToScreen(self, "Arcane Client Initialized")
    
    # Send untyped Message from gamepad to viewer
    Arcane.msg.emitToViews(AEvents.ArcaneBaseEvent.new("HelloFromPad"))
    # Send typed event from gamepad to the viewer
    Arcane.msg.emitToViews(MyCustomEvent.new(5))
    
func onHelloFromView():
    AUtils.writeToScreen(self, "View Says Hello")
    Arcane.pad.vibrate(200)


# INFO: Your custom typed events inherit from ArcaneBaseEvent.
# This could be an attack event or jump or move, etc.
class MyCustomEvent extends AEvents.ArcaneBaseEvent:
    var someVal:int
    
    func _init(_someVal:int):
        super._init("MyCustomEvent")
        someVal = _someVal
php
extends Button


func _ready():
    # Send Event to views on attack button pressed
    connect("pressed", func(): Arcane.msg.emitToViews(AEvents.ArcaneBaseEvent.new("Attack")))
extends Button


func _ready():
    # Send Event to views on attack button pressed
    connect("pressed", func(): Arcane.msg.emitToViews(AEvents.ArcaneBaseEvent.new("Attack")))
php
extends Button


func _ready():
    connect("pressed", func(): Arcane.pad.calibratePointer(true))
extends Button


func _ready():
    connect("pressed", func(): Arcane.pad.calibratePointer(true))
php
extends Button


func _ready():
    connect("pressed", func(): Arcane.pad.calibratePointer(false))
extends Button


func _ready():
    connect("pressed", func(): Arcane.pad.calibratePointer(false))
php
extends Button


func _ready():
    connect("pressed", func(): Arcane.pad.calibrateQuaternion())
extends Button


func _ready():
    connect("pressed", func(): Arcane.pad.calibrateQuaternion())

Downloading the starter template and exploring Main scene logic

Exploring the Gamepad and View scenes

Export game and add it to games library (optional)

Upload your game to Arcanepad

Go to https://dev.arcanepad.com, create an account and after you are verified you can upload your game. The app folder has to be compressed on .zip format.

Setup from zero or update arcane library

If you want to start your project from zero or update the arcane library, you can download the latest version here https://github.com/imvenx/arcanepad-godot-sdk/releases