Skip to content

Commit

Permalink
Add player centered scores and top scores methods
Browse files Browse the repository at this point in the history
Signed-off-by: Jacob Ibáñez Sánchez <jacobibanez@jacobibanez.com>
  • Loading branch information
Iakobs committed Mar 1, 2024
1 parent 7b3937c commit ce67aed
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 4 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
## v1.5.0
### Order of autoloads
The autoloads where causing errors on first launch of the project, due to the load order and dependencies between them. The load order has now been fixed to avoid this errors.
The autoloads where causing errors on first launch of the project, due to the load order and dependencies between them. The load order has now been fixed to avoid this errors. Also, the plugin is now disabled by default in the demo project. Look at the [demo project documentation](https://github.com/Iakobs/godot-play-game-services/tree/main/plugin/demo) for further info.

### Load player centered scores
The [loadPlayerCenteredScores](https://developers.google.com/android/reference/com/google/android/gms/games/LeaderboardsClient#loadPlayerCenteredScores(java.lang.String,%20int,%20int,%20int,%20boolean)) method from Google's API has been added to the plugin. This method returns a list of scores, centered in the signed in player, for a given leaderboard. There's no example in the demo app yet, but it's usage is documented in the code itself.

### Load top scores
The [loadTopScores](https://developers.google.com/android/reference/com/google/android/gms/games/LeaderboardsClient#loadTopScores(java.lang.String,%20int,%20int,%20int,%20boolean)) method from Google's API has been added to the plugin. This method returns the top scores of a leaderboard for a given leaderboard. There's no example in the demo app yet, but it's usage is documented in the code itself.

## v1.4.0
### Increase max length of Game ID
Expand Down
1 change: 0 additions & 1 deletion plugin/demo/scenes/leaderboards/Leaderboards.gd
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ func _ready() -> void:
show_leaderboards_button.pressed.connect(func():
LeaderboardsClient.show_all_leaderboards()
)

106 changes: 104 additions & 2 deletions plugin/export_scripts_template/autoloads/leaderboards_client.gd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ signal score_submitted(is_submitted: bool, leaderboard_id: String)
## retrieving it.
signal score_loaded(leaderboard_id: String, score: Score)

## Signal emitted after calling the [method load_player_centered_scores] method.[br]
## [br]
## [param leaderboard_id]: The leaderboard id.[br]
## [param leaderboard_scores]: The scores for the leaderboard, centered in the player.
signal player_centered_scores_loaded(leaderboard_id: String, leaderboard_scores: LeaderboardScores)

## Signal emitted after calling the [method load_top_scores] method.[br]
## [br]
## [param leaderboard_id]: The leaderboard id.[br]
## [param leaderboard_scores]: The top scores for the leaderboard.
signal top_scores_loaded(leaderboard_id: String, leaderboard_scores: LeaderboardScores)

## Signal emitted after calling the [method load_all_leaderboards] method.[br]
## [br]
## [param leaderboards]: An array containing all the leaderboards for the game.
Expand Down Expand Up @@ -71,6 +83,14 @@ func _connect_signals() -> void:
var safe_dictionary := GodotPlayGameServices.json_marshaller.safe_parse_dictionary(json_data)
leaderboard_loaded.emit(Leaderboard.new(safe_dictionary))
)
GodotPlayGameServices.android_plugin.playerCenteredScoresLoaded.connect(func(leaderboard_id: String, json_data: String):
var safe_dictionary := GodotPlayGameServices.json_marshaller.safe_parse_dictionary(json_data)
player_centered_scores_loaded.emit(leaderboard_id, LeaderboardScores.new(safe_dictionary))
)
GodotPlayGameServices.android_plugin.topScoresLoaded.connect(func(leaderboard_id: String, json_data: String):
var safe_dictionary := GodotPlayGameServices.json_marshaller.safe_parse_dictionary(json_data)
top_scores_loaded.emit(leaderboard_id, LeaderboardScores.new(safe_dictionary))
)

## Use this method to show all leaderbords for this game in a new screen.
func show_all_leaderboards() -> void:
Expand Down Expand Up @@ -160,6 +180,65 @@ func load_leaderboard(leaderboard_id: String, force_reload: bool) -> void:
if GodotPlayGameServices.android_plugin:
GodotPlayGameServices.android_plugin.loadLeaderboard(leaderboard_id, force_reload)

## Use this method and subscribe to the emitted signal to receive an object containing
## the selected leaderboard and an array of scores for that leaderboard, centered in the
## currently signed in player.[br]
## [br]
## The method emits the [signal player_centered_scores_loaded] signal.[br]
## [br]
## [param leaderboard_id]: The leaderboard id.[br]
## [param time_span]: The time span for the leaderboard. See the [enum TimeSpan] enum.[br]
## [param collection]: The collection type for the leaderboard. See the [enum Collection] enum.[br]
## [param max_results]: The maximum number of scores to fetch per page. Must be between 1 and 25.
## [param force_reload]: If true, this call will clear any locally cached
## data and attempt to fetch the latest data from the server. Send it set to [code]true[/code]
## the first time, and [code]false[/code] in subsequent calls, or when you want
## to clear the cache.
func load_player_centered_scores(
leaderboard_id: String,
time_span: TimeSpan,
collection: Collection,
max_results: int,
force_reload: bool
) -> void:
if GodotPlayGameServices.android_plugin:
GodotPlayGameServices.android_plugin.loadPlayerCenteredScores(
leaderboard_id,
time_span,
collection,
max_results,
force_reload
)

## Use this method and subscribe to the emitted signal to receive an object containing
## the selected leaderboard and an array of the top scores for that leaderboard.[br]
## [br]
## The method emits the [signal top_scores_loaded] signal.[br]
## [br]
## [param leaderboard_id]: The leaderboard id.[br]
## [param time_span]: The time span for the leaderboard. See the [enum TimeSpan] enum.[br]
## [param collection]: The collection type for the leaderboard. See the [enum Collection] enum.[br]
## [param max_results]: The maximum number of scores to fetch per page. Must be between 1 and 25.
## [param force_reload]: If true, this call will clear any locally cached
## data and attempt to fetch the latest data from the server. Send it set to [code]true[/code]
## the first time, and [code]false[/code] in subsequent calls, or when you want
## to clear the cache.
func load_top_scores(
leaderboard_id: String,
time_span: TimeSpan,
collection: Collection,
max_results: int,
force_reload: bool
) -> void:
if GodotPlayGameServices.android_plugin:
GodotPlayGameServices.android_plugin.loadTopScores(
leaderboard_id,
time_span,
collection,
max_results,
force_reload
)

## The score of a player for a specific leaderboard.
class Score:
var display_rank: String ## Formatted string for the rank of the player.
Expand All @@ -171,11 +250,11 @@ class Score:
var score_holder_hi_res_image_uri: String ## Hi-res image of the player.
var score_holder_icon_image_uri: String ## Icon image of the player.
var score_tag: String ## Optional score tag associated with this score.
var timestamp_millis: int ## Timestamp (in milliseconds from epoch) at which this score was achieved.
var timestamp_millis: int ## Timestamp (in milliseconds from epoch) at which this score was achieved.

## Constructor that creates a Score from a [Dictionary] containing the properties.
func _init(dictionary: Dictionary) -> void:
if dictionary.has("displayRank"): display_rank = dictionary.displayRank
if dictionary.has("displayRank"): display_rank = dictionary.displayRank
if dictionary.has("displayScore"): display_score = dictionary.displayScore
if dictionary.has("rank"): rank = dictionary.rank
if dictionary.has("rawScore"): raw_score = dictionary.rawScore
Expand Down Expand Up @@ -275,3 +354,26 @@ class LeaderboardVariant:
result.append("time_span: %s" % str(time_span))

return ", ".join(result)

## A class holding a list of scores for a leaderboard.
class LeaderboardScores:
var leaderboard: Leaderboard ## The leaderboard.
var scores: Array[Score] ## The list of scores for this leaderboard.

## Constructor that creates a LeaderboardScores from a [Dictionary] containting
## the properties.
func _init(dictionary: Dictionary) -> void:
if dictionary.has("leaderboard"): leaderboard = Leaderboard.new(dictionary.leaderboard)
if dictionary.has("scores"):
for score: Dictionary in dictionary.scores:
scores.append(Score.new(score))

func _to_string() -> String:
var result := PackedStringArray()

result.append("leaderboard: %s" % leaderboard)

for score: Score in scores:
result.append("{%s}" % str(score))

return ", ".join(result)
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,61 @@ class GodotAndroidPlugin(godot: Godot) : GodotPlugin(godot) {
fun loadLeaderboard(leaderboardId: String, forceReload: Boolean) =
leaderboardsProxy.loadLeaderboard(leaderboardId, forceReload)

/**
* Call this method and subscribe to the emitted signal to receive the selected leaderboard and an
* array of scores for that leaderboard, centered in the currently signed in player. The JSON received
* from the [com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.playerCenteredScoresLoaded]
* signal, contains a representation of the [com.google.android.gms.games.LeaderboardsClient.LeaderboardScores](https://developers.google.com/android/reference/com/google/android/gms/games/LeaderboardsClient.LeaderboardScores) class.
*
* @param leaderboardId The leaderboard id.
* @param timeSpan The time span for the leaderboard refresh. It can be any of the [com.jacobibanez.plugin.android.godotplaygameservices.leaderboards.TimeSpan] values.
* @param collection The collection type for the leaderboard. It can be any of the [[com.jacobibanez.plugin.android.godotplaygameservices.leaderboards.Collection]] values.
* @param maxResults The maximum number of scores to fetch per page. Must be between 1 and 25.
* @param forceReload If true, this call will clear any locally cached data and attempt to fetch
* the latest data from the server.
*/
@UsedByGodot
fun loadPlayerCenteredScores(
leaderboardId: String,
timeSpan: Int,
collection: Int,
maxResults: Int,
forceReload: Boolean
) = leaderboardsProxy.loadPlayerCenteredScores(
leaderboardId,
timeSpan,
collection,
maxResults,
forceReload
)

/**
* Call this method and subscribe to the emitted signal to receive the selected leaderboard and an
* array of the top scores for that leaderboard. The JSON received from the [com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.playerCenteredScoresLoaded]
* signal, contains a representation of the [com.google.android.gms.games.LeaderboardsClient.LeaderboardScores](https://developers.google.com/android/reference/com/google/android/gms/games/LeaderboardsClient.LeaderboardScores) class.
*
* @param leaderboardId The leaderboard id.
* @param timeSpan The time span for the leaderboard refresh. It can be any of the [com.jacobibanez.plugin.android.godotplaygameservices.leaderboards.TimeSpan] values.
* @param collection The collection type for the leaderboard. It can be any of the [[com.jacobibanez.plugin.android.godotplaygameservices.leaderboards.Collection]] values.
* @param maxResults The maximum number of scores to fetch per page. Must be between 1 and 25.
* @param forceReload If true, this call will clear any locally cached data and attempt to fetch
* the latest data from the server.
*/
@UsedByGodot
fun loadTopScores(
leaderboardId: String,
timeSpan: Int,
collection: Int,
maxResults: Int,
forceReload: Boolean
) = leaderboardsProxy.loadTopScores(
leaderboardId,
timeSpan,
collection,
maxResults,
forceReload
)

/**
* Call this method and subscribe to the emitted signal to receive the list of friends for the
* currently signed in player in JSON format. The JSON received from the [com.jacobibanez.plugin.android.godotplaygameservices.signals.PlayerSignals.friendsLoaded]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jacobibanez.plugin.android.godotplaygameservices.leaderboards

import com.google.android.gms.games.LeaderboardsClient
import com.google.android.gms.games.leaderboard.Leaderboard
import com.google.android.gms.games.leaderboard.LeaderboardScore
import com.google.android.gms.games.leaderboard.LeaderboardVariant
Expand Down Expand Up @@ -73,3 +74,10 @@ fun fromLeaderboardVariant(variants: ArrayList<LeaderboardVariant>): List<Dictio
put("hasPlayerInfo", variant.hasPlayerInfo())
}
}.toList()

/** @suppress */
fun fromLeaderboardScores(godot: Godot, scores: LeaderboardsClient.LeaderboardScores) = Dictionary().apply {
put("leaderboard", fromLeaderboard(godot, scores.leaderboard!!))
val scoresMapped = scores.scores.map { fromLeaderboardScore(godot, it) }.toList()
put("scores", scoresMapped)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import com.google.gson.Gson
import com.jacobibanez.plugin.android.godotplaygameservices.BuildConfig
import com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.allLeaderboardsLoaded
import com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.leaderboardLoaded
import com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.playerCenteredScoresLoaded
import com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.scoreLoaded
import com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.scoreSubmitted
import com.jacobibanez.plugin.android.godotplaygameservices.signals.LeaderboardSignals.topScoresLoaded
import org.godotengine.godot.Dictionary
import org.godotengine.godot.Godot
import org.godotengine.godot.plugin.GodotPlugin.emitSignal
Expand Down Expand Up @@ -220,4 +222,96 @@ class LeaderboardsProxy(
}
}
}

fun loadPlayerCenteredScores(
leaderboardId: String,
timeSpan: Int,
collection: Int,
maxResults: Int,
forceReload: Boolean
) {
Log.d(
tag, "Loading player centered scores for leaderboard $leaderboardId, " +
"span ${TimeSpan.fromSpan(timeSpan)?.name}, collection " +
"${Collection.fromType(collection)?.name} and max results $maxResults"
)
leaderboardsClient.loadPlayerCenteredScores(leaderboardId, timeSpan, collection, maxResults, forceReload)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(
tag,
"Player centered scores loaded successfully. Data is stale? ${task.result.isStale}"
)
val leaderboardScores = task.result.get()!!
val result: Dictionary = fromLeaderboardScores(godot, leaderboardScores)
leaderboardScores.release()
emitSignal(
godot,
BuildConfig.GODOT_PLUGIN_NAME,
playerCenteredScoresLoaded,
leaderboardId,
Gson().toJson(result)
)
} else {
Log.e(
tag,
"Failed to load player centered scores. Cause: ${task.exception}",
task.exception
)
emitSignal(
godot,
BuildConfig.GODOT_PLUGIN_NAME,
playerCenteredScoresLoaded,
leaderboardId,
Gson().toJson(null)
)
}
}
}

