Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/main-godot4.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ jobs:
matrix:
include:
- os: ubuntu-latest
godot-version: '4.3'
godot-status: 'stable'
gdunit-version: 'v5.1'
- os: ubuntu-latest
godot-version: '4.4'
godot-version: '4.4.1'
godot-status: 'stable'
gdunit-version: 'v5.1'
- os: ubuntu-latest
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ exports/


[Ee]xport/

steam_appid.txt

# Ignore temp files
~*
6 changes: 3 additions & 3 deletions Scenes/LoggedIn.gd
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ func _ready():
var _error = PlayFabManager.client.connect("api_error", func(error: ApiErrorWrapper):
print_debug(error.errorMessage)
)
update()


# Called when the node enters the scene tree for the first time.
func update():
if login_result != null:
if PlayFabManager.client.is_logged_in():
login_result = PlayFabManager.client.get_login_result()
$VBoxContainer/LoginResultContainer/AccountPlayerId/Edit.text = login_result.PlayFabId
$VBoxContainer/LoginResultContainer/TitlePlayerId/Edit.text = login_result.InfoResultPayload.AccountInfo.TitleInfo.TitlePlayerAccount.Id
$VBoxContainer/LoginResultContainer/TitlePlayerName/Edit.text = login_result.InfoResultPayload.AccountInfo.TitleInfo.DisplayName
Expand Down
1 change: 0 additions & 1 deletion Scenes/Login.gd
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ func _on_logged_in(login_result: LoginResult):
_hide_progess()

$LoggedIn.login_result = login_result
$LoggedIn.update()
$LoggedIn.show()

func _on_api_error(api_error_wrapper: ApiErrorWrapper):
Expand Down
21 changes: 20 additions & 1 deletion Scenes/Main.gd
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
extends HBoxContainer
extends Control

func _ready():
# determine whether godot-steam addon is enabled
if ClassDB.can_instantiate("Steam"):
%LoginWithSteam.disabled = true
# PlayFabSteam.connect("logged_in", _on_logged_in) # Enable, if using GodotSteam/PlayFabSteam (4.4.1+)
else:
print_debug("Steam is NOT installed")
%LoginWithSteam.visible = false
%LoginWithSteam.disconnect("pressed", _on_login_with_steam_pressed)
%StatusLabel.text = "Steam wasn't detected."

func _on_Register_pressed():
SceneManager.goto_scene("res://Scenes/Register.tscn")

func _on_Login_pressed():
SceneManager.goto_scene("res://Scenes/Login.tscn")

func _on_login_with_steam_pressed():
SceneManager.goto_scene("res://Scenes/LoggedIn.tscn")

func _on_logged_in(playfab_id: String, steam_persona_name: String) -> void:
print_debug("PlayFab ID: %s\nSteam Persona: %s" % [ playfab_id, steam_persona_name ])
%LoginWithSteam.disabled = false
%StatusLabel.text = "Steam initialized!\nPlayFab ID %s\nSteam Persona: \"%s\"" % [ playfab_id, steam_persona_name ]
34 changes: 30 additions & 4 deletions Scenes/Main.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,25 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")

