Skip to content

Getting Started With Arcanepad on Godot 3

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

Cool demo

Starter Template Tutorial

WARNING

Breaking changes were made to the way we use events, in the video I use strings to call events like "GetQuaternion", but now is AEventName.GetQuaternion instead, this is explained on the next video after this on the "Breaking changes to event names" section, and also has been updated on the template repo code, so if you read the code and follow how is structured you shouldn't have any problems.

Repo

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

WARNING

Since there is no GDScript code syntax highlight support, we have to use PHP syntax highlight. So the highlight will not be totally accurate.

php
extends Node2D

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

var dir = Directory.new()

func _ready():
	call_deferred("goToPropperScene")
	

func goToPropperScene():
	if dir.file_exists(PadScenePath) and not dir.file_exists(ViewScenePath):
		goToPadScene()
		return
	
	goToViewScene()


func goToPadScene():
	var _sceneChange = get_tree().change_scene(PadScenePath)
	queue_free()


func goToViewScene():
	var viewScene = load(ViewScenePath).instance()
	get_tree().root.add_child(viewScene)
	get_tree().current_scene = viewScene
	queue_free()
extends Node2D

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

var dir = Directory.new()

func _ready():
	call_deferred("goToPropperScene")
	

func goToPropperScene():
	if dir.file_exists(PadScenePath) and not dir.file_exists(ViewScenePath):
		goToPadScene()
		return
	
	goToViewScene()


func goToPadScene():
	var _sceneChange = get_tree().change_scene(PadScenePath)
	queue_free()


func goToViewScene():
	var viewScene = load(ViewScenePath).instance()
	get_tree().root.add_child(viewScene)
	get_tree().current_scene = viewScene
	queue_free()
php
extends Node

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

func _ready():
	initView()

func initView():
	
	Arcane.init()
	
	# LISTEN WHEN THIS CLIENT (THE VIEW) IS INITIALIZED
	Arcane.signals.connect(AEventName.ArcaneClientInitialized, self, "onArcaneClientInitialized")
		
	# LISTEN WHEN ANY GAMEPAD CONNECTS
	Arcane.signals.connect(AEventName.IframePadConnect, self, "onIframePadConnect")
	
	# LISTEN WHEN ANY GAMEPAD DISCONNECTS
	Arcane.signals.connect(AEventName.IframePadDisconnect, self, "onIframePadDisconnect")


# CREATE A PLAYER FOR EACH GAMEPAD THAT WAS CONNECTED BEFORE THE VIEW HAD INITIALIZED
func onArcaneClientInitialized(initialState):
	for pad in initialState.pads:
		createPlayer(pad)


# FOR EACH GAMEPAD THAT CONNECTS, CREATE A PLAYER IF DONT EXIST
func onIframePadConnect(e, _from):
	
	var playerExists = false
	for _player in players:
		if _player.pad.iframeId == e.iframeId:
			playerExists = true
			break
	if playerExists:
		return

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


# DESTROY THE PLAYER ON GAMEPAD DISCONNECT (YOU CAN CHANGE THIS LOGIC, FOR EXAMPLE TO PAUSE OR WARN USERS)
func onIframePadDisconnect(e, _from):
	var player = null
	for _player in players:
		if _player.pad.iframeId == e.iframeId:
			player = _player
			break
			
	if player == null:
		push_error("Player not found to remove on disconnect")
		return

	destroy_player(player)


func createPlayer(pad):
	
	# This prevents creating the player if our pad app haven't been loaded
	if pad.iframeId == null || pad.iframeId == "": return
	
	var newPlayer = playerScene.instance()
	print(newPlayer)
	newPlayer.initialize(pad)
	add_child(newPlayer)
	players.append(newPlayer)


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

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

func _ready():
	initView()

func initView():
	
	Arcane.init()
	
	# LISTEN WHEN THIS CLIENT (THE VIEW) IS INITIALIZED
	Arcane.signals.connect(AEventName.ArcaneClientInitialized, self, "onArcaneClientInitialized")
		
	# LISTEN WHEN ANY GAMEPAD CONNECTS
	Arcane.signals.connect(AEventName.IframePadConnect, self, "onIframePadConnect")
	
	# LISTEN WHEN ANY GAMEPAD DISCONNECTS
	Arcane.signals.connect(AEventName.IframePadDisconnect, self, "onIframePadDisconnect")


# CREATE A PLAYER FOR EACH GAMEPAD THAT WAS CONNECTED BEFORE THE VIEW HAD INITIALIZED
func onArcaneClientInitialized(initialState):
	for pad in initialState.pads:
		createPlayer(pad)