fun loadTopScores(
leaderboardId: String,
timeSpan: Int,
collection: Int,
maxResults: Int,
forceReload: Boolean
) {
Log.d(
tag, "Loading top scores for leaderboard $leaderboardId, " +
"span ${TimeSpan.fromSpan(timeSpan)?.name}, collection " +
"${Collection.fromType(collection)?.name} and max results $maxResults"
)
leaderboardsClient.loadTopScores(leaderboardId, timeSpan, collection, maxResults, forceReload)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(
tag,
"Top scores loaded successfully. Data is stale? ${task.result.isStale}"
)
val leaderboardScores = task.result.get()!!
val result: Dictionary = fromLeaderboardScores(godot, leaderboardScores)
leaderboardScores.release()
emitSignal(
godot,
BuildConfig.GODOT_PLUGIN_NAME,
topScoresLoaded,
leaderboardId,
Gson().toJson(result)
)
} else {
Log.e(
tag,
"Failed to load top scores. Cause: ${task.exception}",
task.exception
)
emitSignal(
godot,
BuildConfig.GODOT_PLUGIN_NAME,
topScoresLoaded,
leaderboardId,
Gson().toJson(null)
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ fun getSignals(): MutableSet<SignalInfo> = mutableSetOf(

LeaderboardSignals.scoreSubmitted,
LeaderboardSignals.scoreLoaded,
LeaderboardSignals.playerCenteredScoresLoaded,
LeaderboardSignals.topScoresLoaded,
LeaderboardSignals.allLeaderboardsLoaded,
LeaderboardSignals.leaderboardLoaded,

Expand Down Expand Up @@ -106,6 +108,20 @@ object LeaderboardSignals {
*/
val scoreLoaded = SignalInfo("scoreLoaded", String::class.java, String::class.java)

/**
* This signal is emitted when calling the [com.jacobibanez.plugin.android.godotplaygameservices.GodotAndroidPlugin.loadPlayerCenteredScores] method.
*
* @return The leaderboard id and a JSON with a [com.google.android.gms.games.LeaderboardsClient.LeaderboardScores](https://developers.google.com/android/reference/com/google/android/gms/games/LeaderboardsClient.LeaderboardScores).
*/
val playerCenteredScoresLoaded = SignalInfo("playerCenteredScoresLoaded", String::class.java, String::class.java)

/**
* This signal is emitted when calling the [com.jacobibanez.plugin.android.godotplaygameservices.GodotAndroidPlugin.loadTopScores] method.
*
* @return The leaderboard id and a JSON with a [com.google.android.gms.games.LeaderboardsClient.LeaderboardScores](https://developers.google.com/android/reference/com/google/android/gms/games/LeaderboardsClient.LeaderboardScores).
*/
val topScoresLoaded = SignalInfo("topScoresLoaded", String::class.java, String::class.java)

/**
* This signal is emitted when calling the [com.jacobibanez.plugin.android.godotplaygameservices.GodotAndroidPlugin.loadAllLeaderboards] method.
*
Expand Down

0 comments on commit ce67aed

Please sign in to comment.