├── EdgeMultiplay.unitypackage ├── EdgeMultiplay ├── Examples │ ├── ChatRooms.unitypackage │ ├── CreateSyncedObj.unitypackage │ ├── PingPongExample.unitypackage │ ├── OwnershipExamples.unitypackage │ ├── PlayerTagsExample.unitypackage │ ├── ChatRooms.unitypackage.meta │ ├── CreateSyncedObj.unitypackage.meta │ ├── OwnershipExamples.unitypackage.meta │ ├── PingPongExample.unitypackage.meta │ └── PlayerTagsExample.unitypackage.meta ├── Logo │ ├── MobiledgeX_Icon_Green_Black.png │ └── MobiledgeX_Icon_Green_Black.png.meta ├── CHANGELOG.md.meta ├── README.md.meta ├── Logo.meta ├── Editor.meta ├── Examples.meta ├── Scripts.meta ├── Resources │ ├── ClientSettings.asset.meta │ └── ClientSettings.asset ├── Scripts │ ├── Util.cs.meta │ ├── EdgeMultiplayCallbacks.cs.meta │ ├── ClientSettings.cs.meta │ ├── DataContracts.cs.meta │ ├── EdgeManager.cs.meta │ ├── NetworkedPlayer.cs.meta │ ├── ObservableView.cs.meta │ ├── OrphanObservable.cs.meta │ ├── EdgeMultiplayObserver.cs.meta │ ├── OrphanObservable.cs │ ├── ClientSettings.cs │ ├── ObservableView.cs │ ├── Util.cs │ ├── EdgeMultiplayCallbacks.cs │ ├── EdgeMultiplayObserver.cs │ ├── NetworkedPlayer.cs │ ├── DataContracts.cs │ └── EdgeManager.cs ├── Editor │ ├── TemplateGenerator.cs.meta │ ├── EdgeMultiplayEditorWindow.cs.meta │ ├── EdgeMultiplayEditorWindow.cs │ └── TemplateGenerator.cs ├── README.md └── CHANGELOG.md ├── License.md ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore └── README.md /EdgeMultiplay.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay.unitypackage -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/ChatRooms.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay/Examples/ChatRooms.unitypackage -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/CreateSyncedObj.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay/Examples/CreateSyncedObj.unitypackage -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/PingPongExample.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay/Examples/PingPongExample.unitypackage -------------------------------------------------------------------------------- /EdgeMultiplay/Logo/MobiledgeX_Icon_Green_Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay/Logo/MobiledgeX_Icon_Green_Black.png -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/OwnershipExamples.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay/Examples/OwnershipExamples.unitypackage -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/PlayerTagsExample.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledgex/edge-multiplay-unity-client/HEAD/EdgeMultiplay/Examples/PlayerTagsExample.unitypackage -------------------------------------------------------------------------------- /EdgeMultiplay/CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c2fe01ccd3c4f430b9b01e0ecb5db2ed 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d0964b4287884a838d149aaf6e47f9b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/Logo.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 21f65ab52044b44f1bc12310e54ba08f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /EdgeMultiplay/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ba8eb8cb4a65416a9b43c09175777b3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /EdgeMultiplay/Examples.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1136a6a873ebb4f8abbb390cbd1e153c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/ChatRooms.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d4b78dab91914f2bae094a7fb14c5e1 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2213faac1190a4aceb8300a7636b1949 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/CreateSyncedObj.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdf2923fe4a424321b6b28536875606f 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/OwnershipExamples.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2eb647807aa4f42b6a85368b54b39be3 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/PingPongExample.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 33837d54e339943289dc3161129fadcf 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/Examples/PlayerTagsExample.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a35455f76929429a9990a725f5505a8 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EdgeMultiplay/Resources/ClientSettings.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f03ae220e86bb4bf085351cad47307aa 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 11400000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/Util.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dda4aa854f0f348deba999f8a1c17898 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/EdgeMultiplayCallbacks.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f7437c7a6c65c4bcb9f1d895da82b2ed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/ClientSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7def378b872384b5b9526675226ca841 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/DataContracts.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 037d2555e62ff4453a856a154f442338 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Editor/TemplateGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 48297db3f38cf4f9e968c7c128b4d44f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Editor/EdgeMultiplayEditorWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 467884a11b4fe4afc80c265d46d20b40 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/EdgeManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6647f8771f53471d9d27799b03112a0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: f6328cde825c8425190e0ad08c7b3602, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/NetworkedPlayer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 30ed73c13f422450ab5056537c9ac3df 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: f6328cde825c8425190e0ad08c7b3602, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/ObservableView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73bb1828891e84c5c8a99ab171b54a09 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: f6328cde825c8425190e0ad08c7b3602, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/OrphanObservable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e71c409e7249c44a98033e8881038970 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: f6328cde825c8425190e0ad08c7b3602, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/EdgeMultiplayObserver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a804ed59794df49859448f348fea5a6c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: f6328cde825c8425190e0ad08c7b3602, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EdgeMultiplay/Resources/ClientSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: ef8ab0f75f1374aeb8bd6b39cb726256, type: 3} 13 | m_Name: ClientSettings 14 | m_EditorClassIdentifier: 15 | useLocalHostServer: 0 16 | hostIPAddress: 127.0.0.1 17 | WebSocketPort: 3000 18 | StatisticsPort: 7776 19 | UDPPort: 5000 20 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 2 | MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: Feature Request 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /EdgeMultiplay/README.md: -------------------------------------------------------------------------------- 1 | To Connect your Unity Project to EdgeMultiplay Server, You have 3 options: 2 | 3 | 1. Using our sample EdgeMultiplay server: 4 | From Unity Toolbar Select MobiledgeX/Setup: 5 | OrgName: MobiledgeX-Samples 6 | AppName: EdgeMultiplay 7 | AppVers: (Same as EdgeMultiplay/Version in Unity Toolbar) 8 | 9 | 2. Run the server locally on your machine: 10 | for more instructions, please refer to https://github.com/mobiledgex/edge-multiplay-node-server. 11 | 12 | 3. You can deploy your server to MobiledgeX and use your own server defintions. 13 | 14 | *Note: 15 | For option 1 you will be using a shared server between other developers. 16 | 17 | If you encountered problems, please reach out to us on discord 18 | https://discord.com/invite/CHCWfgrxh6 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | [Ll]ogs/ 7 | 8 | # Uncomment this line if you wish to ignore the asset store tools plugin 9 | # [Aa]ssets/AssetStoreTools* 10 | 11 | # Visual Studio cache directory 12 | .vs/ 13 | 14 | # Visual Studio Code 15 | .vscode/ 16 | 17 | # Gradle cache directory 18 | .gradle/ 19 | 20 | # Autogenerated VS/MD/Consulo solution and project files 21 | ExportedObj/ 22 | .consulo/ 23 | *.csproj 24 | *.unityproj 25 | *.sln 26 | *.suo 27 | *.tmp 28 | *.user 29 | *.userprefs 30 | *.pidb 31 | *.booproj 32 | *.svd 33 | *.pdb 34 | *.mdb 35 | *.opendb 36 | *.VC.db 37 | *.DS_Store 38 | # Unity3D generated meta files 39 | *.pidb.meta 40 | *.pdb.meta 41 | *.mdb.meta 42 | 43 | # Unity3D generated file on crash reports 44 | sysinfo.txt 45 | 46 | # Builds 47 | *.apk 48 | 49 | # Crashlytics generated file 50 | crashlytics-build.properties 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Smartphone (please complete the following information):** 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Version [e.g. 22] 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![NPM](https://img.shields.io/npm/l/edge-multiplay) 2 | ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/mobiledgexsamples/edge-multiplay?sort=semver) 3 | 4 | follow on Twitter 5 | 6 | 7 | chat on Discord 8 | 9 | 10 | 11 | # EdgeMultiplay-UnityClient 12 | 13 | EdgeMultiplay is a simple solution for creating multiplayer experiences on Edge. 14 | This repository contains the unity client of the EdgeMultiplay Solution. 15 | 16 | ## Prerequisites 17 | 18 | * Unity 2019.2 or newer, along with selected platforms (iOS, Android) for your project 19 | * The Solution is compatible with (IL2CPP & .NET 2.0) , (IL2CPP & .NET 4.x) , (Mono & .NET 2.0) **but not compatible with (Mono & .NET 4.x)** 20 | * [MobiledgeX Unity SDK](https://github.com/mobiledgex/edge-cloud-sdk-unity). 21 | 22 | ## Documentation 23 | To get started, Check [EdgeMultiplay Github Page ](https://mobiledgex.github.io/edge-multiplay-unity-client/) 24 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/OrphanObservable.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using UnityEngine; 19 | 20 | namespace EdgeMultiplay 21 | { 22 | /// 23 | /// Add OrphanObservable to GameObjects that don't have an owner at the time of creation (Ex. created in Unity Editor) 24 | /// the default owner of this observable is the first player in the room, You can change this in SetOwnerShip() below 25 | /// 26 | [AddComponentMenu("EdgeMultiplay/OrphanObservable")] 27 | public class OrphanObservable : MonoBehaviour 28 | { 29 | public Observable observable; 30 | 31 | void Update() 32 | { 33 | if (observable != null && observable.owner == null) 34 | { 35 | if (EdgeManager.gameStarted) 36 | { 37 | observable.SetOwnership(EdgeManager.GetPlayer(0).playerId);//owner is first player in the room 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/ClientSettings.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | namespace EdgeMultiplay 4 | { 5 | [Serializable] 6 | public class ClientSettings : ScriptableObject 7 | { 8 | [Header("Host Configuration", order = 10)] 9 | /// 10 | /// Set to true if you have EdgeMultiplay Server running on your machine 11 | /// 12 | [Tooltip("Set to true if you have EdgeMultiplay Server running on your machine")] 13 | public bool useLocalHostServer; 14 | 15 | /// 16 | /// Use 127.0.0.1 as your IP Address for testing on your computer only 17 | /// Or use the Host IP Address for testing between your Computer and devices connected to the same WifiNetwork 18 | /// For Mac : Open Terminal and type "ifconfig" and copy the "en0" address 19 | /// For Windows : Open CMD and type "Ipconfig /all" and copy the "IPV4" address 20 | /// 21 | [Tooltip("Use 127.0.0.1 as your IP Address for testing on you computer only,\n" + 22 | "Or use the Host IP Address for testing between your Computer and devices connected to the same WifiNetwork\n" + 23 | "For Mac : Open Terminal and type \"ifconfig\" and copy the \"en0\" address \n" + 24 | "For Windows : Open CMD and type \"Ipconfig /all \" and copy the \"IPV4\" address ")] 25 | public string hostIPAddress; 26 | 27 | [Header("Ports Configuration", order = 20)] 28 | public int WebSocketPort; 29 | public int StatisticsPort; 30 | public int UDPPort; 31 | } 32 | 33 | public class Configs 34 | { 35 | public static ClientSettings clientSettings = Resources.Load("ClientSettings"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/ObservableView.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using UnityEngine; 19 | 20 | namespace EdgeMultiplay 21 | { 22 | /// 23 | /// ObservableView is added by default to Observered objects,holding the reference to the observable owner and the observable index in in the observer.observable list 24 | /// 25 | [AddComponentMenu("EdgeMultiplay/ObservableView")] 26 | public class ObservableView : MonoBehaviour 27 | { 28 | public string ownerId; 29 | public int observableIndex; //the observable index in in the observer.observable list 30 | 31 | public void SetupObservableView(string ownerId, int observableIndex) 32 | { 33 | this.ownerId = ownerId; 34 | this.observableIndex = observableIndex; 35 | } 36 | 37 | public bool OwnerIsLocalPlayer() 38 | { 39 | if (EdgeManager.localPlayer.playerId == ownerId) 40 | { 41 | return true; 42 | } 43 | else 44 | { 45 | return false; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /EdgeMultiplay/Logo/MobiledgeX_Icon_Green_Black.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6328cde825c8425190e0ad08c7b3602 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 10 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: -1 38 | wrapV: -1 39 | wrapW: -1 40 | nPOTScale: 1 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 0 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 0 53 | spriteTessellationDetail: -1 54 | textureType: 0 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 3 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | forceMaximumCompressionQuality_BC6H_BC7: 0 73 | spriteSheet: 74 | serializedVersion: 2 75 | sprites: [] 76 | outline: [] 77 | physicsShape: [] 78 | bones: [] 79 | spriteID: 80 | internalID: 0 81 | vertices: [] 82 | indices: 83 | edges: [] 84 | weights: [] 85 | secondaryTextures: [] 86 | spritePackingTag: 87 | pSDRemoveMatte: 0 88 | pSDShowRemoveMatteOption: 0 89 | userData: 90 | assetBundleName: 91 | assetBundleVariant: 92 | -------------------------------------------------------------------------------- /EdgeMultiplay/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this EdgeMultiplay Unity Client will be documented in this file. 3 | 4 | ## [1.3.0] - 2021-11-13 5 | 6 | ### Fixes & Improvements: 7 | - Proper error logs for calling EdgeManager functions before OnGameStart() callback. 8 | 9 | ### New Features: 10 | - EdgeManager.CreateRoom() now have a new argument minPlayersToStartGame (The minimum players threshold to start a game, new players will be able to join the running game as long as the room members count is below maxPlayersPerRoom threshold. 11 | - New ClientSettings menu, where you can set your ports and configure your host address.(Located under EdgeMultiplay/ClientSettings) 12 | - New Server Event rooms-updated, use OnRoomsUpdated() callback to get the latest status of rooms in the lobby. 13 | 14 | 15 | ## [1.2.0] - 2021-05-05 16 | 17 | ### Fixes & Improvements: 18 | - Fixed App Quiting error on PC/Mac Unity Player. 19 | - Fixed customizable fallback location for Oculus/Non Phone devices in your GameManager.cs 20 | ``` 21 | EdgeManager.integration.useFallbackLocation = true; 22 | EdgeManager.integration.setFallbackLocation(longitude, latitude); 23 | ConnectToEdge(useFallBackLocation:true); 24 | ``` 25 | - Use SendGamePlayEvent() and SendGamePlayEventUDP() in your PlayerManager to SendGamePlayEvents. 26 | 27 | ### New Features: 28 | - Server Stats : View analytics of an Edge Multiplay instance in a web browser. To view the closest instance's stats, click on the menu button Server Stats under the EdgeMultiplay menu in the Unity Editor. 29 | - New Ownership Transfer options : TakeOver and RequestOwnership. 30 | - UpdateRate (Fixed Update or EveryFrame) for EdgeMultiplayObserver. 31 | - New Example Scenes added (Chat Rooms, OwnershipExamples) 32 | - New notification callback added OnRoomRemovedFromLobby() 33 | 34 | 35 | ## [1.1.0] - 2021-04-01 36 | 37 | ### Fix & Improvements. 38 | - Custom Tags (App Dependent Tags) added to Players. 39 | - Observables ownership transfer, Creating observables at runtime. 40 | - Added Compatibility with Oculus SDK. 41 | - OrphanObservable added, Add OrphanObservable Component to GameObjects that don't have an owner at the time of creation (Ex. created in Unity Editor). 42 | - Example Scenes added (Creating synced objects at runtime, ownership transfer) 43 | 44 | 45 | ## [1.0.0] - 2021-02-12 46 | 47 | ### Fix & Improvements. 48 | - First release for EdgeMultiplay Unity Client 49 | - Getting started tutorials added https://www.youtube.com/watch?v=9kMz6Q3g0xQ&list=PLwUZZfaECSv18E5d0ooDR7S8416pImW8W 50 | - Example Scenes added (ChatExample, PingPongExample, ARPingPongExample) 51 | -------------------------------------------------------------------------------- /EdgeMultiplay/Editor/EdgeMultiplayEditorWindow.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System.IO; 19 | using System.Threading.Tasks; 20 | using UnityEditor; 21 | using UnityEngine; 22 | using MobiledgeX; 23 | using DistributedMatchEngine; 24 | 25 | namespace EdgeMultiplay 26 | { 27 | [ExecuteInEditMode] 28 | public class EdgeMultiplayEditorWindow : EditorWindow 29 | { 30 | 31 | #region EdgeMultiplay ToolBar Menu items 32 | 33 | [MenuItem("EdgeMultiplay/Getting Started", false, 0)] 34 | public static void OpenGettingStartedURL() 35 | { 36 | Application.OpenURL("https://www.youtube.com/playlist?list=PLwUZZfaECSv18E5d0ooDR7S8416pImW8W"); 37 | } 38 | 39 | [MenuItem("EdgeMultiplay/Examples/PingPong", false, 40)] 40 | public static void ImportPingPongExample() 41 | { 42 | string assetsFolder = Path.GetFullPath(Application.dataPath); 43 | AssetDatabase.ImportPackage(Path.Combine(assetsFolder, "EdgeMultiplay/Examples/PingPongExample.unitypackage"), true); 44 | } 45 | 46 | [MenuItem("EdgeMultiplay/Examples/ChatRooms", false, 40)] 47 | public static void ImportChatExample() 48 | { 49 | string assetsFolder = Path.GetFullPath(Application.dataPath); 50 | AssetDatabase.ImportPackage(Path.Combine(assetsFolder, "EdgeMultiplay/Examples/ChatRooms.unitypackage"), true); 51 | } 52 | 53 | [MenuItem("EdgeMultiplay/Examples/OwnershipExamples", false, 40)] 54 | public static void ImportOwnershipExamples() 55 | { 56 | string assetsFolder = Path.GetFullPath(Application.dataPath); 57 | AssetDatabase.ImportPackage(Path.Combine(assetsFolder, "EdgeMultiplay/Examples/OwnershipExamples.unitypackage"), true); 58 | } 59 | 60 | [MenuItem("EdgeMultiplay/Docs/How It Works?", false, 20)] 61 | public static void OpenHowItWorksURL() 62 | { 63 | Application.OpenURL("https://mobiledgex.github.io/edge-multiplay-unity-client/how_it_works.html"); 64 | } 65 | 66 | [MenuItem("EdgeMultiplay/Docs/Documentation", false, 20)] 67 | public static void OpenDocumentationURL() 68 | { 69 | Application.OpenURL("https://mobiledgex.github.io/edge-multiplay-unity-client/"); 70 | } 71 | 72 | [MenuItem("EdgeMultiplay/Join the Community", false, 20)] 73 | public static void JoinTheCommunity() 74 | { 75 | Application.OpenURL("https://discord.gg/CHCWfgrxh6"); 76 | } 77 | 78 | [MenuItem("EdgeMultiplay/Report a bug", false, 60)] 79 | public static void ReportBug() 80 | { 81 | Application.OpenURL("https://github.com/mobiledgex/edge-multiplay-unity-client/issues/new/choose"); 82 | } 83 | 84 | [MenuItem("EdgeMultiplay/ClientSettings", false, 0)] 85 | public static void ShowSettings() 86 | { 87 | Selection.objects = new Object[] { Configs.clientSettings }; 88 | EditorGUIUtility.PingObject(Configs.clientSettings); 89 | } 90 | 91 | [MenuItem("EdgeMultiplay/Server Stats", false, 80)] 92 | public static async void ServerStats() 93 | { 94 | if (EditorUtility.DisplayDialog("EdgeMultiplay", "Where are you are running the server?", "Locally", "MobiledgeX")) 95 | { 96 | Application.OpenURL("http://localhost:" + Configs.clientSettings.StatisticsPort); 97 | } 98 | else 99 | { 100 | string fqdn = await GetMobiledgeXAppFQDN(); 101 | if (fqdn == "") 102 | { 103 | Debug.LogError("Make sure MobiledgeX Settings AppDefs and Region are correct, Check the console for more details."); 104 | } 105 | else 106 | { 107 | Application.OpenURL("http://" + fqdn + ":" + Configs.clientSettings.StatisticsPort); 108 | } 109 | } 110 | } 111 | 112 | [MenuItem("EdgeMultiplay/Version 1.3", false, 80)] 113 | public static void Version() 114 | { 115 | //placeholder for version number 116 | } 117 | 118 | #endregion 119 | 120 | #region Helper functions 121 | 122 | public static async Task GetMobiledgeXAppFQDN() 123 | { 124 | MobiledgeXIntegration integration = new MobiledgeXIntegration(); 125 | try 126 | { 127 | await integration.RegisterAndFindCloudlet(); 128 | Debug.Log("FindCloudletReply.status = " + integration.FindCloudletReply.status); 129 | if (integration.FindCloudletReply.status == FindCloudletReply.FindStatus.FIND_FOUND) 130 | { 131 | return integration.FindCloudletReply.fqdn; 132 | } 133 | else 134 | { 135 | return ""; 136 | } 137 | } 138 | 139 | //RegisterClientException is thrown if your app is not found 140 | catch (RegisterClientException rce) 141 | { 142 | Debug.LogError("RegisterClientException: " + rce.Message + "Inner Exception: " + rce.InnerException); 143 | return ""; 144 | } 145 | 146 | //FindCloudletException is thrown if there is no app instance in the selected region 147 | catch (FindCloudletException fce) 148 | { 149 | Debug.LogError("FindCloudletException: " + fce.Message + "Inner Exception: " + fce.InnerException); 150 | return ""; 151 | } 152 | } 153 | } 154 | } 155 | 156 | #endregion 157 | -------------------------------------------------------------------------------- /EdgeMultiplay/Editor/TemplateGenerator.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using UnityEditor; 19 | using UnityEngine; 20 | using System; 21 | using System.IO; 22 | namespace EdgeMultiplay 23 | { 24 | public class TemplateGenerator 25 | { 26 | [MenuItem("Assets/Create/EdgeMultiplay/GameManager", false, 1)] 27 | static void CreateGameManager() 28 | { 29 | string fileLocation = GetDirectoryPath() + "/GameManager.cs"; 30 | if (File.Exists(fileLocation) == false) 31 | { 32 | using (StreamWriter outfile = 33 | new StreamWriter(fileLocation)) 34 | { 35 | outfile.WriteLine("using UnityEngine;"); 36 | outfile.WriteLine("using EdgeMultiplay;"); 37 | outfile.WriteLine(" "); 38 | outfile.WriteLine("[RequireComponent(typeof(EdgeManager))]"); 39 | outfile.WriteLine("public class GameManager : EdgeMultiplayCallbacks {"); 40 | outfile.WriteLine(" "); 41 | outfile.WriteLine(" "); 42 | outfile.WriteLine(" // Use this for initialization"); 43 | outfile.WriteLine(" void Start () {"); 44 | outfile.WriteLine(" ConnectToEdge();"); 45 | outfile.WriteLine(" }"); 46 | outfile.WriteLine(" "); 47 | outfile.WriteLine(" // Called once connected to your server deployed on Edge"); 48 | outfile.WriteLine(" public override void OnConnectionToEdge(){"); 49 | outfile.WriteLine(" print (\"Connected to server deployed on Edge\");"); 50 | outfile.WriteLine(" }"); 51 | outfile.WriteLine(" "); 52 | outfile.WriteLine(" // Called once the server registers the player right after the connection is established"); 53 | outfile.WriteLine(" public override void OnRegisterEvent(){"); 54 | outfile.WriteLine(" print (\"Game Session received from server\");"); 55 | outfile.WriteLine(" EdgeManager.JoinOrCreateRoom(\"playerName\", 0, 2);"); 56 | outfile.WriteLine(" }"); 57 | outfile.WriteLine(" "); 58 | outfile.WriteLine(" // Called once the JoinRoom request succeeded "); 59 | outfile.WriteLine(" public override void OnRoomJoin(Room room){"); 60 | outfile.WriteLine(" print (\"Joined room\");"); 61 | outfile.WriteLine(" print (\"Maximum Players in the room :\"+ room.maxPlayersPerRoom); "); 62 | outfile.WriteLine(" print (\"Count of Players in the room :\"+ room.roomMembers.Count); "); 63 | outfile.WriteLine(" }"); 64 | outfile.WriteLine(" "); 65 | outfile.WriteLine(" // Called once the CreateRoom request succeeded "); 66 | outfile.WriteLine(" public override void OnRoomCreated(Room room){"); 67 | outfile.WriteLine(" print (\"Created a room\");"); 68 | outfile.WriteLine(" print (\"Maximum Players in the room :\"+ room.maxPlayersPerRoom); "); 69 | outfile.WriteLine(" print (\"Count of Players in the room :\"+ room.roomMembers.Count); "); 70 | outfile.WriteLine(" }"); 71 | outfile.WriteLine(" "); 72 | outfile.WriteLine(" // Called once the Game start on the server"); 73 | outfile.WriteLine(" // The game starts on the server once the count of room members reachs the maximum players per room"); 74 | outfile.WriteLine(" public override void OnGameStart(){"); 75 | outfile.WriteLine(" print (\"Game Started\"); "); 76 | outfile.WriteLine(" }"); 77 | outfile.WriteLine(" "); 78 | outfile.WriteLine("}"); 79 | } 80 | } 81 | AssetDatabase.Refresh(); 82 | } 83 | 84 | [MenuItem("Assets/Create/EdgeMultiplay/PlayerManager",false, 1)] 85 | static void CreatePlayerManager() 86 | { 87 | string fileLocation = GetDirectoryPath() + "/PlayerManager.cs"; 88 | if (File.Exists(fileLocation) == false) 89 | { 90 | using (StreamWriter outfile = 91 | new StreamWriter(fileLocation)) 92 | { 93 | outfile.WriteLine("using UnityEngine;"); 94 | outfile.WriteLine("using EdgeMultiplay;"); 95 | outfile.WriteLine(" "); 96 | outfile.WriteLine("public class PlayerManager : NetworkedPlayer {"); 97 | outfile.WriteLine(" "); 98 | outfile.WriteLine(" // Use this for initialization"); 99 | outfile.WriteLine(" void OnEnable () {"); 100 | outfile.WriteLine(" ListenToMessages();"); 101 | outfile.WriteLine(" }"); 102 | outfile.WriteLine(" "); 103 | outfile.WriteLine(" // Once the GameObject is destroyed"); 104 | outfile.WriteLine(" void OnDestroy () {"); 105 | outfile.WriteLine(" StopListening();"); 106 | outfile.WriteLine(" }"); 107 | outfile.WriteLine(" "); 108 | outfile.WriteLine(" // Called once a GamePlay Event is received from the server"); 109 | outfile.WriteLine(" public override void OnMessageReceived(GamePlayEvent gamePlayEvent){"); 110 | outfile.WriteLine(" print (\"GamePlayEvent received from server, event name: \" + gamePlayEvent.eventName );"); 111 | outfile.WriteLine(" }"); 112 | outfile.WriteLine(" "); 113 | outfile.WriteLine("}"); 114 | } 115 | } 116 | AssetDatabase.Refresh(); 117 | } 118 | 119 | static string GetDirectoryPath() 120 | { 121 | string path = ""; 122 | var obj = Selection.activeObject; 123 | if (obj == null) path = "Assets"; 124 | else path = AssetDatabase.GetAssetPath(obj.GetInstanceID()); 125 | if (path.Length > 0) 126 | { 127 | if (Directory.Exists(path)) 128 | { 129 | return path; 130 | } 131 | else 132 | { 133 | string[] pathFolders = path.Split('/'); 134 | path = ""; 135 | for (int i = 0; i < pathFolders.Length - 1; i++) 136 | { 137 | if (i == 0) 138 | { 139 | path += pathFolders[i]; 140 | } 141 | else 142 | { 143 | path += "/" + pathFolders[i]; 144 | } 145 | } 146 | return path; 147 | } 148 | 149 | } 150 | else 151 | { 152 | throw new Exception("EdgeMultiplay: Can't create files outside the Assets folder"); 153 | } 154 | 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/Util.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using UnityEngine; 20 | 21 | namespace EdgeMultiplay 22 | { 23 | public class Util 24 | { 25 | /// 26 | /// Extracts position data as float array [3] from a transform component 27 | /// 28 | /// Transform Component 29 | /// float array of size 3 containing transform.position data 30 | public static float[] GetPositionData(Transform transformComponent) 31 | { 32 | return new float[3] { transformComponent.position.x, transformComponent.position.y, transformComponent.position.z }; 33 | } 34 | 35 | /// 36 | /// Extracts rotation euler's angles as float array [3] from a transform component 37 | /// 38 | /// Transform Component 39 | /// float array of size 3 containing transform.rotation.eulerAngles data 40 | public static float[] GetRotationEulerData(Transform transformComponent) 41 | { 42 | return new float[3] { transformComponent.rotation.eulerAngles.x, transformComponent.rotation.eulerAngles.y, transformComponent.rotation.eulerAngles.z }; 43 | } 44 | 45 | /// 46 | /// Extracts position and rotation euler's angles as float array [6] from a transform component 47 | /// 48 | /// Transform Component 49 | /// float array of size 6 containing transform.position data[3] and transform.rotation.eulerAngles[3] data respectively 50 | public static float[] GetPositionAndRotationData(Transform transformComponent) 51 | { 52 | return new float[6] {transformComponent.position.x, transformComponent.position.y, transformComponent.position.z, 53 | transformComponent.rotation.eulerAngles.x, transformComponent.rotation.eulerAngles.y, transformComponent.rotation.eulerAngles.z }; 54 | } 55 | 56 | /// 57 | /// Extracts local position data as float array [3] from a transform component 58 | /// 59 | /// Transform Component 60 | /// float array of size 3 containing transform.localPosition data 61 | public static float[] GetLocalPositionData(Transform transformComponent) 62 | { 63 | return new float[3] { transformComponent.localPosition.x, transformComponent.localPosition.y, transformComponent.localPosition.z }; 64 | } 65 | 66 | /// 67 | /// Extracts local rotation euler's angles as float array [3] from a transform component 68 | /// 69 | /// Transform Component 70 | /// float array of size 3 containing transform.localRotation data 71 | public static float[] GetLocalRotationData(Transform transformComponent) 72 | { 73 | return new float[3] { transformComponent.localRotation.eulerAngles.x, transformComponent.localRotation.eulerAngles.y, transformComponent.localRotation.eulerAngles.z }; 74 | } 75 | 76 | /// 77 | /// Extracts local position and local rotation euler's angles as float array [6] from a transform component 78 | /// 79 | /// Transform Component 80 | /// float array of size 6 containing transform.localPosition data[3] and transform.localRotation.eulerAngles[3] data respectively 81 | public static float[] GetLocalPositionAndRotationData(Transform transformComponent) 82 | { 83 | return new float[6] {transformComponent.localPosition.x, transformComponent.localPosition.y, transformComponent.localPosition.z, 84 | transformComponent.localRotation.eulerAngles.x, transformComponent.localRotation.eulerAngles.y, transformComponent.localRotation.eulerAngles.z }; 85 | } 86 | 87 | /// 88 | /// Sets the local position of the supplied transform component to the supplied targetLocalPositon 89 | /// 90 | /// Transform Component 91 | /// Vector3 object representing local position 92 | public static void SetLocalPostion(Transform transformComponent, Vector3 targetLocalPosition) 93 | { 94 | var tempPosition = transformComponent.localPosition; 95 | tempPosition.Set(targetLocalPosition.x, targetLocalPosition.y, targetLocalPosition.z); 96 | transformComponent.localPosition = tempPosition; 97 | } 98 | 99 | /// 100 | /// Sets the local rotation of the supplied transform component to the supplied targetLocalRotation euler's angles 101 | /// 102 | /// Transform Component 103 | /// Vector3 object representing local rotation euler's angles 104 | public static void SetLocalRotation(Transform transformComponent, Vector3 targetRotationEulers) 105 | { 106 | Quaternion tempRotation = transformComponent.localRotation; 107 | Quaternion rot = Quaternion.Euler(targetRotationEulers); 108 | tempRotation.Set(rot.x, rot.y, rot.z, rot.w); 109 | transformComponent.localRotation = tempRotation; 110 | } 111 | 112 | /// 113 | /// Converts a Vector3 to a float array of size 3 114 | /// 115 | /// Vector3 object 116 | /// float array of size 3 117 | public static float[] ConvertVector3ToFloatArray(Vector3 vector3) 118 | { 119 | return new float[3] { vector3.x, vector3.y, vector3.z }; 120 | } 121 | 122 | /// 123 | /// Converts a quaternion to a float array of size 4 -> (x,y,z,w) 124 | /// 125 | /// Quaternion object 126 | /// float array of size 4 127 | public static float[] ConvertQuaternionToFloatArray(Quaternion quaternion) 128 | { 129 | return new float[4] { quaternion.x, quaternion.y, quaternion.z, quaternion.w }; 130 | } 131 | 132 | /// 133 | /// Returns a Vector 3 from the supplied floatArray starting from the supplied startIndex, your floatArray size must be larger than 2 134 | /// 135 | /// the start index of the Vector3 in the float array 136 | /// Vector3 Object 137 | public static Vector3 ConvertFloatArrayToVector3(float[] floatArray, int startIndex = 0) 138 | { 139 | if (floatArray.Length > (startIndex + 2)) 140 | { 141 | return new Vector3(floatArray[startIndex], floatArray[startIndex + 1], floatArray[startIndex + 2]); 142 | } 143 | else 144 | { 145 | throw new Exception("floatArray starting from the start index doesn't qualify to create a Vector3"); 146 | } 147 | } 148 | 149 | /// 150 | /// Returns a Quaternion from the supplied floatArray starting from the supplied startIndex, your floatArray size must be larger than 3 151 | /// 152 | /// the start index of the Quaternion in the float array 153 | /// Quaternion Object 154 | public static Quaternion ConvertFloatArrayToQuaternion(float[] floatArray, int startIndex = 0) 155 | { 156 | if (floatArray.Length > (startIndex + 2)) 157 | { 158 | return new Quaternion(floatArray[startIndex], floatArray[startIndex + 1], floatArray[startIndex + 2], floatArray[startIndex + 3]); 159 | } 160 | else 161 | { 162 | throw new Exception("floatArray starting from the start index doesn't qualify to create a Vector3"); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/EdgeMultiplayCallbacks.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using UnityEngine; 19 | using System; 20 | using System.Collections; 21 | using System.Collections.Generic; 22 | 23 | namespace EdgeMultiplay 24 | { 25 | public class EdgeMultiplayCallbacks : MonoBehaviour 26 | { 27 | public static Action connectedToEdge; 28 | public static Action failureToConnect; 29 | public static Action registerEvent; 30 | public static Action notificationEvent; 31 | public static Action eventReceived; 32 | public static Action udpEventReceived; 33 | public static Action playerLeft; 34 | public static Action> roomsList; 35 | public static Action roomCreated; 36 | public static Action roomJoin; 37 | public static Action playerRoomJoined; 38 | public static Action joinRoomFaliure; 39 | public static Action newRoomCreatedInLobby; 40 | public static Action roomRemovedFromLobby; 41 | public static Action roomsUpdated; 42 | public static Action leftRoom; 43 | public static Action gameStart; 44 | public static Action gameEnd; 45 | public static Action newObservableCreated; 46 | 47 | /// 48 | /// Starts the connection to your Edge server, server discovery is based on GPS location and the telecommunication carrier 49 | /// 50 | /// True by default, set to false to connect to a specific carrier, set carrier name using EdgeManager.integration.carrierName 51 | /// False by default, location is acquired from user GPS location, if you are using location blind device like Oculus, use EdgeManager.integration.SetFallbackLocation() 52 | /// You can specify a path for your connection to be verified on the server side 53 | public void ConnectToEdge(bool useAnyCarrierNetwork = true, bool useFallBackLocation = false, string path = "") 54 | { 55 | connectedToEdge += OnConnectionToEdge; 56 | failureToConnect += OnFaliureToConnect; 57 | registerEvent += OnRegisterEvent; 58 | notificationEvent += OnNotificationEvent; 59 | roomsList += OnRoomsListReceived; 60 | newRoomCreatedInLobby += OnNewRoomCreatedInLobby; 61 | roomRemovedFromLobby += OnRoomRemovedFromLobby; 62 | roomCreated += OnRoomCreated; 63 | roomJoin += OnRoomJoin; 64 | playerRoomJoined += PlayerJoinedRoom; 65 | joinRoomFaliure += OnJoinRoomFailed; 66 | gameStart += OnGameStart; 67 | roomsUpdated += OnRoomsUpdated; 68 | playerLeft += OnPlayerLeft; 69 | leftRoom += OnLeftRoom; 70 | eventReceived += OnWebSocketEventReceived; 71 | udpEventReceived += OnUDPEventReceived; 72 | newObservableCreated += OnNewObservableCreated; 73 | StartCoroutine(ConnectToEdgeCoroutine(useAnyCarrierNetwork, useFallBackLocation, path)); 74 | } 75 | 76 | IEnumerator ConnectToEdgeCoroutine(bool useAnyCarrierNetwork = true, bool useFallBackLocation = false, string path = "") 77 | { 78 | EdgeManager edgeManager = FindObjectOfType(); 79 | if (Configs.clientSettings.useLocalHostServer == false) 80 | { 81 | if (SystemInfo.supportsLocationService) 82 | { 83 | yield return StartCoroutine(MobiledgeX.LocationService.EnsureLocation()); 84 | } 85 | else 86 | { 87 | useFallBackLocation = true; 88 | yield return null; 89 | } 90 | } 91 | edgeManager.ConnectToServer(useAnyCarrierNetwork, useFallBackLocation, path).ConfigureAwait(false); 92 | } 93 | 94 | /// 95 | /// Called once the ConnectToEdge Request succeed 96 | /// 97 | public virtual void OnConnectionToEdge() 98 | { 99 | Debug.Log("Connected to Edge"); 100 | } 101 | 102 | /// 103 | /// Called once the ConnectToEdge Request fails 104 | /// 105 | /// Reason of connection faliure 106 | public virtual void OnFaliureToConnect(string reason) 107 | { 108 | Debug.Log("Edge Connection Falied because " + reason); 109 | } 110 | 111 | /// 112 | /// Called once the server assigned a playerId to the user right after the connection is established 113 | /// 114 | public virtual void OnRegisterEvent() 115 | { 116 | Debug.Log("Register Event From Server"); 117 | } 118 | 119 | /// 120 | /// Called once a notification is received from the server 121 | /// 122 | /// the notification received from the server 123 | public virtual void OnNotificationEvent(Notification notification) 124 | { 125 | Debug.Log("Notification Event From Server :" + notification.notificationText); 126 | } 127 | 128 | /// 129 | /// The response for EdgeManager.GetRooms() 130 | /// returns list of rooms on the server 131 | /// 132 | /// List of the rooms on the server 133 | public virtual void OnRoomsListReceived(List rooms) 134 | { 135 | Debug.Log("Rooms List Received from the server"); 136 | } 137 | 138 | /// 139 | /// Called automatically once a new room is created on the server 140 | /// You can use this callback to call EdgeManager.GetRooms() to get the updated list of the rooms in the lobby 141 | /// 142 | public virtual void OnNewRoomCreatedInLobby() 143 | { 144 | Debug.Log("New Room Created In the Lobby, Call EdgeManager.GetRooms() to get the updated rooms list"); 145 | } 146 | 147 | /// 148 | /// Called automatically once a room is removed from the lobby 149 | /// You can use this callback to call EdgeManager.GetRooms() to get the updated list of the rooms in the lobby 150 | /// 151 | public virtual void OnRoomRemovedFromLobby() 152 | { 153 | Debug.Log("A Room have been removed from the Lobby, Call EdgeManager.GetRooms() to get the updated rooms list"); 154 | } 155 | 156 | /// 157 | /// Called once the local player creates a room, 158 | /// The response of EdgeManagre.CreateRoom() and might be the response of EdgeManager.JoinOrCreateRoom() in case of room creation 159 | /// 160 | /// Created Room Info 161 | public virtual void OnRoomCreated(Room room) 162 | { 163 | Debug.Log("Room Created Event From Server"); 164 | } 165 | 166 | /// 167 | /// Called once the local player joins a room, 168 | /// The response of EdgeManagre.JoinRoom() and might be the response of EdgeManager.JoinOrCreateRoom() in case of room join 169 | /// 170 | /// Joined Room Info 171 | public virtual void OnRoomJoin(Room room) 172 | { 173 | Debug.Log("Room Join Event From Server"); 174 | } 175 | 176 | /// 177 | /// Called automatically once a player joins the local player room 178 | /// 179 | /// Updated Room Info 180 | public virtual void PlayerJoinedRoom(Room room) 181 | { 182 | Debug.Log("PlayerJoinedRoom Event From Server"); 183 | } 184 | 185 | /// 186 | /// Called once a JoinRoom Request fails on the server 187 | /// 188 | public virtual void OnJoinRoomFailed() 189 | { 190 | Debug.Log("Join Room Failed Event From Server"); 191 | } 192 | 193 | /// 194 | /// Called once the game starts, 195 | /// the game starts on the server once the number of players == the maximum players per room 196 | /// 197 | public virtual void OnGameStart() 198 | { 199 | Debug.Log("Game Start Event From Server"); 200 | } 201 | 202 | /// 203 | /// Called once a player in the same room as the local player leaves the room 204 | /// If the player who left was tracking any transforms 205 | /// this callback will be where you should transfer observables ownership to another player if the game is still running. 206 | /// 207 | /// Info about the player who left the room 208 | public virtual void OnPlayerLeft(RoomMemberLeft playerLeft) 209 | { 210 | Debug.Log("Player Left Event From Server"); 211 | } 212 | 213 | /// 214 | /// Called once a player joins or leaves a room in the lobby 215 | /// this callback can be useful to update rooms status, Call EdgeManager.GetRooms() to get the latest rooms updates 216 | /// 217 | public virtual void OnRoomsUpdated() 218 | { 219 | Debug.Log("RoomsUpdate Event From Server"); 220 | } 221 | 222 | /// 223 | /// Called once ExitRoom request in(EdgeManager.ExitRoom()) succeed 224 | /// 225 | public virtual void OnLeftRoom() 226 | { 227 | Debug.Log("Left Room Event From Server"); 228 | } 229 | 230 | /// 231 | /// During GamePlay once a event received from another player (Websocket) 232 | /// 233 | /// received GamePlayEvent 234 | public virtual void OnWebSocketEventReceived(GamePlayEvent gamePlayEvent) 235 | { 236 | Debug.Log("WebSocket Event Received From Server : " + gamePlayEvent.eventName); 237 | } 238 | 239 | /// 240 | /// Server Callback when a new Observable is created by one of the players 241 | /// 242 | /// The created Observable object 243 | public virtual void OnNewObservableCreated(Observable observable) 244 | { 245 | Debug.Log("New Observable created, owner name : " + observable.owner.playerName); 246 | } 247 | 248 | /// 249 | /// During GamePlay once a event received from another player (UDP) 250 | /// 251 | /// received GamePlayEvent 252 | public virtual void OnUDPEventReceived(GamePlayEvent gamePlayEvent) 253 | { 254 | //Debug.Log("UDP Msg Received Event From Server : " + gamePlayEvent.eventName); 255 | } 256 | 257 | private void OnDestroy() 258 | { 259 | connectedToEdge -= OnConnectionToEdge; 260 | failureToConnect -= OnFaliureToConnect; 261 | registerEvent -= OnRegisterEvent; 262 | notificationEvent -= OnNotificationEvent; 263 | leftRoom += OnLeftRoom; 264 | roomsList -= OnRoomsListReceived; 265 | newRoomCreatedInLobby -= OnNewRoomCreatedInLobby; 266 | roomRemovedFromLobby -= OnRoomRemovedFromLobby; 267 | roomCreated -= OnRoomCreated; 268 | roomJoin -= OnRoomJoin; 269 | playerRoomJoined -= PlayerJoinedRoom; 270 | joinRoomFaliure -= OnJoinRoomFailed; 271 | gameStart -= OnGameStart; 272 | playerLeft -= OnPlayerLeft; 273 | roomsUpdated -= OnRoomsUpdated; 274 | eventReceived -= OnWebSocketEventReceived; 275 | udpEventReceived -= OnUDPEventReceived; 276 | newObservableCreated -= OnNewObservableCreated; 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/EdgeMultiplayObserver.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using UnityEngine; 21 | 22 | 23 | namespace EdgeMultiplay 24 | { 25 | /// 26 | /// The rate which the observer checks if the observed objects' transform data have been updated 27 | /// 28 | public enum UpdateRate 29 | { 30 | EveryFrame, 31 | FixedUpdate 32 | } 33 | 34 | /// 35 | /// EdgeMultiplayObserver can be added to on an object to sync its position and/or rotation between all players 36 | /// You can sync a PlayerObject or Non PlayerObject 37 | /// 38 | [AddComponentMenu("EdgeMultiplay/EdgeMultiplayObserver")] 39 | public class EdgeMultiplayObserver : MonoBehaviour 40 | { 41 | #region EdgeMultiplayObserver Variables 42 | /// 43 | [HideInInspector] 44 | public NetworkedPlayer networkedPlayer; 45 | 46 | /// 47 | /// The rate which the observer checks if the observed objects' transform data have been updated 48 | /// Default is EveryFrame (Update) 49 | /// If you see the observed Game Object movement stutters use FixedUpdate 50 | /// 51 | public UpdateRate updateRate; 52 | 53 | /// 54 | /// List of observed objects 55 | /// 56 | [Tooltip("Add all sync Transforms including the player transform")] 57 | public List observables; 58 | 59 | #endregion 60 | 61 | #region MonoBehaviour Callbacks 62 | 63 | private void Awake() 64 | { 65 | EdgeManager.observers.Add(this); 66 | networkedPlayer = GetComponent(); 67 | UpdateObservables(); 68 | } 69 | 70 | private void OnValidate() 71 | { 72 | if (!GetComponent()) 73 | { 74 | Debug.LogWarning("EdgeMultiplay: EdgeMultiplayObserver requires PlayerManager or a class that inherits from NetworkedPlayer," + 75 | "\nRemove EdgeMultiplayObserver Component from " + gameObject.name); 76 | } 77 | if (observables.Count > 0) 78 | { 79 | for (int i = 0; i < observables.Count; i++) 80 | { 81 | observables[i].SetObservableIndex(i); 82 | if (observables[i].observeredTransform) 83 | { 84 | observables[i].SetupObservable(networkedPlayer); 85 | } 86 | } 87 | } 88 | } 89 | 90 | private void FixedUpdate() 91 | { 92 | if (updateRate == UpdateRate.FixedUpdate) 93 | { 94 | if (EdgeManager.gameStarted) 95 | { 96 | foreach (Observable observable in observables) 97 | { 98 | if (RequiresUpdate(observable)) 99 | { 100 | if (networkedPlayer && networkedPlayer.isLocalPlayer) 101 | { 102 | observable.SendDataToServer(); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | private void Update() 111 | { 112 | if (updateRate == UpdateRate.EveryFrame) 113 | { 114 | if (EdgeManager.gameStarted) 115 | { 116 | foreach (Observable observable in observables) 117 | { 118 | if (RequiresUpdate(observable)) 119 | { 120 | if (networkedPlayer && networkedPlayer.isLocalPlayer) 121 | { 122 | observable.SendDataToServer(); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | #endregion 131 | 132 | #region EdgeMultiplayObserver Functions 133 | /// 134 | ///UpdateObservers does the following: 135 | /// 1.Update the observables list indices. 136 | /// 2.Updates/Assigns an Obserview View to the observed game objects. 137 | /// 3.Disables the rigid body 2d or 3d on the observed game objects, so the physics can be simulated from the owner only. 138 | /// You should call UpdateObservables() at these situations: 139 | /// 1.New observable added. 140 | /// 2.Observable ownership change. 141 | /// 142 | public void UpdateObservables() 143 | { 144 | for (int i = 0; i < observables.Count; i++) 145 | { 146 | ObservableView observerView; 147 | observables[i].SetObservableIndex(i); 148 | if (observables[i].observeredTransform.gameObject.GetComponent()) 149 | { 150 | observerView = observables[i].observeredTransform.gameObject.GetComponent(); 151 | } 152 | else 153 | { 154 | observerView = observables[i].observeredTransform.gameObject.AddComponent(); 155 | } 156 | observerView.SetupObservableView(networkedPlayer.playerId, i); 157 | 158 | if (observables[i].observeredTransform != null) 159 | { 160 | observables[i].SetupObservable(networkedPlayer); 161 | } 162 | if (networkedPlayer && !networkedPlayer.isLocalPlayer && observables[i].owner == null) 163 | { 164 | if (observables[i].observeredTransform && observables[i].observeredTransform.GetComponent()) 165 | { 166 | observables[i].observeredTransform.GetComponent().isKinematic = true; 167 | } 168 | if (observables[i].observeredTransform && observables[i].observeredTransform.GetComponent()) 169 | { 170 | observables[i].observeredTransform.GetComponent().isKinematic = true; 171 | } 172 | } 173 | else 174 | { 175 | if (observables[i].owner != null && !observables[i].owner.isLocalPlayer && observables[i].attachedToPlayer) 176 | { 177 | if (observables[i].observeredTransform.GetComponent()) 178 | { 179 | observables[i].observeredTransform.GetComponent().isKinematic = true; 180 | } 181 | if (observables[i].observeredTransform.GetComponent()) 182 | { 183 | observables[i].observeredTransform.GetComponent().isKinematic = true; 184 | } 185 | } 186 | else 187 | { 188 | if (observables[i].owner != null && observables[i].owner.isLocalPlayer && observables[i].attachedToPlayer) 189 | { 190 | if (observables[i].observeredTransform.GetComponent()) 191 | { 192 | observables[i].observeredTransform.GetComponent().isKinematic = false; 193 | } 194 | if (observables[i].observeredTransform.GetComponent()) 195 | { 196 | observables[i].observeredTransform.GetComponent().isKinematic = false; 197 | } 198 | } 199 | } 200 | } 201 | } 202 | } 203 | 204 | private bool RequiresUpdate(Observable observable) 205 | { 206 | switch (observable.syncOption) 207 | { 208 | case SyncOptions.SyncPosition: 209 | if (observable.lastPosition != observable.observeredTransform.position) 210 | return true; 211 | else 212 | return false; 213 | case SyncOptions.SyncRotation: 214 | if (observable.lastRotation != observable.observeredTransform.rotation.eulerAngles) 215 | return true; 216 | else 217 | return false; 218 | default: 219 | case SyncOptions.SyncPositionAndRotation: 220 | if (observable.lastPosition != observable.observeredTransform.position || observable.lastRotation != observable.observeredTransform.rotation.eulerAngles) 221 | return true; 222 | else 223 | return false; 224 | } 225 | } 226 | 227 | /// 228 | /// Call TakeOverObservable() to take over the ownership of an observable you don't own 229 | /// For TakeOverObservable() to work, the observable must have the squattingAllowed boolean sat to true 230 | /// 231 | /// The ObservableView component attached to the Synced GameObject 232 | /// 233 | /// All Synced GameObjects have ObservableView component added at runtime 234 | /// 235 | /// 236 | public void TakeOverObservable(ObservableView observableView) 237 | { 238 | EdgeMultiplayObserver currentOwner = EdgeManager.observers.Find(observer => observer.networkedPlayer.playerId == observableView.ownerId); 239 | if (currentOwner == null) 240 | { 241 | Debug.LogWarning("EdgeMultiplay: Couldn't find the owner"); 242 | return; 243 | } 244 | 245 | if (currentOwner.networkedPlayer.playerId == networkedPlayer.playerId) 246 | { 247 | Debug.LogWarning("EdgeMultiplay: No ownership change needed you already own the observable object"); 248 | return; 249 | } 250 | 251 | Observable observable = currentOwner.observables.Find(obs => obs.observableIndex == observableView.observableIndex); 252 | 253 | if (observable == null) 254 | { 255 | Debug.LogWarning("EdgeMultiplay: Couldn't find the observable"); 256 | return; 257 | } 258 | 259 | if (!observable.squattingAllowed) 260 | { 261 | Debug.LogWarning("EdgeMultiplay: Couldn't TakeOver Observable, Squatting is not Allowed"); 262 | return; 263 | } 264 | else 265 | { 266 | EdgeManager.MessageSender.SendGamePlayEvent(new GamePlayEvent() 267 | { 268 | eventName = "TakeOverObservable", 269 | stringData = new string[1] { observableView.ownerId }, 270 | integerData = new int[1] { observableView.observableIndex } 271 | }); 272 | } 273 | } 274 | 275 | /// 276 | /// Call RequestOwnership() to request the ownership transfer of an observable from its owner 277 | /// It's up to the owner to approve or reject the ownership transfer 278 | /// This will trigger OnOwnershipRequestReceived() callback on the observable owner 279 | /// 280 | /// The ObservableView component attached to the Synced GameObject 281 | /// 282 | /// All Synced GameObjects have ObservableView component added at runtime 283 | /// 284 | /// 285 | public void RequestOwnership(ObservableView observableView) 286 | { 287 | EdgeMultiplayObserver currentOwner = 288 | EdgeManager.observers.Find(observer => observer.networkedPlayer.playerId == observableView.ownerId); 289 | 290 | if (currentOwner == null) 291 | { 292 | Debug.LogWarning("EdgeMultiplay: Couldn't find the owner"); 293 | return; 294 | } 295 | 296 | if (currentOwner.networkedPlayer.playerId == networkedPlayer.playerId) 297 | { 298 | Debug.LogWarning("EdgeMultiplay: No ownership change needed you already own the observable object"); 299 | return; 300 | } 301 | 302 | Observable observable = currentOwner.observables 303 | .Find(obs => obs.observableIndex == observableView.observableIndex); 304 | 305 | if (observable == null) 306 | { 307 | Debug.LogWarning("EdgeMultiplay: Couldn't find the observable"); 308 | return; 309 | } 310 | 311 | EdgeManager.MessageSender.SendGamePlayEvent(new GamePlayEvent() 312 | { 313 | eventName = "OwnershipRequest", 314 | stringData = new string[1] { observableView.ownerId }, 315 | integerData = new int[1] { observableView.observableIndex } 316 | }); 317 | 318 | } 319 | #endregion 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/NetworkedPlayer.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using UnityEngine; 21 | 22 | namespace EdgeMultiplay 23 | { 24 | /// 25 | /// The player script should inherit from Networked Player 26 | /// NetworkedPlayer contains all the player data retrieved from the server 27 | /// 28 | [AddComponentMenu("EdgeMultiplay/NetworkedPlayer")] 29 | public class NetworkedPlayer : EdgeMultiplayCallbacks 30 | { 31 | #region NetworkedPlayer Variables 32 | 33 | /// 34 | /// the id of the room that the player is a member of 35 | /// 36 | public string roomId; 37 | /// 38 | /// the player order in the room, ex. the first player to join a room has playerIndex of 0 39 | /// 40 | public int playerIndex; 41 | /// 42 | /// The player Id that the server assigns to the player 43 | /// 44 | public string playerId; 45 | public string playerName; 46 | public Action ownershipRequested; 47 | public Action playerEvent; 48 | public bool isLocalPlayer; 49 | /// 50 | /// Can be used to set the player to be active or inactive although the player is still an active connection in the room 51 | /// 52 | public bool ActivePlayer = true; 53 | EdgeManager edgeManager; 54 | /// 55 | /// The observer responsible for tracking the observable transforms 56 | /// 57 | public EdgeMultiplayObserver observer 58 | { 59 | get 60 | { 61 | if (GetComponent()) 62 | { 63 | return GetComponent(); 64 | } 65 | else 66 | { 67 | return null; 68 | } 69 | } 70 | set { } 71 | } 72 | 73 | #endregion 74 | 75 | #region NetworkedPlayer Functions 76 | 77 | private void Awake() 78 | { 79 | edgeManager = FindObjectOfType(); 80 | } 81 | 82 | /// 83 | /// Call ListenToMessages() to start listening to messages from the server 84 | /// 85 | public void ListenToMessages() 86 | { 87 | if (!isLocalPlayer) 88 | { 89 | playerEvent += OnMessageReceived; 90 | } 91 | else 92 | { 93 | ownershipRequested += OnOwnershipRequestReceived; 94 | } 95 | } 96 | /// 97 | /// Add StopListening when you want to stop listening to messages from the server 98 | /// Recommended to have it under the Monobehaviour callback OnDestroy() 99 | /// 100 | public void StopListening() 101 | { 102 | if (!isLocalPlayer) 103 | { 104 | playerEvent -= OnMessageReceived; 105 | } 106 | else 107 | { 108 | ownershipRequested += OnOwnershipRequestReceived; 109 | } 110 | } 111 | 112 | public override void OnWebSocketEventReceived(GamePlayEvent gameplayEvent) 113 | { 114 | if (!isLocalPlayer) 115 | { 116 | if (gameplayEvent.senderId == playerId) 117 | { 118 | playerEvent(gameplayEvent); 119 | } 120 | } 121 | } 122 | 123 | public override void OnUDPEventReceived(GamePlayEvent udpEvent) 124 | { 125 | if (!isLocalPlayer) 126 | { 127 | if (udpEvent.senderId == playerId) 128 | { 129 | playerEvent(udpEvent); 130 | } 131 | } 132 | } 133 | 134 | /// 135 | /// Called once GamePlay Events Received from the server 136 | /// 137 | /// the received gamePlayEvent 138 | public virtual void OnMessageReceived(GamePlayEvent gamePlayEvent) 139 | { 140 | Debug.Log("GamePlayEvent Received"); 141 | } 142 | 143 | /// 144 | /// OnOwnershipRequestReceived is Received from the server 145 | /// once another player requests the ownership of an observable 146 | /// 147 | /// The player who requested the ownership 148 | /// The observable which the request is about 149 | public virtual void OnOwnershipRequestReceived(NetworkedPlayer requestee, Observable observable) 150 | { 151 | Debug.Log("Ownership Request Received");//To Approve the transfer >> observable.ChangeOwnership(requestee.playerId); 152 | } 153 | 154 | public void SetUpPlayer(Player playerFromServer, string roomId, bool isLocalPlayer = false) 155 | { 156 | this.roomId = roomId; 157 | playerName = playerFromServer.playerName; 158 | playerIndex = playerFromServer.playerIndex; 159 | playerId = playerFromServer.playerId; 160 | this.isLocalPlayer = isLocalPlayer; 161 | ActivePlayer = true; 162 | } 163 | 164 | /// 165 | /// Sends an event to all players in the room 166 | /// 167 | /// The GamePlayEvent to send 168 | public void SendGamePlayEvent(GamePlayEvent gamePlayEvent) 169 | { 170 | GamePlayEvent gameplayEvent = new GamePlayEvent() 171 | { 172 | eventName = gamePlayEvent.eventName, 173 | booleanData = gamePlayEvent.booleanData, 174 | stringData = gamePlayEvent.stringData, 175 | integerData = gamePlayEvent.integerData, 176 | floatData = gamePlayEvent.floatData, 177 | }; 178 | edgeManager.SendGamePlayEvent(gameplayEvent); 179 | } 180 | 181 | /// 182 | /// Sends an event to all players in the room (UDP) 183 | /// 184 | /// The GamePlayEvent to send 185 | public void SendGamePlayEventUDP(GamePlayEvent gamePlayEvent) 186 | { 187 | GamePlayEvent gameplayEvent = new GamePlayEvent() 188 | { 189 | eventName = gamePlayEvent.eventName, 190 | booleanData = gamePlayEvent.booleanData, 191 | stringData = gamePlayEvent.stringData, 192 | integerData = gamePlayEvent.integerData, 193 | floatData = gamePlayEvent.floatData, 194 | }; 195 | EdgeManager.SendUDPMessage(gameplayEvent); 196 | } 197 | 198 | /// 199 | /// Sends an event to all players in the room except the sender 200 | /// 201 | public void BroadcastMessage(GamePlayEvent gamePlayEvent) 202 | { 203 | GamePlayEvent gameplayEvent = new GamePlayEvent() 204 | { 205 | eventName = gamePlayEvent.eventName, 206 | booleanData = gamePlayEvent.booleanData, 207 | stringData = gamePlayEvent.stringData, 208 | integerData = gamePlayEvent.integerData, 209 | floatData = gamePlayEvent.floatData, 210 | }; 211 | edgeManager.SendGamePlayEvent(gameplayEvent); 212 | } 213 | 214 | /// 215 | /// Sends an event to all players in the room except the sender 216 | /// 217 | public void BroadcastMessage(string eventName, Vector3 position) 218 | { 219 | GamePlayEvent gameplayEvent = new GamePlayEvent(eventName, position); 220 | edgeManager.SendGamePlayEvent(gameplayEvent); 221 | } 222 | 223 | /// 224 | /// Sends an event to all players in the room except the sender 225 | /// 226 | public void BroadcastMessage(string eventName, Quaternion rotation) 227 | { 228 | GamePlayEvent gameplayEvent = new GamePlayEvent(eventName, rotation); 229 | edgeManager.SendGamePlayEvent(gameplayEvent); 230 | } 231 | 232 | /// 233 | /// Sends an event to all players in the room except the sender 234 | /// 235 | public void BroadcastMessage(string eventName, Vector3 position, Quaternion rotation) 236 | { 237 | GamePlayEvent gameplayEvent = new GamePlayEvent(eventName, position, rotation); 238 | edgeManager.SendGamePlayEvent(gameplayEvent); 239 | } 240 | 241 | /// 242 | /// Sends an event to all players in the room except the sender 243 | /// 244 | public void BroadcastMessage(string eventName, List integerArray) 245 | { 246 | GamePlayEvent gameplayEvent = new GamePlayEvent(eventName, integerArray); 247 | edgeManager.SendGamePlayEvent(gameplayEvent); 248 | } 249 | 250 | /// 251 | /// Sends an event to all players in the room except the sender 252 | /// 253 | public void BroadcastMessage(string eventName, List floatArray) 254 | { 255 | GamePlayEvent gameplayEvent = new GamePlayEvent(eventName, floatArray); 256 | edgeManager.SendGamePlayEvent(gameplayEvent); 257 | } 258 | 259 | /// 260 | /// Sends an event to all players in the room except the sender 261 | /// 262 | public void BroadcastMessage(string eventName, string[] stringData = null, int[] commandInts = null, 263 | float[] floatData = null, 264 | bool[] booleanData = null) 265 | { 266 | GamePlayEvent gamePlayEvent = new GamePlayEvent(this.roomId, playerId, eventName, stringData, commandInts, floatData, booleanData); 267 | edgeManager.SendGamePlayEvent(gamePlayEvent); 268 | } 269 | 270 | /// 271 | /// Creates synced GameObject in runtime in the local player's world 272 | /// and sends an event to all players in the room to create the observable in their worlds 273 | /// 274 | /// The name of your prefab (Game Object) stored in Resources Folder without extensions ex. 'Ball' 275 | /// the inital position of the spawning 276 | /// the inital rotation of the spawning 277 | /// Which Synchronization option will be applied 278 | /// Set to true if you want to smoothen the tracked position if you have network lag 279 | /// Set to true if you want to smoothen the tracked rotation if you have network lag 280 | /// Set Interpolation factor between 0.1 and 1 281 | /// The created Observer object 282 | public Observable CreateObservableObject(string prefabName, Vector3 startPosition, Quaternion startRotation, SyncOptions syncOption, bool interpolatePosition = false, bool interpolateRotation = false, float interpolationFactor = 0) 283 | { 284 | GameObject prefab = Resources.Load(prefabName); 285 | GameObject syncedObject; 286 | syncedObject = Instantiate(prefab, startPosition, startRotation); 287 | if (!observer) 288 | { 289 | observer = gameObject.AddComponent(); 290 | } 291 | Observable newObservable = new Observable(syncedObject.transform, syncOption, interpolatePosition, interpolateRotation, Mathf.Clamp(interpolationFactor, 0.1f, 1f), observer.observables.Count); 292 | observer.observables.Add(newObservable); 293 | newObservable.SetupObservable(this); 294 | observer.UpdateObservables(); 295 | if (isLocalPlayer) 296 | { 297 | GamePlayEvent newObserverEvent = new GamePlayEvent 298 | { 299 | eventName = "NewObservableCreated", 300 | booleanData = new bool[2] { interpolatePosition, interpolateRotation }, 301 | stringData = new string[2] { playerId, prefabName }, 302 | integerData = new int[1] { (int)syncOption }, 303 | floatData = new float[7] { startPosition.x, startPosition.y, startPosition.z, startRotation.eulerAngles.x, startRotation.eulerAngles.y, startRotation.eulerAngles.z, interpolationFactor }, 304 | }; 305 | edgeManager.SendGamePlayEvent(newObserverEvent); 306 | } 307 | return newObservable; 308 | } 309 | 310 | #endregion 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/DataContracts.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.Runtime.Serialization.Json; 20 | using System.IO; 21 | using System.Text; 22 | using System.Runtime.Serialization; 23 | using System.Collections.Generic; 24 | using UnityEngine; 25 | using System.Collections; 26 | using DistributedMatchEngine; 27 | 28 | namespace EdgeMultiplay 29 | { 30 | #region EdgeMultiplay Data Structures 31 | 32 | /// 33 | /// Synchronization Options 34 | /// 35 | public enum SyncOptions 36 | { 37 | SyncPosition, 38 | SyncRotation, 39 | SyncPositionAndRotation, 40 | SyncLocalPosition, 41 | SyncLocalRotation, 42 | SyncLocalPositionAndRotation 43 | } 44 | 45 | /// 46 | /// Called once a notification is received from the server 47 | /// Example of Notifications are new room created on server 48 | /// 49 | [DataContract] 50 | public class Notification 51 | { 52 | [DataMember] 53 | public string type = "notification"; 54 | 55 | [DataMember] 56 | public string notificationText; 57 | } 58 | 59 | /// 60 | /// Register Event is being emitted from the server once the connection starts 61 | /// 62 | [DataContract] 63 | public class Register 64 | { 65 | [DataMember] 66 | public string type = "register"; 67 | 68 | [DataMember] 69 | public string sessionId; 70 | 71 | [DataMember] 72 | public string playerId; 73 | 74 | } 75 | 76 | /// 77 | /// Indicates that server created a room but still waiting for more players to Join 78 | /// 79 | [DataContract] 80 | public class RoomCreated 81 | { 82 | [DataMember] 83 | public string type = "roomCreated"; 84 | [DataMember] 85 | public Room room; 86 | } 87 | 88 | 89 | /// 90 | /// Indicates that player joined a room but still waiting for more players to Join 91 | /// 92 | [DataContract] 93 | public class RoomJoin 94 | { 95 | [DataMember] 96 | public string type = "roomJoin"; 97 | [DataMember] 98 | public Room room; 99 | 100 | } 101 | 102 | /// 103 | /// Event is received as the response for EdgeManager.GetRooms() 104 | /// 105 | [DataContract] 106 | public class RoomsList 107 | { 108 | [DataMember] 109 | public string type = "roomsList"; 110 | [DataMember] 111 | public List rooms; 112 | } 113 | 114 | /// 115 | /// Event is received as the response for EdgeManager.GetAvailableRooms() 116 | /// 117 | [DataContract] 118 | public class AvailableRoomsList 119 | { 120 | [DataMember] 121 | public string type = "availableRoomsList"; 122 | [DataMember] 123 | public List availableRooms; 124 | 125 | } 126 | 127 | /// 128 | /// Event is received once a new member joins a room that the local player is a member of 129 | /// the room member has the new updated list of room members 130 | /// 131 | [DataContract] 132 | public class PlayerJoinedRoom 133 | { 134 | [DataMember] 135 | public string type = "playerJoinedRoom"; 136 | [DataMember] 137 | public Room room; 138 | } 139 | 140 | /// 141 | /// Event is received once a room member leaves a room that the local player is a member of 142 | /// 143 | [DataContract] 144 | public class RoomMemberLeft 145 | { 146 | [DataMember] 147 | public string type = "memberLeft"; 148 | 149 | [DataMember] 150 | public string idOfPlayerLeft; 151 | 152 | } 153 | 154 | 155 | /// 156 | /// Indicates the start of the Game 157 | /// Once the number of players in a room reaches the room maximum player limit the game starts 158 | /// (Ex. If a room have maximum players (3), once the server assigns the third player to the room, the server will send GameStartEvent to all the room members) 159 | /// 160 | [DataContract] 161 | public class GameStart 162 | { 163 | [DataMember] 164 | public string type = "gameStart"; 165 | 166 | [DataMember] 167 | public Room room; 168 | } 169 | 170 | /// 171 | /// GamePlayEvent is the main event for GamePlayEvents 172 | /// 173 | [DataContract] 174 | public class GamePlayEvent 175 | { 176 | [DataMember(IsRequired = true)] 177 | public string type; 178 | [DataMember(IsRequired = true)] 179 | public string roomId; 180 | [DataMember(IsRequired = true)] 181 | public string senderId; 182 | [DataMember(IsRequired = true)] 183 | public string eventName; 184 | [DataMember(EmitDefaultValue = false)] 185 | public string[] stringData; 186 | [DataMember(EmitDefaultValue = false)] 187 | public int[] integerData; 188 | [DataMember(EmitDefaultValue = false)] 189 | public float[] floatData; 190 | [DataMember(EmitDefaultValue = false)] 191 | public bool[] booleanData; 192 | 193 | public GamePlayEvent() 194 | { 195 | type = "GamePlayEvent"; 196 | } 197 | 198 | public GamePlayEvent(string eventName, Vector3 position) 199 | { 200 | type = "GamePlayEvent"; 201 | this.eventName = eventName; 202 | floatData = Util.ConvertVector3ToFloatArray(position); 203 | } 204 | 205 | public GamePlayEvent(string eventName, Quaternion rotation) 206 | { 207 | type = "GamePlayEvent"; 208 | this.eventName = eventName; 209 | floatData = Util.ConvertVector3ToFloatArray(rotation.eulerAngles); 210 | } 211 | 212 | public GamePlayEvent(string eventName, Vector3 position, Quaternion rotation) 213 | { 214 | type = "GamePlayEvent"; 215 | this.eventName = eventName; 216 | 217 | floatData = new float[6] 218 | { 219 | position.x, position.y, position.z,rotation.eulerAngles.x, rotation.eulerAngles.y, rotation.eulerAngles.z 220 | }; 221 | } 222 | 223 | public GamePlayEvent(string eventName, List scoreArray) 224 | { 225 | type = "GamePlayEvent"; 226 | this.eventName = eventName; 227 | integerData = scoreArray.ToArray(); 228 | } 229 | 230 | public GamePlayEvent(string eventName, List scoreArray) 231 | { 232 | type = "GamePlayEvent"; 233 | this.eventName = eventName; 234 | floatData = scoreArray.ToArray(); 235 | } 236 | 237 | public GamePlayEvent(string roomId, string playerId, 238 | string eventName, string[] stringData, int[] integerData, float[] floatData, 239 | bool[] booleanData) 240 | { 241 | type = "GamePlayEvent"; 242 | this.roomId = roomId; 243 | senderId = playerId; 244 | this.eventName = eventName; 245 | this.stringData = stringData; 246 | this.integerData = integerData; 247 | this.floatData = floatData; 248 | this.booleanData = booleanData; 249 | } 250 | 251 | public string ToJson() 252 | { 253 | return JsonUtility.ToJson(this); ; 254 | } 255 | } 256 | 257 | [Serializable] 258 | public class Observable 259 | { 260 | /// 261 | /// The Transform you want to Sync its position and/or rotation 262 | /// 263 | public Transform observeredTransform; 264 | /// 265 | /// Synchronization Option 266 | /// 267 | public SyncOptions syncOption; 268 | /// 269 | /// Set to true if you want to smoothen the tracked position if you have network lag 270 | /// 271 | [Tooltip("Check if you want to smoothen the tracked position if you have network lag")] 272 | public bool InterpolatePosition; 273 | /// 274 | /// Set to true if you want to smoothen the tracked rotation if you have network lag 275 | /// 276 | [Tooltip("Check if you want to smoothen the tracked rotation if you have network lag")] 277 | public bool InterpolateRotation; 278 | /// 279 | /// Set Interpolation factor between 0.1 and 1 280 | /// 281 | [Tooltip("Set Interpolation factor between 0.1 and 1")] 282 | [Range(0.1f, 1f)] 283 | public float InterpolationFactor; 284 | [HideInInspector] 285 | public int observableIndex; 286 | [HideInInspector] 287 | public Vector3 lastPosition; 288 | [HideInInspector] 289 | public Vector3 lastRotation; 290 | [HideInInspector] 291 | public NetworkedPlayer owner; 292 | [HideInInspector] 293 | public bool attachedToPlayer; 294 | 295 | /// 296 | /// Set to true to allow ownership takeover by other players 297 | /// 298 | [Tooltip("Set to true to allow ownership takeover by other players")] 299 | public bool squattingAllowed; 300 | 301 | /// 302 | /// Observable Constructor 303 | /// 304 | /// The Transform you want to Sync its position and/or rotation 305 | /// Synchronization Option 306 | /// Set to true if you want to smoothen the tracked position if there is network lag 307 | /// Set to true if you want to smoothen the tracked rotation if there is network lag 308 | /// Set Interpolation factor value between 0.1 and 1 309 | /// Observable index in observer.observables list 310 | public Observable(Transform targetTransform, SyncOptions syncOption, bool interpolatePosition, bool interpolateRotation, float interpolationFactor, int observableIndex = 0) 311 | { 312 | this.observeredTransform = targetTransform; 313 | this.syncOption = syncOption; 314 | InterpolatePosition = interpolatePosition; 315 | InterpolateRotation = interpolateRotation; 316 | InterpolationFactor = interpolationFactor; 317 | this.observableIndex = observableIndex; 318 | 319 | } 320 | /// 321 | /// Observable Constructor 322 | /// 323 | /// The Transform you want to Sync its position and/or rotation 324 | /// Synchronization Option 325 | /// Set to true if you want to smoothen the tracked rotation if there is network lag 326 | /// Set to true if you want to smoothen the tracked rotation if there is network lag 327 | /// Set Interpolation factor value between 0.1 and 1 328 | public Observable(Transform targetTransform, SyncOptions syncOption, bool interpolatePosition, bool interpolateRotation, float interpolationFactor) 329 | { 330 | this.observeredTransform = targetTransform; 331 | this.syncOption = syncOption; 332 | InterpolatePosition = interpolatePosition; 333 | InterpolateRotation = interpolateRotation; 334 | InterpolationFactor = interpolationFactor; 335 | } 336 | 337 | public void SetObservableIndex(int index) 338 | { 339 | observableIndex = index; 340 | } 341 | 342 | public void SetupObservable(NetworkedPlayer observableOwner) 343 | { 344 | owner = observableOwner; 345 | switch (syncOption) 346 | { 347 | case SyncOptions.SyncPosition: 348 | lastPosition = observeredTransform.transform.position; 349 | break; 350 | case SyncOptions.SyncRotation: 351 | lastRotation = observeredTransform.transform.rotation.eulerAngles; 352 | break; 353 | case SyncOptions.SyncPositionAndRotation: 354 | lastPosition = observeredTransform.transform.position; 355 | lastRotation = observeredTransform.transform.rotation.eulerAngles; 356 | break; 357 | case SyncOptions.SyncLocalPosition: 358 | lastPosition = observeredTransform.transform.localPosition; 359 | break; 360 | case SyncOptions.SyncLocalRotation: 361 | lastRotation = observeredTransform.transform.localRotation.eulerAngles; 362 | break; 363 | case SyncOptions.SyncLocalPositionAndRotation: 364 | lastPosition = observeredTransform.transform.localPosition; 365 | lastRotation = observeredTransform.transform.localRotation.eulerAngles; 366 | break; 367 | } 368 | } 369 | 370 | public void SendDataToServer() 371 | { 372 | GamePlayEvent observerEvent = new GamePlayEvent(); 373 | observerEvent.eventName = "EdgeMultiplayObserver"; 374 | observerEvent.integerData = new int[2] { (int)syncOption, observableIndex }; 375 | switch (syncOption) 376 | { 377 | case SyncOptions.SyncPosition: 378 | observerEvent.floatData = Util.GetPositionData(observeredTransform.transform); 379 | lastPosition = observeredTransform.transform.position; 380 | break; 381 | case SyncOptions.SyncRotation: 382 | observerEvent.floatData = Util.GetRotationEulerData(observeredTransform.transform); 383 | lastRotation = observeredTransform.transform.rotation.eulerAngles; 384 | break; 385 | case SyncOptions.SyncPositionAndRotation: 386 | observerEvent.floatData = Util.GetPositionAndRotationData(observeredTransform.transform); 387 | lastRotation = observeredTransform.transform.rotation.eulerAngles; 388 | lastPosition = observeredTransform.transform.position; 389 | break; 390 | case SyncOptions.SyncLocalPosition: 391 | observerEvent.floatData = Util.GetLocalPositionData(observeredTransform.transform); 392 | lastPosition = observeredTransform.localPosition; 393 | break; 394 | case SyncOptions.SyncLocalRotation: 395 | observerEvent.floatData = Util.GetLocalRotationData(observeredTransform.transform); 396 | lastRotation = observeredTransform.transform.rotation.eulerAngles; 397 | break; 398 | case SyncOptions.SyncLocalPositionAndRotation: 399 | observerEvent.floatData = Util.GetLocalPositionAndRotationData(observeredTransform.transform); 400 | lastPosition = observeredTransform.transform.localRotation.eulerAngles; 401 | lastRotation = observeredTransform.transform.localPosition; 402 | break; 403 | } 404 | EdgeManager.SendUDPMessage(observerEvent); 405 | } 406 | 407 | public void SetOwnership(string ownerId) 408 | { 409 | owner = EdgeManager.GetPlayer(ownerId); 410 | if (owner == null) 411 | { 412 | throw new Exception("EdgeMultiplay: Couldn't find player with id: " + ownerId); 413 | } 414 | 415 | if (owner.observer == null) 416 | { 417 | owner.gameObject.AddComponent(); 418 | } 419 | owner.observer.observables.Add(this); 420 | owner.observer.UpdateObservables(); 421 | } 422 | /// 423 | /// Changes the owner of an Observable object 424 | /// ChangeOwnership() will change the owner in the local player's world and 425 | /// update all room members about Ownership change. 426 | /// 427 | /// The new owner player id 428 | public void ChangeOwnership(string newOwnerId) 429 | { 430 | if (owner != null) 431 | { 432 | if (newOwnerId == owner.playerId) 433 | { 434 | Debug.LogWarning("EdgeMultiplay: No ownership change needed you already own the observable object"); 435 | return; 436 | } 437 | if (owner.observer != null) 438 | { 439 | owner.observer.observables.Remove(this); 440 | } 441 | owner = EdgeManager.GetPlayer(newOwnerId); 442 | if (owner == null) 443 | { 444 | throw new Exception("EdgeMultiplay: Couldn't find player with id: " + newOwnerId); 445 | } 446 | if (owner.observer == null) 447 | { 448 | owner.gameObject.AddComponent(); 449 | } 450 | owner.observer.observables.Add(this); 451 | owner.observer.UpdateObservables(); 452 | GamePlayEvent changeOwnershipEvent = new GamePlayEvent() 453 | { 454 | eventName = "ObservableOwnershipChange", 455 | stringData = new string[1] { newOwnerId }, 456 | integerData = new int[1] { observableIndex } 457 | }; 458 | EdgeManager.MessageSender.BroadcastMessage(changeOwnershipEvent); 459 | } 460 | else 461 | { 462 | Debug.LogError("EdgeMultiplay: Observer has no owner, Use observer.SetOwnership() first"); 463 | return; 464 | } 465 | } 466 | } 467 | 468 | #endregion 469 | #region Requests 470 | 471 | /// 472 | /// CreateRoomRequest is sent to the server to create a room 473 | /// 474 | [DataContract] 475 | public class CreateRoomRequest 476 | { 477 | [DataMember] 478 | public string type = "CreateRoom"; 479 | [DataMember] 480 | public string playerId; 481 | [DataMember] 482 | public string playerName; 483 | [DataMember] 484 | public int playerAvatar; 485 | [DataMember] 486 | public int maxPlayersPerRoom; 487 | [DataMember] 488 | public int minPlayersToStartGame; 489 | [DataMember(EmitDefaultValue = false)] 490 | public Hashtable playerTags; 491 | public CreateRoomRequest(string PlayerName, int PlayerAvatar, int MaxPlayersPerRoom, Hashtable playerTags = null, int MinPlayersToStartGame = 0) 492 | { 493 | type = "CreateRoom"; 494 | playerId = EdgeManager.gameSession.playerId; 495 | playerName = PlayerName; 496 | playerAvatar = PlayerAvatar; 497 | maxPlayersPerRoom = MaxPlayersPerRoom; 498 | minPlayersToStartGame = MinPlayersToStartGame; 499 | this.playerTags = playerTags; 500 | } 501 | } 502 | 503 | /// 504 | /// JoinOrCreateRoomRequest is sent to the server to join a room or create a new room if there are no available rooms 505 | /// 506 | [DataContract] 507 | public class JoinOrCreateRoomRequest 508 | { 509 | [DataMember] 510 | public string type = "JoinOrCreateRoom"; 511 | [DataMember] 512 | public string playerId; 513 | [DataMember] 514 | public string playerName; 515 | [DataMember] 516 | public int playerAvatar; 517 | [DataMember] 518 | public int maxPlayersPerRoom; 519 | [DataMember] 520 | public int minPlayersToStartGame; 521 | [DataMember(EmitDefaultValue = false)] 522 | public Hashtable playerTags; 523 | 524 | public JoinOrCreateRoomRequest(string PlayerName, int PlayerAvatar, int MaxPlayersPerRoom, Hashtable playerTags = null, int MinPlayersToStartGame = 0) 525 | { 526 | type = "JoinOrCreateRoom"; 527 | playerId = EdgeManager.gameSession.playerId; 528 | playerName = PlayerName; 529 | playerAvatar = PlayerAvatar; 530 | maxPlayersPerRoom = MaxPlayersPerRoom; 531 | minPlayersToStartGame = MinPlayersToStartGame; 532 | this.playerTags = playerTags; 533 | } 534 | } 535 | 536 | /// 537 | /// JoinRoomRequest is sent to the server to join a specific room 538 | /// 539 | [DataContract] 540 | public class JoinRoomRequest 541 | { 542 | [DataMember] 543 | public string type = "JoinRoom"; 544 | [DataMember] 545 | public string playerId; 546 | [DataMember] 547 | public string playerName; 548 | [DataMember] 549 | public int playerAvatar; 550 | [DataMember] 551 | public string roomId; 552 | [DataMember(EmitDefaultValue = false)] 553 | public Hashtable playerTags; 554 | 555 | public JoinRoomRequest(string RoomId, string PlayerName, int PlayerAvatar, Hashtable playerTags = null) 556 | { 557 | type = "JoinRoom"; 558 | roomId = RoomId; 559 | playerId = EdgeManager.gameSession.playerId; 560 | playerName = PlayerName; 561 | playerAvatar = PlayerAvatar; 562 | this.playerTags = playerTags; 563 | } 564 | } 565 | 566 | /// 567 | /// GetRoomsRequest is sent to the server to get a list of the rooms on the server 568 | /// 569 | [DataContract] 570 | public class GetRoomsRequest 571 | { 572 | [DataMember] 573 | public string type = "GetRooms"; 574 | public GetRoomsRequest() 575 | { 576 | type = "GetRooms"; 577 | } 578 | } 579 | 580 | /// 581 | /// GetAvailableRooms is sent to the server to get a list of the availble rooms on the server 582 | /// available room is a room that is not full 583 | /// room x is available if room x have 2 members and the maximum players per room is 3 584 | /// 585 | [DataContract] 586 | public class GetAvailableRoomsRequest 587 | { 588 | [DataMember] 589 | public string type = "GetAvailableRooms"; 590 | public GetAvailableRoomsRequest() 591 | { 592 | type = "GetAvailableRooms"; 593 | } 594 | } 595 | 596 | [DataContract] 597 | public class ExitRoomRequest 598 | { 599 | [DataMember] 600 | public string type = "ExitRoom"; 601 | [DataMember] 602 | public string playerId; 603 | [DataMember] 604 | public string roomId; 605 | 606 | public ExitRoomRequest() 607 | { 608 | type = "ExitRoom"; 609 | playerId = EdgeManager.gameSession.playerId; 610 | roomId = EdgeManager.gameSession.roomId; 611 | } 612 | } 613 | 614 | #endregion 615 | #region EdgeMultiplay Helper Classes 616 | 617 | /// 618 | /// Wrapper class to hold game session data received from the server 619 | /// 620 | public class Session 621 | { 622 | public string sessionId; 623 | public string roomId = ""; 624 | public string playerId; 625 | public string playerName; 626 | public string playerAvatar; 627 | public int playerIndex; 628 | public Player[] currentPlayers; 629 | } 630 | 631 | /// 632 | /// Wrapper class for room info received the server 633 | /// 634 | [DataContract] 635 | public class Room 636 | { 637 | /// 638 | /// Room Id assigned on ther server 639 | /// 640 | [DataMember] 641 | public string roomId; 642 | 643 | /// 644 | /// Generic List of players in the room 645 | /// 646 | [DataMember] 647 | public List roomMembers; 648 | 649 | /// 650 | /// Maximum Number of players per room 651 | /// 652 | [DataMember] 653 | public int maxPlayersPerRoom; 654 | 655 | /// 656 | /// The minimum players threshold to start a game, if less than 2, minPlayersPerRoom == maxPlayersPerRoom 657 | /// 658 | [DataMember] 659 | public int minPlayersToStartGame; 660 | 661 | /// 662 | /// Indicates that the game is running in the room 663 | /// 664 | [DataMember] 665 | public bool gameStarted; 666 | } 667 | 668 | /// 669 | /// Wrapper class for the player info received from the server 670 | /// 671 | [DataContract] 672 | public class Player 673 | { 674 | /// 675 | /// Unique ID assigned to player once the connection is established with the server, assigned on RegisterEvent 676 | /// 677 | [HideInInspector] 678 | [DataMember] 679 | public string playerId; 680 | 681 | [DataMember] 682 | public string playerName; 683 | 684 | /// 685 | /// the player order in the room, ex. the first player to join a room has playerIndex of 0 686 | /// 687 | [DataMember] 688 | public int playerIndex; 689 | /// 690 | /// the player Avatar will be selected from EdgeManager SpawnPrefabs Array by index 691 | /// (ex. if playerAvatar=3 then the player will take the Avatar from EdgeManager.SpawnPrefabs[3] 692 | /// 693 | [DataMember] 694 | public int playerAvatar = 0; 695 | 696 | /// 697 | /// helper instance variable for serializing and deserializing playerTags 698 | /// 699 | [DataMember(EmitDefaultValue = false)] 700 | #pragma warning disable 0649 701 | internal Hashtable playerTags; 702 | 703 | /// 704 | /// Dictionary custom data associated with the player 705 | /// 706 | public Dictionary playerTagsDict 707 | { 708 | get 709 | { 710 | if (playerTags != null) 711 | { 712 | return Tag.HashtableToDictionary(playerTags); 713 | } 714 | else 715 | { 716 | return null; 717 | } 718 | } 719 | } 720 | } 721 | 722 | /// 723 | /// Wrapper class to hold the return of GamePlayEvent.GetPositionAndRotation() 724 | /// 725 | public class PositionAndRotation 726 | { 727 | public Vector3 position; 728 | public Quaternion rotation; 729 | 730 | public PositionAndRotation(Vector3 vector3, Quaternion quaternion) 731 | { 732 | position = vector3; 733 | rotation = quaternion; 734 | } 735 | } 736 | 737 | /// 738 | /// Wrapper class for Position And Rotation Eulers Used for EdgeManager Spawn Info 739 | /// 740 | [Serializable] 741 | public class PositionAndRotationEulers 742 | { 743 | public Vector3 position; 744 | /// 745 | /// Rotation Eulers similar as to the Rotation in the inspector 746 | /// 747 | public Vector3 rotation; 748 | } 749 | 750 | #endregion 751 | #region Serialization Helpers 752 | 753 | [DataContract] 754 | class MessageWrapper 755 | { 756 | [DataMember] 757 | public string type = "utf8"; 758 | [DataMember] 759 | public string utf8Data; 760 | public static MessageWrapper WrapTextMessage(string jsonStr) 761 | { 762 | var wrapper = new MessageWrapper(); 763 | wrapper.utf8Data = jsonStr; 764 | return wrapper; 765 | } 766 | public static MessageWrapper UnWrapMessage(string wrappedJsonStr) 767 | { 768 | var wrapper = Messaging.Deserialize(wrappedJsonStr); 769 | return wrapper; 770 | } 771 | } 772 | 773 | static class Messaging 774 | { 775 | private static string StreamToString(Stream s) 776 | { 777 | s.Position = 0; 778 | StreamReader reader = new StreamReader(s); 779 | string jsonStr = reader.ReadToEnd(); 780 | return jsonStr; 781 | } 782 | 783 | public static string Serialize(T t) 784 | { 785 | DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); 786 | MemoryStream ms = new MemoryStream(); 787 | 788 | serializer.WriteObject(ms, t); 789 | string jsonStr = StreamToString(ms); 790 | 791 | return jsonStr; 792 | } 793 | 794 | public static T Deserialize(string jsonString) 795 | { 796 | using (MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString ?? ""))) 797 | { 798 | return Deserialize(memStream); 799 | } 800 | } 801 | 802 | public static T Deserialize(Stream stream) 803 | { 804 | DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T)); 805 | T t = (T)deserializer.ReadObject(stream); 806 | return t; 807 | } 808 | } 809 | 810 | #endregion 811 | } 812 | -------------------------------------------------------------------------------- /EdgeMultiplay/Scripts/EdgeManager.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-2021 MobiledgeX, Inc. All rights and licenses reserved. 3 | * MobiledgeX, Inc. 156 2nd Street #408, San Francisco, CA 94105 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using UnityEngine; 21 | using System; 22 | using System.Text; 23 | using MobiledgeX; 24 | using DistributedMatchEngine; 25 | using System.Threading.Tasks; 26 | 27 | namespace EdgeMultiplay 28 | { 29 | /// 30 | /// EdgeManager is the class responsible for Connection to the server, Handling Server Events and storing game session 31 | /// EdgeManager requires LocationService if you are using a MobiledgeX Server 32 | /// EdgeManager have many static variables, your app/game should have only one EdgeManager 33 | /// 34 | [RequireComponent(typeof(MobiledgeX.LocationService))] 35 | [AddComponentMenu("EdgeMultiplay/EdgeManager")] 36 | public class EdgeManager : MonoBehaviour 37 | { 38 | #region EdgeManager static variables and constants 39 | 40 | public static MobiledgeXIntegration integration; 41 | static MobiledgeXWebSocketClient wsClient; 42 | static MobiledgeXUDPClient udpClient; 43 | /// 44 | /// The Game Session stored after the player is registered on the server 45 | /// 46 | public static Session gameSession; 47 | /// 48 | /// list of the current players in the same room as the local player 49 | /// 50 | public static List currentRoomPlayers = new List(); 51 | /// 52 | /// Represents the local player, to send messages to the server use EdgeManager.MessageSender.BroadcastMessage() 53 | /// 54 | public static NetworkedPlayer MessageSender; 55 | public static NetworkedPlayer localPlayer; 56 | /// 57 | /// Indicates that the game have already started 58 | /// 59 | public static bool gameStarted; 60 | public static List observers = new List(); 61 | 62 | /// 63 | /// If you want to have a different World Origin, Players will be spawned relative to the Transform specified 64 | /// 65 | public Transform WorldOriginTransform; 66 | #endregion 67 | 68 | #region private variables 69 | 70 | byte[] udpMsg; 71 | string wsMsg; 72 | 73 | #endregion 74 | 75 | #region EdgeManager Editor Exposed Variables 76 | 77 | /// 78 | /// List of GameObjects to be used as player avatar 79 | /// 80 | [Header("Players", order = 0)] 81 | [Tooltip("Player Avatars to be instatiated in once the Game starts")] 82 | public List SpawnPrefabs; 83 | 84 | /// 85 | /// List of Spawn Info, Spawn Info consists of Position and the Rotation Eulers 86 | /// 87 | [Tooltip("Positions for spawning player prefabs, the order is based on who connects first")] 88 | public List SpawnInfo; 89 | private static EdgeManager instance; 90 | #endregion 91 | 92 | #region MonoBehaviour Callbacks 93 | private void Awake() 94 | { 95 | DontDestroyOnLoad(this); 96 | if (instance == null) 97 | { 98 | instance = this; 99 | } 100 | else 101 | { 102 | Destroy(gameObject); 103 | } 104 | integration = new MobiledgeXIntegration(); 105 | } 106 | 107 | void Update() 108 | { 109 | if (wsClient == null) 110 | { 111 | return; 112 | } 113 | //websocket receive queue 114 | var ws_queue = wsClient.receiveQueue; 115 | while (ws_queue.TryPeek(out wsMsg)) 116 | { 117 | ws_queue.TryDequeue(out wsMsg); 118 | HandleWebSocketMessage(wsMsg); 119 | wsMsg = null; 120 | } 121 | if (udpClient == null) 122 | { 123 | return; 124 | } 125 | //udp receive queue 126 | while (udpClient.receiveQueue.TryPeek(out udpMsg)) 127 | { 128 | udpClient.receiveQueue.TryDequeue(out udpMsg); 129 | HandleUDPMessage(Encoding.UTF8.GetString(udpMsg)); 130 | udpMsg = null; 131 | } 132 | } 133 | 134 | void OnDestroy() 135 | { 136 | observers = null; 137 | currentRoomPlayers = null; 138 | if (wsClient != null) 139 | { 140 | wsClient.tokenSource.Cancel(); 141 | } 142 | if (udpClient != null) 143 | { 144 | udpClient.Dispose(); 145 | } 146 | if (Application.platform == RuntimePlatform.OSXPlayer 147 | || Application.platform == RuntimePlatform.WindowsPlayer 148 | || Application.platform == RuntimePlatform.LinuxPlayer) 149 | { 150 | Environment.Exit(0); 151 | } 152 | } 153 | 154 | #endregion 155 | 156 | #region Static EdgeManager functions 157 | /// 158 | /// Connect to your EdgeMultiplay server based on location and carrier info 159 | /// 160 | /// use public override void OnConnectionToEdge() to get the server response 161 | /// 162 | /// 163 | /// set to true for connection based on location info only 164 | /// set to true to use overloaded location sat in setFallbackLocation() 165 | /// You can specify a path for your connection to be verified on the server side 166 | /// Connection Task, use OnConnectionToEdge() to listen to the async task result 167 | public async Task ConnectToServer(bool useAnyCarrierNetwork = true, bool useFallBackLocation = false, string path = "") 168 | { 169 | if (Configs.clientSettings.useLocalHostServer) 170 | { 171 | try 172 | { 173 | gameSession = new Session(); 174 | wsClient = new MobiledgeXWebSocketClient(); 175 | Uri uri = new Uri("ws://" + Configs.clientSettings.hostIPAddress + ":" + Configs.clientSettings.WebSocketPort + path); 176 | if (wsClient.isOpen()) 177 | { 178 | wsClient.Dispose(); 179 | wsClient = new MobiledgeXWebSocketClient(); 180 | } 181 | await wsClient.Connect(@uri); 182 | EdgeMultiplayCallbacks.connectedToEdge(); 183 | } 184 | catch (Exception e) 185 | { 186 | EdgeMultiplayCallbacks.failureToConnect(e.Message); 187 | Debug.LogError("EdgeMultiplay: Failed to connect to your Local Host, Check console for more details"); 188 | } 189 | } 190 | else 191 | { 192 | try 193 | { 194 | gameSession = new Session(); 195 | integration.UseWifiOnly(useAnyCarrierNetwork); 196 | integration.useFallbackLocation = useFallBackLocation; 197 | wsClient = new MobiledgeXWebSocketClient(); 198 | await integration.RegisterAndFindCloudlet(); 199 | integration.GetAppPort(LProto.L_PROTO_TCP, Configs.clientSettings.WebSocketPort); 200 | string url = integration.GetUrl("ws") + path; 201 | Uri uri = new Uri(url); 202 | if (wsClient.isOpen()) 203 | { 204 | wsClient.Dispose(); 205 | wsClient = new MobiledgeXWebSocketClient(); 206 | } 207 | await wsClient.Connect(@uri); 208 | EdgeMultiplayCallbacks.connectedToEdge(); 209 | } 210 | catch (Exception e) 211 | { 212 | EdgeMultiplayCallbacks.failureToConnect(e.Message); 213 | Debug.LogError("EdgeMultiplay: Failed to connect to Edge, Check console for more details"); 214 | } 215 | } 216 | } 217 | 218 | /// 219 | /// Get Player using the player id 220 | /// 221 | /// playerId is a unique Id assigned to player during OnRegister() and saved into EdgeManager.gameSession.playerId 222 | /// The NetworkedPlayer of the supplied playerId 223 | public static NetworkedPlayer GetPlayer(string playerId) 224 | { 225 | NetworkedPlayer playerRequested; 226 | playerRequested = currentRoomPlayers.Find(player => player.playerId == playerId); 227 | if (playerRequested == null) 228 | { 229 | Debug.LogError("Error finding player with id: " + playerId); 230 | } 231 | return playerRequested; 232 | } 233 | 234 | /// 235 | /// Get Player using the player index in the room 236 | /// 237 | /// playerIndex is an id assigned to player based on the precedence of joining the room 238 | /// The NetworkedPlayer of the supplied playerIndex 239 | public static NetworkedPlayer GetPlayer(int playerIndex) 240 | { 241 | NetworkedPlayer playerRequested; 242 | playerRequested = currentRoomPlayers.Find(player => player.playerIndex == playerIndex); 243 | if (playerRequested == null) 244 | { 245 | Debug.LogError("Error finding player with index: " + playerIndex); 246 | } 247 | return playerRequested; 248 | } 249 | 250 | /// 251 | /// Sends Join Or Create Room request to the server, the server will try to match a player with any available room 252 | /// if the server didn't find an available room, the server will create a room for the player 253 | /// 254 | /// use public override void OnRoomCreated or public override void OnRoomJoin to get the server response 255 | /// 256 | /// 257 | /// player name to be assigned to player 258 | /// (integer value) Avatar Index from EdgeManager Spawn Prefabs 259 | /// In case of room creation, the maximum players allowed in the room 260 | /// In case of room creation, the minimum players threshold to start a game, if less than 2, minPlayersPerRoom == maxPlayersPerRoom 261 | /// Dictionary custom data associated with the player 262 | public static void JoinOrCreateRoom(string playerName, int playerAvatar, int maxPlayersPerRoom, Dictionary playerTags = null, int minPlayersToStartGame = 0) 263 | { 264 | if (maxPlayersPerRoom < 2) 265 | { 266 | Debug.LogError("EdgeMultiplay : maxPlayersPerRoom must be greater than 1"); 267 | return; 268 | } 269 | Hashtable playertagsHashtable; 270 | if (playerTags != null) 271 | { 272 | playertagsHashtable = Tag.DictionaryToHashtable(playerTags); 273 | } 274 | else 275 | { 276 | playertagsHashtable = null; 277 | } 278 | JoinOrCreateRoomRequest createOrJoinRoomRequest = new JoinOrCreateRoomRequest(playerName, playerAvatar, maxPlayersPerRoom, playertagsHashtable, minPlayersToStartGame); 279 | wsClient.Send(Messaging.Serialize(createOrJoinRoomRequest)); 280 | } 281 | 282 | /// 283 | /// Sends a request to the server to get a full list of rooms on the servers 284 | /// 285 | /// use public override void OnRoomsListReceived to get the server response 286 | /// 287 | /// 288 | public static void GetRooms() 289 | { 290 | GetRoomsRequest getRoomsRequest = new GetRoomsRequest(); 291 | wsClient.Send(Messaging.Serialize(getRoomsRequest)); 292 | } 293 | 294 | /// 295 | /// Sends a request to the server to get a full list of rooms on the servers 296 | /// 297 | /// use public override void OnRoomsListReceived to get the server response 298 | /// 299 | /// 300 | public static void GetAvailableRooms() 301 | { 302 | GetAvailableRoomsRequest getAvailableRoomsRequest = new GetAvailableRoomsRequest(); 303 | wsClient.Send(Messaging.Serialize(getAvailableRoomsRequest)); 304 | } 305 | 306 | /// 307 | /// Sends a request to the server to create a room 308 | /// 309 | /// use public override void OnRoomCreated to get the server response 310 | /// 311 | /// 312 | /// player name to be assigned to player 313 | /// (integer value) Avatar Index from EdgeManager Spawn Prefabs 314 | /// The maximum players allowed in the room 315 | /// In case of room creation, the minimum players threshold to start a game, if less than 2, minPlayersPerRoom == maxPlayersPerRoom 316 | /// Dictionary custom data associated with the player 317 | public static void CreateRoom(string playerName, int playerAvatar, int maxPlayersPerRoom, Dictionary playerTags = null, int minPlayersToStartGame = 0) 318 | { 319 | if (maxPlayersPerRoom < 2) 320 | { 321 | Debug.LogError("EdgeMultiplay : maxPlayersPerRoom must be greater than 1"); 322 | return; 323 | } 324 | // Assure Player is not already a member of another room 325 | if (gameSession.roomId == "") 326 | { 327 | Hashtable playertagsHashtable; 328 | if (playerTags != null) 329 | { 330 | playertagsHashtable = Tag.DictionaryToHashtable(playerTags); 331 | } 332 | else 333 | { 334 | playertagsHashtable = null; 335 | } 336 | CreateRoomRequest createRoomRequest = new CreateRoomRequest(playerName, playerAvatar, maxPlayersPerRoom, playertagsHashtable, minPlayersToStartGame); 337 | wsClient.Send(Messaging.Serialize(createRoomRequest)); 338 | } 339 | else 340 | { 341 | Debug.LogError("EdgeMultiplay : Player is already a member in another room"); 342 | } 343 | } 344 | 345 | /// 346 | /// Sends a request to the server to join a room 347 | /// 348 | /// use public override void OnRoomJoin to get the server response 349 | /// 350 | /// 351 | /// Id of the room intended to join 352 | /// (integer value) Avatar Index from EdgeManager Spawn Prefabs 353 | /// Dictionary custom data associated with the player 354 | public static void JoinRoom(string roomId, string playerName, int playerAvatar, Dictionary playerTags = null) 355 | { 356 | if (gameSession.roomId == "") 357 | { 358 | Hashtable playertagsHashtable; 359 | if (playerTags != null) 360 | { 361 | playertagsHashtable = Tag.DictionaryToHashtable(playerTags); 362 | } 363 | else 364 | { 365 | playertagsHashtable = null; 366 | } 367 | JoinRoomRequest joinRoomRequest = new JoinRoomRequest(roomId, playerName, playerAvatar, playertagsHashtable); 368 | wsClient.Send(Messaging.Serialize(joinRoomRequest)); 369 | } 370 | else 371 | { 372 | if (gameSession.roomId == roomId) 373 | { 374 | Debug.LogError("EdgeMultiplay : Player is already a member in this room"); 375 | } 376 | else 377 | { 378 | Debug.LogError("EdgeMultiplay : Player is already a member in another room"); 379 | } 380 | } 381 | } 382 | 383 | /// 384 | /// Sends a UDP Message from local player to other room members, can be used only after the game starts (OnGameStart()) 385 | /// 386 | /// on the player manager that inherits from NetworkedPlayer use 387 | /// public override void OnMessageReceived to get the forwarded message 388 | /// 389 | /// 390 | /// UDP Messages are limited to 508 bytes if you want to exceed that make sure you slice and reassemble your buffer 391 | /// 392 | /// 393 | /// the GamePlay Event to be forwarded to other room members 394 | public static void SendUDPMessage(GamePlayEvent gameplayEvent) 395 | { 396 | if (!gameStarted) 397 | { 398 | Debug.LogError("Cannot perform this operation, Game didn't start yet"); 399 | return; 400 | } 401 | gameplayEvent.roomId = gameSession.roomId; 402 | gameplayEvent.senderId = gameSession.playerId; 403 | if (udpClient.run) 404 | { 405 | udpClient.Send(gameplayEvent.ToJson()); 406 | } 407 | else 408 | { 409 | Debug.LogError("EdgeMultiplay: Error in sending UDP Message"); 410 | } 411 | } 412 | 413 | /// 414 | /// Sends a WebSocket Message from local player to other room members, can be used only after the game starts (OnGameStart()) 415 | /// 416 | /// on the player manager that inherits from NetworkedPlayer use 417 | /// public override void OnMessageReceived to get the forwarded message 418 | /// 419 | /// 420 | /// 421 | public void SendGamePlayEvent(GamePlayEvent gamePlayEvent) 422 | { 423 | if (!gameStarted) 424 | { 425 | Debug.LogError("Cannot perform this operation, Game didn't start yet"); 426 | return; 427 | } 428 | gamePlayEvent.roomId = gameSession.roomId; 429 | gamePlayEvent.senderId = gameSession.playerId; 430 | wsClient.Send(Messaging.Serialize(gamePlayEvent)); 431 | } 432 | 433 | /// 434 | /// Kill the connection to your EdgeMultiplay server 435 | /// 436 | public static void Disconnect() 437 | { 438 | wsClient = null; 439 | udpClient = null; 440 | foreach (NetworkedPlayer player in currentRoomPlayers) 441 | { 442 | Destroy(player.gameObject); 443 | } 444 | currentRoomPlayers = new List(); 445 | gameSession = new Session(); 446 | gameStarted = false; 447 | MessageSender = null; 448 | localPlayer = null; 449 | observers = new List(); 450 | } 451 | 452 | /// 453 | /// Exit the current room you are in 454 | /// 455 | public static void ExitRoom() 456 | { 457 | ExitRoomRequest exitRoomRequest = new ExitRoomRequest(); 458 | wsClient.Send(Messaging.Serialize(exitRoomRequest)); 459 | } 460 | 461 | #endregion 462 | 463 | #region EdgeManager Functions 464 | 465 | void HandleWebSocketMessage(string message) 466 | { 467 | var msg = MessageWrapper.UnWrapMessage(message); 468 | switch (msg.type) 469 | { 470 | case "register": 471 | Register register = Messaging.Deserialize(message); 472 | gameSession.sessionId = register.sessionId; 473 | gameSession.playerId = register.playerId; 474 | EdgeMultiplayCallbacks.registerEvent(); 475 | break; 476 | 477 | case "notification": 478 | Notification notification = Messaging.Deserialize(message); 479 | switch (notification.notificationText) 480 | { 481 | case "left-room": 482 | gameSession.roomId = ""; 483 | EdgeMultiplayCallbacks.leftRoom(); 484 | break; 485 | case "join-room-faliure": 486 | EdgeMultiplayCallbacks.joinRoomFaliure(); 487 | break; 488 | case "new-room-created-in-lobby": 489 | EdgeMultiplayCallbacks.newRoomCreatedInLobby(); 490 | break; 491 | case "room-removed-from-lobby": 492 | EdgeMultiplayCallbacks.roomRemovedFromLobby(); 493 | break; 494 | case "rooms-updated": 495 | EdgeMultiplayCallbacks.roomsUpdated(); 496 | break; 497 | } 498 | EdgeMultiplayCallbacks.notificationEvent(notification); 499 | break; 500 | 501 | case "roomsList": 502 | RoomsList roomsList = Messaging.Deserialize(message); 503 | EdgeMultiplayCallbacks.roomsList(roomsList.rooms); 504 | break; 505 | 506 | case "roomCreated": 507 | RoomCreated roomCreated = Messaging.Deserialize(message); 508 | gameSession.roomId = roomCreated.room.roomId; 509 | EdgeMultiplayCallbacks.roomCreated(roomCreated.room); 510 | break; 511 | 512 | case "roomJoin": 513 | RoomJoin roomJoin = Messaging.Deserialize(message); 514 | gameSession.roomId = roomJoin.room.roomId; 515 | EdgeMultiplayCallbacks.roomJoin(roomJoin.room); 516 | int roomMembersCount = roomJoin.room.roomMembers.Count; 517 | if (roomMembersCount >= roomJoin.room.minPlayersToStartGame) //game already started 518 | { 519 | StartGame(roomJoin.room); 520 | EdgeMultiplayCallbacks.gameStart(); 521 | } 522 | break; 523 | 524 | case "playerJoinedRoom": 525 | PlayerJoinedRoom playerJoinedRoom = Messaging.Deserialize(message); 526 | EdgeMultiplayCallbacks.playerRoomJoined(playerJoinedRoom.room); 527 | if (playerJoinedRoom.room.roomMembers[playerJoinedRoom.room.roomMembers.Count - 1].playerId == gameSession.playerId) 528 | { 529 | break; 530 | } 531 | else 532 | { 533 | if (gameStarted) 534 | { 535 | NetworkedPlayer networkedPlayer = SpawnPlayer(playerJoinedRoom.room.roomMembers[playerJoinedRoom.room.roomMembers.Count - 1]); 536 | currentRoomPlayers.Add(networkedPlayer); 537 | EdgeMultiplayCallbacks.eventReceived += networkedPlayer.OnWebSocketEventReceived; 538 | EdgeMultiplayCallbacks.udpEventReceived += networkedPlayer.OnUDPEventReceived; 539 | } 540 | else if (playerJoinedRoom.room.roomMembers.Count == playerJoinedRoom.room.minPlayersToStartGame) 541 | { 542 | StartGame(playerJoinedRoom.room); 543 | EdgeMultiplayCallbacks.gameStart(); 544 | } 545 | } 546 | break; 547 | 548 | case "GamePlayEvent": 549 | GamePlayEvent gamePlayEvent = Messaging.Deserialize(message); 550 | switch (gamePlayEvent.eventName) 551 | { 552 | case "NewObservableCreated": 553 | CreateObservableObject(gamePlayEvent); 554 | break; 555 | case "ObservableOwnershipChange": 556 | UpdateObserverOwnership(gamePlayEvent); 557 | break; 558 | case "TakeOverObservable": 559 | TakeOverObservable(gamePlayEvent); 560 | break; 561 | case "OwnershipRequest": 562 | OwnershipRequestReceived(gamePlayEvent); 563 | break; 564 | default: 565 | ReflectEvent(gamePlayEvent); 566 | break; 567 | } 568 | break; 569 | 570 | case "memberLeft": 571 | RoomMemberLeft playerLeft = Messaging.Deserialize(message); 572 | EdgeMultiplayCallbacks.playerLeft(playerLeft); 573 | break; 574 | 575 | default: 576 | Debug.LogError("Unknown WebSocket message arrived: " + msg.type + ", message: " + message); 577 | break; 578 | } 579 | } 580 | 581 | void StartGame(Room room) 582 | { 583 | gameSession.currentPlayers = room.roomMembers.ToArray(); 584 | foreach (Player player in room.roomMembers) 585 | { 586 | if (player.playerId == gameSession.playerId) 587 | { 588 | gameSession.playerIndex = player.playerIndex; 589 | } 590 | } 591 | CreatePlayers(gameSession.currentPlayers); 592 | gameStarted = true; 593 | if (Configs.clientSettings.useLocalHostServer) 594 | { 595 | udpClient = new MobiledgeXUDPClient(Configs.clientSettings.hostIPAddress, Configs.clientSettings.UDPPort); 596 | } 597 | else 598 | { 599 | udpClient = new MobiledgeXUDPClient(integration.GetHost(), integration.GetAppPort(LProto.L_PROTO_UDP, Configs.clientSettings.UDPPort).public_port); 600 | } 601 | SendUDPMessage(new GamePlayEvent() { eventName = "Start" }); 602 | } 603 | 604 | void HandleUDPMessage(string message) 605 | { 606 | GamePlayEvent gamePlayEvent = JsonUtility.FromJson(message); 607 | switch (gamePlayEvent.type) 608 | { 609 | case "GamePlayEvent": 610 | if (gamePlayEvent.eventName == "EdgeMultiplayObserver") 611 | { 612 | SyncObject(gamePlayEvent); 613 | } 614 | else 615 | { 616 | // if used for other than that Syncing GameObjects Position & Rotation 617 | // it wil trigger OnUDPMessagesReceived() 618 | EdgeMultiplayCallbacks.udpEventReceived(gamePlayEvent); 619 | } 620 | break; 621 | default: 622 | Debug.LogError("Unknown UDP message arrived: " + message); 623 | break; 624 | } 625 | } 626 | 627 | void CreateObservableObject(GamePlayEvent newObservableEvent) 628 | { 629 | if (!gameStarted) 630 | { 631 | Debug.LogError("Cannot create observables, Game didn't start yet"); 632 | return; 633 | } 634 | if (localPlayer.playerId == newObservableEvent.stringData[0]) 635 | { 636 | return; 637 | } 638 | NetworkedPlayer playerCreatedObserver = GetPlayer(newObservableEvent.stringData[0]); 639 | Observable observable = playerCreatedObserver.CreateObservableObject( 640 | prefabName: newObservableEvent.stringData[1], 641 | startPosition: Util.ConvertFloatArrayToVector3(newObservableEvent.floatData, 0), 642 | startRotation: Quaternion.Euler(Util.ConvertFloatArrayToVector3(newObservableEvent.floatData, 3)), 643 | syncOption: (SyncOptions)Enum.ToObject(typeof(SyncOptions), newObservableEvent.integerData[0]), 644 | interpolatePosition: newObservableEvent.booleanData[0], 645 | interpolateRotation: newObservableEvent.booleanData[1], 646 | interpolationFactor: newObservableEvent.floatData[6]); 647 | EdgeMultiplayCallbacks.newObservableCreated(observable); 648 | 649 | } 650 | 651 | /// 652 | /// When an observable object change its owner, OwnershipChangeEvent is sent from the owner to other players 653 | /// Changing ownership must occur at the owner world 654 | /// 655 | /// OwnershipChangeEvent contains (oldOwnerId, the observable Index and the newOwnerId) 656 | void UpdateObserverOwnership(GamePlayEvent ownershipChangeEvent) 657 | { 658 | if (localPlayer.playerId == ownershipChangeEvent.senderId) 659 | { 660 | return; 661 | } 662 | NetworkedPlayer oldOwner = GetPlayer(ownershipChangeEvent.senderId); 663 | Observable observer; 664 | if (oldOwner.observer != null) 665 | { 666 | observer = oldOwner.observer.observables[ownershipChangeEvent.integerData[0]]; 667 | } 668 | else 669 | { 670 | Debug.LogError("Couldn't find old owner.observer"); 671 | return; 672 | } 673 | NetworkedPlayer newOwner = GetPlayer(ownershipChangeEvent.stringData[0]); 674 | if (newOwner.observer == null) 675 | { 676 | newOwner.gameObject.AddComponent(); 677 | } 678 | newOwner.observer.observables.Add(observer); 679 | newOwner.observer.UpdateObservables(); 680 | oldOwner.observer.observables.RemoveAt(ownershipChangeEvent.integerData[0]); 681 | } 682 | 683 | void TakeOverObservable(GamePlayEvent gamePlayEvent) 684 | { 685 | // check if the observable owner is the local player 686 | if (gamePlayEvent.stringData[0] == localPlayer.playerId) 687 | { 688 | //get the observable 689 | Observable observable = localPlayer.observer.observables 690 | .Find(obs => obs.observableIndex == gamePlayEvent.integerData[0]); 691 | if (observable == null) 692 | { 693 | Debug.LogWarning("EdgeMultiplay: couldn't find the observable"); 694 | return; 695 | } 696 | // change the observable ownership from the current owner to the sender 697 | observable.ChangeOwnership(gamePlayEvent.senderId); 698 | } 699 | } 700 | 701 | void OwnershipRequestReceived(GamePlayEvent gamePlayEvent) 702 | { 703 | // check if the observable owner is the local player 704 | if (gamePlayEvent.stringData[0] == localPlayer.playerId) 705 | { 706 | //get the observable 707 | Observable observable = localPlayer.observer.observables 708 | .Find(obs => obs.observableIndex == gamePlayEvent.integerData[0]); 709 | if (observable == null) 710 | { 711 | Debug.LogWarning("EdgeMultiplay: couldn't find the observable"); 712 | return; 713 | } 714 | NetworkedPlayer requestee = GetPlayer(gamePlayEvent.senderId); 715 | 716 | // Inform the current owner about the ownership request 717 | // Triggers OnOwnershipRequestReceived() callback 718 | localPlayer.ownershipRequested(requestee, observable); 719 | } 720 | } 721 | 722 | /// 723 | /// If the LocalPlayer is observing any transforms, once there is any update to the observed transform 724 | /// the local player will send the updated transfrom to its clones in the other players' world. 725 | /// 726 | /// the received gameplay event contains (observable owner id, observable index, syncOption, updated transform data) 727 | void SyncObject(GamePlayEvent receivedEvent) 728 | { 729 | if (receivedEvent.senderId == localPlayer.playerId) 730 | { 731 | return; 732 | } 733 | NetworkedPlayer sourcePlayer = GetPlayer(receivedEvent.senderId); 734 | if (sourcePlayer.isLocalPlayer) 735 | { 736 | return; 737 | } 738 | Observable observableObj; 739 | int observableIndex = receivedEvent.integerData[1]; 740 | observableObj = sourcePlayer.observer.observables.Find(observer => observer.observableIndex == observableIndex); 741 | if (observableObj == null) 742 | { 743 | Debug.LogError("No observer found with this id " + receivedEvent.integerData[1]); 744 | return; 745 | } 746 | switch (receivedEvent.integerData[0]) 747 | { 748 | case (int)SyncOptions.SyncPosition: 749 | observableObj.observeredTransform.transform.position = Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 0); 750 | break; 751 | case (int)SyncOptions.SyncRotation: 752 | observableObj.observeredTransform.transform.rotation = Quaternion.Euler(Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 0)); 753 | break; 754 | case (int)SyncOptions.SyncPositionAndRotation: 755 | observableObj.observeredTransform.transform.position = Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 0); 756 | observableObj.observeredTransform.transform.rotation = Quaternion.Euler(Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 3)); 757 | break; 758 | case (int)SyncOptions.SyncLocalPosition: 759 | Util.SetLocalPostion(observableObj.observeredTransform.transform, Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 0)); 760 | break; 761 | case (int)SyncOptions.SyncLocalRotation: 762 | Util.SetLocalRotation(observableObj.observeredTransform.transform, Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 0)); 763 | break; 764 | case (int)SyncOptions.SyncLocalPositionAndRotation: 765 | Util.SetLocalPostion(observableObj.observeredTransform.transform, Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 0)); 766 | Util.SetLocalRotation(observableObj.observeredTransform.transform, Util.ConvertFloatArrayToVector3(receivedEvent.floatData, 3)); 767 | break; 768 | } 769 | } 770 | 771 | void ReflectEvent(GamePlayEvent receivedEvent) 772 | { 773 | EdgeMultiplayCallbacks.eventReceived(receivedEvent); 774 | } 775 | 776 | void CreatePlayers(Player[] gamePlayers) 777 | { 778 | try 779 | { 780 | foreach (Player player in gamePlayers) 781 | { 782 | NetworkedPlayer networkedPlayer = SpawnPlayer(player); 783 | currentRoomPlayers.Add(networkedPlayer); 784 | EdgeMultiplayCallbacks.eventReceived += networkedPlayer.OnWebSocketEventReceived; 785 | EdgeMultiplayCallbacks.udpEventReceived += networkedPlayer.OnUDPEventReceived; 786 | if (player.playerId == gameSession.playerId) 787 | { 788 | localPlayer = MessageSender = networkedPlayer; 789 | } 790 | } 791 | } 792 | catch (NullReferenceException) 793 | { 794 | throw new Exception("EdgeMultiplay: Error in creating players, Make sure to attach your Prefabs to EdgeManager.SpawnInfo in the inspector"); 795 | } 796 | catch (ArgumentOutOfRangeException) 797 | { 798 | throw new Exception("EdgeMultiplay: Error in creating players, Make sure Size of EdgeManager Spawn Info equal or greater than number of players in the room"); 799 | } 800 | catch (Exception) 801 | { 802 | throw new Exception("EdgeMultiplay: Error in creating players"); 803 | } 804 | } 805 | NetworkedPlayer SpawnPlayer(Player player) 806 | { 807 | GameObject playerObj = SpawnPrefabs[player.playerAvatar].gameObject; 808 | playerObj.GetComponent().SetUpPlayer(player, gameSession.roomId, player.playerId == gameSession.playerId); 809 | 810 | GameObject playerCreated; 811 | if (WorldOriginTransform != null) 812 | { 813 | playerCreated = Instantiate(playerObj, WorldOriginTransform); 814 | Util.SetLocalPostion(playerCreated.transform, SpawnInfo[player.playerIndex].position); 815 | Util.SetLocalRotation(playerCreated.transform, SpawnInfo[player.playerIndex].rotation); 816 | } 817 | else 818 | { 819 | playerCreated = Instantiate(playerObj, SpawnInfo[player.playerIndex].position, Quaternion.Euler(SpawnInfo[player.playerIndex].rotation)); 820 | } 821 | NetworkedPlayer networkedPlayer = playerCreated.GetComponent(); 822 | if (player.playerName == "") 823 | { 824 | playerCreated.name = networkedPlayer.playerName = "Player " + (player.playerIndex + 1); 825 | } 826 | else 827 | { 828 | playerCreated.name = player.playerName; 829 | } 830 | 831 | return networkedPlayer; 832 | } 833 | #endregion 834 | 835 | } 836 | } 837 | --------------------------------------------------------------------------------