# FOR EACH GAMEPAD THAT CONNECTS, CREATE A PLAYER IF DONT EXIST
func onIframePadConnect(e, _from):
	
	var playerExists = false
	for _player in players:
		if _player.pad.iframeId == e.iframeId:
			playerExists = true
			break
	if playerExists:
		return

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


# DESTROY THE PLAYER ON GAMEPAD DISCONNECT (YOU CAN CHANGE THIS LOGIC, FOR EXAMPLE TO PAUSE OR WARN USERS)
func onIframePadDisconnect(e, _from):
	var player = null
	for _player in players:
		if _player.pad.iframeId == e.iframeId:
			player = _player
			break
			
	if player == null:
		push_error("Player not found to remove on disconnect")
		return

	destroy_player(player)


func createPlayer(pad):
	
	# This prevents creating the player if our pad app haven't been loaded
	if pad.iframeId == null || pad.iframeId == "": return
	
	var newPlayer = playerScene.instance()
	print(newPlayer)
	newPlayer.initialize(pad)
	add_child(newPlayer)
	players.append(newPlayer)


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

func _ready():
	initPad()
	
	
func initPad():
	
	Arcane.init({'padOrientation': 'Portrait', 'deviceType': 'pad'})
	
	# LISTEN WHEN THIS CLIENT (GAMEPAD) IS INITIALIZED
	Arcane.signals.connect(AEventName.ArcaneClientInitialized, self, "onArcaneClientInitialized")
	
	
func onArcaneClientInitialized(initialState):
	
	# LISTEN CUSTOM EVENT FROM THE VIEW
	Arcane.signals.addSignal(EventName.TakeDamage)
	Arcane.signals.connect(EventName.TakeDamage, self, "onTakeDamage")
	
	
func _on_CalibrateQuaternion_pressed():
	Arcane.pad.calibrateQuaternion()


func _on_CalibratePointerTopLeft_pressed():
	Arcane.pad.calibratePointer(true)


func _on_CalibratePointerBottomRight_pressed():
	Arcane.pad.calibratePointer(false)


func _on_Attack_pressed():
	
	# EMIT CUSTOM EVENT TO THE VIEWS
	Arcane.msg.emitToViews(Events.AttackEvent.new())


func onTakeDamage(e, from):
	Arcane.pad.vibrate(200)
	Arcane.utils.writeToScreen("Taken " + str(e.damage) + " damage! Ouch!")
extends Node2D

func _ready():
	initPad()
	
	
func initPad():
	
	Arcane.init({'padOrientation': 'Portrait', 'deviceType': 'pad'})
	
	# LISTEN WHEN THIS CLIENT (GAMEPAD) IS INITIALIZED
	Arcane.signals.connect(AEventName.ArcaneClientInitialized, self, "onArcaneClientInitialized")
	
	
func onArcaneClientInitialized(initialState):
	
	# LISTEN CUSTOM EVENT FROM THE VIEW
	Arcane.signals.addSignal(EventName.TakeDamage)
	Arcane.signals.connect(EventName.TakeDamage, self, "onTakeDamage")
	
	
func _on_CalibrateQuaternion_pressed():
	Arcane.pad.calibrateQuaternion()


func _on_CalibratePointerTopLeft_pressed():
	Arcane.pad.calibratePointer(true)


func _on_CalibratePointerBottomRight_pressed():
	Arcane.pad.calibratePointer(false)


func _on_Attack_pressed():
	
	# EMIT CUSTOM EVENT TO THE VIEWS
	Arcane.msg.emitToViews(Events.AttackEvent.new())


func onTakeDamage(e, from):
	Arcane.pad.vibrate(200)
	Arcane.utils.writeToScreen("Taken " + str(e.damage) + " damage! Ouch!")
php
extends Node


var pad:ArcanePad
var padQuaternion = Quat()
onready var meshChild = get_child(0)
onready var pointer = get_child(2)

