diff --git a/Nakama+Hiro/.cookie b/Nakama+Hiro/.cookie deleted file mode 100644 index 16d48c7..0000000 Binary files a/Nakama+Hiro/.cookie and /dev/null differ diff --git a/Nakama+Hiro/.editorconfig b/Nakama+Hiro/.editorconfig new file mode 100644 index 0000000..99d8e0a --- /dev/null +++ b/Nakama+Hiro/.editorconfig @@ -0,0 +1,12 @@ +## editorconfig.org + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.go] +indent_style = tab diff --git a/Nakama+Hiro/.gitignore b/Nakama+Hiro/.gitignore index 5ed4b8a..51c700c 100644 --- a/Nakama+Hiro/.gitignore +++ b/Nakama+Hiro/.gitignore @@ -1 +1,246 @@ -local.yml \ No newline at end of file +.cookie +local.yml +lib/*.bin + +# Created by https://gitignore.io/api/linux,macos,windows,go,intellij+all,sublimetext,visualstudiocode +# Edit at https://gitignore.io?templates=linux,macos,windows,go,intellij+all,sublimetext,visualstudiocode + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### SublimeText ### +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json +sftp-config-alt*.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://gitignore.io/api/linux,macos,windows,go,intellij+all,sublimetext,visualstudiocode + diff --git a/Nakama+Hiro/definitions/dev1/base-challenges.json b/Nakama+Hiro/definitions/dev1/base-challenges.json index 353c51b..c1eb298 100644 --- a/Nakama+Hiro/definitions/dev1/base-challenges.json +++ b/Nakama+Hiro/definitions/dev1/base-challenges.json @@ -1,203 +1,203 @@ { - "challenges": { - "speed_runner": { - "reward_tiers": [ - { - "rank_min": 1, - "rank_max": 3, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 300 + "challenges": { + "speed_runner": { + "reward_tiers": [ + { + "rank_min": 1, + "rank_max": 3, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 300 + }, + "gems": { + "min": 15 + } + } + } + } }, - "gems": { - "min": 15 + { + "rank_min": 4, + "rank_max": 10, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 150 + }, + "gems": { + "min": 8 + } + } + } + } } - } + ], + "additional_properties": { + "display_name": "Speed Runner", + "description": "Score the most points by collecting crystals!", + "category": "arcade", + "difficulty": "expert", + "dungeon_id": "crystal_caverns" + }, + "max_num_score": 3, + "start_delay_max_sec": 60, + "ascending": false, + "operator": "best", + "duration": { + "min_sec": 1800, + "max_sec": 3600 + }, + "players": { + "min": 2, + "max": 100 } - } }, - { - "rank_min": 4, - "rank_max": 10, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 150 + "team_conquest": { + "reward_tiers": [ + { + "rank_min": 1, + "rank_max": 1, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 500 + }, + "gems": { + "min": 25 + } + } + } + } }, - "gems": { - "min": 8 - } - } - } - } - } - ], - "additional_properties": { - "display_name": "Speed Runner", - "description": "Score the most points by collecting crystals!", - "category": "arcade", - "difficulty": "expert", - "dungeon_id": "crystal_caverns" - }, - "max_num_score": 3, - "start_delay_max_sec": 60, - "ascending": false, - "operator": "best", - "duration": { - "min_sec": 1800, - "max_sec": 3600 - }, - "players": { - "min": 2, - "max": 100 - } - }, - "team_conquest": { - "reward_tiers": [ - { - "rank_min": 1, - "rank_max": 1, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 500 + { + "rank_min": 2, + "rank_max": 5, + "reward": { + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 250 + }, + "gems": { + "min": 12 + } + } + } + } + } }, - "gems": { - "min": 25 + { + "rank_min": 6, + "rank_max": 20, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 100 + }, + "gems": { + "min": 5 + } + } + } + } } - } + ], + "additional_properties": { + "display_name": "Team Conquest", + "description": "Capture and hold the most strategic points with your team!", + "category": "team_pvp", + "difficulty": "hard", + "map_id": "fortress_battleground" + }, + "max_num_score": 10, + "start_delay_max_sec": 120, + "ascending": false, + "operator": "best", + "duration": { + "min_sec": 3600, + "max_sec": 7200 + }, + "players": { + "min": 4, + "max": 50 } - } }, - { - "rank_min": 2, - "rank_max": 5, - "reward": { - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 250 - }, - "gems": { - "min": 12 - } - } - } - } - } - }, - { - "rank_min": 6, - "rank_max": 20, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 100 + "survival_gauntlet": { + "reward_tiers": [ + { + "rank_min": 1, + "rank_max": 5, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 400 + }, + "gems": { + "min": 20 + } + } + } + } }, - "gems": { - "min": 5 - } - } - } - } - } - ], - "additional_properties": { - "display_name": "Team Conquest", - "description": "Capture and hold the most strategic points with your team!", - "category": "team_pvp", - "difficulty": "hard", - "map_id": "fortress_battleground" - }, - "max_num_score": 10, - "start_delay_max_sec": 120, - "ascending": false, - "operator": "best", - "duration": { - "min_sec": 3600, - "max_sec": 7200 - }, - "players": { - "min": 4, - "max": 50 - } - }, - "survival_gauntlet": { - "reward_tiers": [ - { - "rank_min": 1, - "rank_max": 5, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 400 - }, - "gems": { - "min": 20 - } - } - } - } - }, - { - "rank_min": 6, - "rank_max": 15, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 200 - }, - "gems": { - "min": 10 - } - } - } - } - }, - { - "rank_min": 16, - "rank_max": 50, - "reward": { - "guaranteed": { - "currencies": { - "coins": { - "min": 75 + { + "rank_min": 6, + "rank_max": 15, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 200 + }, + "gems": { + "min": 10 + } + } + } + } }, - "gems": { - "min": 3 + { + "rank_min": 16, + "rank_max": 50, + "reward": { + "guaranteed": { + "currencies": { + "coins": { + "min": 75 + }, + "gems": { + "min": 3 + } + } + } + } } - } + ], + "additional_properties": { + "display_name": "Survival Gauntlet", + "description": "Survive endless waves of enemies for as long as possible!", + "category": "survival", + "difficulty": "medium", + "arena_id": "shadow_arena" + }, + "max_num_score": 3, + "start_delay_max_sec": 30, + "ascending": false, + "operator": "best", + "duration": { + "min_sec": 900, + "max_sec": 1800 + }, + "players": { + "min": 2, + "max": 200 } - } } - ], - "additional_properties": { - "display_name": "Survival Gauntlet", - "description": "Survive endless waves of enemies for as long as possible!", - "category": "survival", - "difficulty": "medium", - "arena_id": "shadow_arena" - }, - "max_num_score": 3, - "start_delay_max_sec": 30, - "ascending": false, - "operator": "best", - "duration": { - "min_sec": 900, - "max_sec": 1800 - }, - "players": { - "min": 2, - "max": 200 - } } - } } diff --git a/Nakama+Hiro/definitions/dev1/base-economy.json b/Nakama+Hiro/definitions/dev1/base-economy.json index 41a8172..f5e5702 100644 --- a/Nakama+Hiro/definitions/dev1/base-economy.json +++ b/Nakama+Hiro/definitions/dev1/base-economy.json @@ -1,8 +1,8 @@ { - "initialize_user": { - "currencies": { - "coins": 999, - "gems": 50 + "initialize_user": { + "currencies": { + "coins": 999, + "gems": 50 + } } - } -} \ No newline at end of file +} diff --git a/Nakama+Hiro/definitions/dev1/base-leaderboards.json b/Nakama+Hiro/definitions/dev1/base-leaderboards.json new file mode 100644 index 0000000..091176b --- /dev/null +++ b/Nakama+Hiro/definitions/dev1/base-leaderboards.json @@ -0,0 +1,20 @@ +{ + "leaderboards": [ + { + "authoritative": false, + "id": "global_leaderboard", + "operator": "best", + "//reset_schedule": "No reset schedule.", + "reset_schedule": "", + "sort_order": "desc" + }, + { + "authoritative": false, + "id": "weekly_leaderboard", + "operator": "best", + "//reset_schedule": "At 00:00 UTC+0 on Monday.", + "reset_schedule": "0 0 * * 1", + "sort_order": "desc" + } + ] +} diff --git a/Nakama+Hiro/lib/.gitignore b/Nakama+Hiro/lib/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Nakama+Hiro/lib/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Nakama+Hiro/lib/.gitkeep b/Nakama+Hiro/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Nakama+Hiro/local.yml b/Nakama+Hiro/local.yml new file mode 100644 index 0000000..f231217 --- /dev/null +++ b/Nakama+Hiro/local.yml @@ -0,0 +1,17 @@ +console: + max_message_size_bytes: 409600 +leaderboard: + blacklist_rank_cache: + - "*" +logger: + level: "DEBUG" +runtime: + env: + - "ENV=dev1" + - "HIRO_LICENSE=" +session: + token_expiry_sec: 86400 # 24 hours + refresh_token_expiry_sec: 604800 # 7 days +socket: + max_message_size_bytes: 4096 # reserved buffer + max_request_size_bytes: 131072 diff --git a/Nakama+Hiro/main.go b/Nakama+Hiro/main.go index 73e29e5..d0842cb 100644 --- a/Nakama+Hiro/main.go +++ b/Nakama+Hiro/main.go @@ -15,16 +15,6 @@ import ( ) func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error { - if err := nk.LeaderboardCreate(ctx, "weekly_leaderboard", false, "desc", "best", - "0 0 * * 1", map[string]interface{}{}, true); err != nil { - // Handle error. - } - - if err := nk.LeaderboardCreate(ctx, "global_leaderboard", false, "desc", "best", - "", map[string]interface{}{}, true); err != nil { - // Handle error. - } - err := createTournament(ctx, logger, nk, "daily-dash", "0 12 * * *", "Daily Dash", "Dash past your opponents for high scores and big rewards!", 86400, 0, 1, false) if err != nil { // Handle error. @@ -71,6 +61,7 @@ func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runti systems, err := hiro.Init(ctx, logger, nk, initializer, binPath, hiroLicense, hiro.WithBaseSystem(fmt.Sprintf("definitions/%s/base-system.json", env), true), + hiro.WithLeaderboardsSystem(fmt.Sprintf("definitions/%s/base-leaderboards.json", env), true), hiro.WithChallengesSystem(fmt.Sprintf("definitions/%s/base-challenges.json", env), true), hiro.WithEconomySystem(fmt.Sprintf("definitions/%s/base-economy.json", env), true)) if err != nil { diff --git a/README.md b/README.md index 7e4b216..b4ef4c3 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,21 @@ Ready-to-use templates for **Nakama** game development. Download, play, and copy **Nakama Templates** -- [Cloud Save](./UnityNakamaCloudSave/) - Save player data in the cloud that syncs between devices. -- [Friends](./UnityNakamaFriends/) - Manage friend lists and requests, block and unblock players. -- [Groups](./UnityNakamaGroups/) - Create in-game communities for players to band together. -- [Leaderboards](./UnityNakamaLeaderboards/) - Weekly and global rankings with real-time record updates. -- [Tournaments](./UnityNakamaTournaments/) - Run tournaments with rules around participation and scoring. +- [Cloud Save](./UnityNakamaCloudSave/) - Save player data in the cloud that syncs between devices. +- [Friends](./UnityNakamaFriends/) - Manage friend lists and requests, block and unblock players. +- [Groups](./UnityNakamaGroups/) - Create in-game communities for players to band together. +- [Leaderboards](./UnityNakamaLeaderboards/) - Weekly and global rankings with real-time record updates. +- [Tournaments](./UnityNakamaTournaments/) - Run tournaments with rules around participation and scoring. + +**Hiro Templates** + +- [Challenges](./UnityHiroChallenges/) - Compete against friends in different game modes. ## Documentation -- 📚 **[Sample Project Guides](https://heroiclabs.com/docs/sample-projects)** - Overviews and guides for each project -- 🎮 **[Nakama Docs](https://heroiclabs.com/docs/nakama/)** - Complete Nakama documentation -- 🚀 **[Hiro Docs](https://heroiclabs.com/docs/hiro/)** - Complete Hiro documentation +- 📚 **[Sample Project Guides](https://heroiclabs.com/docs/sample-projects)** - Overviews and guides for each project +- 🎮 **[Nakama Docs](https://heroiclabs.com/docs/nakama/)** - Complete Nakama documentation +- 🚀 **[Hiro Docs](https://heroiclabs.com/docs/hiro/)** - Complete Hiro documentation ## About Nakama & Hiro @@ -30,8 +34,8 @@ See `Third-Party Notices.txt` in each project for licensing details. ## Support -- 💬 **[Community Forum](https://forum.heroiclabs.com/)** - Get help from the community -- 🐛 **[Issues](https://github.com/heroiclabs/sample-projects/issues)** - Report bugs or request features +- 💬 **[Community Forum](https://forum.heroiclabs.com/)** - Get help from the community +- 🐛 **[Issues](https://github.com/heroiclabs/sample-projects/issues)** - Report bugs or request features ## Contributing diff --git a/UnityHiroChallenges/Assets/UnityHiroChallenges/Editor/AccountSwitcher.cs b/UnityHiroChallenges/Assets/UnityHiroChallenges/Editor/AccountSwitcher.cs index 9ed6778..de5ff37 100644 --- a/UnityHiroChallenges/Assets/UnityHiroChallenges/Editor/AccountSwitcher.cs +++ b/UnityHiroChallenges/Assets/UnityHiroChallenges/Editor/AccountSwitcher.cs @@ -64,7 +64,7 @@ private void CreateGUI() var rootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects(); foreach (var rootGameObject in rootGameObjects) { - if (!rootGameObject.TryGetComponent(out var challengesController)) continue; + if (!rootGameObject.TryGetComponent(out var challengesController)) continue; if (HiroCoordinator.Instance.GetSystem().Session is Session session) { @@ -77,7 +77,7 @@ private void CreateGUI() } } - private void OnControllerInitialized(ISession session, HiroChallengesController challengesController = null) + private void OnControllerInitialized(ISession session, ChallengesController challengesController = null) { accountUsernames[accountDropdown.choices[0]] = session.Username; UpdateUsernameLabels(); @@ -138,7 +138,7 @@ private async void SwitchAccount(ChangeEvent changeEvt) var rootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects(); foreach (var rootGameObject in rootGameObjects) { - if (!rootGameObject.TryGetComponent(out var challengesController)) continue; + if (!rootGameObject.TryGetComponent(out var challengesController)) continue; var coordinator = HiroCoordinator.Instance as HiroChallengesCoordinator; if (coordinator == null) return; diff --git a/UnityHiroChallenges/Assets/UnityHiroChallenges/Readme.asset b/UnityHiroChallenges/Assets/UnityHiroChallenges/Readme.asset index 2b5c152..9c2f0e0 100644 --- a/UnityHiroChallenges/Assets/UnityHiroChallenges/Readme.asset +++ b/UnityHiroChallenges/Assets/UnityHiroChallenges/Readme.asset @@ -13,10 +13,10 @@ MonoBehaviour: m_Name: Readme m_EditorClassIdentifier: icon: {fileID: 2800000, guid: 2287ceace33d64348b1c2701d0b0bd2e, type: 3} - title: Groups, powered by Nakama + title: Challenges, powered by Hiro sections: - - heading: Groups - text: The official sample project for the Nakama Groups system. Built with minimal + - heading: Challenges + text: The official sample project for the Hiro Challenges system. Built with minimal boilerplate so you can easily copy the code into your project and adapt the scripts and UI to suit your game. linkText: @@ -39,10 +39,10 @@ MonoBehaviour: url: https://github.com/heroiclabs/sample-projects - heading: text: - linkText: Nakama Unity Client SDK Guide - url: https://heroiclabs.com/docs/nakama/client-libraries/unity/index.html#friends + linkText: Hiro Unity Client SDK Guide + url: https://heroiclabs.com/docs/hiro/unity/challenges/ - heading: text: - linkText: Learn about Nakama - url: https://heroiclabs.com/docs/nakama/getting-started/ + linkText: Learn about Hiro + url: https://heroiclabs.com/docs/hiro/concepts/introduction/ loadedLayout: 1 diff --git a/UnityHiroChallenges/Assets/UnityHiroChallenges/Scenes/Main.unity b/UnityHiroChallenges/Assets/UnityHiroChallenges/Scenes/Main.unity index a9f0944..b1b6378 100644 --- a/UnityHiroChallenges/Assets/UnityHiroChallenges/Scenes/Main.unity +++ b/UnityHiroChallenges/Assets/UnityHiroChallenges/Scenes/Main.unity @@ -364,7 +364,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d09862c4a7ec4898bc057b8378ab100c, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::HiroChallenges.HiroChallengesController - challengeEntriesLimit: 100 challengeEntryTemplate: {fileID: 9197481963319205126, guid: 8dd3c785e50de45048715a68169a3967, type: 3} challengeParticipantTemplate: {fileID: 9197481963319205126, guid: 1b43f002f8c6f4d02973ddbd956d1939, type: 3} --- !u!1 &2121322951 diff --git a/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs b/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs index c7f8393..3c734a0 100644 --- a/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs +++ b/UnityHiroChallenges/Assets/UnityHiroChallenges/Scripts/ChallengeParticipantView.cs @@ -1,33 +1,47 @@ +// Copyright 2025 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using Hiro; using UnityEngine.UIElements; namespace HiroChallenges { - public class ChallengeParticipantView + public sealed class ChallengeParticipantView { - private Label usernameLabel; - private Label scoreLabel; - private Label subScoreLabel; - private Label rankLabel; + private Label _usernameLabel; + private Label _scoreLabel; + private Label _subScoreLabel; + private Label _rankLabel; public void SetVisualElement(VisualElement visualElement) { - usernameLabel = visualElement.Q