diff --git a/README.md b/README.md
index 5ff973e..56d65c8 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ Ready-to-use templates for **Nakama** game development. Download, play, and copy
- [Leaderboards](./UnityNakamaLeaderboards/) - Weekly and global rankings with real-time record updates
- [Friends](./UnityNakamaFriends/) - Manage friend lists and requests, block and unblock players.
- [Cloud Save](./UnityNakamaCloudSave/) - Save player data in the cloud that syncs between devices.
+- [Groups](./UnityNakamaGroups/) - Create in-game communities for players to band together.
## Documentation
diff --git a/UnityNakamaCloudSave/.gitignore b/UnityNakamaCloudSave/.gitignore
index e7b802e..ccf423b 100644
--- a/UnityNakamaCloudSave/.gitignore
+++ b/UnityNakamaCloudSave/.gitignore
@@ -1,7 +1,16 @@
-# This .gitignore file should be placed at the root of your Unity project directory
-#
-# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
-#
+
+# Created by https://www.gitignore.io/api/unity
+# Edit at https://www.gitignore.io/?templates=unity
+
+# Jetbrain Rider Cache
+.idea/
+Assets/Plugins/Editor/JetBrains*
+
+# Visual Studio Code
+.vscode/
+
+
+### Unity ###
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
@@ -9,26 +18,13 @@
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
-
-# MemoryCaptures can get excessive in size.
-# They also could contain extremely sensitive data
-/[Mm]emoryCaptures/
-
-# Recordings can get excessive in size
-/[Rr]ecordings/
-
-# Uncomment this line if you wish to ignore the asset store tools plugin
-# /[Aa]ssets/AssetStoreTools*
-
-# Autogenerated Jetbrains Rider plugin
-/[Aa]ssets/Plugins/Editor/JetBrains*
+Assets/AssetStoreTools*
+# Unity local user project setting
+UserSettings/
# Visual Studio cache directory
.vs/
-# Gradle cache directory
-.gradle/
-
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
@@ -43,31 +39,18 @@ ExportedObj/
*.booproj
*.svd
*.pdb
-*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
-*.mdb.meta
-# Unity3D generated file on crash reports
+# Unity3D Generated File On Crash Reports
sysinfo.txt
# Builds
*.apk
-*.aab
*.unitypackage
-*.unitypackage.met
-*.app
-
-# Crashlytics generated file
-crashlytics-build.properties
-
-# Packed Addressables
-/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
-# Temporary auto-generated Android Assets
-/[Aa]ssets/[Ss]treamingAssets/aa.meta
-/[Aa]ssets/[Ss]treamingAssets/aa/*
\ No newline at end of file
+# End of https://www.gitignore.io/api/unity
\ No newline at end of file
diff --git a/UnityNakamaCloudSave/.idea/.gitignore b/UnityNakamaCloudSave/.idea/.gitignore
deleted file mode 100644
index 6aebcc2..0000000
--- a/UnityNakamaCloudSave/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/modules.xml
-/projectSettingsUpdater.xml
-/.idea.UnityNakamaCloudSave.iml
-/contentModel.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/.gitignore b/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/.gitignore
deleted file mode 100644
index 8cbd94c..0000000
--- a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/contentModel.xml
-/.idea.UnityNakamaCloudSave.iml
-/modules.xml
-/projectSettingsUpdater.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/encodings.xml b/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/encodings.xml
deleted file mode 100644
index df87cf9..0000000
--- a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/indexLayout.xml b/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163..0000000
--- a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/vcs.xml b/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/UnityNakamaCloudSave/.idea/.idea.UnityNakamaCloudSave/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/UnityNakamaCloudSave/.idea/indexLayout.xml b/UnityNakamaCloudSave/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163..0000000
--- a/UnityNakamaCloudSave/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/UnityNakamaCloudSave/.idea/vcs.xml b/UnityNakamaCloudSave/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/UnityNakamaCloudSave/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/UnityNakamaGroups/.gitignore b/UnityNakamaGroups/.gitignore
new file mode 100644
index 0000000..ccf423b
--- /dev/null
+++ b/UnityNakamaGroups/.gitignore
@@ -0,0 +1,56 @@
+
+# Created by https://www.gitignore.io/api/unity
+# Edit at https://www.gitignore.io/?templates=unity
+
+# Jetbrain Rider Cache
+.idea/
+Assets/Plugins/Editor/JetBrains*
+
+# Visual Studio Code
+.vscode/
+
+
+### Unity ###
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Uu]ser[Ss]ettings/
+Assets/AssetStoreTools*
+# Unity local user project setting
+UserSettings/
+
+# Visual Studio cache directory
+.vs/
+
+# Autogenerated VS/MD/Consulo solution and project files
+ExportedObj/
+.consulo/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+*.opendb
+*.VC.db
+
+# Unity3D generated meta files
+*.pidb.meta
+*.pdb.meta
+
+# Unity3D Generated File On Crash Reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.unitypackage
+
+# End of https://www.gitignore.io/api/unity
\ No newline at end of file
diff --git a/UnityNakamaGroups/Assets/Packages.meta b/UnityNakamaGroups/Assets/Packages.meta
new file mode 100644
index 0000000..4cd567e
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dc4d2650bb80546188668b54487d2f22
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama.meta b/UnityNakamaGroups/Assets/Packages/Nakama.meta
new file mode 100644
index 0000000..c492aad
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c6b4ec9240a8742f08d7ddd2f12645cd
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/CHANGELOG.md b/UnityNakamaGroups/Assets/Packages/Nakama/CHANGELOG.md
new file mode 100644
index 0000000..6596fcd
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/CHANGELOG.md
@@ -0,0 +1,398 @@
+# Change Log
+All notable changes to this project are documented below.
+
+The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/).
+
+## [3.17.0] - 2025-07-17
+### Added
+- Add request logging to the "UnityWebRequestAdapter" type. Thanks @epishev-m
+
+### Changed
+- Update to use Nakama and Satori .NET 3.17.0 release.
+- Set the minimum supported version of Unity engine to be on 2022.3 LTS release.
+
+## [3.16.0] - 2025-02-13
+### Changed
+- Update to use Nakama and Satori .NET 3.16.0 release.
+
+## [3.15.0] - 2025-01-29
+### Changed
+- Update to use Nakama and Satori .NET 3.15.0 release.
+
+### Fixed
+- Fix compatibility with WebGL builds in Unity 6.
+
+## [3.14.0] - 2024-11-05
+### Changed
+- Update to use Nakama and Satori .NET 3.14.0 release.
+
+## [3.13.0] - 2024-07-10
+### Changed
+- Updated to use the Nakama and Satori .NET 3.13.0 release.
+
+## [3.12.1] - 2024-05-30
+### Changed
+- Updated to use the Nakama and Satori .NET 3.12.1 release.
+
+## [3.12.0] - 2024-04-08
+### Changed
+- Updated to use the Nakama and Satori .NET 3.12.0 release.
+
+### Fixed
+- Removed unnecessary reference to old Nakama Unity version in Demo scene.
+
+## [3.11.0] - 2024-03-08
+### Changed
+- Default socket adapter changed from `WebSocketAdapter` to `WebSocketStdlibAdapter`. This was done to utilize the native .NET Websocket library for improved stability and maintenance.
+- Updated to use the Nakama and Satori .NET 3.11.0 release.
+
+## [3.10.1] - 2023-12-12
+### Changed
+- Retry functionality restored for `UnityWebRequest.Result.ConnectionError`.
+
+## [3.10.0] - 2023-11-21
+### Changed
+- Updated to use the Nakama and Satori .NET 3.10.0 release.
+- Restricted retry attempts to more specific 500-level error codes from the server.
+
+## [3.9.0] - 2023-08-11
+### Changed
+- Updated to use the Nakama and Satori .NET 3.9.0 release.
+
+## [3.8.0] - 2023-06-09
+### Changed
+- Updated to use the Nakama and Satori .NET 3.8.0 release.
+
+## [3.7.0] - 2023-03-10
+### Changed
+- Updated to use the Nakama and Satori .NET 3.7.0 release.
+### Fixed
+- Fixed an issue where the OnClose event would not fire in Unity WebGL.
+
+## [3.6.0] - 2023-02-7
+### Changed
+- Update to use the Nakama and Satori .NET 3.6.0 release.
+### Fixed
+- Fixed multiple race conditions that could occur when Unity web requests were cancelled.
+
+## [3.5.0] - 2022-09-18
+### Changed
+- Update to use Nakama .NET 3.5.0 release.
+### Fixed
+- Don't parse response messages on 500 responses from the server.
+
+## [3.4.1] - 2022-05-13
+### Fixed
+- Updated to correct Nakama .NET 3.4.0 release binary.
+
+## [3.4.0] - 2022-05-02
+### Changed
+- Update to use Nakama .NET 3.4.0 release.
+
+## [3.3.0] - 2022-01-25
+### Changed
+- Update to use Nakama .NET 3.3.0 release.
+- Use Task objects with the WebGL export.
+
+## [3.2.0] - 2021-10-11
+### Added
+- Add additional group listing filters.
+- Add ability to overwrite leaderboard/tournament ranking operators from the client.
+
+### Fixed
+- Fix url-safe encoding of query params that were passed to the client as arrays of strings.
+
+## [3.1.1] - 2021-08-11
+### Changed
+- Remove `autoRefreshSession` from overloaded `Client` constructors. This can still be customized with the base `Client` constructor. This is a workaround for an internal compiler error in Unity's WebGL toolchain.
+
+### Fixed
+- Remove use of deprecated WWW fields in newer versions of Unity.
+
+## [3.1.0] - 2021-08-11
+### Added
+- Add ability for user to retry requests if they fail due to a transient network error.
+- Add ability for user to cancel requests that are in-flight.
+
+## [3.0.0] - 2021-07-14
+### Added
+- The language tag for the user can be configured with the socket on connect.
+
+### Changed
+- An `IPartyMatchmakerTicket` is now received by the party leader when they add their party to the matchmaker via `AddMatchmakerPartyAsync`.
+- Renamed `PromotePartyMember` to `PromotePartyMemberAsync`.
+
+## [2.9.5] - 06-18-21
+### Fixed
+- Fix issue where UnityLogger did not implement a Debug log method.
+
+## [2.9.4] - 06-17-21
+### Fixed
+- Fix issue where refreshing a session with metadata threw an exception due to the key already existing.
+
+## [2.9.3] - 05-21-21
+### Fixed
+- Fix issue where `IUserPresence` objects were not being deserialized properly by the client as part of the `IParty` object.
+
+## [2.9.2] 05-19-21
+### Added
+- The `Socket.ReceivedParty` event can now be subscribed to in order to listen for acceptance events from the leader of a closed party.
+
+## [2.9.1] 05-18-21
+### Fixed
+- Fix incorrect .DLL version being pulled in from nakama-dotnet.
+
+## [2.9.0] 05-17-21
+
+### Added
+- A session can be refreshed on demand with "SessionRefreshAsync" method.
+- Session and/or refresh tokens can now be disabled with a client logout.
+- The client now supports session auto-refresh using refresh tokens. This is enabled by default.
+- New socket RPC and MatchSend methods using ArraySegment to allow developers to manage memory re-use.
+- Add IAP validation APIs for purchase receipts with Apple App Store, Google Play Store, and Huawei AppGallery.
+- Add Realtime Parties feature.
+
+### Changed
+- Use lock object with socket operations instead of ConcurrentDictionary as a workaround for a Unity engine WebGL regression.
+- Avoid use of extension methods as a workaround for a Unity engine WebGL regression.
+- Unity sockets now dispatch events on Unity's main thread by default. If you have been using code to move socket message to the main thread (e.g., UnityMainThreadDispatcher) you may now remove that code. This new default behavior can overridden by passing `useMainThread: false` to `client.NewSocket`. When passed this way, sockets default to their pre-2.9 behavior by dispatching messages in a separate thread.
+
+### Fixed
+- Parse HTTP responses defensively in case of bad load balancer configurations.
+
+## [2.8.1] - 2021-03-16
+### Fixed
+- Fixed a bug with parsing error responses that did not contain a message or grpc code.
+### Changed
+- Made names of asmdef files more specific for easier searching inside the Unity editor.
+
+## [2.8.0] - 2021-02-19
+### Changed
+- Listing tournaments can now be done without providing start or end time filters.
+- Can now import Steam friends after authenticating or linking to a Steam account.
+
+## [2.7.1] - 2021-02-18
+### Fixed
+- HTTP Client now properly reads off timeout value.
+
+## [2.7.0] - 2020-10-19
+### Changed
+Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.7.0.
+Added namespace to JavaScript web socket adapter internals.
+
+## [2.6.0] - 2020-09-21
+### Changed
+Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.6.0.
+
+## [2.5.0] - 2020-08-14
+### Added
+- Add support for the Unity Package Manager. See the README for usage.
+
+### Changed
+- Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.5.0.
+- Update minimum required Unity version to 2018.4.26f1 LTS. While older releases may work YMMV.
+
+## [2.4.0] - 2020-05-04 :star:
+### Added
+- Add new scene as an example for WebGL basics. Thanks @humbertodias.
+
+### Changed
+- Add a default error handler to the socket adapter to make common errors more visible.
+- Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.4.0.
+
+### Fixed
+- UnityWebRequest downloadHandler is null on DELETE methods. Thanks @hasbean.
+
+## [2.3.2] - 2019-10-23
+### Fixed
+- Update interface impl signatures with JS socket adapter.
+
+## [2.3.1] - 2019-09-21
+### Changed
+- Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.3.1.
+
+## [2.3.0] - 2019-09-02
+### Changed
+- Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.3.0.
+
+## [2.2.2] - 2019-07-02
+### Changed
+- Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.2.2.
+
+## [2.2.1] - 2019-06-21
+### Added
+- New example on how to manage the client/socket/session as a singleton.
+- Various improvements to existing code examples.
+
+### Changed
+- Update Nakama .NET dependency. See release notes: heroiclabs/nakama-dotnet@2.2.1.
+
+## [2.2.0] - 2019-06-12
+### Added
+- Support WebGL builds.
+- Add new Leaderboard and Tournament API methods.
+
+### Changed
+- Use new socket library instead of WebSocketListener.
+- Update socket event names to match csharp style guide.
+- Update TinyJson dependency.
+
+### Fixed
+- Socket logger must not disable the socket events.
+- Deserialize data in stream messages correctly.
+
+## [2.1.0] - 2018-08-17
+### Added
+- Linker instructions to preserve code in dependent DLLs.
+- New code snippets for multiplayer and matchmaker examples.
+
+### Changed
+- Update dependent DLLs for lowlevel websocket driver and .NET client.
+
+## [2.0.0] - 2018-06-18
+### Added
+- New documentation on the client.
+- Many new features and APIs.
+- Support for Nakama 2 release.
+
+### Changed
+- Rewrite client with async/await sockets.
+- New project structure for simpler Unity builds.
+
+---
+
+## [0.10.2] - 2017-11-27
+### Fixed
+- Use correct JS transport listener bindings.
+- Correctly calculate session expiry client-side.
+
+### Changed
+- MatchmakeAddMessage correctly follows C# naming scheme.
+- Improve memory allocation profile when using UDP transport.
+
+## [0.10.1] - 2017-11-11
+### Fixed
+- Build system now includes `BCCrypto.dll` in `.unitypackage`.
+
+## [0.10.0] - 2017-11-06
+### Added
+- New experimental rUDP socket protocol option.
+
+### Changed
+- Use string identifiers instead of byte arrays for compatibility across Lua, JSON, and client representations.
+
+## [0.9.0] - 2017-10-17
+### Added
+- Advanced Matchmaking with custom filters and user properties.
+- Expose Collation ID when client operations result in an error.
+
+## [0.8.0] - 2017-08-01
+### Added
+- A paging cursor can now be serialized and restored.
+- New storage partial update feature.
+- New storage list feature.
+- A new Unity code example which shows how to dispatch actions on the main thread.
+- A session now exposes `.ExpiresAt` and `.Handle` from the token.
+
+### Changed
+- Add default builder for notification list and remove messages.
+- A group self list operation now return the user's membership state with each group.
+- A group leave operation now return a specific error code when the last admin attempts to leave.
+- The client interface now uses action delegates instead of event handlers to support a proxy pattern.
+
+## [0.7.0] - 2017-07-18
+### Added
+- A new Unity example scene which shows how to matchmake users.
+- New `NIds` helper class and extension methods to compare byte arrays.
+- Add new In-App Notification feature.
+- Add new In-App Purchase Validation feature.
+
+### Changed
+- Update client to support the new batch-orientated server protocol.
+
+### Fixed
+- Accept SSL certificates.
+- Improve handling transport errors.
+- Improve fetching global storage records.
+
+## [0.6.1] - 2017-05-30
+### Changed
+- Remove unnecessary headers from HTTP requests.
+- Update user fetch add handle method name to avoid a type cast.
+
+### Fixed
+- Accept SSL certificates on Android devices.
+- Improve leaderboard list message to handle multiple filters.
+
+## [0.6.0] - 2017-05-29
+### Added
+- New matchmaking feature.
+- Optionally send match data to a subset of match participants.
+- Expose a way to toggle `TCP_NODELAY` socket option.
+- Send RPC messages to run custom code.
+- Fetch users by handle.
+- Add friend by handle.
+- Filter by IDs in leaderboard list message.
+- Storage messages can now set records with public read permission.
+
+### Fixed
+- Dispatch callbacks when sending match data.
+
+## [0.5.1] - 2017-03-28
+### Added
+- Support for fetching groups by name.
+
+## [0.5.0] - 2017-03-19
+### Added
+- Add support for dynamic leaderboards.
+- Add error codes for error messages in server protocol.
+
+### Changed
+- Use preprocessor directive to skip WebGL specific code with other build profiles.
+- Update session token parse code for user's handle.
+- Update user presence protocol message to contain user handles.
+
+## [0.4.2] - 2017-02-27
+### Added
+- Repackage client with Unity 5.4.0 support.
+
+### Changed
+- Setup logger in client transport.
+
+## [0.4.1] - 2017-02-26
+### Fixed
+- Add '.jslib' files to Unity package builds.
+
+## [0.4.0] - 2017-02-26
+### Added
+- Add WebGL support.
+
+### Changed
+- Update the package structure generated by the build system for simpler Asset Store submissions.
+
+## [0.3.0] - 2017-02-18
+### Added
+- Add new impl of realtime match entities.
+
+### Changed
+- Merge match entities into single `INMatch`.
+
+### Fixed
+- Incoming realtime messages do not need collation.
+- Add event handlers to `INClient` interface.
+
+## [0.2.0] - 2017-02-12
+### Added
+- Add new impl and test cases for storage, friends, and groups.
+- Add new impl for realtime and chat messages.
+
+### Changed
+- Do not close the connection on logout. It will be closed by the server.
+- Update client usages for friend messages due to changes in server protocol.
+
+### Fixed
+- Fix various small test cases caused by changes in the server.
+
+## [0.1.0] - 2017-01-14
+### Added
+- Initial public release.
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/CHANGELOG.md.meta b/UnityNakamaGroups/Assets/Packages/Nakama/CHANGELOG.md.meta
new file mode 100644
index 0000000..9c78cbb
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/CHANGELOG.md.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: bdf4b7cccedb9480f97755cc4dec1147
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/CHANGELOG.md
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation.meta
new file mode 100644
index 0000000..b8781c6
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a373253cd471e4793a64466d58b2f88a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md
new file mode 100644
index 0000000..960be27
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md
@@ -0,0 +1,243 @@
+footer: © Heroic Labs, 2019
+slidenumbers: true
+
+# Nakama 2 + Unity Engine
+
+### Heroic Labs
+
+---
+
+# Setup Server
+
+* Local development use Docker or native binaries.
+
+ ```shell
+ docker-compose -f ./docker-compose.yml up
+ ```
+
+* Always run Nakama servers and databases on dedicated hardware at launch.
+
+* Production-ready server environments with our [Managed Cloud](https://heroiclabs.com/managed-cloud) service.
+
+---
+
+# Development Configuration
+
+* `"--logger.level=debug"`
+
+ Enable additional logs (can be noisy).
+
+* `"--socket.max_message_size_bytes=8192"`
+
+ Increase message size limits to match max packet size.
+
+* `"--session.token_expiry_sec=604800"`
+
+ Adjust session lifetime to fit gameplay sessions.
+
+---
+
+# Unity / .NET Client
+
+```csharp
+// using Nakama;
+var client = new Client("http", "127.0.0.1", 7350, "defaultkey");
+```
+
+* Updated for Unity 2018 or newer.
+* Requires [.NET4.6 scripting runtime](https://docs.unity3d.com/Manual/ScriptingRuntimeUpgrade.html) compatibility level.
+* Uses `async/await` for simple asynchronous code.
+* Divided into "low level" client and Unity wrapper.
+* Unity wrapper contains features specific to the engine.
+
+---
+
+# Sessions
+
+* Authenticate to register/login. Many sign-in options.
+
+```csharp
+var deviceId = SystemInfo.deviceUniqueIdentifier;
+var session = await client.AuthenticateDeviceAsync(deviceId);
+```
+
+* Sessions can be cached on device and restored.
+
+```csharp
+PlayerPrefs.SetString("nakama.session", session.AuthToken);
+// Restore.
+var authtoken = PlayerPrefs.GetString("nakama.session");
+var session = Session.Restore(authtoken);
+```
+
+* Session used to authenticate all requests.
+
+---
+
+# Users & Accounts
+
+* Sessions contain essential user details.
+
+```csharp
+Debug.Log(session.UserId); // "ea1e7609-372a-4d67-a495-58f955f3328b"
+Debug.Log(session.Username); // "wRkuUTbKmY"
+```
+
+* Each player has an account (private) and a user profile.
+
+```csharp
+var account = await client.GetAccountAsync(session);
+Debug.Log(account.User.Id); // "ea1e7609-372a-4d67-a495-58f955f3328b"
+```
+
+---
+
+# Social Accounts
+
+* A user account can "link" additional sign-in options.
+* Useful to enable users to sign-in across multiple devices.
+
+```csharp
+// using Facebook.Unity;
+var perms = new List(){"public_profile", "email"};
+FB.LogInWithReadPermissions(perms, async (ILoginResult result) => {
+ if (FB.IsLoggedIn) {
+ var accessToken = Facebook.Unity.AccessToken.CurrentAccessToken;
+ await client.LinkFacebookAsync(session, accessToken);
+ }
+});
+```
+
+---
+
+# Friends
+
+* Create a social graph of friends within the server.
+
+```csharp
+// Both users must add each other to become friends. Double opt-in.
+await client.AddFriendsAsync(session, new[] { "user id" });
+var result = await client.ListFriendsAsync(session);
+
+foreach (var f in result.Friends) {
+ Debug.Log("Friend name {0}", f.User.DisplayName);
+ // State one of: friend(0), invite_sent(1), invite_received(2), blocked(3)
+ Debug.Log("Friend state {0}", f.State);
+}
+```
+
+---
+
+# Groups
+
+* Groups have 3 membership levels: superadmin, admin, and member.
+* Use groups for guilds, clans, or any kind of team-based gameplay.
+
+```csharp
+const string name = "heroic";
+const string desc = "game server devs";
+var group = await client.CreateGroupAsync(session, name, desc);
+Debug.Log("New group {0}", group.Id);
+```
+
+---
+
+# Rpc Functions
+
+* Define functions on the server in Lua or Go.
+
+```lua
+-- in lua
+local nk = require("nakama")
+local function some_action(context, payload)
+ return nk.json_encode({ message = "PONG" })
+end
+nk.register_rpc(some_action, "")
+```
+
+* Execute them with the client.
+
+```csharp
+var rpc = await client.RpcAsync(session, "");
+// using Nakama.TinyJson;
+var content = rpc.Payload.FromJson>();
+Debug.Log("Response content {0}", content);
+```
+
+---
+
+# Leaderboards
+
+* Create unlimited leaderboards.
+* A record can have a score and subscore.
+* Build friend or guild leaderboards with a filter on user ids.
+
+```csharp
+var result = await client.ListLeaderboardRecordsAsync(session, "", null, 100);
+foreach (var r in result.Records) {
+ Debug.Log("Score '{0}' for user '{1}'", r.Score, r.Username);
+}
+```
+
+---
+
+# In-app Notifications
+
+* Send notifications which can be received in realtime.
+* Notifications must be sent authoritatively (Lua or Go).
+
+```lua
+-- in lua
+local nk = require("nakama")
+local notification = { code = 1, content = {}, persistent = true,
+ sender_id = "someid", subject = "Match winner!", user_id = "userid" }
+nk.notifications_send({ notification })
+```
+
+* List notifications received while offline.
+
+```csharp
+var result = await client.ListNotificationsAsync(session, 100);
+Debug.Log("Received {0} notifications", result.Notifications.Count());
+```
+
+---
+
+# Sockets
+
+* Power chat, multiplayer, status events, in-app notifications, etc.
+* Create a socket from a client object.
+
+```csharp
+var socket = client.NewSocket();
+socket.Connected += () => Debug.Log("Socket connected.");
+socket.Closed += () => Debug.Log("Socket closed.");
+await socket.ConnectAsync(session);
+Debug.Log("After socket connected.");
+await socket.CloseAsync();
+```
+
+* Have separate sockets for multiplayer and chat or,
+* Share a single socket for all realtime communication.
+
+---
+
+# Lots more APIs
+
+* Realtime chat
+* Status events
+* Authoritative multiplayer
+* Realtime multiplayer
+* Matchmaker
+* Storage engine
+* Remote configuration, etc...
+
+---
+
+# Summary
+
+* Designed as production-ready infrastructure.
+* Minimal database or other external dependencies.
+* First-class Unity engine support.
+* Modern client designed for asynchronous code.
+* Built for scale by Heroic Labs.
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md.meta
new file mode 100644
index 0000000..42dcd72
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 5cceb5b82cf7b4b56a7687f3b28b0a82
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.md
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf
new file mode 100644
index 0000000..05c5839
Binary files /dev/null and b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf differ
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf.meta
new file mode 100644
index 0000000..4f2ba5e
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: d6fc326d328214623ace85dbd2078846
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Documentation/Nakama 2 + Unity Engine.pdf
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/README.pdf b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/README.pdf
new file mode 100644
index 0000000..14a3c53
Binary files /dev/null and b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/README.pdf differ
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/README.pdf.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/README.pdf.meta
new file mode 100644
index 0000000..ef3a478
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Documentation/README.pdf.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: f2a24a6efddf044fd8b50313d2667c11
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Documentation/README.pdf
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor.meta
new file mode 100644
index 0000000..d7c260a
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 99fc7c1958226438d902a70460ed79b4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/NakamaEditor.asmdef b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/NakamaEditor.asmdef
new file mode 100644
index 0000000..b36d266
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/NakamaEditor.asmdef
@@ -0,0 +1,15 @@
+{
+ "name": "NakamaEditor",
+ "references": [
+ "NakamaRuntime"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/NakamaEditor.asmdef.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/NakamaEditor.asmdef.meta
new file mode 100644
index 0000000..1917b26
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/NakamaEditor.asmdef.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 25ab73e5e772c43669ee57183c7e8e62
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/NakamaEditor.asmdef
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/SampleScene.unity b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/SampleScene.unity
new file mode 100644
index 0000000..ccf9533
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/SampleScene.unity
@@ -0,0 +1,279 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 9
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 3
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 11
+ m_GIWorkflowMode: 1
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 0
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_FinalGather: 0
+ m_FinalGatherFiltering: 1
+ m_FinalGatherRayCount: 256
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 0
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 500
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 500
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 2
+ m_PVRDenoiserTypeDirect: 0
+ m_PVRDenoiserTypeIndirect: 0
+ m_PVRDenoiserTypeAO: 0
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 0
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 5
+ m_PVRFilteringGaussRadiusAO: 2
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 0}
+ m_UseShadowmask: 1
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &519420028
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 519420032}
+ - component: {fileID: 519420031}
+ - component: {fileID: 519420029}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &519420029
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 519420028}
+ m_Enabled: 1
+--- !u!20 &519420031
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 519420028}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 2
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_FocalLength: 50
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 1
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 0
+ m_HDR: 1
+ m_AllowMSAA: 0
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 0
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &519420032
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 519420028}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1042132734
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1042132735}
+ - component: {fileID: 1042132736}
+ m_Layer: 0
+ m_Name: GameObject
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 0
+--- !u!4 &1042132735
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1042132734}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: -0.108223915, y: 0.13129173, z: 0.006435648}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1042132736
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1042132734}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 1ba16ca475cf40b6b4a71c19328d89cf, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!1 &1358802968
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1358802969}
+ m_Layer: 0
+ m_Name: SatoriExample
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1358802969
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1358802968}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 2
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/SampleScene.unity.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/SampleScene.unity.meta
new file mode 100644
index 0000000..1c96a02
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/SampleScene.unity.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 9c0ce5994ac4b4970ab7e47eeed0b1f6
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/SampleScene.unity
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets.meta
new file mode 100644
index 0000000..8ae80f8
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cc7c6c2052fcd449f9926ee6a12d99cc
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs
new file mode 100644
index 0000000..b139e4f
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs
@@ -0,0 +1,47 @@
+// Copyright 2019 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 UnityEngine;
+
+namespace Nakama.Snippets
+{
+ public class AccountAndUsers : MonoBehaviour
+ {
+ private readonly IClient _client = new Client("defaultkey");
+
+ private async void Awake()
+ {
+ var deviceid = SystemInfo.deviceUniqueIdentifier;
+ const string username = "myusername";
+ var session = await _client.AuthenticateDeviceAsync(deviceid, username);
+
+ var account = await _client.GetAccountAsync(session);
+ // Account properties.
+ Debug.LogFormat("Account devices: [{0}]", string.Join(",", account.Devices));
+ Debug.LogFormat("Account custom id: '{0}'", account.CustomId);
+ Debug.LogFormat("Account email: '{0}'", account.Email);
+ Debug.LogFormat("Account verify time: '{0}'", account.VerifyTime);
+ Debug.LogFormat("Account wallet: '{0}'", account.Wallet);
+
+ // User properties.
+ Debug.LogFormat("User id: '{0}'", account.User.Id);
+ Debug.LogFormat("User metadata: '{0}'", account.User.Metadata);
+ Debug.LogFormat("User username: '{0}'", account.User.Username);
+ Debug.LogFormat("User online: {0}", account.User.Online);
+
+ var result = await _client.GetUsersAsync(session, new[] {session.UserId});
+ Debug.LogFormat("Users: [{0}]", string.Join(",\n", result.Users));
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs.meta
new file mode 100644
index 0000000..2bf55e4
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 8c3a71dc6ff64337a47cffda0682814c
+timeCreated: 1559895277
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/AccountAndUsers.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs
new file mode 100644
index 0000000..1f11058
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs
@@ -0,0 +1,52 @@
+// Copyright 2019 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 System;
+using UnityEngine;
+
+namespace Nakama.Snippets
+{
+ public class DeviceAuthentication : MonoBehaviour
+ {
+ private const string SessionTokenKey = "nksession";
+ private const string UdidKey = "udid";
+
+ private readonly IClient _client = new Client("defaultkey");
+
+ private async void Awake()
+ {
+ var deviceId = SystemInfo.deviceUniqueIdentifier;
+
+ // Restore session from PlayerPrefs if possible.
+ var sessionToken = PlayerPrefs.GetString(SessionTokenKey);
+ var session = Session.Restore(sessionToken);
+ // Add a day so we check whether the token is within a day of expiration to refresh it.
+ var expiredDate = DateTime.UtcNow.AddDays(1);
+ if (session == null || session.HasExpired(expiredDate))
+ {
+ session = await _client.AuthenticateDeviceAsync(deviceId);
+ PlayerPrefs.SetString(UdidKey, deviceId);
+ PlayerPrefs.SetString(SessionTokenKey, session.AuthToken);
+ }
+
+ Debug.LogFormat("Session user id: '{0}'", session.UserId);
+ Debug.LogFormat("Session username: '{0}'", session.Username);
+ Debug.LogFormat("Session expired: {0}", session.IsExpired);
+ Debug.LogFormat("Session expires: '{0}'", session.ExpireTime); // in seconds.
+
+ var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+ Debug.LogFormat("Session expires on: '{0}'", unixEpoch.AddSeconds(session.ExpireTime).ToLocalTime());
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs.meta
new file mode 100644
index 0000000..3c23396
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 956b69bdb3f44a97a23ab3d8804f7353
+timeCreated: 1559900024
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/DeviceAuthentication.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs
new file mode 100644
index 0000000..b1aa816
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs
@@ -0,0 +1,82 @@
+// Copyright 2019 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 System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Nakama.Snippets
+{
+ public class MatchmakerWithRelayedMultiplayer : MonoBehaviour
+ {
+ private readonly IClient _client = new Client("defaultkey");
+ private ISocket _socket;
+
+ private async void Start()
+ {
+ var deviceId = SystemInfo.deviceUniqueIdentifier;
+ var session = await _client.AuthenticateDeviceAsync(deviceId);
+ Debug.Log(session);
+
+ _socket = _client.NewSocket();
+ _socket.Connected += () => Debug.Log("Socket connected.");
+ _socket.Closed += () => Debug.Log("Socket closed.");
+ _socket.ReceivedError += Debug.LogError;
+
+ IUserPresence self = null;
+ var connectedOpponents = new List(2);
+ _socket.ReceivedMatchmakerMatched += async matched =>
+ {
+ Debug.LogFormat("Matched result: {0}", matched);
+ var match = await _socket.JoinMatchAsync(matched);
+
+ self = match.Self;
+ Debug.LogFormat("Self: {0}", self);
+ connectedOpponents.AddRange(match.Presences);
+ };
+ _socket.ReceivedMatchPresence += presenceEvent =>
+ {
+ foreach (var presence in presenceEvent.Leaves)
+ {
+ connectedOpponents.Remove(presence);
+ }
+ connectedOpponents.AddRange(presenceEvent.Joins);
+ // Remove yourself from connected opponents.
+ connectedOpponents.Remove(self);
+ Debug.LogFormat("Connected opponents: [{0}]", string.Join(",\n ", connectedOpponents));
+ };
+ await _socket.ConnectAsync(session);
+ Debug.Log("After socket connected.");
+ await _socket.AddMatchmakerAsync("*", 2, 2);
+
+
+ // NOTE As an example create a second user and socket to matchmake against.
+ var deviceId2 = Guid.NewGuid().ToString();
+ var session2 = await _client.AuthenticateDeviceAsync(deviceId2);
+ var socket2 = _client.NewSocket();
+ socket2.ReceivedMatchmakerMatched += async matched => await socket2.JoinMatchAsync(matched);
+ await socket2.ConnectAsync(session2);
+ await socket2.AddMatchmakerAsync("*", 2, 2);
+ await Task.Delay(TimeSpan.FromSeconds(10)); // disconnect after 10 seconds.
+ Debug.Log("After delay socket2 closed.");
+ await socket2.CloseAsync();
+ }
+
+ private void OnApplicationQuit()
+ {
+ _socket?.CloseAsync();
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs.meta
new file mode 100644
index 0000000..ede7405
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: e04008b99192461ab81d22ad0af6841b
+timeCreated: 1559904046
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/MatchmakerWithRelayedMultiplayer.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs
new file mode 100644
index 0000000..a5a19ba
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs
@@ -0,0 +1,116 @@
+// Copyright 2019 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 System;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Nakama.Snippets
+{
+ ///
+ /// Manages a Nakama client and optional socket connection.
+ ///
+ ///
+ public class NakamaManager : MonoBehaviour
+ {
+ private const string SessionPrefName = "nakama.session";
+ private const string SingletonName = "/[NakamaManager]";
+
+ private static readonly object Lock = new object();
+ private static NakamaManager _instance;
+
+ ///
+ /// The singleton instance of the Nakama sdk manager.
+ ///
+ public static NakamaManager Instance
+ {
+ get
+ {
+ lock (Lock)
+ {
+ if (_instance != null) return _instance;
+ var go = GameObject.Find(SingletonName);
+ if (go == null)
+ {
+ go = new GameObject(SingletonName);
+ }
+
+ if (go.GetComponent() == null)
+ {
+ go.AddComponent();
+ }
+ DontDestroyOnLoad(go);
+ _instance = go.GetComponent();
+ return _instance;
+ }
+ }
+ }
+
+ public IClient Client { get; }
+ public ISocket Socket { get; }
+
+ public Task Session { get; private set; }
+
+ private NakamaManager()
+ {
+ Client = new Client("http", "127.0.0.1", 7350, "defaultkey")
+ {
+#if UNITY_EDITOR
+ Logger = new UnityLogger()
+#endif
+ };
+ Socket = Client.NewSocket();
+ }
+
+ private Task AuthenticateAsync()
+ {
+ // Modify to fit the authentication strategy you want within your game.
+ // EXAMPLE:
+ const string deviceIdPrefName = "deviceid";
+ var deviceId = PlayerPrefs.GetString(deviceIdPrefName, SystemInfo.deviceUniqueIdentifier);
+#if UNITY_EDITOR
+ Debug.LogFormat("Device id: {0}", deviceId);
+#endif
+ // With device IDs save it locally in case of OS updates which can change the value on device.
+ PlayerPrefs.SetString(deviceIdPrefName, deviceId);
+ return Client.AuthenticateDeviceAsync(deviceId);
+ }
+
+ private void Awake()
+ {
+ // Restore session or create a new one.
+ var authToken = PlayerPrefs.GetString(SessionPrefName);
+ var session = Nakama.Session.Restore(authToken);
+ var expiredDate = DateTime.UtcNow.AddDays(1);
+ if (session == null || session.HasExpired(expiredDate))
+ {
+ var sessionTask = AuthenticateAsync();
+ Session = sessionTask;
+ sessionTask.ContinueWith(t =>
+ {
+ if (t.IsCompleted)
+ {
+ PlayerPrefs.SetString(SessionPrefName, t.Result.AuthToken);
+ }
+ }, TaskScheduler.FromCurrentSynchronizationContext());
+ }
+ else
+ {
+ Session = Task.FromResult(session);
+ }
+ }
+
+ private void OnApplicationQuit() => Socket?.CloseAsync();
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs.meta
new file mode 100644
index 0000000..da30410
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 5c97dc1262b44d49b10e1a2cc88c37ec
+timeCreated: 1560454360
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/NakamaManager.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs
new file mode 100644
index 0000000..24b2c9a
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs
@@ -0,0 +1,35 @@
+// Copyright 2019 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 UnityEngine;
+
+namespace Nakama.Snippets
+{
+ ///
+ public class NakamaManagerUsage : MonoBehaviour
+ {
+ private async void Start()
+ {
+ var session = await NakamaManager.Instance.Session;
+ Debug.LogFormat("Active Session: {0}", session);
+ var account = await NakamaManager.Instance.Client.GetAccountAsync(session);
+ Debug.LogFormat("Account id: {0}", account.User.Id);
+
+ NakamaManager.Instance.Socket.Closed += () => Debug.Log("Socket closed.");
+ NakamaManager.Instance.Socket.Connected += () => Debug.Log("Socket connected.");
+ NakamaManager.Instance.Socket.ReceivedError += Debug.LogError;
+ await NakamaManager.Instance.Socket.ConnectAsync(session);
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs.meta
new file mode 100644
index 0000000..e5d105f
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 244c2c95261a4830ba629a855566b7ff
+timeCreated: 1560497577
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/NakamaManagerUsage.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs
new file mode 100644
index 0000000..bc4cd8d
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs
@@ -0,0 +1,93 @@
+// Copyright 2019 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 System.Collections.Generic;
+using Nakama.TinyJson;
+using UnityEngine;
+
+namespace Nakama.Snippets
+{
+ public class RealtimeChatRoom : MonoBehaviour
+ {
+ private const string RoomName = "heroes";
+
+ private IClient _client;
+ private ISocket _socket;
+
+ private async void Start()
+ {
+ _client = new Client("defaultkey", UnityWebRequestAdapter.Instance);
+
+ var deviceId = SystemInfo.deviceUniqueIdentifier;
+ var session = await _client.AuthenticateDeviceAsync(deviceId);
+ Debug.LogFormat("Session user id: '{0}'", session.UserId);
+
+ _socket = _client.NewSocket();
+ _socket.Connected += () => Debug.Log("Socket connected.");
+ _socket.Closed += () => Debug.Log("Socket closed.");
+ _socket.ReceivedError += Debug.LogError;
+
+ var roomUsers = new List(10);
+ _socket.ReceivedChannelPresence += presenceEvent =>
+ {
+ foreach (var presence in presenceEvent.Leaves)
+ {
+ roomUsers.Remove(presence);
+ }
+
+ roomUsers.AddRange(presenceEvent.Joins);
+ Debug.LogFormat("Room users: [{0}]", string.Join(",\n ", roomUsers));
+ };
+
+ var messageList = new List(100);
+ _socket.ReceivedChannelMessage += message =>
+ {
+ Debug.LogFormat("Received message: '{0}'", message);
+ AddListSorted(messageList, message);
+ Debug.LogFormat("Message list: {0}", string.Join(",\n ", messageList));
+ };
+ await _socket.ConnectAsync(session);
+ Debug.Log("After socket connected.");
+
+ // Join chat channel.
+ var channel = await _socket.JoinChatAsync(RoomName, ChannelType.Room);
+ roomUsers.AddRange(channel.Presences);
+ Debug.LogFormat("Joined chat channel: {0}", channel);
+
+ // Send many chat messages.
+ var content = new Dictionary {{"hello", "world"}}.ToJson();
+ _ = _socket.WriteChatMessageAsync(channel, content);
+ _ = _socket.WriteChatMessageAsync(channel, content);
+ _ = _socket.WriteChatMessageAsync(channel, content);
+ _ = _socket.WriteChatMessageAsync(channel, content);
+ _ = _socket.WriteChatMessageAsync(channel, content);
+ _ = _socket.WriteChatMessageAsync(channel, content);
+ }
+
+ private void OnApplicationQuit()
+ {
+ _socket?.CloseAsync();
+ }
+
+ private static void AddListSorted(List messageList, IApiChannelMessage message)
+ {
+ messageList.Add(message);
+ messageList.Sort((a, b) =>
+ {
+ var ordinal = string.CompareOrdinal(a.CreateTime, b.CreateTime);
+ return ordinal == 0 ? string.CompareOrdinal(a.MessageId, b.MessageId) : ordinal;
+ });
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs.meta
new file mode 100644
index 0000000..f399ef5
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 1ba16ca475cf40b6b4a71c19328d89cf
+timeCreated: 1560264727
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/RealtimeChatRoom.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs
new file mode 100644
index 0000000..f29f323
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs
@@ -0,0 +1,78 @@
+// Copyright 2019 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 System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Nakama.Snippets
+{
+ public class RejoinChatAfterDisconnect : MonoBehaviour
+ {
+ private const string RoomName = "heroes";
+
+ private readonly IClient _client = new Client("defaultkey");
+ private ISocket _socket;
+
+ private async void Start()
+ {
+ var deviceId = SystemInfo.deviceUniqueIdentifier;
+ var session = await _client.AuthenticateDeviceAsync(deviceId);
+ Debug.LogFormat("Session user id: '{0}'", session.UserId);
+
+ var roomUsers = new List(10);
+ _socket = _client.NewSocket();
+ _socket.Connected += () => Debug.Log("Socket connected.");
+ _socket.ReceivedError += Debug.LogError;
+ _socket.Closed += () =>
+ {
+ Debug.Log("Socket closed.");
+ roomUsers.Clear();
+ };
+ _socket.ReceivedChannelPresence += presenceEvent =>
+ {
+ foreach (var presence in presenceEvent.Leaves)
+ {
+ roomUsers.Remove(presence);
+ }
+
+ roomUsers.AddRange(presenceEvent.Joins);
+ Debug.LogFormat("Room users: [{0}]", string.Join(",\n ", roomUsers));
+ };
+ _socket.ReceivedChannelMessage += message => Debug.LogFormat("Received message: '{0}'", message);
+ await _socket.ConnectAsync(session);
+
+ // Join chat channel.
+ var channel = await _socket.JoinChatAsync(RoomName, ChannelType.Room);
+ roomUsers.AddRange(channel.Presences);
+
+ // Simulate a disconnect.
+ await Task.Delay(TimeSpan.FromSeconds(3));
+ await _socket.CloseAsync();
+ await Task.Delay(TimeSpan.FromSeconds(3));
+
+ // Reconnect and rejoin chat channel(s).
+ await _socket.ConnectAsync(session);
+ var channel2 = await _socket.JoinChatAsync(RoomName, ChannelType.Room);
+ roomUsers.AddRange(channel2.Presences);
+ Debug.Log("Rejoined chat!");
+ }
+
+ private void OnApplicationQuit()
+ {
+ _socket?.CloseAsync();
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs.meta
new file mode 100644
index 0000000..1f7cc7e
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 0005fa3038e840b3afa093735c5d46cb
+timeCreated: 1560335143
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/RejoinChatAfterDisconnect.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs
new file mode 100644
index 0000000..6aca375
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs
@@ -0,0 +1,51 @@
+
+// Copyright 2023 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 System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace Satori.Snippets
+{
+ public class SatoriExample : MonoBehaviour
+ {
+ private const string ApiKey = "bb4b2da1-71ba-429e-b5f3-36556abbf4c9";
+
+ private IClient _testClient;
+
+ private async void Awake()
+ {
+ _testClient = new Client("http", "localhost", 7450, ApiKey, UnityWebRequestAdapter.Instance);
+ Debug.Log("authenticating satori");
+ try
+ {
+ var session = await _testClient.AuthenticateAsync($"{Guid.NewGuid()}");
+ await _testClient.GetExperimentsAsync(session, Array.Empty());
+ var experiments = await _testClient.GetAllExperimentsAsync(session);
+ Debug.Log("num experiments is " + experiments.Experiments.Count());
+ await _testClient.AuthenticateLogoutAsync(session);
+ Debug.Log("logged out of satori");
+
+ }
+ catch (Exception e)
+ {
+ Debug.LogError(e.Message);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs.meta
new file mode 100644
index 0000000..2afec14
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: e3e33d25298794d5b8ec1a164c995240
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/SatoriExample.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs
new file mode 100644
index 0000000..fb93014
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs
@@ -0,0 +1,42 @@
+// Copyright 2019 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 UnityEngine;
+
+namespace Nakama.Snippets
+{
+ public class SocketConnect : MonoBehaviour
+ {
+ private readonly IClient _client = new Client("defaultkey");
+ private ISocket _socket;
+
+ private async void Awake()
+ {
+ _socket = _client.NewSocket();
+ _socket.Closed += () => Debug.Log("Socket closed.");
+ _socket.Connected += () => Debug.Log("Socket connected.");
+ _socket.ReceivedError += e => Debug.LogErrorFormat("Socket error: {0}", e.Message);
+
+ var deviceId = SystemInfo.deviceUniqueIdentifier;
+ var session = await _client.AuthenticateDeviceAsync(deviceId);
+ await _socket.ConnectAsync(session);
+ Debug.Log("After socket connected.");
+ }
+
+ private void OnApplicationQuit()
+ {
+ _socket?.CloseAsync();
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs.meta
new file mode 100644
index 0000000..afb5b6a
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: fa133de15c7554171874f86930c58729
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/SocketConnect.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs
new file mode 100644
index 0000000..8bd3bd6
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs
@@ -0,0 +1,79 @@
+// Copyright 2019 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 System;
+using UnityEngine;
+
+namespace Nakama.Snippets
+{
+ // ReSharper disable once InconsistentNaming
+ public class WebGLConnect : MonoBehaviour
+ {
+ private const string SessionTokenKey = "nksession";
+ private const string UdidKey = "udid";
+
+ private IClient _client;
+ private ISocket _socket;
+
+ public string serverText;
+ public string serverPortText;
+
+ public async void Awake()
+ {
+ try
+ {
+ const string scheme = "http";
+ string host = serverText;
+ int port = Int32.Parse(serverPortText);
+ const string serverKey = "defaultkey";
+
+ _client = new Client(scheme, host, port, serverKey, UnityWebRequestAdapter.Instance);
+ _socket = _client.NewSocket();
+ _socket.Closed += () => Debug.Log("Socket closed.");
+ _socket.Connected += () => Debug.Log("Socket connected.");
+ _socket.ReceivedError += e => Debug.Log("Socket error: " + e.Message);
+
+ // Cant use SystemInfo.deviceUniqueIdentifier with WebGL builds.
+ var udid = PlayerPrefs.GetString(UdidKey, Guid.NewGuid().ToString());
+ Debug.Log("Unique Device ID: " + udid);
+
+ ISession session;
+ var sessionToken = PlayerPrefs.GetString(SessionTokenKey);
+ if (string.IsNullOrEmpty(sessionToken) || (session = Session.Restore(sessionToken)).IsExpired)
+ {
+ session = await _client.AuthenticateDeviceAsync(udid);
+ PlayerPrefs.SetString(UdidKey, udid);
+ PlayerPrefs.SetString(SessionTokenKey, session.AuthToken);
+ }
+
+ Debug.Log("Session Token: " + session.AuthToken);
+ await _socket.ConnectAsync(session, true);
+ Debug.Log("Connected ");
+ var match = await _socket.CreateMatchAsync();
+ Debug.Log("Created match: " + match.Id);
+
+ await _socket.CloseAsync();
+ }
+ catch (Exception e)
+ {
+ Debug.LogError(e.ToString());
+ }
+ }
+
+ private void OnApplicationQuit()
+ {
+ _socket?.CloseAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs.meta
new file mode 100644
index 0000000..93d5fb0
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 09ed43bc6e8254522ad7044145b2f5bf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/Snippets/WebGLConnect.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/WebGL.unity b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/WebGL.unity
new file mode 100644
index 0000000..7068ef9
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/WebGL.unity
@@ -0,0 +1,241 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 9
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 3
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 11
+ m_GIWorkflowMode: 1
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 0
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 10
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_FinalGather: 0
+ m_FinalGatherFiltering: 1
+ m_FinalGatherRayCount: 256
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 0
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 500
+ m_PVRBounces: 2
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVRFilteringMode: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 5
+ m_PVRFilteringGaussRadiusAO: 2
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ShowResolutionOverlay: 1
+ m_LightingDataAsset: {fileID: 0}
+ m_UseShadowmask: 1
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &519420028
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 519420032}
+ - component: {fileID: 519420031}
+ - component: {fileID: 519420029}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &519420029
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 519420028}
+ m_Enabled: 1
+--- !u!20 &519420031
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 519420028}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 2
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_GateFitMode: 2
+ m_FocalLength: 50
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 1
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 0
+ m_HDR: 1
+ m_AllowMSAA: 0
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 0
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &519420032
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 519420028}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1042132734
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1042132735}
+ - component: {fileID: 1042132736}
+ m_Layer: 0
+ m_Name: Client
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1042132735
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1042132734}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: -0.108223915, y: 0.13129173, z: 0.006435648}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1042132736
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1042132734}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 09ed43bc6e8254522ad7044145b2f5bf, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ serverText: localhost
+ serverPortText: 7350
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/WebGL.unity.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/WebGL.unity.meta
new file mode 100644
index 0000000..d76eaf0
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/WebGL.unity.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: e38e939f366484dbc8e08deaf6c5978b
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/WebGL.unity
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/link.xml b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/link.xml
new file mode 100644
index 0000000..fda292e
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/link.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Editor/link.xml.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/link.xml.meta
new file mode 100644
index 0000000..0dfa72f
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Editor/link.xml.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 88d9c783afddc4d9f83d4e1ea3c1b009
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Editor/link.xml
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/LICENSE.md b/UnityNakamaGroups/Assets/Packages/Nakama/LICENSE.md
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/LICENSE.md
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/LICENSE.md.meta b/UnityNakamaGroups/Assets/Packages/Nakama/LICENSE.md.meta
new file mode 100644
index 0000000..e50bf6b
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/LICENSE.md.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 6d4f557e08aa849f5b62a0ac338e8009
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/LICENSE.md
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/README.md b/UnityNakamaGroups/Assets/Packages/Nakama/README.md
new file mode 100644
index 0000000..df25e23
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/README.md
@@ -0,0 +1,265 @@
+This is Heroic Labs' UnityEngine monorepository that contains libraries for accessing two different backend services, Nakama and Satori.
+
+The clients are built on the [.NET client](https://github.com/heroiclabs/nakama-dotnet) with extensions for Unity Engine. They require the .NET 4.6 scripting runtime version to be set in the editor.
+
+# Nakama
+
+[Nakama](https://github.com/heroiclabs/nakama) is an open-source server designed to power modern games and apps. Features include user accounts, chat, social, matchmaker, realtime multiplayer, and much [more](https://heroiclabs.com).
+
+Full documentation is online - https://heroiclabs.com/docs/unity-client-guide
+
+## Getting Started
+
+You'll need to setup the server and database before you can connect with the client. The simplest way is to use Docker but have a look at the [server documentation](https://github.com/heroiclabs/nakama#getting-started) for other options.
+
+### Installing the SDK
+
+1. Install and run the servers. Follow these [instructions](https://heroiclabs.com/docs/install-docker-quickstart).
+
+2. Install the Unity SDK. You have three options for this.
+
+ 1. To use an official release, you may download either the .unitypackage or .tar from the [releases page](https://github.com/heroiclabs/nakama-unity/releases) and import it into your project. If you chose the .tar option, you can import it from a dropdown in the Unity Package Manager window.
+
+ 2. Alternatively, if you'd like to checkout a specific release or commit from Github and are using Unity 2019.4.1 or later, you can add the following to the `manifest.json` file in your project's `Packages` folder:
+
+ ```json
+ "com.heroiclabs.nakama-unity": "https://github.com/heroiclabs/nakama-unity.git?path=/Packages/Nakama#"
+ ```
+
+ 3. Your final option is to download prebuilt binaries from the [Asset Store](https://assetstore.unity.com/packages/tools/network/nakama-81338).
+
+3. Use the connection credentials to build a client object.
+
+ ```csharp
+ using Nakama;
+ const string scheme = "http";
+ const string host = "127.0.0.1";
+ const int port = 7350;
+ const string serverKey = "defaultkey";
+ var client = new Client(scheme, host, port, serverKey, UnityWebRequestAdapter.Instance);
+ ```
+
+## Usage
+
+The client object has many methods to execute various features in the server or open realtime socket connections with the server.
+
+### Authenticate
+
+There's a variety of ways to [authenticate](https://heroiclabs.com/docs/authentication) with the server. Authentication can create a user if they don't already exist with those credentials. It's also easy to authenticate with a social profile from Google Play Games, Facebook, Game Center, etc.
+
+```csharp
+var deviceId = SystemInfo.deviceUniqueIdentifier;
+var session = await client.AuthenticateDeviceAsync(deviceId);
+Debug.Log(session);
+```
+
+### Sessions
+
+When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a `Session` object.
+
+```csharp
+Debug.Log(session.AuthToken); // raw JWT token
+Debug.LogFormat("Session user id: '{0}'", session.UserId);
+Debug.LogFormat("Session user username: '{0}'", session.Username);
+Debug.LogFormat("Session has expired: {0}", session.IsExpired);
+Debug.LogFormat("Session expires at: {0}", session.ExpireTime); // in seconds.
+```
+
+It is recommended to store the auth token from the session and check at startup if it has expired. If the token has expired you must reauthenticate. The expiry time of the token can be changed as a setting in the server.
+
+```csharp
+const string prefKeyName = "nakama.session";
+ISession session;
+var authToken = PlayerPrefs.GetString(prefKeyName);
+if (string.IsNullOrEmpty(authToken) || (session = Session.Restore(authToken)).IsExpired)
+{
+ Debug.Log("Session has expired. Must reauthenticate!");
+};
+Debug.Log(session);
+```
+
+### Requests
+
+The client includes lots of builtin APIs for various features of the game server. These can be accessed with the async methods. It can also call custom logic as RPC functions on the server. These can also be executed with a socket object.
+
+All requests are sent with a session object which authorizes the client.
+
+```csharp
+var account = await client.GetAccountAsync(session);
+Debug.LogFormat("User id: '{0}'", account.User.Id);
+Debug.LogFormat("User username: '{0}'", account.User.Username);
+Debug.LogFormat("Account virtual wallet: '{0}'", account.Wallet);
+```
+
+Requests can be supplied with a retry configurations in cases of transient network or server errors.
+
+A single configuration can be used to control all request retry behavior:
+
+```csharp
+var retryConfiguration = new RetryConfiguration(baseDelay: 1, maxRetries: 5, delegate { System.Console.Writeline("about to retry."); });
+
+client.GlobalRetryConfiguration = retryConfiguration;
+var account = await client.GetAccountAsync(session);
+```
+
+Or, the configuration can be supplied on a per-request basis:
+
+```csharp
+
+var retryConfiguration = new RetryConfiguration(baseDelay: 1, maxRetries: 5, delegate { System.Console.Writeline("about to retry."); });
+
+var account = await client.GetAccountAsync(session, retryConfiguration);
+
+```
+Per-request retry configurations override the global retry
+configuration.
+
+Requests also can be supplied with a cancellation token if you need to cancel them mid-flight:
+
+```csharp
+var canceller = new CancellationTokenSource();
+var account = await client.GetAccountAsync(session, retryConfiguration: null, canceller);
+
+await Task.Delay(25);
+
+canceller.Cancel(); // will raise a TaskCanceledException
+```
+
+### Socket
+
+The client can create one or more sockets with the server. Each socket can have it's own event listeners registered for responses received from the server.
+
+```csharp
+var socket = client.NewSocket();
+socket.Connected += () => Debug.Log("Socket connected.");
+socket.Closed += () => Debug.Log("Socket closed.");
+await socket.ConnectAsync(session);
+```
+
+If you'd like socket handlers to execute outside Unity's main thread, pass the `useMainThread: false` argument:
+
+```csharp
+var socket = client.NewSocket(useMainThread: false);
+```
+
+### Errors
+
+You can capture errors when you use `await` scaffolding with Tasks in C#.
+
+```csharp
+try
+{
+ var account = await client.GetAccountAsync(session);
+ Debug.LogFormat("User id: '{0}'", account.User.Id);
+}
+catch (ApiResponseException e)
+{
+ Debug.LogFormat("{0}", e);
+}
+```
+
+### Error Callbacks
+
+You can avoid the use of `await` where exceptions will need to be caught and use `Task.ContinueWith(...)` as a callback style with standard C# if you prefer.
+
+```csharp
+client.GetAccountAsync(session).ContinueWith(t =>
+{
+ if (t.IsFaulted || t.IsCanceled)
+ {
+ Debug.LogFormat("{0}", t.Exception);
+ return;
+ }
+ var account = t.Result;
+ Debug.LogFormat("User id: '{0}'", account.User.Id);
+});
+```
+
+# Satori
+
+Satori is a liveops server for games that powers actionable analytics, A/B testing and remote configuration. Use the Satori Unity Client to coomunicate with Satori from within your Unity game.
+
+Full documentation is online - https://heroiclabs.com/docs/satori/client-libraries/unity
+
+## Getting Started
+
+Create a client object that accepts the API you were given as a Satori customer.
+
+```csharp
+using Satori;
+
+const string scheme = "https";
+const string host = "127.0.0.1"; // add your host here
+const int port = 443;
+const string apiKey = "apiKey"; // add the api key that was given to you as a Satori customer.
+
+var client = new Client(scheme, host, port, apiKey);
+```
+
+Then authenticate with the server to obtain your session.
+
+
+```csharp
+// Authenticate with the Satori server.
+try
+{
+ session = await client.AuthenticateAsync(id);
+ Debug.Log("Authenticated successfully.");
+}
+catch(ApiResponseException ex)
+{
+ Debug.LogFormat("Error authenticating: {0}", ex.Message);
+}
+```
+
+Using the client you can get any experiments or feature flags, the user belongs to.
+
+```csharp
+var experiments = await client.GetExperimentsAsync(session);
+var flag = await client.GetFlagAsync(session, "FlagName");
+```
+
+You can also send arbitrary events to the server:
+
+```csharp
+
+await client.EventAsync(session, new Event("gameLaunched", DateTime.UtcNow));
+
+```
+
+This is only a subset of the Satori client API, so please see the documentation link listed earlier for the full API.
+
+# Unity WebGL
+
+For both Nakama and Satori WebGL builds you should make sure the `IHttpAdapter` passed into the client is a `UnityWebRequestAdapter`.
+
+```csharp
+var client = new Client("defaultkey", UnityWebRequestAdapter.Instance);
+```
+
+For Nakama, use the `NewSocket()` extension method to create the socket OR manually set the right `ISocketAdapter` per platform.
+
+```csharp
+var socket = client.NewSocket();
+
+// or
+#if UNITY_WEBGL && !UNITY_EDITOR
+ ISocketAdapter adapter = new JsWebSocketAdapter();
+#else
+ ISocketAdapter adapter = new WebSocketAdapter();
+#endif
+var socket = Socket.From(client, adapter);
+```
+
+When testing our example WebGL scene before 2021.1, be sure to go into the Build Settings and set the C++ Compiler Configuration to Release instead
+of Debug due to an outstanding issue in Unity WebGL builds: https://issuetracker.unity3d.com/issues/webgl-build-throws-threads-are-not-enabled-for-this-platform-error-when-programs-built-using-debug-c-plus-plus-compiler-configuration
+
+# Contribute
+
+The development roadmap is managed as GitHub issues and pull requests are welcome. If you're interested to enhance the code please open an issue to discuss the changes or drop in and discuss it in the [community forum](https://forum.heroiclabs.com).
+
+This project can be opened in Unity to create a ".unitypackage".
+
+# License
+
+This project is licensed under the [Apache-2 License](https://github.com/heroiclabs/nakama-unity/blob/master/LICENSE).
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/README.md.meta b/UnityNakamaGroups/Assets/Packages/Nakama/README.md.meta
new file mode 100644
index 0000000..0b4d161
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/README.md.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 58ab85b7cb9c2459bb4811b138b653e5
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/README.md
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime.meta
new file mode 100644
index 0000000..5d66f0c
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 970fc2305f270462cb0228a933258a96
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/ClientExtensions.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/ClientExtensions.cs
new file mode 100644
index 0000000..a5c49a5
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/ClientExtensions.cs
@@ -0,0 +1,47 @@
+// Copyright 2019 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 UnityEngine;
+
+namespace Nakama
+{
+ ///
+ /// A set of client extensions to help with conditional Unity engine code.
+ ///
+ public static class ClientExtensions
+ {
+ ///
+ /// Build a new socket with conditional compilation on the adapter.
+ ///
+ /// The client object.
+ /// Whether or not socket events should be dispatched on Unity's main thread.
+ /// A new socket.
+ public static ISocket NewSocket(this IClient client, bool useMainThread = false, ISocketAdapter defaultAdapter = null)
+ {
+ ISocketAdapter threadedAdapter;
+#if UNITY_WEBGL && !UNITY_EDITOR
+ threadedAdapter = new JsWebSocketAdapter();
+#else
+ threadedAdapter = defaultAdapter ?? new WebSocketStdlibAdapter();
+#endif
+
+ var adapter = useMainThread ? UnitySocket.Create(threadedAdapter) : threadedAdapter;
+ var socket = Socket.From(client, adapter);
+#if UNITY_EDITOR
+ socket.ReceivedError += Debug.LogError;
+#endif
+ return socket;
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/ClientExtensions.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/ClientExtensions.cs.meta
new file mode 100644
index 0000000..dc8c0b2
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/ClientExtensions.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 99be4aa47b3a3473eb3265bf4087061e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/ClientExtensions.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs
new file mode 100644
index 0000000..65cfb3c
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs
@@ -0,0 +1,309 @@
+// Copyright 2020 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.
+
+#if UNITY_WEBGL && !UNITY_EDITOR
+
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Nakama
+{
+ ///
+ /// An adapter which uses the WebSocket protocol with Nakama server on a JavaScript bridge with Unity engine.
+ ///
+ public class JsWebSocketAdapter : ISocketAdapter
+ {
+ ///
+ public event Action Connected;
+
+ ///
+ public event Action Closed;
+
+ ///
+ public event Action ReceivedError;
+
+ ///
+ public event Action> Received;
+
+ ///
+ /// If the WebSocket is connected.
+ ///
+ public bool IsConnected { get; private set; }
+
+ ///
+ /// If the WebSocket is connecting.
+ ///
+ public bool IsConnecting { get; private set; }
+
+ private int Ref { get; set; } // unique jslib socket ref
+ private Uri _uri;
+
+ public JsWebSocketAdapter()
+ {
+ Ref = -1;
+ }
+
+ ///
+ public Task CloseAsync()
+ {
+ UnityWebGLSocketBridge.Instance.Close(Ref);
+ Ref = -1;
+ IsConnecting = false;
+ IsConnected = false;
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task ConnectAsync(Uri uri, int timeout)
+ {
+ // TODO will need to use window.setTimeout to implement timeouts on DOM WebSocket.
+ if (Ref > -1)
+ {
+ return Task.CompletedTask;
+ }
+
+ _uri = uri;
+ IsConnecting = true;
+ var tcs = new TaskCompletionSource();
+
+ Action open = () =>
+ {
+ IsConnected = true;
+ IsConnecting = false;
+ tcs.TrySetResult(true);
+ Connected?.Invoke();
+ };
+ Action close = (code, reason) =>
+ {
+ IsConnected = false;
+ IsConnecting = false;
+ Ref = -1;
+ Closed?.Invoke();
+ };
+ Action error = reason =>
+ {
+ IsConnected = false;
+ Ref = -1;
+ if (!tcs.Task.IsCompleted)
+ {
+ tcs.TrySetException(new Exception(reason));
+ }
+ else
+ {
+ ReceivedError?.Invoke(new Exception(reason));
+ }
+ };
+ Action handler = message =>
+ {
+ Received?.Invoke(new ArraySegment(Encoding.UTF8.GetBytes(message)));
+ };
+ Ref = UnityWebGLSocketBridge.Instance.CreateSocket(uri.AbsoluteUri, open, close, error, handler);
+ return tcs.Task;
+ }
+
+ ///
+ public Task SendAsync(ArraySegment buffer, bool reliable = true, CancellationToken canceller = default)
+ {
+ if (Ref == -1)
+ {
+ throw new SocketException((int)SocketError.NotConnected);
+ }
+
+ var payload = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
+ UnityWebGLSocketBridge.Instance.Send(Ref, payload);
+ return Task.CompletedTask;
+ }
+
+ public override string ToString() =>
+ $"JsWebSocketAdapter(IsConnected={IsConnected}, IsConnecting={IsConnecting}, Uri='{_uri}')";
+ }
+
+ // ReSharper disable once InconsistentNaming
+ public class UnityWebGLSocketBridge : MonoBehaviour
+ {
+ private static readonly IDictionary CloseErrorMessages = new Dictionary
+ {
+ { 1000, "Normal" },
+ { 1001, "Away" },
+ { 1002, "ProtocolError" },
+ { 1003, "UnsupportedData" },
+ { 1004, "Undefined" },
+ { 1005, "NoStatus" },
+ { 1006, "Abnormal" },
+ { 1007, "InvalidData" },
+ { 1008, "PolicyViolation" },
+ { 1009, "TooBig" },
+ { 1010, "MandatoryExtension" },
+ { 1011, "ServerError" },
+ { 1015, "TlsHandshakeFailure" }
+ };
+
+ private static int _globalSocketRef;
+
+ private readonly Dictionary _handlers =
+ new Dictionary();
+
+ private static readonly object Lock = new object();
+ private static UnityWebGLSocketBridge _instance;
+
+ public static UnityWebGLSocketBridge Instance
+ {
+ get
+ {
+ lock (Lock)
+ {
+ if (_instance != null) return _instance;
+
+ var go = GameObject.Find("/[Nakama]");
+ if (go == null)
+ {
+ go = new GameObject("[Nakama]");
+ }
+
+ if (go.GetComponent() == null)
+ {
+ go.AddComponent();
+ }
+
+ DontDestroyOnLoad(go);
+ _instance = go.GetComponent();
+ return _instance;
+ }
+ }
+ }
+
+ private UnityWebGLSocketBridge()
+ {
+ }
+
+ public int CreateSocket(string address, Action socketOpenHandle, Action socketCloseHandle,
+ Action socketErrorHandle, Action socketMessageHandle)
+ {
+ var handler = new UnityWebGLSocketBridgeHandler
+ {
+ OnOpen = socketOpenHandle,
+ OnClose = socketCloseHandle,
+ OnMessage = socketMessageHandle,
+ OnError = socketErrorHandle
+ };
+
+ var socketRef = _globalSocketRef++;
+ _handlers.Add(socketRef, handler);
+ NKCreateSocket(socketRef, address);
+ return socketRef;
+ }
+
+ public void Close(int socketRef)
+ {
+ NKCloseSocket(socketRef);
+ }
+
+ public void Send(int socketRef, string payload)
+ {
+ NKSendData(socketRef, payload);
+ }
+
+ private UnityWebGLSocketBridgeHandler GetHandler(int socketRef)
+ {
+ UnityWebGLSocketBridgeHandler handler;
+ _handlers.TryGetValue(socketRef, out handler);
+ return handler;
+ }
+
+ [DllImport("__Internal")]
+ private static extern void NKCreateSocket(int socketRef, string address);
+
+ [DllImport("__Internal")]
+ private static extern void NKCloseSocket(int socketRef);
+
+ [DllImport("__Internal")]
+ private static extern void NKSendData(int socketRef, string data);
+
+ [DllImport("__Internal")]
+ private static extern int NKSocketState();
+
+ // called by jslib
+ private void NKBridgeOnOpen(string bridgeMsg)
+ {
+ var index = bridgeMsg.IndexOf('_');
+ if (index < 0)
+ {
+ return;
+ }
+
+ var socketRef = Convert.ToInt32(bridgeMsg.Substring(0, index));
+ GetHandler(socketRef)?.OnOpen?.Invoke();
+ }
+
+ // called by jslib
+ private void NKBridgeOnMessage(string bridgeMsg)
+ {
+ var index = bridgeMsg.IndexOf('_');
+ if (index < 0 || index + 1 >= bridgeMsg.Length)
+ {
+ return;
+ }
+
+ var socketRef = Convert.ToInt32(bridgeMsg.Substring(0, index));
+ var msg = bridgeMsg.Substring(index + 1);
+ GetHandler(socketRef)?.OnMessage?.Invoke(msg);
+ }
+
+ // called by jslib
+ private void NKBridgeOnClose(string bridgeMsg)
+ {
+ var index = bridgeMsg.IndexOf('_');
+ if (index < 0 || index + 1 >= bridgeMsg.Length)
+ {
+ return;
+ }
+
+ var socketRef = Convert.ToInt32(bridgeMsg.Substring(0, index));
+ var code = Convert.ToInt32(bridgeMsg.Substring(index + 1));
+ GetHandler(socketRef)?.OnClose?.Invoke(code, CloseErrorMessages[code]);
+ _handlers.Remove(socketRef);
+ }
+
+ // called by jslib
+ private void NKBridgeOnError(string bridgeMsg)
+ {
+ var index = bridgeMsg.IndexOf('_');
+ if (index >= 0 && index + 1 < bridgeMsg.Length)
+ {
+ var socketRef = Convert.ToInt32(bridgeMsg.Substring(0, index));
+ var msg = bridgeMsg.Substring(index + 1);
+
+ GetHandler(socketRef)?.OnError?.Invoke(msg);
+ _handlers.Remove(socketRef);
+ }
+ }
+
+ // ReSharper disable once InconsistentNaming
+ private class UnityWebGLSocketBridgeHandler
+ {
+ public Action OnOpen;
+ public Action OnClose;
+ public Action OnError;
+ public Action OnMessage;
+ }
+ }
+}
+
+#endif
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs.meta
new file mode 100644
index 0000000..44fd418
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: c49f370c669eb4f5ea65084ed4075eab
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/JsWebSocketAdapter.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef
new file mode 100644
index 0000000..e984f90
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef
@@ -0,0 +1,13 @@
+{
+ "name": "NakamaRuntime",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef.meta
new file mode 100644
index 0000000..110183d
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 69810832b544b46da9804f1af9373521
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/NakamaRuntime.asmdef
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins.meta
new file mode 100644
index 0000000..4ba5579
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fa9ce33e53c64408cb8005cd610d6761
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll
new file mode 100644
index 0000000..25561a6
Binary files /dev/null and b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll differ
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll.meta
new file mode 100644
index 0000000..afc958b
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll.meta
@@ -0,0 +1,40 @@
+fileFormatVersion: 2
+guid: a4c98355d3b704ef99f71b957061b12b
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/Plugins/Nakama.dll
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Satori.dll b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Satori.dll
new file mode 100644
index 0000000..8301145
Binary files /dev/null and b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Satori.dll differ
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Satori.dll.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Satori.dll.meta
new file mode 100644
index 0000000..5c802a7
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/Satori.dll.meta
@@ -0,0 +1,40 @@
+fileFormatVersion: 2
+guid: 1138b613907684d04a31aaacb5323219
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/Plugins/Satori.dll
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib
new file mode 100644
index 0000000..5075a4c
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2020 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.
+ */
+
+var UnityWebGLSocketBridge = {
+ $BRIDGE_NAME: {},
+ $OPEN_METHOD_NAME: {},
+ $CLOSE_METHOD_NAME: {},
+ $MESSAGE_METHOD_NAME: {},
+ $ERROR_METHOD_NAME: {},
+ $SOCKET_REF_MAP: {},
+
+ $INITIALIZE: function() {
+ SOCKET_REF_MAP = new Map();
+ BRIDGE_NAME = "[Nakama]";
+ OPEN_METHOD_NAME = "NKBridgeOnOpen";
+ CLOSE_METHOD_NAME = "NKBridgeOnClose";
+ MESSAGE_METHOD_NAME = "NKBridgeOnMessage";
+ ERROR_METHOD_NAME = "NKBridgeOnError";
+ },
+
+ NKCreateSocket: function(socketRef, addressPtr) {
+ if (!(SOCKET_REF_MAP instanceof Map)) {
+ INITIALIZE();
+ }
+
+ if (SOCKET_REF_MAP.has(socketRef)) {
+ SendMessage(BRIDGE_NAME, ERROR_METHOD_NAME, socketRef + "_" + "A WebSocket already exists for this reference.");
+ return;
+ }
+
+ var address = UTF8ToString(addressPtr);
+ var ws = new WebSocket(address);
+ ws.onmessage = function(e) {
+ if (typeof e.data == 'string') {
+ SendMessage(BRIDGE_NAME, MESSAGE_METHOD_NAME, socketRef + "_" + e.data);
+ } else {
+ SendMessage(BRIDGE_NAME, ERROR_METHOD_NAME, socketRef + "_" + "Received invalid data from remote.");
+ }
+ };
+ ws.onopen = function(e) {
+ SendMessage(BRIDGE_NAME, OPEN_METHOD_NAME, socketRef + "_");
+ };
+ ws.onclose = function(e) {
+ SOCKET_REF_MAP.delete(socketRef);
+ SendMessage(BRIDGE_NAME, CLOSE_METHOD_NAME, socketRef + "_" + e.code);
+ };
+ ws.onerror = function(e) {
+ // https://stackoverflow.com/questions/18803971/websocket-onerror-how-to-read-error-description
+ SOCKET_REF_MAP.delete(socketRef);
+ SendMessage(BRIDGE_NAME, ERROR_METHOD_NAME, socketRef + "_" + "WebSocket error: " + e.reason);
+ };
+ SOCKET_REF_MAP.set(socketRef, ws);
+ },
+
+ NKSocketState: function (socketRef) {
+ if (!(SOCKET_REF_MAP instanceof Map)) {
+ INITIALIZE();
+ }
+
+ if(!SOCKET_REF_MAP.has(socketRef)) {
+ SendMessage(BRIDGE_NAME, ERROR_METHOD_NAME, socketRef + "_" + "Did not find websocket with given reference.");
+ return -1;
+ }
+ return SOCKET_REF_MAP.get(socketRef).readyState;
+ },
+
+ NKCloseSocket: function (socketRef) {
+ if (!(SOCKET_REF_MAP instanceof Map)) {
+ INITIALIZE();
+ }
+
+ if(!SOCKET_REF_MAP.has(socketRef)) {
+ SendMessage(BRIDGE_NAME, ERROR_METHOD_NAME, socketRef + "_" + "Did not find websocket with given reference.");
+ } else {
+ SOCKET_REF_MAP.get(socketRef).close();
+ }
+ },
+
+ NKSendData: function (socketRef, msg) {
+ if (!(SOCKET_REF_MAP instanceof Map)) {
+ INITIALIZE();
+ }
+
+ if(!SOCKET_REF_MAP.has(socketRef)) {
+ SendMessage(BRIDGE_NAME, ERROR_METHOD_NAME, socketRef + "_" + "Did not find websocket with given reference.");
+ } else {
+ var data = UTF8ToString(msg);
+ SOCKET_REF_MAP.get(socketRef).send(data);
+ }
+ },
+};
+
+// Auto add to depends
+autoAddDeps(UnityWebGLSocketBridge, '$INITIALIZE');
+autoAddDeps(UnityWebGLSocketBridge, '$SOCKET_REF_MAP');
+autoAddDeps(UnityWebGLSocketBridge, '$BRIDGE_NAME');
+autoAddDeps(UnityWebGLSocketBridge, '$OPEN_METHOD_NAME');
+autoAddDeps(UnityWebGLSocketBridge, '$CLOSE_METHOD_NAME');
+autoAddDeps(UnityWebGLSocketBridge, '$MESSAGE_METHOD_NAME');
+autoAddDeps(UnityWebGLSocketBridge, '$ERROR_METHOD_NAME');
+mergeInto(LibraryManager.library, UnityWebGLSocketBridge);
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib.meta
new file mode 100644
index 0000000..987e0bc
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib.meta
@@ -0,0 +1,44 @@
+fileFormatVersion: 2
+guid: eda8d55c6bade4c56b4fe6d7104b3eb3
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Facebook: WebGL
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ WebGL: WebGL
+ second:
+ enabled: 1
+ settings: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/Plugins/UnityWebGLSocketBridge.jslib
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori.meta
new file mode 100644
index 0000000..329e4e0
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e1c018c562f0e4ce79a148fb4c42630e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs
new file mode 100644
index 0000000..dfd4f62
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs
@@ -0,0 +1,36 @@
+// Copyright 2018 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 UnityEngine;
+
+namespace Satori
+{
+ ///
+ /// A logger which captures using Unity Engine.
+ ///
+ public class UnityLogger : ILogger
+ {
+ ///
+ public void DebugFormat(string format, params object[] args) => Debug.LogFormat(format, args);
+
+ ///
+ public void ErrorFormat(string format, params object[] args) => Debug.LogErrorFormat(format, args);
+
+ ///
+ public void InfoFormat(string format, params object[] args) => Debug.LogFormat(format, args);
+
+ ///
+ public void WarnFormat(string format, params object[] args) => Debug.LogWarningFormat(format, args);
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs.meta
new file mode 100644
index 0000000..e5396b1
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: ae3b7b34adbd0434f837d2b5a6058065
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/Satori/UnityLogger.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs
new file mode 100644
index 0000000..beaedd3
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs
@@ -0,0 +1,184 @@
+// Copyright 2023 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 System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Nakama.TinyJson;
+using UnityEngine;
+using UnityEngine.Networking;
+
+namespace Satori
+{
+ ///
+ /// Unity web request adapter which uses the UnityWebRequest to send requests.
+ ///
+ ///
+ /// Note Content-Type header is always set as 'application/json'.
+ ///
+ public class UnityWebRequestAdapter : MonoBehaviour, IHttpAdapter
+ {
+ ///
+ public ILogger Logger { get; set; }
+
+ public TransientExceptionDelegate TransientExceptionDelegate => IsTransientException;
+
+ private static readonly object Lock = new object();
+ private static UnityWebRequestAdapter _instance;
+
+ public static UnityWebRequestAdapter Instance
+ {
+ get
+ {
+ lock (Lock)
+ {
+ if (_instance != null) return _instance;
+
+ var go = GameObject.Find("/[Nakama]");
+ if (go == null)
+ {
+ go = new GameObject("[Nakama]");
+ }
+
+ if (go.GetComponent() == null)
+ {
+ go.AddComponent();
+ }
+
+ DontDestroyOnLoad(go);
+ _instance = go.GetComponent();
+ return _instance;
+ }
+ }
+ }
+
+ private UnityWebRequestAdapter()
+ {
+ }
+
+ ///
+ public Task SendAsync(string method, Uri uri, IDictionary headers, byte[] body,
+ int timeout, CancellationToken? cancellationToken)
+ {
+ var www = BuildRequest(method, uri, headers, body, timeout);
+ var tcs = new TaskCompletionSource();
+ cancellationToken?.Register(() => tcs.TrySetCanceled());
+ StartCoroutine(SendRequest(www, resp => tcs.TrySetResult(resp), err => tcs.TrySetException(err)));
+ return tcs.Task;
+ }
+
+ private static UnityWebRequest BuildRequest(string method, Uri uri, IDictionary headers,
+ byte[] body, int timeout)
+ {
+ UnityWebRequest www;
+ if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase))
+ {
+ www = new UnityWebRequest(uri, method)
+ {
+ uploadHandler = new UploadHandlerRaw(body),
+ downloadHandler = new DownloadHandlerBuffer()
+ };
+ }
+ else if (string.Equals(method, "DELETE", StringComparison.OrdinalIgnoreCase))
+ {
+ www = UnityWebRequest.Delete(uri);
+ }
+ else
+ {
+ www = UnityWebRequest.Get(uri);
+ }
+
+ www.SetRequestHeader("Content-Type", "application/json");
+ foreach (var kv in headers)
+ {
+ www.SetRequestHeader(kv.Key, kv.Value);
+ }
+
+ www.timeout = timeout;
+ return www;
+ }
+
+ private static IEnumerator SendRequest(UnityWebRequest www, Action callback,
+ Action errback)
+ {
+ yield return www.SendWebRequest();
+ if (IsNetworkError(www))
+ {
+ errback(new ApiResponseException(www.error));
+ }
+ else if (IsHttpError(www))
+ {
+ if (www.responseCode >= 500)
+ {
+ // TODO think of best way to map HTTP code to GRPC code since we can't rely
+ // on server to process it. Manually adding the mapping to SDK seems brittle.
+ errback(new ApiResponseException((int) www.responseCode, www.downloadHandler.text, -1));
+ www.Dispose();
+ yield break;
+ }
+
+ var decoded = www.downloadHandler.text.FromJson>();
+
+ var e = new ApiResponseException(www.downloadHandler.text);
+
+ if (decoded != null)
+ {
+ var msg = decoded.ContainsKey("message") ? decoded["message"].ToString() : string.Empty;
+ var grpcCode = decoded.ContainsKey("code") ? (int) decoded["code"] : -1;
+
+ e = new ApiResponseException(www.responseCode, msg, grpcCode);
+
+ if (decoded.ContainsKey("error"))
+ {
+ HttpAdapterUtil.CopyResponseError(Instance, decoded["error"], e);
+ }
+ }
+
+ errback(e);
+ }
+ else
+ {
+ callback?.Invoke(www.downloadHandler?.text);
+ }
+
+ www.Dispose();
+ }
+
+ private static bool IsHttpError(UnityWebRequest www)
+ {
+#if UNITY_2020_2_OR_NEWER
+ return www.result == UnityWebRequest.Result.ProtocolError;
+#else
+ return www.isHttpError;
+#endif
+ }
+
+ private static bool IsNetworkError(UnityWebRequest www)
+ {
+#if UNITY_2020_2_OR_NEWER
+ return www.result == UnityWebRequest.Result.ConnectionError;
+#else
+ return www.isNetworkError;
+#endif
+ }
+
+ private static bool IsTransientException(Exception e)
+ {
+ return e is ApiResponseException apiException && (apiException.StatusCode >= 500 || apiException.StatusCode == -1);
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs.meta
new file mode 100644
index 0000000..cf942ef
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 7328a9c3e42f84294ac53e8712835c35
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/Satori/UnityWebRequestAdapter.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityLogger.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityLogger.cs
new file mode 100644
index 0000000..1c108e2
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityLogger.cs
@@ -0,0 +1,36 @@
+// Copyright 2018 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 UnityEngine;
+
+namespace Nakama
+{
+ ///
+ /// A logger which captures using Unity Engine.
+ ///
+ public class UnityLogger : ILogger
+ {
+ ///
+ public void DebugFormat(string format, params object[] args) => Debug.LogFormat(format, args);
+
+ ///
+ public void ErrorFormat(string format, params object[] args) => Debug.LogErrorFormat(format, args);
+
+ ///
+ public void InfoFormat(string format, params object[] args) => Debug.LogFormat(format, args);
+
+ ///
+ public void WarnFormat(string format, params object[] args) => Debug.LogWarningFormat(format, args);
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityLogger.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityLogger.cs.meta
new file mode 100644
index 0000000..3d807d4
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityLogger.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 7286bc6ea2d7f4437bc18f2e5063e1b5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/UnityLogger.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnitySocket.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnitySocket.cs
new file mode 100644
index 0000000..cfb65fb
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnitySocket.cs
@@ -0,0 +1,145 @@
+// Copyright 2021 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 System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Nakama
+{
+ ///
+ /// A WebSocket adapter for Unity that dispatches events to the main thread.
+ ///
+ public sealed class UnitySocket : MonoBehaviour, ISocketAdapter
+ {
+ event Action ISocketAdapter.Connected
+ {
+ add => _connectedHandlers.Add(value);
+
+ remove => _connectedHandlers.Remove(value);
+ }
+
+ event Action ISocketAdapter.Closed
+ {
+ add => _closedHandlers.Add(value);
+
+ remove => _closedHandlers.Remove(value);
+ }
+
+ event Action ISocketAdapter.ReceivedError
+ {
+ add => _errorHandlers.Add(value);
+
+ remove => _errorHandlers.Remove(value);
+ }
+
+ event Action> ISocketAdapter.Received
+ {
+ add => _receivedHandlers.Add(value);
+
+ remove => _receivedHandlers.Remove(value);
+ }
+
+ bool ISocketAdapter.IsConnected => _socketAdapter.IsConnected;
+ bool ISocketAdapter.IsConnecting => _socketAdapter.IsConnecting;
+
+ private readonly ConcurrentQueue _eventQueue = new ConcurrentQueue();
+ private readonly List _connectedHandlers = new List();
+ private readonly List _closedHandlers = new List();
+ private readonly List> _errorHandlers = new List>();
+ private readonly List>> _receivedHandlers = new List>>();
+
+ private ISocketAdapter _socketAdapter;
+
+ public static UnitySocket Create(ISocketAdapter adapter)
+ {
+ var adapterGO = new GameObject("[Nakama Socket]");
+ DontDestroyOnLoad(adapterGO);
+ var unityAdapter = adapterGO.AddComponent();
+ unityAdapter._socketAdapter = adapter;
+ unityAdapter._socketAdapter.Closed += unityAdapter.OnClosed;
+ unityAdapter._socketAdapter.Connected += unityAdapter.OnConnected;
+ unityAdapter._socketAdapter.Received += unityAdapter.OnReceived;
+ unityAdapter._socketAdapter.ReceivedError += unityAdapter.OnReceivedError;
+ return unityAdapter;
+ }
+
+ Task ISocketAdapter.CloseAsync() => _socketAdapter.CloseAsync();
+
+ Task ISocketAdapter.ConnectAsync(Uri uri, int timeout) => _socketAdapter.ConnectAsync(uri, timeout);
+
+ Task ISocketAdapter.SendAsync(ArraySegment buffer, bool reliable, CancellationToken canceller) =>
+ _socketAdapter.SendAsync(buffer, reliable, canceller);
+
+ private void OnClosed() => _eventQueue.Enqueue(new QueuedEvent(_closedHandlers));
+
+ private void OnConnected() => _eventQueue.Enqueue(new QueuedEvent(_connectedHandlers));
+
+ private void OnReceivedError(Exception obj) => _eventQueue.Enqueue(new QueuedEvent(_errorHandlers, obj));
+
+ private void OnReceived(ArraySegment obj)
+ {
+ // copy the segment into a new segment with a new backing array
+ // this avoids threading issues with the socket and unity main thread
+ // accessing the same range within the same backing array.
+
+ var copy = new byte[obj.Count];
+ var j = 0;
+ for (var i = obj.Offset; i < obj.Offset + obj.Count; i++)
+ {
+ copy[j] = obj.Array[i];
+ j++;
+ }
+
+ _eventQueue.Enqueue(new QueuedEvent(_receivedHandlers, new ArraySegment(copy)));
+ }
+
+ private void Update()
+ {
+ while (_eventQueue.Count > 0)
+ {
+ QueuedEvent evt;
+
+ if (_eventQueue.TryDequeue(out evt))
+ {
+ evt.Dispatch();
+ }
+ }
+ }
+
+ private class QueuedEvent
+ {
+ private readonly IEnumerable _listeners;
+ private readonly object[] _eventArgs;
+
+ public QueuedEvent(IEnumerable listeners, params object[] eventArgs)
+ {
+ _listeners = listeners;
+ _eventArgs = eventArgs;
+ }
+
+ public void Dispatch()
+ {
+ var listenersCopy = new List(_listeners);
+ foreach (var listener in listenersCopy)
+ {
+ listener.DynamicInvoke(_eventArgs);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnitySocket.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnitySocket.cs.meta
new file mode 100644
index 0000000..dc46aa5
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnitySocket.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: f3a776f6712cb47c28dd3d0802f283f8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/UnitySocket.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs
new file mode 100644
index 0000000..92a368c
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs
@@ -0,0 +1,233 @@
+// Copyright 2019 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 System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Nakama.TinyJson;
+using UnityEngine;
+using UnityEngine.Networking;
+
+namespace Nakama
+{
+ ///
+ /// Unity web request adapter which uses the UnityWebRequest to send requests.
+ ///
+ ///
+ /// Note Content-Type header is always set as 'application/json'.
+ ///
+ public class UnityWebRequestAdapter : MonoBehaviour, IHttpAdapter
+ {
+ ///
+ public ILogger Logger { get; set; }
+
+ public TransientExceptionDelegate TransientExceptionDelegate => IsTransientException;
+
+ private static readonly object Lock = new object();
+ private static UnityWebRequestAdapter _instance;
+
+ public static UnityWebRequestAdapter Instance
+ {
+ get
+ {
+ lock (Lock)
+ {
+ if (_instance != null) return _instance;
+
+ var go = GameObject.Find("/[Nakama]");
+ if (go == null)
+ {
+ go = new GameObject("[Nakama]");
+ }
+
+ if (go.GetComponent() == null)
+ {
+ go.AddComponent();
+ }
+
+ DontDestroyOnLoad(go);
+ _instance = go.GetComponent();
+ return _instance;
+ }
+ }
+ }
+
+ private UnityWebRequestAdapter()
+ {
+ }
+
+ ///
+ public Task SendAsync(string method, Uri uri, IDictionary headers, byte[] body,
+ int timeout, CancellationToken? cancellationToken)
+ {
+ var www = BuildRequest(method, uri, headers, body, timeout);
+ var tcs = new TaskCompletionSource();
+ cancellationToken?.Register(() => tcs.TrySetCanceled());
+
+ StartCoroutine(
+ SendRequest(
+ www,
+ resp =>
+ {
+ Logger?.InfoFormat(
+ "Received: status=OK, uri='{0}' contents='{1}'",
+ www.uri,
+ resp);
+ tcs.TrySetResult(resp);
+ },
+ err =>
+ {
+ Logger?.ErrorFormat(
+ "Received: status={0}, grpc_status={1}, contents='{2}'",
+ err.StatusCode,
+ err.GrpcStatusCode,
+ err.Message);
+ tcs.TrySetException(err);
+ }
+ )
+ );
+
+ return tcs.Task;
+ }
+
+ private static UnityWebRequest BuildRequest(string method, Uri uri, IDictionary headers,
+ byte[] body, int timeout)
+ {
+ UnityWebRequest www;
+ if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase))
+ {
+ www = new UnityWebRequest(uri, method)
+ {
+ uploadHandler = new UploadHandlerRaw(body),
+ downloadHandler = new DownloadHandlerBuffer()
+ };
+ }
+ else if (string.Equals(method, "DELETE", StringComparison.OrdinalIgnoreCase))
+ {
+ www = UnityWebRequest.Delete(uri);
+ }
+ else
+ {
+ www = UnityWebRequest.Get(uri);
+ }
+
+ www.SetRequestHeader("Content-Type", "application/json");
+ foreach (var kv in headers)
+ {
+ www.SetRequestHeader(kv.Key, kv.Value);
+ }
+
+ www.timeout = timeout;
+ return www;
+ }
+
+ private IEnumerator SendRequest(UnityWebRequest www, Action callback,
+ Action errback)
+ {
+ yield return www.SendWebRequest();
+ if (IsNetworkError(www))
+ {
+ errback(new ApiResponseException(www.error));
+ }
+ else if (IsHttpError(www))
+ {
+ if (www.responseCode >= 500)
+ {
+ // TODO think of best way to map HTTP code to GRPC code since we can't rely
+ // on server to process it. Manually adding the mapping to SDK seems brittle.
+ errback(new ApiResponseException((int) www.responseCode, www.downloadHandler.text, -1));
+ www.Dispose();
+ yield break;
+ }
+
+ Dictionary decoded;
+ try
+ {
+ decoded = www.downloadHandler.text.FromJson>();
+ }
+ catch (Exception exception)
+ {
+ Logger?.ErrorFormat(
+ "Failed to decode error response: {0}. {1}",
+ www.downloadHandler.text,
+ exception.Message);
+ decoded = null;
+ }
+
+ var e = new ApiResponseException(www.downloadHandler.text);
+
+ if (decoded != null)
+ {
+ var msg = decoded.ContainsKey("message") ? decoded["message"].ToString() : string.Empty;
+ var grpcCode = decoded.ContainsKey("code") ? (int) decoded["code"] : -1;
+
+ e = new ApiResponseException(www.responseCode, msg, grpcCode);
+
+ if (decoded.ContainsKey("error"))
+ {
+ IHttpAdapterUtil.CopyResponseError(Instance, decoded["error"], e);
+ }
+ }
+
+ errback(e);
+ }
+ else
+ {
+ callback?.Invoke(www.downloadHandler?.text);
+ }
+
+ www.Dispose();
+ }
+
+ private static bool IsHttpError(UnityWebRequest www)
+ {
+#if UNITY_2020_2_OR_NEWER
+ return www.result == UnityWebRequest.Result.ProtocolError;
+#else
+ return www.isHttpError;
+#endif
+ }
+
+ private static bool IsNetworkError(UnityWebRequest www)
+ {
+#if UNITY_2020_2_OR_NEWER
+ return www.result == UnityWebRequest.Result.ConnectionError;
+#else
+ return www.isNetworkError;
+#endif
+ }
+
+ private static bool IsTransientException(Exception e)
+ {
+ if (e is ApiResponseException apiException)
+ {
+ switch (apiException.StatusCode)
+ {
+ case -1: // No HTTP Status Code. This should correspond directly to `UnityWebRequest.Result.ConnectionError`. Note this will retry on potentially non-transient errors, e.g., wrong URL/scheme/port, wrong SSL certificate, LB misconfiguration. But
+ // we think it's worth retrying despite these cases because there are real transient errors that occur due to temporary connection issues prior to the establishment of an HTTP connection (e.g., during TCP handshake, SSL negotation).
+ case 500: // Internal Server Error often (but not always) indicates a transient issue in Nakama, e.g., DB connectivity.
+ case 502: // LB returns this to client if server sends corrupt/invalid data to LB, which may be a transient issue.
+ case 503: // LB returns this to client if LB determines or is told that server is unable to handle forwarded from LB, which may be a transient issue.
+ case 504: // LB returns this to client if LB cannot communicate with server, which may be a temporary issue.
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs.meta b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs.meta
new file mode 100644
index 0000000..43485f8
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: c23b35d6f5f35479bb9b985f3e4348d8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/Runtime/UnityWebRequestAdapter.cs
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/package.json b/UnityNakamaGroups/Assets/Packages/Nakama/package.json
new file mode 100644
index 0000000..0826180
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "com.heroiclabs.nakama-unity",
+ "version": "3.17.0",
+ "unity": "2022.3",
+ "displayName": "Nakama Unity",
+ "description": "Unity3D client for Nakama and Satori server written in C#.",
+ "keywords": [
+ "app server",
+ "client library",
+ "game server",
+ "nakama",
+ "realtime",
+ "realtime chat"
+ ],
+ "author": {
+ "name": "Heroic Labs",
+ "email": "chris@heroiclabs.com",
+ "url": "https://www.heroiclabs.com"
+ },
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0"
+ }
+}
diff --git a/UnityNakamaGroups/Assets/Packages/Nakama/package.json.meta b/UnityNakamaGroups/Assets/Packages/Nakama/package.json.meta
new file mode 100644
index 0000000..a037a89
--- /dev/null
+++ b/UnityNakamaGroups/Assets/Packages/Nakama/package.json.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 8889c9d7b72c345f38a6eef6423bf0ef
+PackageManifestImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 81338
+ packageName: Nakama
+ packageVersion: 3.15.0
+ assetPath: Assets/Packages/Nakama/package.json
+ uploadId: 773062
diff --git a/UnityNakamaGroups/Assets/UnityNakamaGroups.meta b/UnityNakamaGroups/Assets/UnityNakamaGroups.meta
new file mode 100644
index 0000000..6daece2
--- /dev/null
+++ b/UnityNakamaGroups/Assets/UnityNakamaGroups.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 051a355ed4cf14deb84f9c89447ad30e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/UnityNakamaGroups/Editor.meta b/UnityNakamaGroups/Assets/UnityNakamaGroups/Editor.meta
new file mode 100644
index 0000000..8504a2c
--- /dev/null
+++ b/UnityNakamaGroups/Assets/UnityNakamaGroups/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dd78068405c264cd6beb0ee55cfa0eda
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityNakamaGroups/Assets/UnityNakamaGroups/Editor/AccountSwitcher.cs b/UnityNakamaGroups/Assets/UnityNakamaGroups/Editor/AccountSwitcher.cs
new file mode 100644
index 0000000..03c6c21
--- /dev/null
+++ b/UnityNakamaGroups/Assets/UnityNakamaGroups/Editor/AccountSwitcher.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Nakama;
+using SampleProjects.Groups;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using UnityEngine.UIElements;
+
+namespace SampleProjects.NakamaGroups.Editor
+{
+ public class AccountSwitcherEditor : EditorWindow
+ {
+ [SerializeField] private VisualTreeAsset tree;
+
+ private DropdownField accountDropdown;
+ private Label usernamesLabel;
+
+ private readonly SortedDictionary accountUsernames = new();
+
+ private const string ACCOUNT_USERNAMES_KEY = "AccountSwitcher_Usernames";
+
+ [MenuItem("Tools/Nakama/Account Switcher")]
+ public static void ShowWindow()
+ {
+ var inspectorType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InspectorWindow");
+ var window = GetWindow("Account Switcher", inspectorType);
+
+ window.Focus();
+ }
+
+ [MenuItem("Tools/Nakama/Clear Test Accounts")]
+ public static void ClearSavedAccounts()
+ {
+ EditorPrefs.DeleteKey(ACCOUNT_USERNAMES_KEY);
+ Debug.Log("Cleared all saved account usernames");
+
+ // Refresh any open Account Switcher windows
+ var windows = Resources.FindObjectsOfTypeAll();
+ foreach (var window in windows)
+ {
+ window.accountUsernames.Clear();
+ window.UpdateUsernameLabels();
+ }
+ }
+
+ private void CreateGUI()
+ {
+ tree.CloneTree(rootVisualElement);
+
+ accountDropdown = rootVisualElement.Q("account-dropdown");
+ accountDropdown.RegisterValueChangedCallback(SwitchAccount);
+
+ usernamesLabel = rootVisualElement.Q