func initialize(_pad:ArcanePad) -> void:
	
	pad = _pad
	prints("Pad user", pad.user.name, "initialized")
	
	
	# LISTEN WHEN THIS GAMEPAD CONNECTS
	pad.connect(AEventName.IframePadConnect, self, 'onIframePadConnect')
	
	# LISTEN WHEN THIS GAMEPAD DISCONNECTS
	pad.connect(AEventName.IframePadDisconnect, self, 'onIframePadDisconnect')
	
	
	# ASK FOR DEVICE ROTATION AND POINTER
	pad.startGetQuaternion()
	pad.startGetPointer()
	
	# LISTEN FOR DEVICE ROTATION AND POINTER
	pad.connect(AEventName.GetQuaternion, self, 'onGetQuaternion')
	pad.connect(AEventName.GetPointer, self, 'onGetPointer')
	
	
	# LISTEN CUSTOM EVENT FROM PAD
	pad.addSignal(EventName.Attack)
	pad.connect(EventName.Attack, self, 'Attack')
	
	
func _process(_delta):
	meshChild.transform.basis = Basis(padQuaternion)


func _exit_tree():
	pad.queue_free()
	
	
func onIframePadConnect(_e):
	pass
	
func onIframePadDisconnect(_e):
	pass
	
	
func onGetQuaternion(q):
	if(q.w == null || q.x == null || q.y == null || q.z == null): return
	
	padQuaternion.x = -q.x
	padQuaternion.y = -q.y
	padQuaternion.z = q.z
	padQuaternion.w = q.w
	
	
func onGetPointer(e):
	if(e.x == null || e.y == null): return
	
	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 Attack(e):
	Arcane.utils.writeToScreen(pad.user.name + " Attacked")
	print(pad.user.name + " Attacked!")
	print(e)
	
	# EMIT CUSTOM EVENT TO THE PAD
	pad.emit(Events.TakeDamageEvent.new(3))
extends Node


var pad:ArcanePad
var padQuaternion = Quat()
onready var meshChild = get_child(0)
onready var pointer = get_child(2)

func initialize(_pad:ArcanePad) -> void:
	
	pad = _pad
	prints("Pad user", pad.user.name, "initialized")
	
	
	# LISTEN WHEN THIS GAMEPAD CONNECTS
	pad.connect(AEventName.IframePadConnect, self, 'onIframePadConnect')
	
	# LISTEN WHEN THIS GAMEPAD DISCONNECTS
	pad.connect(AEventName.IframePadDisconnect, self, 'onIframePadDisconnect')
	
	
	# ASK FOR DEVICE ROTATION AND POINTER
	pad.startGetQuaternion()
	pad.startGetPointer()
	
	# LISTEN FOR DEVICE ROTATION AND POINTER
	pad.connect(AEventName.GetQuaternion, self, 'onGetQuaternion')
	pad.connect(AEventName.GetPointer, self, 'onGetPointer')
	
	
	# LISTEN CUSTOM EVENT FROM PAD
	pad.addSignal(EventName.Attack)
	pad.connect(EventName.Attack, self, 'Attack')
	
	
func _process(_delta):
	meshChild.transform.basis = Basis(padQuaternion)


func _exit_tree():
	pad.queue_free()
	
	
func onIframePadConnect(_e):
	pass
	
func onIframePadDisconnect(_e):
	pass
	
	
func onGetQuaternion(q):
	if(q.w == null || q.x == null || q.y == null || q.z == null): return
	
	padQuaternion.x = -q.x
	padQuaternion.y = -q.y
	padQuaternion.z = q.z
	padQuaternion.w = q.w
	
	
func onGetPointer(e):
	if(e.x == null || e.y == null): return
	
	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 Attack(e):
	Arcane.utils.writeToScreen(pad.user.name + " Attacked")
	print(pad.user.name + " Attacked!")
	print(e)
	
	# EMIT CUSTOM EVENT TO THE PAD
	pad.emit(Events.TakeDamageEvent.new(3))
php
# YOUR CUSTOM EVENTS
class_name Events


class AttackEvent extends AEvents.ArcaneBaseEvent:
	func _init().(EventName.Attack):
		pass


class TakeDamageEvent extends AEvents.ArcaneBaseEvent:
	
	var damage: int
	
	func _init(_damage: int).(EventName.TakeDamage):
		damage = _damage
# YOUR CUSTOM EVENTS
class_name Events


class AttackEvent extends AEvents.ArcaneBaseEvent:
	func _init().(EventName.Attack):
		pass


class TakeDamageEvent extends AEvents.ArcaneBaseEvent:
	
	var damage: int
	
	func _init(_damage: int).(EventName.TakeDamage):
		damage = _damage
php
# YOUR CUSTOM EVENT NAMES
class_name EventName


const Attack = "Attack"
const TakeDamage = "TakeDamage"
# YOUR CUSTOM EVENT NAMES
class_name EventName


const Attack = "Attack"
const TakeDamage = "TakeDamage"

Breaking changes to event names

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.