[node name="StatusLabel" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -230.0
offset_top = 199.0
offset_right = 231.0
offset_bottom = 271.0
grow_horizontal = 2
text = "Trying login with Steam..."
horizontal_alignment = 1

[node name="Main" type="HBoxContainer" parent="."]
layout_mode = 0
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
Expand All @@ -20,19 +36,29 @@ offset_left = -232.5
offset_top = -46.0
offset_right = 232.5
offset_bottom = 46.0
script = ExtResource("1")
grow_horizontal = 2
grow_vertical = 2

[node name="Register" type="Button" parent="Main"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
text = "Register"

[node name="Login" type="Button" parent="Main"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
text = "Login"

[connection signal="pressed" from="Main/Register" to="Main" method="_on_Register_pressed"]
[connection signal="pressed" from="Main/Login" to="Main" method="_on_Login_pressed"]
[node name="LoginWithSteam" type="Button" parent="Main"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Steam Login"

[connection signal="pressed" from="Main/Register" to="." method="_on_Register_pressed"]
[connection signal="pressed" from="Main/Login" to="." method="_on_Login_pressed"]
[connection signal="pressed" from="Main/LoginWithSteam" to="." method="_on_login_with_steam_pressed"]
11 changes: 11 additions & 0 deletions addons/godot-playfab/PlayFab.gd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ signal registered(RegisterPlayFabUserResult)
## Emitted when the player logged in successfully
## @param login_result: LoginResult
signal logged_in(login_result)
var _login_result: LoginResult

enum AUTH_TYPE {SESSION_TICKET, ENTITY_TOKEN}

Expand All @@ -28,6 +29,7 @@ func _ready():

func _on_logged_in(login_result: LoginResult):
# Setting SessionTicket for subsequent client requests
_login_result = login_result
PlayFabManager.client_config.session_ticket = login_result.SessionTicket
PlayFabManager.client_config.master_player_account_id = login_result.PlayFabId
PlayFabManager.client_config.entity_token = login_result.EntityToken
Expand Down Expand Up @@ -192,3 +194,12 @@ func _add_auth_headers(additional_headers: Dictionary, auth_type) -> bool:
push_error("auth_type \"" + auth_type + "\" is invalid")

return true

func is_logged_in():
return _login_result != null

func get_login_result():
if (!is_logged_in()):
push_error("No use logged in with PlayFab")

return _login_result
3 changes: 3 additions & 0 deletions addons/godot-playfab/PlayFabEditor.gd
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func _init():

func _enter_tree():
add_autoload_singleton("PlayFabManager", "res://addons/godot-playfab/PlayFabManager.gd")
if Engine.get_version_info().hex >= 0x040401: # load only if Godot 4.4.1
add_autoload_singleton("PlayFabSteam", "res://addons/godot-playfab/PlayFabSteam.gd")

main_panel_instance = MainPanel.instantiate()
# Add the main panel to the editor's main viewport.
Expand All @@ -33,6 +35,7 @@ func _enter_tree():


func _exit_tree():
remove_autoload_singleton("PlayFabSteam")
remove_autoload_singleton("PlayFabManager")

if main_panel_instance:
Expand Down
125 changes: 125 additions & 0 deletions addons/godot-playfab/PlayFabSteam.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
extends Node

const PRICE_REFRESH_INTERVAL: int = 3600 # Specifies, after how many seconds prices will be refreshed.

var steam_auth_ticket : Dictionary
var steam_name: String = ""
var playfab_id: String = ""

var inventory_items: Array = []
var inventory_item_definitions: Array = []

var _refresh_prices_timer: Timer = Timer.new()

signal logged_in(playfab_id, steam_persona_name)
signal inventory_updated(items: Array)

func _init() -> void:
self.connect("logged_in", _update_player_inventory, CONNECT_ONE_SHOT)
if not ClassDB.can_instantiate("Steam"):
print_debug("Steam class is not available!")
return

if not Steam.isSteamRunning():
print_debug("Steam client is not running!")
return

PlayFabManager.client.logged_in.connect(_on_logged_in)
PlayFabManager.client.api_error.connect(_on_api_error)
PlayFabManager.client.server_error.connect(_on_server_error)
Steam.get_auth_session_ticket_response.connect(_on_get_auth_sesssion_ticket)
Steam.get_ticket_for_web_api.connect(_on_get_auth_ticket_for_web_api_response)

var result : Dictionary = Steam.steamInitEx(false) # Set to true if you want some local user's data
if result.status > 0:
print("Failure to initialize Steam with status %s" % result.status)
else:
create_auth_session_ticket()
#create_auth_ticket_for_web_api() #Use this line instead if you need Steam Auth Ticket for Web Api

func _enter_tree() -> void:
add_child(_refresh_prices_timer)
_request_prices()

func _process(_delta: float) -> void:
Steam.run_callbacks()

func _exit_tree() -> void:
if steam_auth_ticket.size() > 0:
cancel_auth_ticket()

func _on_logged_in(login_result: LoginResult) -> void:
print("PlayFab Login successful: %s" % login_result)
logged_in.emit(login_result.PlayFabId, Steam.getPersonaName())

func _on_api_error(error_wrapper: ApiErrorWrapper) -> void:
print("PlayFab API Error: %s" % error_wrapper.errorMessage)

func _on_server_error(error_wrapper: ApiErrorWrapper) -> void:
print("PlayFab Server Error: %s" % error_wrapper.errorMessage)

func login(ticket: String, is_auth_ticket_for_api: bool) -> void:
var combined_info_request_params = GetPlayerCombinedInfoRequestParams.new()
combined_info_request_params.show_all()
var player_profile_view_constraints = PlayerProfileViewConstraints.new()
combined_info_request_params.ProfileConstraints = player_profile_view_constraints
PlayFabManager.client.login_with_steam(ticket, is_auth_ticket_for_api, true, combined_info_request_params)

func cancel_auth_ticket() -> void:
Steam.cancelAuthTicket(steam_auth_ticket.id)

func create_auth_session_ticket() -> void:
steam_auth_ticket = Steam.getAuthSessionTicket()

func create_auth_ticket_for_web_api() -> void:
Steam.getAuthTicketForWebApi("AzurePlayFab")

func convert_auth_ticket() -> String:
var ticket: String = ""
for number in steam_auth_ticket.buffer:
ticket += "%02X" % number
return ticket

func _on_get_auth_sesssion_ticket(auth_ticket_id: int, result: int) -> void:
print("Auth Session Ticket (%s) return with result %s" % [auth_ticket_id, result])
if result == 1:
login(convert_auth_ticket(), false)

func _on_get_auth_ticket_for_web_api_response(auth_ticket: int, result: int, ticket_size: int, ticket_buffer: Array) -> void:
print("Auth Ticket for Web API (%s) return with the result %s" % [auth_ticket, result])
steam_auth_ticket.id = auth_ticket
steam_auth_ticket.buffer = ticket_buffer
steam_auth_ticket.size = ticket_size
if result == 1:
login(convert_auth_ticket(), true)


func _request_prices():
print("Requesting Steam prices...")
Steam.requestPrices()
print("Prices requested.")
_refresh_prices_timer.start(PRICE_REFRESH_INTERVAL)
_refresh_prices_timer.connect("timeout", _request_prices, CONNECT_ONE_SHOT)

func _on_inventory_definition_update(definitions: Array) -> void:
inventory_item_definitions = definitions
print("Loaded %s definitions." % inventory_item_definitions.size())

func _update_player_inventory(_playFabId, _steam_persona_name) -> void:
Steam.inventory_definition_update.connect(_on_inventory_definition_update)
Steam.loadItemDefinitions()
Steam.getAllItems()

Steam.inventory_result_ready.connect(_on_inventory_result_ready)

func _on_inventory_result_ready(_result: int, _inventory_handle: int):
inventory_items = Steam.getResultItems(Steam.inventory_handle)
inventory_updated.emit(inventory_items)
print("Items in inventory: %s" % inventory_items.size())


func _grant_test_items() -> int:
var items: PackedInt64Array = [1, 2]
var quantities: PackedInt32Array = [2, 3]
var handler = Steam.generateItems(items, quantities)
return handler
1 change: 1 addition & 0 deletions addons/godot-playfab/PlayFabSteam.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bwjpctbep1gob
44 changes: 44 additions & 0 deletions addons/godot-playfab/docs/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Feature Overview

`godot-playfab` is a Godot addon that provides easy access to the [PlayFab](https://playfab.com/)
Game Backend-as-a-Service (BaaS) platform for Godot Engine games/applications.





## Authentication
`godot-playfab` supports multiple authentication methods to log in users:

- **Anonymous Login**: Allows users to log in without any credentials.
- **Custom ID Login**: Users can log in using a custom identifier, with optional password.
- **Steam Login**: Integrates with Steam for user authentication.

It also takes care of some behind the scenes fundamentals:
- **Session Management**: Manages session tokens and handles re-authentication as needed.
- **Signal-Based Callbacks**: Signals for handling login success and failure.
- **Example Implementations**: The example project includes implementations for each login method.

## Events & Analytics
With the `PlayFabEvents` API, you can send telemetry and PlayStream events.
These events can be used in PlayFab's Data & Analytics features.

Events can be sent in two ways:
- **Batched Send**: To save cost/requests, Events are batched and sent when a configured threshold is met or manually flushed.
- **Direct Write**: Events are sent immediately, suitable for urgent events, mainly used for PlayStream events.

## Steam integration
godot-playfab has a built-in integration with [GodotSteam](https://godotsteam.com/).

> ⚠️ Steam integration needs to be enabled in the example project, as it requires you to set up a Steam App ID and have the Steam client running.
>
> You can easily enable Steam login in your game by following the steps in the [Login with Steam](addons/godot-playfab/docs/Logins/login-steam.md) documentation.


## Example Project
If you clone the full repository, you get a full example project where you can find out how different features
are implemented.

You can absolutely use the example project as a starting point for your own game! In fact, I encourage you to do so!

Start with the default scene in `Scenes/Main.tscn` to see how to use `godot-playfab` in your game.
22 changes: 22 additions & 0 deletions addons/godotsteam/godotsteam.gdextension
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[configuration]
entry_symbol = "godotsteam_init"
compatibility_minimum = "4.4"

[libraries]
macos.debug = "res://addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework"
macos.release = "res://addons/godotsteam/osx/libgodotsteam.macos.template_release.framework"
windows.debug.x86_64 = "res://addons/godotsteam/win64/libgodotsteam.windows.template_debug.x86_64.dll"
windows.debug.x86_32 = "res://addons/godotsteam/win32/libgodotsteam.windows.template_debug.x86_32.dll"
windows.release.x86_64 = "res://addons/godotsteam/win64/libgodotsteam.windows.template_release.x86_64.dll"
windows.release.x86_32 = "res://addons/godotsteam/win32/libgodotsteam.windows.template_release.x86_32.dll"
linux.debug.x86_64 = "res://addons/godotsteam/linux64/libgodotsteam.linux.template_debug.x86_64.so"
linux.debug.x86_32 = "res://addons/godotsteam/linux32/libgodotsteam.linux.template_debug.x86_32.so"
linux.release.x86_64 = "res://addons/godotsteam/linux64/libgodotsteam.linux.template_release.x86_64.so"
linux.release.x86_32 = "res://addons/godotsteam/linux32/libgodotsteam.linux.template_release.x86_32.so"

[dependencies]
macos.universal = { "res://addons/godotsteam/osx/libsteam_api.dylib": "" }
windows.x86_64 = { "res://addons/godotsteam/win64/steam_api64.dll": "" }
windows.x86_32 = { "res://addons/godotsteam/win32/steam_api.dll": "" }
linux.x86_64 = { "res://addons/godotsteam/linux64/libsteam_api.so": "" }
linux.x86_32 = { "res://addons/godotsteam/linux32/libsteam_api.so": "" }
1 change: 1 addition & 0 deletions addons/godotsteam/godotsteam.gdextension.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bg8pn4laey8st
Loading