Initial commit of Unity files, Network Library, Project settings (Compiler flags for networking to work on Unity), Unity Version 2019.4 LTS
This commit is contained in:
parent
8ec6828fa0
commit
9b69003715
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.VisualStudio.Workload.ManagedGame"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0763d456e1ae157498c954ce5f728954
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c948a1a65858f7947baef36c7dc71069
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class ArrayElementTitleAttribute : PropertyAttribute {
|
||||
public string fieldName;
|
||||
|
||||
public ArrayElementTitleAttribute() {
|
||||
this.fieldName = "name";
|
||||
}
|
||||
|
||||
public ArrayElementTitleAttribute(string fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e8550d86f434d784e98b16c3530703d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a0346cc8c59a10f4a966240dd9e440b3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,71 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
[CustomPropertyDrawer(typeof(ArrayElementTitleAttribute))]
|
||||
public class ArrayElementTitleDrawer : PropertyDrawer {
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
|
||||
return EditorGUI.GetPropertyHeight(property, label, true);
|
||||
}
|
||||
|
||||
protected virtual ArrayElementTitleAttribute titleAttribute => (ArrayElementTitleAttribute)attribute;
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
string fullpath = property.propertyPath + "." + titleAttribute.fieldName;
|
||||
var prop = property.serializedObject.FindProperty(fullpath);
|
||||
string newlabel = GetTitle(prop);
|
||||
if (string.IsNullOrEmpty(newlabel))
|
||||
newlabel = label.text;
|
||||
|
||||
EditorGUI.PropertyField(position, property, new GUIContent(newlabel, label.tooltip), true);
|
||||
}
|
||||
|
||||
|
||||
private string GetTitle(SerializedProperty prop) {
|
||||
switch (prop.propertyType) {
|
||||
case SerializedPropertyType.Generic:
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
return prop.intValue.ToString();
|
||||
case SerializedPropertyType.Boolean:
|
||||
return prop.boolValue.ToString();
|
||||
case SerializedPropertyType.Float:
|
||||
return prop.floatValue.ToString();
|
||||
case SerializedPropertyType.String:
|
||||
return prop.stringValue;
|
||||
case SerializedPropertyType.Color:
|
||||
return prop.colorValue.ToString();
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
return prop.objectReferenceValue.ToString();
|
||||
case SerializedPropertyType.LayerMask:
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
return prop.enumNames[prop.enumValueIndex];
|
||||
case SerializedPropertyType.Vector2:
|
||||
return prop.vector2Value.ToString();
|
||||
case SerializedPropertyType.Vector3:
|
||||
return prop.vector3Value.ToString();
|
||||
case SerializedPropertyType.Vector4:
|
||||
return prop.vector4Value.ToString();
|
||||
case SerializedPropertyType.Rect:
|
||||
break;
|
||||
case SerializedPropertyType.ArraySize:
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
break;
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
break;
|
||||
case SerializedPropertyType.Bounds:
|
||||
break;
|
||||
case SerializedPropertyType.Gradient:
|
||||
break;
|
||||
case SerializedPropertyType.Quaternion:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e22b2b5dabe661b4498b020007dbb838
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
|
||||
public class EnumFlagDrawer : PropertyDrawer {
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
EnumFlagAttribute flagSettings = (EnumFlagAttribute)attribute;
|
||||
Enum targetEnum = GetBaseProperty<Enum>(property);
|
||||
|
||||
string propName = flagSettings.enumName;
|
||||
if (string.IsNullOrEmpty(propName))
|
||||
propName = property.displayName;
|
||||
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
Enum enumNew = EditorGUI.EnumFlagsField(position, propName, targetEnum);
|
||||
property.intValue = (int)Convert.ChangeType(enumNew, targetEnum.GetType());
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
static T GetBaseProperty<T>(SerializedProperty prop) {
|
||||
// Separate the steps it takes to get to this property
|
||||
string[] separatedPaths = prop.propertyPath.Split('.');
|
||||
|
||||
// Go down to the root of this serialized property
|
||||
System.Object reflectionTarget = prop.serializedObject.targetObject as object;
|
||||
// Walk down the path to get the target object
|
||||
foreach (var path in separatedPaths) {
|
||||
FieldInfo fieldInfo = reflectionTarget.GetType().GetField(path);
|
||||
reflectionTarget = fieldInfo.GetValue(reflectionTarget);
|
||||
}
|
||||
return (T)reflectionTarget;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bde9bcb8cf46e7e4090257fbff464d17
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer {
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
GUI.enabled = false;
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
GUI.enabled = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e772dac1e610df54fbbf25f1073012dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,11 @@
|
|||
using UnityEngine;
|
||||
|
||||
public class EnumFlagAttribute : PropertyAttribute {
|
||||
public string enumName;
|
||||
|
||||
public EnumFlagAttribute() { }
|
||||
|
||||
public EnumFlagAttribute(string name) {
|
||||
enumName = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6c03bbb4c7afaf746ab43c4fbeb3ae9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,5 @@
|
|||
using UnityEngine;
|
||||
|
||||
public class ReadOnlyAttribute : PropertyAttribute {
|
||||
public ReadOnlyAttribute() { }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2175955395688374191c019f19c355be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9bbd61e2d44aca241a19686e7dbb6203
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,55 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public static class DoubleDictionary<Key, Value>{
|
||||
|
||||
static Dictionary<Key, Value> keys;
|
||||
static Dictionary<Value, Key> values;
|
||||
|
||||
static DoubleDictionary(){
|
||||
Create();
|
||||
}
|
||||
|
||||
public static void Create(){
|
||||
keys = new Dictionary<Key, Value>();
|
||||
values = new Dictionary<Value, Key>();
|
||||
}
|
||||
|
||||
public static void Set(Key key, Value value){
|
||||
keys.Add(key, value);
|
||||
values.Add(value, key);
|
||||
}
|
||||
|
||||
public static void Remove(Key key){
|
||||
Value value;
|
||||
if (keys.TryGetValue(key, out value)){
|
||||
keys.Remove(key);
|
||||
values.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(Value value){
|
||||
Key key;
|
||||
if (values.TryGetValue(value, out key)){
|
||||
keys.Remove(key);
|
||||
values.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(Key key, Value value){
|
||||
keys.Remove(key);
|
||||
values.Remove(value);
|
||||
}
|
||||
|
||||
public static Key Get(Value value){
|
||||
Key key;
|
||||
return values.TryGetValue(value, out key) ? key : default;
|
||||
}
|
||||
|
||||
public static Value Get(Key key){
|
||||
Value value;
|
||||
return keys.TryGetValue(key, out value) ? value : default;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f87146a5f00172e4c8bde3d6fb4918df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,21 @@
|
|||
public static class StringExtensionMethods
|
||||
{
|
||||
public static int GetStableHashCode(this string str)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash1 = 5381;
|
||||
int hash2 = hash1;
|
||||
|
||||
for(int i = 0; i < str.Length && str[i] != '\0'; i += 2)
|
||||
{
|
||||
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||
if (i == str.Length - 1 || str[i+1] == '\0')
|
||||
break;
|
||||
hash2 = ((hash2 << 5) + hash2) ^ str[i+1];
|
||||
}
|
||||
|
||||
return hash1 + (hash2*1566083941);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 91365f7329a827044a590282cb8a77c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0b45bb8cb359039458f36fee5d02af4a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 488818778f517a047ad888c9ccfb7b5a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,797 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
using ExitGames.Client.Photon.LoadBalancing;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using PConst = PhotonConstants;
|
||||
|
||||
|
||||
using Attribute = System.Attribute;
|
||||
using Type = System.Type;
|
||||
using Action = System.Action;
|
||||
|
||||
using EntityNetwork;
|
||||
|
||||
public abstract class EntityBase : MonoBehaviour {
|
||||
|
||||
/// <summary>
|
||||
/// Entity ID for the entity, all entities require a unique ID.
|
||||
/// </summary>
|
||||
//[System.NonSerialized]
|
||||
public int EntityID;
|
||||
|
||||
/// <summary>
|
||||
/// Player ID number who is considered the authority for the object.
|
||||
/// isMine / isRemote / isUnclaimed are determined by the authority holder.
|
||||
/// Defaults to -1 if unassigned.
|
||||
/// </summary>
|
||||
//[System.NonSerialized]
|
||||
public int authorityID = -1;
|
||||
|
||||
#region Helpers
|
||||
/// <summary>
|
||||
/// A helper that determines if the object is owned locally.
|
||||
/// </summary>
|
||||
/// <seealso cref="isRemote"/>
|
||||
public bool isMine {
|
||||
get {
|
||||
// Everything is ours when we're not connected
|
||||
if (NetworkManager.net == null) return true;
|
||||
|
||||
// If we're the master and have the appropriate interface, ingore the Authority ID and use the master status
|
||||
if (this is IMasterOwnsUnclaimed && isUnclaimed) {
|
||||
return NetworkManager.isMaster;
|
||||
}
|
||||
|
||||
return NetworkManager.localID == authorityID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper to determine if the object is remote.
|
||||
/// Returns false if we're disconnected
|
||||
/// </summary>
|
||||
/// <seealso cref="isMine"/>
|
||||
public bool isRemote {
|
||||
get {
|
||||
if (NetworkManager.net == null) return false;
|
||||
|
||||
// Similar to isMine, ignore the master status if unclaimed
|
||||
if (this is IMasterOwnsUnclaimed && isUnclaimed) {
|
||||
return !NetworkManager.isMaster;
|
||||
}
|
||||
|
||||
return NetworkManager.localID != authorityID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to evaluate our authority ID being -1. It should be -1 if unclaimed.
|
||||
/// </summary>
|
||||
public bool isUnclaimed {
|
||||
get {
|
||||
return authorityID == -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query to see if we're registered. This is slightly expensive.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if is registered; otherwise, <c>false</c>.</value>
|
||||
public bool isRegistered {
|
||||
get {
|
||||
return EntityManager.Entity(authorityID) != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendIDs(Hashtable h) {
|
||||
h.Add(PConst.eidChar, EntityID);
|
||||
h.Add(PConst.athChar, authorityID);
|
||||
}
|
||||
|
||||
public void Register() {
|
||||
EntityManager.Register(this);
|
||||
}
|
||||
|
||||
public void RaiseEvent(char c, bool includeLocal, params object[] parameters) {
|
||||
var h = new Hashtable();
|
||||
|
||||
AppendIDs(h);
|
||||
|
||||
h.Add(0, c);
|
||||
if (parameters != null)
|
||||
h.Add(1, parameters);
|
||||
|
||||
NetworkManager.netMessage(PhotonConstants.EntityEventCode, h, true);
|
||||
|
||||
if (includeLocal) {
|
||||
InternallyInvokeEvent(c, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RaiseStaticEvent<T>(char c, bool includeLocal, params object[] parameters) where T : EntityBase{
|
||||
var h = new Hashtable();
|
||||
|
||||
// Given we have no instance ID's, we don't append IDs
|
||||
|
||||
h.Add(0, c);
|
||||
|
||||
if (parameters != null)
|
||||
h.Add(1, parameters);
|
||||
|
||||
//var name = typeof(T).
|
||||
h.Add(2,IDfromType(typeof(T)));
|
||||
|
||||
NetworkManager.netMessage(PhotonConstants.EntityEventCode, h, true);
|
||||
|
||||
if (includeLocal)
|
||||
InternallyInvokeStatic(typeof(T),c, parameters);
|
||||
}
|
||||
|
||||
static Dictionary<int,Type> EBTypeIDs;
|
||||
static Dictionary<Type,int> IDToEBs;
|
||||
static void buildTypeIDs(){ // Build a bidirectional lookup of all EntityBase's in the assembly and assign them unique ID's
|
||||
EBTypeIDs = new Dictionary<int,Type>();
|
||||
IDToEBs = new Dictionary<Type,int>();
|
||||
|
||||
var ebType = typeof(EntityBase);
|
||||
var derivedTypes = System.AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()).Where(t=>ebType.IsAssignableFrom(t));
|
||||
|
||||
var sorted = derivedTypes.OrderBy(t => t.FullName);
|
||||
int newID = 0;
|
||||
foreach(var type in sorted) {
|
||||
EBTypeIDs.Add(newID, type);
|
||||
IDToEBs.Add(type, newID);
|
||||
++newID;
|
||||
}
|
||||
|
||||
if (Debug.isDebugBuild || Application.isEditor) {
|
||||
var debugString = new System.Text.StringBuilder();
|
||||
foreach(var pair in EBTypeIDs) {
|
||||
debugString.AppendFormat("{0} -> {1} \n", pair.Value, pair.Key);
|
||||
}
|
||||
|
||||
Debug.Log(debugString.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a unique ID for this objects class that dervives from EntityBase
|
||||
/// </summary>
|
||||
public int typeID {
|
||||
get {
|
||||
return IDfromType(this.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
public static int IDfromType(Type t) {
|
||||
if (IDToEBs != null)
|
||||
return IDToEBs[t];
|
||||
|
||||
buildTypeIDs();
|
||||
|
||||
return IDfromType(t);
|
||||
}
|
||||
public static Type TypeFromID(int id) {
|
||||
if (EBTypeIDs != null)
|
||||
return EBTypeIDs[id];
|
||||
|
||||
buildTypeIDs();
|
||||
|
||||
return TypeFromID(id); // Return the original request
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Serializers
|
||||
|
||||
/// <summary>
|
||||
/// Serialize all tokens with the given label into HashTable h
|
||||
/// Returns true if any contained token requires a reliable update
|
||||
/// If you use IAutoSerialize, this is only neccessary for manual tokens
|
||||
/// </summary>
|
||||
protected bool SerializeToken(Hashtable h, params char[] ca) {
|
||||
bool needsReliable = false;
|
||||
|
||||
var tH = tokenHandler;
|
||||
foreach(char c in ca) {
|
||||
h.Add(c, tH.get(c, this));
|
||||
|
||||
// If we're not already reliable, check if we need reliable
|
||||
if (!needsReliable)
|
||||
needsReliable = tH.alwaysReliable[c];
|
||||
}
|
||||
|
||||
return needsReliable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internally used for building and dispatching entity updates, build a full serialization of auto tokens and ID's
|
||||
/// Due to inconsistent handling/calling contexts, ID's are added safely
|
||||
/// </summary>
|
||||
/// <returns>0 if nothing is sent, 1 if there is content to send, 2 if content should be sent reliably</returns>
|
||||
public int SerializeAuto(Hashtable h) {
|
||||
var tH = tokenHandler;
|
||||
|
||||
var time = Time.realtimeSinceStartup;
|
||||
|
||||
bool reliableFlag = false;
|
||||
bool isSending = false;
|
||||
|
||||
foreach (var c in tH.autoTokens) {
|
||||
if (this[c] < time) {
|
||||
this[c] = time + tH.updateTimes[c];
|
||||
isSending = true;
|
||||
|
||||
h.Add(c, tH.get(c, this));
|
||||
|
||||
if (!reliableFlag)
|
||||
reliableFlag = tH.reliableTokens.Contains(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSending) {
|
||||
SerializeAlwaysTokensSafely(h);
|
||||
//toUpdate.AddRange(tH.alwaysSendTokens);
|
||||
}
|
||||
|
||||
// If none of the tokens actually updated, return 0
|
||||
// Otherwise, return 1 for a normal update, 2 for a reliable update
|
||||
if (!isSending)
|
||||
return 0;
|
||||
|
||||
h.AddOrSet(PConst.eidChar, EntityID);
|
||||
h.AddOrSet(PConst.athChar, authorityID);
|
||||
|
||||
//SerializeToken(toUpdate.Distinct().ToArray());
|
||||
|
||||
return reliableFlag ? 2 : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read specified values out of the hashtable
|
||||
/// In most cases, you'll want to use DeserializeFull instead
|
||||
/// </summary>
|
||||
protected void DeserializeToken(Hashtable h, params char[] ca) {
|
||||
var tH = tokenHandler;
|
||||
foreach(char c in ca) {
|
||||
object value;
|
||||
if (h.TryGetValue(c, out value))
|
||||
tH.set(c, this, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read all attributed tokens fields of the hashtable and update corresponding values.
|
||||
/// This will be called automatically if implementing IAutoDeserialize
|
||||
/// </summary>
|
||||
public void DeserializeFull(Hashtable h) {
|
||||
var tH = tokenHandler;
|
||||
foreach(char c in TokenList()) {
|
||||
object value;
|
||||
if (h.TryGetValue(c, out value))
|
||||
tH.set(c, this, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key function describing what to serialize. Be sure to call Base.Serialize(h)
|
||||
/// Helper SerializeToken will automatically write fields with matching tokens into the table
|
||||
/// </summary>
|
||||
public virtual void Serialize(Hashtable h) {
|
||||
AppendIDs(h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize the entity out of the provided hashtable.
|
||||
/// Use helper function DeserializeToken automatically unpack any tokens
|
||||
/// </summary>
|
||||
public virtual void Deserialize(Hashtable h) {
|
||||
h.SetOnKey(PConst.eidChar, ref EntityID);
|
||||
h.SetOnKey(PConst.athChar, ref authorityID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if the hashtable already contains each always send token, and if not, add it.
|
||||
/// </summary>
|
||||
private bool SerializeAlwaysTokensSafely(Hashtable h) {
|
||||
var tH = tokenHandler;
|
||||
foreach(var c in tH.alwaysSendTokens) {
|
||||
// If the hashtable doesn't contain our token, add it in
|
||||
if (!h.ContainsKey(c)) {
|
||||
h.Add(c, tH.get(c,this));
|
||||
}
|
||||
}
|
||||
return tH.alwaysIsRelaible;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Send a reliable update with <b>only</b> the provided tokens, immediately.
|
||||
/// This does not send the alwaysSend autotokens, and exists solely so that you can have a field update as soon as possible,
|
||||
/// such as menu or input events
|
||||
/// </summary>
|
||||
public void UpdateExclusively(params char[] ca) {
|
||||
var h = new Hashtable();
|
||||
|
||||
AppendIDs(h);
|
||||
|
||||
SerializeToken(h, ca);
|
||||
NetworkManager.netMessage(PConst.EntityUpdateCode, h, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately sent a network update with our current state. This includes auto tokens if IAutoSerialize is implemented.
|
||||
/// Reliable flag, though it defaults to false, may be forced true when sending always or reliable tokens.
|
||||
/// </summary>
|
||||
public void UpdateNow(bool reliable = false) {
|
||||
var h = new Hashtable();
|
||||
|
||||
Serialize(h);
|
||||
|
||||
if (this is IAutoSerialize) {
|
||||
int autoCode = SerializeAuto(h);
|
||||
if (autoCode == 2) reliable = true;
|
||||
} else {
|
||||
if (SerializeAlwaysTokensSafely(h))
|
||||
reliable = true;
|
||||
}
|
||||
|
||||
|
||||
NetworkManager.netMessage(PConst.EntityUpdateCode, h, reliable);
|
||||
}
|
||||
|
||||
#region timing
|
||||
// updateTimers coordinates when each value's server representation 'expires' and should be resent
|
||||
// For values which didn't specify an update time, this value is set to +inf, so that it will always be greater than the current time
|
||||
|
||||
private Dictionary<char,float> updateTimers = new Dictionary<char,float>();
|
||||
|
||||
|
||||
float this[char c] {
|
||||
get {
|
||||
float t;
|
||||
if(!updateTimers.TryGetValue(c, out t)) {
|
||||
var updateTime = tokenHandler.updateTimes[c];
|
||||
updateTimers.Add(c, updateTime >= 0 ? 0 : Mathf.Infinity);
|
||||
}
|
||||
return updateTimers[c];
|
||||
}
|
||||
set {
|
||||
updateTimers[c] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
// Token management is a system that assigns a character token to each field for serialization, via attributes
|
||||
// This is used to automatically pull get/set for variables to assist in auto serializing as much as possible and reducing the amount of manual network messaging
|
||||
|
||||
[ContextMenu("Claim as mine")]
|
||||
public bool ClaimAsMine() {
|
||||
if (!NetworkManager.inRoom && NetworkManager.isReady) return false;
|
||||
|
||||
authorityID = NetworkManager.localID;
|
||||
|
||||
UpdateNow(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region TokenManagement
|
||||
|
||||
/// TODO: Modifying a token at this level needs to clone the TokenHandler specially for this EntityBase object so changes don't propegate to other entities
|
||||
|
||||
/// <summary>
|
||||
/// Runtime modify the parameters of a token. Modifying the reliability of a token is slightly intensive.
|
||||
/// </summary>
|
||||
/// <param name="token">The token to be modified</param>
|
||||
/// <param name="updateMs">Milliseconds between updates. 0 is every frame, use cautiously. Set negative to unsubcribe automaic updates.</param>
|
||||
/// <param name="alwaysSend">If the token should always be sent with other tokens</param>
|
||||
/// <param name="isReliable">If the token needs to be sent reliably.</param>
|
||||
public void ModifyToken(char token, int? updateMs = null, bool? alwaysSend = null, bool? isReliable = null) {
|
||||
|
||||
var tH = tokenHandler;
|
||||
if (tH.shared) {
|
||||
_tokenHandler = tH.DeepClone();
|
||||
tH = _tokenHandler;
|
||||
}
|
||||
|
||||
|
||||
// If we have a value for reliability
|
||||
if (isReliable.HasValue) {
|
||||
if (tH.reliableTokens.Contains(token)){
|
||||
if (!isReliable.Value)
|
||||
tH.reliableTokens.Remove(token);
|
||||
} else {
|
||||
if (isReliable.Value)
|
||||
tH.reliableTokens.Add(token);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a value for always sending
|
||||
if (alwaysSend.HasValue) {
|
||||
if (tH.alwaysSend.ContainsKey(token)){
|
||||
if (!alwaysSend.Value)
|
||||
tH.alwaysSend.Remove(token);
|
||||
} else {
|
||||
if (alwaysSend.Value)
|
||||
tH.alwaysSend.Add(token,alwaysSend.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (alwaysSend.HasValue || isReliable.HasValue)
|
||||
tH.ReEvalAlwaysIsReliable();
|
||||
|
||||
if (updateMs.HasValue) {
|
||||
float fUpdateTime = updateMs.Value / 1000f;
|
||||
tH.updateTimes[token] = fUpdateTime;
|
||||
|
||||
// Unsubscribing
|
||||
if (fUpdateTime < 0) {
|
||||
if (tH.autoTokens.Contains(token))
|
||||
tH.autoTokens.Remove(token);
|
||||
if (!tH.manualTokens.Contains(token))
|
||||
tH.manualTokens.Add(token);
|
||||
|
||||
this[token] = Mathf.Infinity; // Never auto-update
|
||||
} else {
|
||||
if (!tH.autoTokens.Contains(token))
|
||||
tH.autoTokens.Add(token);
|
||||
if (tH.manualTokens.Contains(token))
|
||||
tH.manualTokens.Remove(token);
|
||||
|
||||
this[token] = 0; // Auto update next check
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a labeled character event. You should never need to use this method manually.
|
||||
/// </summary>
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public void InternallyInvokeEvent(char c, params object[] parameters) {
|
||||
tokenHandler.NetEvents[c].Invoke(this, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a labeled character event when the event is static. You should hopefully never need to use this method manually.
|
||||
/// </summary>
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static void InternallyInvokeStatic(Type T, char c, params object[] parameters) {
|
||||
if (!handlers.ContainsKey(T)) {
|
||||
BuildTokenList(T);
|
||||
}
|
||||
|
||||
TokenHandler th = handlers[T];
|
||||
|
||||
th.NetEvents[c].Invoke(null, parameters);
|
||||
}
|
||||
|
||||
private static Dictionary<Type,List<char>> tokens = new Dictionary<Type,List<char>>();
|
||||
private static Dictionary<Type,TokenHandler> handlers = new Dictionary<Type,TokenHandler>();
|
||||
|
||||
/// <summary>
|
||||
/// Cached token handler reference
|
||||
/// </summary>
|
||||
private TokenHandler _tokenHandler;
|
||||
/// <summary>
|
||||
/// Gets the token for class in question. Will generate the token list if it doesn't exist.
|
||||
/// If the object modifies it's tokens parameters, it will clone a new handler specific to the object
|
||||
/// </summary>
|
||||
protected TokenHandler tokenHandler {
|
||||
get {
|
||||
if (_tokenHandler != null) return _tokenHandler;
|
||||
|
||||
var T = GetType();
|
||||
|
||||
if (handlers.ContainsKey(T))
|
||||
return _tokenHandler = handlers[T];
|
||||
|
||||
BuildTokenList(T);
|
||||
return _tokenHandler = handlers[T];
|
||||
}
|
||||
}
|
||||
|
||||
protected class TokenHandler {
|
||||
private Dictionary<char,System.Action<EntityBase,object>> setters;
|
||||
private Dictionary<char,System.Func<EntityBase,object>> getters;
|
||||
|
||||
public Dictionary<char,MethodInfo> NetEvents = new Dictionary<char,MethodInfo>();
|
||||
|
||||
public TokenHandler() {
|
||||
setters = new Dictionary<char,System.Action<EntityBase,object>>();
|
||||
getters = new Dictionary<char,System.Func<EntityBase,object>>();
|
||||
}
|
||||
|
||||
public TokenHandler DeepClone() {
|
||||
TokenHandler nTH = new TokenHandler();
|
||||
|
||||
nTH.setters = new Dictionary<char,System.Action<EntityBase,object>>(this.setters);
|
||||
nTH.getters = new Dictionary<char,System.Func<EntityBase,object>>(this.getters);
|
||||
|
||||
nTH.alwaysSend = new Dictionary<char,bool>(this.alwaysSend);
|
||||
nTH.alwaysReliable = new Dictionary<char,bool>(this.alwaysReliable);
|
||||
|
||||
nTH.alwaysIsRelaible = this.alwaysIsRelaible;
|
||||
|
||||
nTH.reliableTokens.AddRange(this.reliableTokens);
|
||||
nTH.alwaysSendTokens.AddRange(this.alwaysSendTokens);
|
||||
nTH.autoTokens.AddRange(this.autoTokens);
|
||||
nTH.manualTokens.AddRange(this.manualTokens);
|
||||
|
||||
nTH.NetEvents = this.NetEvents; // This one we keep the reference for
|
||||
|
||||
// Flag the new TokenHandler as not being shared
|
||||
nTH.shared = false;
|
||||
|
||||
return nTH;
|
||||
}
|
||||
|
||||
public bool shared = true;
|
||||
|
||||
public object get(char c, EntityBase eb) {
|
||||
return getter(c)(eb);
|
||||
}
|
||||
public void set(char c, EntityBase eb, object o) {
|
||||
setter(c)(eb, o);
|
||||
}
|
||||
|
||||
public System.Func<EntityBase,object> getter(char c){
|
||||
return getters[c];
|
||||
}
|
||||
public System.Action<EntityBase,object> setter(char c) {
|
||||
return setters[c];
|
||||
}
|
||||
|
||||
public Dictionary<char,bool>
|
||||
alwaysSend = new Dictionary<char,bool>(),
|
||||
alwaysReliable = new Dictionary<char,bool>();
|
||||
|
||||
/// <summary>
|
||||
/// If any always send tokens are reliable, implicity, all of them are.
|
||||
/// </summary>
|
||||
public bool alwaysIsRelaible = false;
|
||||
|
||||
public Dictionary<char,float> updateTimes = new Dictionary<char,float>();
|
||||
/// <summary>
|
||||
/// Tokens that should always be sent reliably
|
||||
/// </summary>
|
||||
public List<char> reliableTokens = new List<char>();
|
||||
/// <summary>
|
||||
/// Tokens that should always be sent whenever another token is sent
|
||||
/// </summary>
|
||||
public List<char> alwaysSendTokens = new List<char>();
|
||||
/// <summary>
|
||||
/// Tokens that are automatically dispatched according to the update timer
|
||||
/// </summary>
|
||||
public List<char> autoTokens = new List<char>();
|
||||
/// <summary>
|
||||
/// Tokens that are not always send or auto tokens. Useful for getting the subsection of tokens to serialize manually
|
||||
/// </summary>
|
||||
public List<char> manualTokens = new List<char>();
|
||||
|
||||
/// <summary>
|
||||
/// Check each reliable token for if it needs to always be sent, and if any do, always sent tokens require reliable updates.
|
||||
/// </summary>
|
||||
public void ReEvalAlwaysIsReliable() {
|
||||
alwaysIsRelaible = false;
|
||||
|
||||
foreach(var token in reliableTokens) {
|
||||
if (alwaysSend.ContainsKey(token)) {
|
||||
alwaysIsRelaible = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterField(char c, FieldInfo fi, NetVar nv) {
|
||||
getters.Add(c, (e)=> { return fi.GetValue(e); });
|
||||
setters.Add(c, (e,v)=> { fi.SetValue(e,v); });
|
||||
alwaysSend.Add(c,nv.alwaysSend);
|
||||
alwaysReliable.Add(c,nv.alwaysReliable);
|
||||
updateTimes.Add(c, nv.updateTime);
|
||||
|
||||
if (nv.alwaysSend)
|
||||
alwaysSendTokens.Add(c);
|
||||
if (nv.alwaysReliable)
|
||||
reliableTokens.Add(c);
|
||||
if (nv.updateTime >= 0f) {
|
||||
autoTokens.Add(c);
|
||||
} else if (!nv.alwaysSend) {
|
||||
manualTokens.Add(c);
|
||||
}
|
||||
|
||||
if(nv.alwaysSend && nv.alwaysReliable) {
|
||||
alwaysIsRelaible = true;
|
||||
}
|
||||
|
||||
Debug.LogFormat("{0} -> {1}", c, fi.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all tokens used in this class.
|
||||
/// </summary>
|
||||
public List<char> TokenList() {
|
||||
var T = this.GetType();
|
||||
|
||||
if (tokens.ContainsKey(T)) {
|
||||
return tokens[T];
|
||||
}
|
||||
|
||||
BuildTokenList(T);
|
||||
return tokens[T];
|
||||
}
|
||||
|
||||
static char AutoCharField(FieldInfo fi, ushort offset = 0) {
|
||||
var strategies = new[] {
|
||||
fi.Name[0],
|
||||
fi.Name.ToLowerInvariant()[0],
|
||||
fi.Name.ToUpperInvariant()[0] };
|
||||
|
||||
var suggest = strategies[offset % 3];
|
||||
// Advance the token if we are still colliding
|
||||
if (offset > 3)
|
||||
suggest = (char)(System.Convert.ToUInt16(suggest) + offset);
|
||||
return suggest;
|
||||
}
|
||||
|
||||
static void BuildTokenList(Type T) {
|
||||
if (!T.IsSubclassOf(typeof(EntityBase)))
|
||||
throw new System.Exception("Cannot build a token list for a class that doesn't derive EntityBase");
|
||||
|
||||
// Setup the char list
|
||||
List<char> charList;
|
||||
|
||||
TokenHandler th;
|
||||
// Establish the token handler
|
||||
if (!tokens.ContainsKey(T)) {
|
||||
charList = new List<char>();
|
||||
th = new TokenHandler();
|
||||
tokens.Add(T,charList);
|
||||
|
||||
handlers.Add(T,th);
|
||||
} else {
|
||||
charList = tokens[T];
|
||||
th = handlers[T];
|
||||
}
|
||||
|
||||
var fields = T.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
// Build the list of net vars, including ones without assigned chars
|
||||
var pendingInfos = new List<(char c, FieldInfo fi, NetVar nv)>();
|
||||
foreach(FieldInfo fi in fields) {
|
||||
var fieldInfo = fi; // Closure fi to prevent variable capture in lambdas
|
||||
var netVar = fieldInfo.GetCustomAttributes(typeof(NetVar),true).FirstOrDefault() as NetVar;
|
||||
if (netVar == null) continue; // This field has no netvar associated, skip it
|
||||
|
||||
if (netVar.token != ' ') charList.Add(netVar.token);
|
||||
pendingInfos.Add((netVar.token, fi, netVar));
|
||||
// Enforce order so we can generate tokens consistently
|
||||
pendingInfos = pendingInfos.OrderBy(p => p.fi.Name).ToList();
|
||||
}
|
||||
|
||||
|
||||
foreach(var (c, fi, nv) in pendingInfos) {
|
||||
if (c != ' ') {
|
||||
th.RegisterField(c, fi, nv);
|
||||
continue;
|
||||
}
|
||||
|
||||
var suggest = AutoCharField(fi);
|
||||
if (!charList.Contains(suggest)) {
|
||||
charList.Add(suggest);
|
||||
th.RegisterField(suggest, fi, nv);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Search for an unused key
|
||||
ushort off = 0;
|
||||
while(charList.Contains(suggest))
|
||||
suggest = AutoCharField(fi, ++off);
|
||||
|
||||
charList.Add(suggest);
|
||||
th.RegisterField(suggest, fi, nv);
|
||||
}
|
||||
|
||||
var methods = T.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
// Store all event method infos for remote invocation
|
||||
foreach(MethodInfo mi in methods) {
|
||||
var methodInfo = mi; // Closure
|
||||
var netEvent = methodInfo.GetCustomAttributes(typeof(NetEvent),true).FirstOrDefault() as NetEvent;
|
||||
|
||||
if (netEvent == null) {
|
||||
//Debug.LogFormat("Skipping {0}'s {1}", T.Name, methodInfo.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Debug.LogFormat("EVENT {0}'s {1} -> {2}", T.Name, methodInfo.Name, netEvent.token);
|
||||
th.NetEvents.Add(netEvent.token, methodInfo);
|
||||
}
|
||||
|
||||
// Search for all static events on this type; In theory this could be merged with the non-static search, but at time of implementing I thought I may process them seperately.
|
||||
var staticMethods = T.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
foreach (MethodInfo mi in staticMethods) {
|
||||
var smi = mi; // Closure
|
||||
var netEvent = smi.GetCustomAttributes(typeof(NetEvent),true).FirstOrDefault() as NetEvent;
|
||||
|
||||
if (netEvent == null) continue;
|
||||
|
||||
th.NetEvents.Add(netEvent.token, smi);
|
||||
}
|
||||
|
||||
var autoTok = string.Join(",", th.autoTokens.Select(t => t.ToString()).ToArray());
|
||||
//Debug.LogFormat("{0} Auto Tokens: {1}", T.Name, autoTok);
|
||||
var alTok = string.Join(",", th.alwaysSendTokens.Select(t => t.ToString()).ToArray());
|
||||
//Debug.LogFormat("{0} Alwy Tokens: {1}", T.Name, alTok);
|
||||
}
|
||||
|
||||
public virtual void Awake() {
|
||||
if (this is IEarlyAutoRegister)
|
||||
Register();
|
||||
|
||||
else if (this is IAutoRegister)
|
||||
StartCoroutine(DeferredRegister());
|
||||
}
|
||||
|
||||
IEnumerator DeferredRegister() {
|
||||
while (!NetworkManager.inRoom)
|
||||
yield return null;
|
||||
Register();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network variable attribute, specifying the desired token.
|
||||
/// Set alwaysReliable to hint that a reliable update is required
|
||||
/// Set alwaysSend to always include the variable in all dispatches
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Always Reliable -> Token must be sent reliably every time
|
||||
/// Always Send -> Token will be sent whenever any other token is sent
|
||||
/// updateMs -> If set, the token will automatically dispatch every updateMs milliseconds
|
||||
/// </remarks>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field,AllowMultiple=false)]
|
||||
public class NetVar : Attribute {
|
||||
public readonly char token;
|
||||
public readonly bool alwaysReliable, alwaysSend;
|
||||
public readonly float updateTime;
|
||||
|
||||
public NetVar(char token = ' ', bool alwaysReliable = false, bool alwaysSend = false, int updateMs = -1) {
|
||||
this.token = token;
|
||||
this.alwaysReliable = alwaysReliable;
|
||||
this.alwaysSend = alwaysSend;
|
||||
this.updateTime = updateMs / 1000f; // Convert milliseconds to seconds
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This attribute describes a networked event function; This function must be non-static and is called on a specific entity.
|
||||
/// It may have any network serializable parameters
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Method,AllowMultiple=false)]
|
||||
public class NetEvent : Attribute {
|
||||
public readonly char token;
|
||||
|
||||
public NetEvent(char token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attach to a static field returning either a GameObject or EntityBase
|
||||
/// This function will be called to create a networked entity.
|
||||
/// The method may contain any number of parameters that are serializable
|
||||
/// The EntityID
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Method)]
|
||||
public class Instantiation : Attribute { }
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4c9b2b5002413374ca9dd3d2f5d26bcf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,476 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using System.Linq;
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
using ExitGames.Client.Photon;
|
||||
using ExitGames.Client.Photon.LoadBalancing;
|
||||
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
using Type = System.Type;
|
||||
using Action = System.Action;
|
||||
|
||||
namespace EntityNetwork {
|
||||
|
||||
public static class EntityManager {
|
||||
public static Dictionary<int,EntityBase> entities = new Dictionary<int,EntityBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Get an unused EntityID for a given PlayerID.
|
||||
/// This ID is ensured to be unique for the client, assuming each client has a different PlayerID it will not collide.
|
||||
/// Each playerID's ID space is about two hundred and sixty eight million IDs.
|
||||
/// </summary>
|
||||
/// <returns>An unused EntityBase ID number</returns>
|
||||
/// <param name="playerID">The player number, ranged [0,127]</param>
|
||||
public static int GetUnusedID(int playerID) {
|
||||
if (playerID > 127)
|
||||
throw new System.ArgumentOutOfRangeException("playerID cannot exceed 127");
|
||||
if (playerID < 0)
|
||||
throw new System.ArgumentOutOfRangeException("playerID cannot be less than zero");
|
||||
|
||||
|
||||
// Fill all but the topmost byte randomly, then the topmost byte will be an sbyte for player id
|
||||
|
||||
int player = playerID << 28;
|
||||
int randomInt = Random.Range(0, 0x0FFFFFFF);
|
||||
|
||||
int proposedID = player | randomInt;
|
||||
|
||||
// Recursively dig for new ID's on collision
|
||||
while (entities.ContainsKey(proposedID)) {
|
||||
proposedID = GetUnusedID(playerID);
|
||||
}
|
||||
|
||||
return proposedID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to an entity of a given ID.
|
||||
/// There is a chance that this entity may have been deleted.
|
||||
/// </summary>
|
||||
/// <param name="id">Entity ID</param>
|
||||
public static EntityBase Entity(int id) {
|
||||
EntityBase eb;
|
||||
return entities.TryGetValue(id, out eb) ? eb : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a reference to an entity of a given ID.
|
||||
/// There is a chance that this entity may have been deleted.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static T Entity<T>(int id) where T : EntityBase{
|
||||
return Entity(id) as T;
|
||||
}
|
||||
|
||||
private static void CleanEntities() {
|
||||
var toRemove = new List<int>(entities.Count);
|
||||
|
||||
foreach(var pair in entities)
|
||||
if (!pair.Value) toRemove.Add(pair.Key);
|
||||
|
||||
foreach (var key in toRemove)
|
||||
entities.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an entity to receive network events/updates.
|
||||
/// This will fail if the EntityID is already in use.
|
||||
/// </summary>
|
||||
/// <remarks>Registering an entity validates all existing entities and will unsubcribe dead entities.</remarks>
|
||||
public static void Register(EntityBase eb) {
|
||||
CleanEntities();
|
||||
|
||||
//Debug.LogFormat("Registered Entity {0} : {1}",eb.name,eb.EntityID);
|
||||
|
||||
//Debug.LogFormat("{0} -> {1}", eb.name, string.Join(", ", eb.GetType().GetInterfaces().Select(t => t.Name).ToArray()));
|
||||
if (eb is IAutoSerialize) {
|
||||
eb.StartCoroutine(autoDispatchEntity(eb));
|
||||
}
|
||||
|
||||
if (entities.ContainsKey(eb.EntityID)) {
|
||||
var otherEntity = Entity(eb.EntityID);
|
||||
Debug.LogErrorFormat(eb, "{0} has attempted to register over an existing ID {1}, Which belongs to {2}",
|
||||
eb.gameObject.name, eb.EntityID, otherEntity.gameObject.name);
|
||||
throw new System.Exception("Entity ID already in use!");
|
||||
}
|
||||
|
||||
entities.Add(eb.EntityID, eb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deregister an EntityBase. This requires enumeraing all entities and is slower than just destroying the EntityBase
|
||||
/// However, in certain cases, like re-registering as a new pooled object or if the object must exist after being removed, it's worth using
|
||||
/// </summary>
|
||||
public static void DeRegister(EntityBase eb) {
|
||||
// Grab all keyvaluepairs where the entity is the entity base being deregistered - ToArray is used to collapse the linq to avoid sync issues
|
||||
var toRemove = entities.Select(t => new {id = t.Key, Entity = t.Value}).Where(t => t.Entity == eb).ToArray();
|
||||
foreach(var removal in toRemove) {
|
||||
entities.Remove(removal.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static IEnumerator autoDispatchEntity(EntityBase eb) {
|
||||
Debug.LogFormat("Creating Serial Dispatcher for {0} : {1}", eb.name,eb.EntityID);
|
||||
|
||||
Hashtable h = new Hashtable();
|
||||
|
||||
while (true) {
|
||||
h.Clear();
|
||||
|
||||
if (eb.isMine) {
|
||||
int code = eb.SerializeAuto(h);
|
||||
|
||||
if (code != 0) {
|
||||
// If code is 2, message should be reliable
|
||||
// Debug.LogFormat("Dispatching {0}/{2}: {1}", eb.name, h.ToStringFull(), PhotonConstants.EntityUpdateCode);
|
||||
NetworkManager.netMessage(PhotonConstants.EntityUpdateCode, h, code == 2);
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
static EntityManager() {
|
||||
NetworkManager.netHook += OnNet;
|
||||
NetworkManager.onLeave += AllowOrphanSuicidesAndCalls;
|
||||
}
|
||||
|
||||
// Hook the main events
|
||||
static void OnNet(EventData ev) {
|
||||
if (ev.Code == PhotonConstants.EntityUpdateCode) {
|
||||
var h = (Hashtable)ev[ParameterCode.Data];
|
||||
|
||||
// Reject self-aimed events
|
||||
if ((int)ev[ParameterCode.ActorNr] == NetworkManager.localID){
|
||||
// Show a red particle for an outgoing signal, before rejecting the event
|
||||
var ebs = Entity((int)h[PhotonConstants.eidChar]);
|
||||
NetworkManager.netParticle(ebs, Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
var eb = Entity((int)h[PhotonConstants.eidChar]);
|
||||
|
||||
if (eb) {
|
||||
// Show a blue particle for an incoming singal
|
||||
NetworkManager.netParticle(eb, Color.blue);
|
||||
|
||||
if (eb is IAutoDeserialize) {
|
||||
eb.DeserializeFull(h);
|
||||
}
|
||||
eb.Deserialize(h);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.Code == PhotonConstants.EntityEventCode) {
|
||||
var h = (Hashtable)ev[ParameterCode.Data];
|
||||
|
||||
// --- Static Events ---
|
||||
// Param labeled 2 in the hashtable is the EntityBase's ID Type, if a static event call, so if the table contains key 2, run it as a static event
|
||||
object idObject;
|
||||
if (h.TryGetValue(2,out idObject)) {
|
||||
var typeID = (int)idObject;
|
||||
Type entityType;
|
||||
try {
|
||||
entityType = EntityBase.TypeFromID(typeID);
|
||||
} catch {
|
||||
throw new System.Exception("Attempting to call static event on a non-existant type");
|
||||
}
|
||||
|
||||
var controlChar = (char)h[0];
|
||||
|
||||
object paramObject;
|
||||
if (h.TryGetValue(1,out paramObject)) {
|
||||
EntityBase.InternallyInvokeStatic(entityType, controlChar,(object[])paramObject);
|
||||
} else {
|
||||
EntityBase.InternallyInvokeStatic(entityType, controlChar, null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Instance Events ---
|
||||
var eb = Entity((int)h[PhotonConstants.eidChar]);
|
||||
|
||||
if (eb) {
|
||||
var controlChar = (char)h[0];
|
||||
|
||||
object paramObject;
|
||||
if (h.TryGetValue(1,out paramObject)) {
|
||||
eb.InternallyInvokeEvent(controlChar,(object[])paramObject);
|
||||
} else {
|
||||
eb.InternallyInvokeEvent(controlChar, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.Code == PhotonConstants.EntityInstantiateCode) {
|
||||
var h = (Hashtable)ev[ParameterCode.Data];
|
||||
DeserializeInstantiate(h);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a hashtable describing an object instantiaton for use with DeserializeInstantiate
|
||||
/// Use helper method Instantiate to automatically call and fire this as an event.
|
||||
/// </summary>
|
||||
/// <seealso cref="DeserializeInstantiate"/>
|
||||
public static Hashtable SerializeInstantiate<T>(int authID, Vector3 pos, Quaternion rot, params object[] param) {
|
||||
var H = new Hashtable();
|
||||
|
||||
//H.Add('T', typeof(T).ToString());
|
||||
|
||||
H.Add('O', authID);
|
||||
H.Add('I', GetUnusedID(authID));
|
||||
|
||||
H.Add('T', typeof(T).FullName);
|
||||
|
||||
H.Add('P', pos);
|
||||
H.Add('R', rot);
|
||||
H.Add('p', param);
|
||||
|
||||
return H;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locally creates the instantiated object described in Hashtable H.
|
||||
/// </summary>
|
||||
/// <seealso cref="Instantiate"/>
|
||||
public static void DeserializeInstantiate(Hashtable H) {
|
||||
CheckInstantiators();
|
||||
|
||||
//Debug.Log(H.ToStringFull());
|
||||
var type = typeLookup[H['T'] as string];
|
||||
|
||||
var eid = (int)H['I'];
|
||||
var ID = (int)H['O'];
|
||||
|
||||
var pos = (Vector3)H['P'];
|
||||
var rot = (Quaternion)H['R'];
|
||||
|
||||
var options = H['p'] as object[];
|
||||
|
||||
ActionInstantiate(ID, eid, type, pos, rot, options);
|
||||
|
||||
//Instantiate<type>(pos, rot, options);
|
||||
}
|
||||
|
||||
#region Instantiation
|
||||
// Instantiation uses InstanceGameObject / InstanceGameEntity attributes
|
||||
|
||||
// Actually construct an instantiator object
|
||||
private static void ActionInstantiate(int authID, int entityID, Type T, Vector3 pos, Quaternion rot, object[] param) {
|
||||
MethodInfo mi;
|
||||
if (!InstantiateMethods.TryGetValue(T, out mi)) {
|
||||
throw new System.Exception(string.Format("Type {0} doesn't have an Instantiate Attributed method and isn't Instantiable.", T.Name));
|
||||
}
|
||||
|
||||
if (typeof(GameObject).IsAssignableFrom(mi.ReturnType)) {
|
||||
var val = mi.Invoke(null, param) as GameObject;
|
||||
|
||||
var go = Object.Instantiate<GameObject>(val, pos, rot);
|
||||
|
||||
// Attempt to set the ID of the entitybase
|
||||
var eb = go.GetComponentInChildren<EntityBase>();
|
||||
|
||||
if (eb) {
|
||||
eb.EntityID = entityID;
|
||||
eb.authorityID = authID;
|
||||
}
|
||||
|
||||
go.SendMessage("OnInstantiate", SendMessageOptions.DontRequireReceiver);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var rt = mi.ReturnType;
|
||||
if (typeof(EntityBase).IsAssignableFrom(rt)) {
|
||||
var eb = mi.Invoke(null, param) as EntityBase;
|
||||
|
||||
eb.authorityID = authID;
|
||||
eb.EntityID = entityID;
|
||||
|
||||
var go = eb.gameObject;
|
||||
var t = eb.transform;
|
||||
|
||||
if (pos != Vector3.zero)
|
||||
t.position = pos;
|
||||
if (rot != Quaternion.identity)
|
||||
t.rotation = rot;
|
||||
|
||||
go.SendMessage("OnInstantiate", SendMessageOptions.DontRequireReceiver);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new System.Exception(string.Format("Type {0}'s Instantiate Method doesn't return an EntityBase or GameObject", T.Name));
|
||||
}
|
||||
|
||||
// Helper dictionaries. typeLookup is to help us send types over the wire, InstantiateMethods stores each types instantiator
|
||||
static Dictionary<string,System.Type> typeLookup;
|
||||
static Dictionary<System.Type,MethodInfo> InstantiateMethods;
|
||||
|
||||
|
||||
// This is a mess of autodocumentation, mostly due to usage of params and overloads.
|
||||
|
||||
/// <summary>
|
||||
/// Activate type T's EntityBase.Instantation attribute remotely with given parameters, Generating and assigning the appropriate actor ID
|
||||
/// This method returns the HashTable describing the instantation request that can be used to also create the object locally.
|
||||
/// </summary>
|
||||
/// <seealso cref="DeserializeInstantiate"/>
|
||||
public static Hashtable Instantiate<T>(int authID, params object[] param) { return Instantiate<T>(authID, Vector3.zero, Quaternion.identity, param); }
|
||||
/// <summary>
|
||||
/// Activate type T's EntityBase.Instantation attribute remotely with given parameters, Generating and assigning the appropriate actor ID
|
||||
/// This method returns the HashTable describing the instantation request that can be used to also create the object locally.
|
||||
/// </summary>
|
||||
/// <seealso cref="DeserializeInstantiate"/>
|
||||
public static Hashtable Instantiate<T>(int authID, Vector3 pos, params object[] param) { return Instantiate<T>(authID, pos, Quaternion.identity, param); }
|
||||
/// <summary>
|
||||
/// Activate type T's EntityBase.Instantation attribute remotely with given parameters, Generating and assigning the appropriate actor ID
|
||||
/// This method returns the HashTable describing the instantation request that can be used to also create the object locally.
|
||||
/// </summary>
|
||||
/// <seealso cref="DeserializeInstantiate"/>
|
||||
public static Hashtable Instantiate<T>(int authID, Vector3 pos, Quaternion rot) { return Instantiate<T>(authID, pos, rot, null); }
|
||||
/// <summary>
|
||||
/// Activate type T's EntityBase.Instantation attribute remotely with given parameters, Generating and assigning the appropriate actor ID
|
||||
/// This method returns the HashTable describing the instantation request that can be used to also create the object locally.
|
||||
/// </summary>
|
||||
/// <seealso cref="DeserializeInstantiate"/>
|
||||
public static Hashtable Instantiate<T>(int authID, Vector3 pos, Quaternion rot,params object[] param) {
|
||||
var table = SerializeInstantiate<T>(authID, pos, rot, param);
|
||||
|
||||
if (NetworkManager.isReady) {
|
||||
NetworkManager.net.OpRaiseEvent(PhotonConstants.EntityInstantiateCode, table, true, RaiseEventOptions.Default);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
static bool InstantiatorsBuilt = false;
|
||||
static void CheckInstantiators() {
|
||||
if (InstantiatorsBuilt) return;
|
||||
|
||||
BuildInstantiators();
|
||||
InstantiatorsBuilt = true;
|
||||
}
|
||||
|
||||
// Gather all the instantiaton attributes on entity classes
|
||||
static void BuildInstantiators() {
|
||||
Debug.Log("Buiding Instantiator cache");
|
||||
|
||||
InstantiateMethods = new Dictionary<System.Type,MethodInfo>();
|
||||
typeLookup = new Dictionary<string,System.Type>();
|
||||
|
||||
var ebT = typeof(EntityBase);
|
||||
|
||||
var AllEntityTypes =
|
||||
System.AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(t => ebT.IsAssignableFrom(t));
|
||||
|
||||
//var AllEntityTypes = Assembly.GetTypes().Where(t => ebT.IsAssignableFrom(t));
|
||||
|
||||
foreach(var entityType in AllEntityTypes) {
|
||||
var methods = entityType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
typeLookup.Add(entityType.FullName, entityType);
|
||||
//Debug.LogFormat("Scanning Type {0}", entityType);
|
||||
|
||||
foreach(var method in methods) {
|
||||
//Debug.LogFormat("Scanning Method {0}", method.Name);
|
||||
// First look for a GameObject instantiator
|
||||
var ia = method.GetCustomAttributes(typeof(EntityBase.Instantiation),true).FirstOrDefault() as EntityBase.Instantiation;
|
||||
if (ia != null) {
|
||||
InstantiateMethods.Add(entityType, method);
|
||||
Debug.LogFormat("Registering Instantiator {0} for {1} (R: {2})",method.Name,entityType.FullName,method.ReturnType.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AllowOrphanSuicidesAndCalls(EventData ev) {
|
||||
var toKill = new List<int>();
|
||||
var players = NetworkManager.net.CurrentRoom.Players.Select(t => t.Value.ID);
|
||||
|
||||
foreach(var pair in entities) {
|
||||
var e = pair.Value;
|
||||
|
||||
if (e is IAutoKillOrphans) {
|
||||
if (e.authorityID == -1) continue;
|
||||
if (players.Contains(e.authorityID)) continue;
|
||||
|
||||
toKill.Add(e.EntityID);
|
||||
}
|
||||
// Send out the orphan callbacks
|
||||
if (e is IOrphanCallback) {
|
||||
if (e.authorityID == -1) return;
|
||||
if (players.Contains(e.authorityID)) continue;
|
||||
|
||||
(e as IOrphanCallback).OnOrphaned();
|
||||
}
|
||||
}
|
||||
|
||||
// Kill the orphans
|
||||
foreach(var killable in toKill) {
|
||||
if (Application.isEditor || Debug.isDebugBuild) {
|
||||
var killEntity = Entity(killable);
|
||||
|
||||
Debug.LogFormat("Destroying orphaned entity {0} as it's owner {1} has left the room.",killEntity.gameObject.name,killEntity.authorityID);
|
||||
}
|
||||
Object.Destroy(Entity(killable).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <summary>
|
||||
/// Specify that deserialization should be automaticly handled.
|
||||
/// All registered field tokens will be automaticly set using cached setters
|
||||
/// This is not appropriate if you have custom serialization/deserialization logic
|
||||
/// </summary>
|
||||
public interface IAutoDeserialize {}
|
||||
|
||||
/// <summary>
|
||||
/// Specify that automatic token handling should be performed on the entity.
|
||||
/// In most cases, this should remove the need to write custom serializers
|
||||
/// This only applies to NetVar's with alwaysSend or updateTime set
|
||||
/// </summary>
|
||||
public interface IAutoSerialize {}
|
||||
|
||||
/// <summary>
|
||||
/// Only appropriate for Entities with fixed, pre-determined ID's.
|
||||
/// The entity will attempt to register itself on Awake()
|
||||
/// </summary>
|
||||
public interface IAutoRegister {}
|
||||
|
||||
/// <summary>
|
||||
/// Only appropriate for Entities with fixed, pre-determined ID's.
|
||||
/// The entity will absolutely to register itself on Awake()
|
||||
/// </summary>
|
||||
public interface IEarlyAutoRegister {}
|
||||
|
||||
/// <summary>
|
||||
/// Assign to an EntityBase so that any time an AuthorityID would be checked we instead check if we're the room master
|
||||
/// Used to clarify network ownership for objects that aren't owned by a player but instead by the room itself
|
||||
/// </summary>
|
||||
public interface IMasterOwnsUnclaimed {}
|
||||
|
||||
/// <summary>
|
||||
/// When the authority player disconnects, destroy the entity and attached gameobject that aren't owned by players (EXCEPT with AuthID -1)
|
||||
/// </summary>
|
||||
public interface IAutoKillOrphans {}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an OnOrphaned callback - Note this is run whenever a player quits and we are unclaimed without a -1 authority, not just when our authority quits.
|
||||
/// </summary>
|
||||
public interface IOrphanCallback {
|
||||
void OnOrphaned();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 118662652baff8841bb52034c1795789
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c1f784b32aefb84cad6d24f7d2a37b5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,52 @@
|
|||
using UnityEngine;
|
||||
using ExitGames.Client.Photon.LoadBalancing;
|
||||
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions of Exitgames Hashtable to allow for concise conditional setting logic
|
||||
/// </summary>
|
||||
public static class HashtableExtension {
|
||||
/// <summary>
|
||||
/// Checks if the hashtable contains key, if so, it will update toSet. Struct version
|
||||
/// </summary>
|
||||
/// <param name="key">Key to check for</param>
|
||||
/// <param name="toSet">Reference to the variable to set</param>
|
||||
/// <typeparam name="T">Type to cast toSet to</typeparam>
|
||||
public static void SetOnKey<T>(this Hashtable h, object key, ref T toSet) where T : struct {
|
||||
if (h.ContainsKey(key))
|
||||
toSet = (T)h[key];
|
||||
}
|
||||
|
||||
public static void AddOrSet<T>(this Hashtable h, object key, T val) where T : struct {
|
||||
if (h.ContainsKey(key)) {
|
||||
h[key] = val;
|
||||
} else {
|
||||
h.Add(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a value to the hashtable if and only if it mismatches the previous provided
|
||||
/// Returns true if the replacement was made
|
||||
/// </summary>
|
||||
public static bool AddWithDirty<T>(this Hashtable h, char key, T tracked, ref T previous) {
|
||||
if (tracked.Equals(previous)) return false;
|
||||
|
||||
h.Add (key,tracked);
|
||||
previous = tracked;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds and updates the keys/value based on <paramref name="propertiesToSet"/>.
|
||||
/// Any other keys are uneffected.
|
||||
/// </summary>
|
||||
/// <param name="h"></param>
|
||||
/// <param name="propertiesToSet"></param>
|
||||
public static void SetHashtable(this Hashtable h, Hashtable propertiesToSet){
|
||||
var customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||||
h.Merge(customProps);
|
||||
h.StripKeysWithNullValues();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 91b1fc7f0e237bb4e8fc167e80d4b8e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,34 @@
|
|||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static class PhotonConstants {
|
||||
public static readonly byte EntityUpdateCode = 110;
|
||||
public static readonly byte EntityEventCode = 105;
|
||||
public static readonly byte EntityInstantiateCode = 106;
|
||||
public static readonly char eidChar = (char)206; // 'Î'
|
||||
public static readonly char athChar = (char)238; // 'î'
|
||||
public static readonly char insChar = (char)207; // 'Ï'
|
||||
public static readonly char tpeChar = (char)208;
|
||||
|
||||
public static readonly string propScene = "sc";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Region names strings
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string,string> RegionNames = new Dictionary<string,string>() {
|
||||
{"asia","Signapore"},
|
||||
{"au","Australia"},
|
||||
{"cae","Montreal"},
|
||||
{"cn","Shanghai"},
|
||||
{"eu","Europe"},
|
||||
{"in","India"},
|
||||
{"jp","Japan"},
|
||||
{"ru","Moscow"},
|
||||
{"rue","East Russia"},
|
||||
{"sa","Brazil"},
|
||||
{"kr","South Korea"},
|
||||
{"us","Eastern US"},
|
||||
{"usw","Western US"}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3090fc43b95be2244909c218fbf35512
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ExitGames.Client.Photon;
|
||||
using UnityEngine;
|
||||
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
public static class StreamCustomTypes {
|
||||
public static void Register() {
|
||||
PhotonPeer.RegisterType(typeof(Vector2), (byte)'V', SerializeVector2, DeserializeVector2);
|
||||
PhotonPeer.RegisterType(typeof(Vector3), (byte)'W', SerializeVector3, DeserializeVector3);
|
||||
PhotonPeer.RegisterType(typeof(Quaternion), (byte)'Q', SerializeQuaternion, DeserializeQuaternion);
|
||||
PhotonPeer.RegisterType(typeof(Color), (byte)'C', SerializeColor, DeserializeColor);
|
||||
PhotonPeer.RegisterType(typeof(char), (byte)'c', SerializeChar, DeserializeChar);
|
||||
}
|
||||
|
||||
private static short SerializeVector2(StreamBuffer outStream, object customObj) {
|
||||
var vo = (Vector2)customObj;
|
||||
|
||||
var ms = new MemoryStream(2 * 4);
|
||||
|
||||
ms.Write(BitConverter.GetBytes(vo.x), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.y), 0, 4);
|
||||
|
||||
outStream.Write(ms.ToArray(), 0, 2 * 4);
|
||||
return 2 * 4;
|
||||
}
|
||||
|
||||
private static object DeserializeVector2(StreamBuffer inStream, short length) {
|
||||
var bytes = new Byte[2 * 4];
|
||||
inStream.Read(bytes, 0, 2 * 4);
|
||||
return new
|
||||
Vector2(
|
||||
BitConverter.ToSingle(bytes, 0),
|
||||
BitConverter.ToSingle(bytes, 4));
|
||||
|
||||
// As best as I can tell, the new Protocol.Serialize/Deserialize are written around WP8 restrictions
|
||||
// It's not worth the pain.
|
||||
|
||||
//int index = 0;
|
||||
//float x, y;
|
||||
//Protocol.Deserialize(out x, bytes, ref index);
|
||||
//Protocol.Deserialize(out y, bytes, ref index);
|
||||
|
||||
//return new Vector2(x, y);
|
||||
}
|
||||
|
||||
private static short SerializeVector3(StreamBuffer outStream, object customObj) {
|
||||
Vector3 vo = (Vector3)customObj;
|
||||
|
||||
var ms = new MemoryStream(3 * 4);
|
||||
|
||||
ms.Write(BitConverter.GetBytes(vo.x), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.y), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.z), 0, 4);
|
||||
|
||||
outStream.Write(ms.ToArray(), 0, 3 * 4);
|
||||
return 3 * 4;
|
||||
}
|
||||
|
||||
private static object DeserializeVector3(StreamBuffer inStream, short length) {
|
||||
var bytes = new byte[3 * 4];
|
||||
|
||||
inStream.Read(bytes, 0, 3 * 4);
|
||||
|
||||
return new
|
||||
Vector3(
|
||||
BitConverter.ToSingle(bytes, 0),
|
||||
BitConverter.ToSingle(bytes, 4),
|
||||
BitConverter.ToSingle(bytes, 8));
|
||||
}
|
||||
|
||||
private static short SerializeQuaternion(StreamBuffer outStream, object customObj) {
|
||||
Quaternion vo = (Quaternion)customObj;
|
||||
|
||||
var ms = new MemoryStream(4 * 4);
|
||||
|
||||
ms.Write(BitConverter.GetBytes(vo.x), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.y), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.z), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.w), 0, 4);
|
||||
|
||||
outStream.Write(ms.ToArray(), 0, 4 * 4);
|
||||
return 4 * 4;
|
||||
}
|
||||
|
||||
private static object DeserializeQuaternion(StreamBuffer inStream, short length) {
|
||||
var bytes = new byte[4 * 4];
|
||||
|
||||
inStream.Read(bytes, 0, 4 * 4);
|
||||
|
||||
return new
|
||||
Quaternion(
|
||||
BitConverter.ToSingle(bytes, 0),
|
||||
BitConverter.ToSingle(bytes, 4),
|
||||
BitConverter.ToSingle(bytes, 8),
|
||||
BitConverter.ToSingle(bytes, 12));
|
||||
}
|
||||
|
||||
private static short SerializeColor(StreamBuffer outStream, object customObj) {
|
||||
Color vo = (Color)customObj;
|
||||
|
||||
var ms = new MemoryStream(4 * 4);
|
||||
|
||||
ms.Write(BitConverter.GetBytes(vo.r), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.g), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.b), 0, 4);
|
||||
ms.Write(BitConverter.GetBytes(vo.a), 0, 4);
|
||||
|
||||
outStream.Write(ms.ToArray(), 0, 4 * 4);
|
||||
return 4 * 4;
|
||||
}
|
||||
|
||||
private static object DeserializeColor(StreamBuffer inStream, short length) {
|
||||
var bytes = new byte[4 * 4];
|
||||
|
||||
inStream.Read(bytes, 0, 4 * 4);
|
||||
|
||||
return new
|
||||
Color(
|
||||
BitConverter.ToSingle(bytes, 0),
|
||||
BitConverter.ToSingle(bytes, 4),
|
||||
BitConverter.ToSingle(bytes, 8),
|
||||
BitConverter.ToSingle(bytes, 12));
|
||||
}
|
||||
|
||||
private static short SerializeChar(StreamBuffer outStream, object customObj) {
|
||||
outStream.Write(new[]{ (byte)((char)customObj) }, 0, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static object DeserializeChar(StreamBuffer inStream, short Length) {
|
||||
var bytes = new Byte[1];
|
||||
inStream.Read(bytes, 0, 1);
|
||||
|
||||
return (char)bytes[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e4402087c29fe6f4888bd19ae6c229d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d974f1ce0926d5e4e9e6b1d5bccacc0e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,282 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
using Player = ExitGames.Client.Photon.LoadBalancing.Player;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
public class PlayerPropertyEntry<T> {
|
||||
/// <summary>
|
||||
/// Unique key for <see cref="Player.CustomProperties"/>.
|
||||
/// </summary>
|
||||
public readonly string id;
|
||||
|
||||
/// <summary>
|
||||
/// Make sure <paramref name="id"/> is unique.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public PlayerPropertyEntry(string id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value from local player.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T GetLocal(){
|
||||
return Get(NetworkManager.net.LocalPlayer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value from <paramref name="player"/>.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
/// <returns></returns>
|
||||
public T Get(Player player){
|
||||
return (T)player.CustomProperties[id];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="value"/> for local player.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetLocal(T value){
|
||||
Set(NetworkManager.net.LocalPlayer, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="value"/> for <paramref name="player"/>.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Set(Player player, T value){
|
||||
var h = new Hashtable();
|
||||
h.Add(id, value);
|
||||
player.SetCustomProperties(h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="hashtable"/> with (<see cref="id"/>, <paramref name="value"/>).
|
||||
/// </summary>
|
||||
/// <param name="hashtable"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Initialilze(Hashtable hashtable, T value){
|
||||
hashtable.Add(id, value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Seems like the best place to put this
|
||||
// dunno
|
||||
// ----------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Gets value of <see cref="id"/> in <see cref="PlayerPrefs"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetPlayerPrefInt() => PlayerPrefs.GetInt(id, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="value"/> using <see cref="id"/> into <see cref="PlayerPrefs"/>.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetPlayerPrefInt(int value) => PlayerPrefs.SetInt(id, value);
|
||||
}
|
||||
|
||||
public static class PlayerProperties {
|
||||
|
||||
/// <summary>
|
||||
/// A huge list of a bunch of touhous.
|
||||
/// </summary>
|
||||
public static readonly string[] touhous = new[]{
|
||||
"Reimu", "Marsia",
|
||||
"Rumia", "Daiyousei", "Cirno", "Hong", "Koakuma", "Patchouli", "Sakuya", "Flandre",
|
||||
"Letty", "Chen", "Alice", "Lily", "Lyrica", "Lunasa", "Merlin", "Youmu", "Yuyuko", "Ran", "Yakari",
|
||||
"Suika",
|
||||
"Wriggle", "Mystia", "Keine", "Tewi", "Reisen", "Eirin", "Kaguya", "Mokou",
|
||||
"Aya", "Medicine", "Yuuka", "Komachi", "Eiki",
|
||||
"Shizuha", "Minoriko", "Hina", "Nitori", "Momiji", "Sanae", "Kanako", "Suwako",
|
||||
"Iku", "Tenshi", "Hatate", "Kokoro",
|
||||
"Kisume", "Yamame", "Parsee", "Yuugi", "Satori", "Rin", "Utsuho", "Koishi",
|
||||
"Kyouko", "Yoshika", "Seiga", "Tojiko", "Futo", "Miko", "Mamizou",
|
||||
"Wakasagihime", "Sekibanki", "Kagerou", "Benben", "Yatsuhashi", "Shinmyoumaru", "Raiko",
|
||||
"Sumireko",
|
||||
"Joon", "Shion",
|
||||
"Seiran", "Ringo", "Doremy", "Sagume", "Clownpiece", "Junko", "Hecatia",
|
||||
"Eternity", "Nemuno", "Auun", "Narumi", "Satono", "Mai", "Okina",
|
||||
"Eika", "Urumi", "Kutaka", "Yachie", "Mayumi", "Keiki", "Saki",
|
||||
"Rinnosuke", "Sunny", "Luna", "Star", "Chang'e", "Kasen", "Kosuzu"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// As name implies, gives a random touhou.
|
||||
/// </summary>
|
||||
public static string getRandomTouhou => touhous[Random.Range(0, touhous.Length)];
|
||||
|
||||
public static readonly string playerNickname = "nn";
|
||||
|
||||
/// <summary>
|
||||
/// Player's status in lobby. (ready or not)
|
||||
/// </summary>
|
||||
public static readonly PlayerPropertyEntry<bool> lobbyStatus = new PlayerPropertyEntry<bool>("ls");
|
||||
/// <summary>
|
||||
/// Player's status in loading a scene. (ready or not)
|
||||
/// </summary>
|
||||
public static readonly PlayerPropertyEntry<bool> gameStatus = new PlayerPropertyEntry<bool>("gs");
|
||||
|
||||
/// <summary>
|
||||
/// Player's selected character.
|
||||
/// </summary>
|
||||
public static readonly PlayerPropertyEntry<int> playerCharacter = new PlayerPropertyEntry<int>("pc");
|
||||
/// <summary>
|
||||
/// Player's selected team.
|
||||
/// </summary>
|
||||
public static readonly PlayerPropertyEntry<int> playerTeam = new PlayerPropertyEntry<int>("pt");
|
||||
|
||||
/// <summary>
|
||||
/// Player's selected response.
|
||||
/// </summary>
|
||||
public static readonly PlayerPropertyEntry<int> playerResponse = new PlayerPropertyEntry<int>("pr");
|
||||
|
||||
public static Player localPlayer {
|
||||
get {
|
||||
return NetworkManager.net.LocalPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes all custom properties for the local player.
|
||||
/// </summary>
|
||||
public static void CreatePlayerHashtable(){
|
||||
var h = new Hashtable();
|
||||
|
||||
localPlayer.NickName = GetPlayerNickname();
|
||||
|
||||
lobbyStatus.Initialilze(h, false);
|
||||
gameStatus.Initialilze(h, false);
|
||||
|
||||
playerCharacter.Initialilze(h, playerCharacter.GetPlayerPrefInt());
|
||||
playerTeam.Initialilze(h, playerCharacter.GetPlayerPrefInt());
|
||||
|
||||
playerResponse.Initialilze(h, -1);
|
||||
|
||||
localPlayer.SetCustomProperties(h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets a select few custom properties for the local player.
|
||||
/// </summary>
|
||||
public static void ResetPlayerHashtable(){
|
||||
var h = new Hashtable();
|
||||
|
||||
lobbyStatus.Initialilze(h, false);
|
||||
playerResponse.Initialilze(h, -1);
|
||||
|
||||
localPlayer.SetCustomProperties(h);
|
||||
}
|
||||
|
||||
#region Ready Status
|
||||
|
||||
/// <summary>
|
||||
/// If inside a room, returns if all players are lobby ready.
|
||||
/// If not, returns if the local player is lobby ready.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool GetAllLobbyStatus(){
|
||||
if (!NetworkManager.inRoom) return lobbyStatus.GetLocal();
|
||||
|
||||
var players = NetworkManager.net.CurrentRoom.Players.Values;
|
||||
|
||||
if (players.Count < 2) return false;
|
||||
|
||||
foreach(var p in players){
|
||||
if (!lobbyStatus.Get(p)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If inside a room, returns if all players are game ready.
|
||||
/// If not, returns if the local player is game ready.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool GetAllGameStatus() {
|
||||
if (!NetworkManager.inRoom) return gameStatus.GetLocal();
|
||||
|
||||
var players = NetworkManager.net.CurrentRoom.Players.Values;
|
||||
|
||||
if (players.Count < 2) return false;
|
||||
|
||||
foreach (var p in players) {
|
||||
if (!gameStatus.Get(p)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the highest value player response from all players.
|
||||
/// Returns -3 if no room is active.
|
||||
/// Returns -2 if there is less than 2 players in the room.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int GetAllResponse(){
|
||||
if (!NetworkManager.inRoom) return -3;
|
||||
|
||||
var players = NetworkManager.net.CurrentRoom.Players.Values;
|
||||
|
||||
if (players.Count < 2) return -2;
|
||||
|
||||
var response = int.MaxValue;
|
||||
foreach (var p in players) {
|
||||
response = Mathf.Min(playerResponse.Get(p), response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If inside a room, returns true if team mode is inactive OR if team mode is active and there is 2+ unique teams.
|
||||
/// If not inside a room, returns true.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool GetAllTeamDifferent(){
|
||||
if (!NetworkManager.inRoom) return true;
|
||||
if (!RoomProperties.teamStatus.Get()) return true;
|
||||
|
||||
var uniqueTeams = NetworkManager.net.CurrentRoom.Players.Values.Select(p => playerTeam.Get(p)).Distinct();
|
||||
|
||||
return uniqueTeams.Count() > 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nickname
|
||||
|
||||
/// <summary>
|
||||
/// Gets local player's nickname from <see cref="PlayerPrefs"/>.
|
||||
/// Returns a random touhou if no nickname is found.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetPlayerNickname(){
|
||||
var key = playerNickname;
|
||||
if (PlayerPrefs.HasKey(key)){
|
||||
return PlayerPrefs.GetString(key);
|
||||
} else {
|
||||
var value = getRandomTouhou;
|
||||
PlayerPrefs.SetString(key, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="nickname"/> into <see cref="PlayerPrefs"/> and <see cref="NetworkManager.net.LocalPlayer"/>.
|
||||
/// </summary>
|
||||
/// <param name="nickname"></param>
|
||||
public static void SetPlayerNickname(string nickname){
|
||||
var key = playerNickname;
|
||||
PlayerPrefs.SetString(key, nickname);
|
||||
localPlayer.NickName = key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0acc2d84cbeb940498c3e42bcfa41d67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,130 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
using ExitGames.Client.Photon;
|
||||
using ExitGames.Client.Photon.LoadBalancing;
|
||||
|
||||
using Player = ExitGames.Client.Photon.LoadBalancing.Player;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
|
||||
public class RoomPropertyEntry<T>{
|
||||
/// <summary>
|
||||
/// Unique key for <see cref="Room.CustomProperties"/>.
|
||||
/// </summary>
|
||||
public readonly string id;
|
||||
|
||||
/// <summary>
|
||||
/// Make sure <paramref name="id"/> is unique.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public RoomPropertyEntry(string id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="value"/> for the room.
|
||||
/// Performs a <see cref="NetworkManager.isMaster"/> check if <paramref name="masterOnly"/> is true.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="masterOnly"></param>
|
||||
public void Set(T value, bool masterOnly = true){
|
||||
if (masterOnly && !NetworkManager.isMaster) return;
|
||||
|
||||
var h = new Hashtable();
|
||||
h.Add(id, value);
|
||||
RoomProperties.UpdateRoomHashtable(h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value from room.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T Get(){
|
||||
var prop = RoomProperties.GetRoomHashtable();
|
||||
return (T)prop[id];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="hashtable"/> with (<see cref="id"/>, <paramref name="value"/>).
|
||||
/// </summary>
|
||||
/// <param name="hashtable"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Initialize(Hashtable hashtable, T value){
|
||||
hashtable.Add(id, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class RoomProperties {
|
||||
|
||||
private static Hashtable _localRoomProperties = new Hashtable();
|
||||
|
||||
public static RoomPropertyEntry<bool> teamStatus = new RoomPropertyEntry<bool>("ts");
|
||||
|
||||
public static RoomPropertyEntry<string> sceneLoaded = new RoomPropertyEntry<string>("sl");
|
||||
public static RoomPropertyEntry<Hashtable> entities = new RoomPropertyEntry<Hashtable>("et");
|
||||
public static RoomPropertyEntry<string> entities2 = new RoomPropertyEntry<string>("et2");
|
||||
|
||||
/// <summary>
|
||||
/// Initalizes all custom properties of the room.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Hashtable CreateRoomHashtable(){
|
||||
_localRoomProperties.Clear();
|
||||
|
||||
sceneLoaded.Initialize(_localRoomProperties, "RoomScene1");
|
||||
entities.Initialize(_localRoomProperties, new Hashtable());
|
||||
entities2.Initialize(_localRoomProperties, "");
|
||||
|
||||
return _localRoomProperties;
|
||||
}
|
||||
|
||||
public static Hashtable GetRoomHashtable(){
|
||||
var room = NetworkManager.net != null ? NetworkManager.net.CurrentRoom : null;
|
||||
if (room != null) {
|
||||
_localRoomProperties = room.CustomProperties;
|
||||
}
|
||||
return _localRoomProperties;
|
||||
}
|
||||
|
||||
public static void UpdateRoomHashtable(Hashtable propertiesToSet){
|
||||
_localRoomProperties.SetHashtable(propertiesToSet);
|
||||
var room = NetworkManager.net != null ? NetworkManager.net.CurrentRoom : null;
|
||||
if (room != null){
|
||||
room.SetCustomProperties(propertiesToSet);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadRoomHashtable(){
|
||||
|
||||
// Put anything here
|
||||
|
||||
/*
|
||||
// load scene before doing anything else
|
||||
|
||||
var roomscene = sceneLoaded.Get();
|
||||
|
||||
for(var i = 0; i < SceneManager.sceneCount; i++){
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
if (roomscene == scene.name) {
|
||||
Debug.Log("Current room scene loaded");
|
||||
goto LoadRoomProperties;
|
||||
}
|
||||
}
|
||||
|
||||
// The room scene we need loaded isn't loaded
|
||||
// Force load it
|
||||
Debug.Log("Loading room scene");
|
||||
SceneManager.LoadScene(roomscene);
|
||||
return;
|
||||
|
||||
// We have the current room loaded. Load other properties if needed
|
||||
LoadRoomProperties:
|
||||
|
||||
return;
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 98ad96430aae9e241a4dedc659a0fa4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,298 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using ExitGames.Client.Photon.LoadBalancing;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using System.Linq;
|
||||
|
||||
public class NetworkManager : MonoBehaviour {
|
||||
|
||||
#region Inspector
|
||||
|
||||
public string serverAddress, appID, gameVersion;
|
||||
|
||||
[Header("Debug")]
|
||||
public ParticleSystem NetworkDebugParticles;
|
||||
|
||||
public bool expectedOnline = false;
|
||||
public byte expectedMaxPlayers = 2;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Particles
|
||||
public static bool visParticles = true;
|
||||
// Spawn a particle on an entity, used for visualizing updates
|
||||
public static void netParticle(EntityBase eb, Color pColor) {
|
||||
if (!visParticles) return;
|
||||
|
||||
if (instance.NetworkDebugParticles) {
|
||||
var pparams = new ParticleSystem.EmitParams();
|
||||
pparams.position = eb.transform.position;
|
||||
pparams.startColor = pColor;
|
||||
instance.NetworkDebugParticles.Emit(pparams, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Network Helpers
|
||||
public static NetLogic net { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A time value that is 'approximately' (+/- 10ms most of the time) synced across clients
|
||||
/// </summary>
|
||||
public static double serverTime {
|
||||
get {
|
||||
if (net == null || net.loadBalancingPeer == null)
|
||||
return Time.realtimeSinceStartup;
|
||||
|
||||
return net.loadBalancingPeer.ServerTimeInMilliSeconds * (1d/1000d);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On non WebSocketSecure platforms, encryption handshake must occur before opCustom can be sent.
|
||||
/// This is important in cases such as getting the room or region list.
|
||||
/// </summary>
|
||||
public static bool delayForEncrypt {
|
||||
get {
|
||||
// We use secure websockets in UnityGL, so no encrypt handshake needs to occur
|
||||
#if UNITY_WEBGL
|
||||
return true;
|
||||
#else
|
||||
return !net.loadBalancingPeer.IsEncryptionAvailable;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if able to send network connections.
|
||||
/// </summary>
|
||||
public static bool isReady {
|
||||
get {
|
||||
if (net == null) return false;
|
||||
|
||||
return net.IsConnectedAndReady;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this client is considered owner of the room.
|
||||
/// </summary>
|
||||
public static bool isMaster {
|
||||
get {
|
||||
// If have no networking, we're the owner.
|
||||
if (net == null) return true;
|
||||
if (!inRoom) return true;
|
||||
|
||||
return net.LocalPlayer.IsMasterClient;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean for if we are on the name server. Used for awaiting the name server connection.
|
||||
/// Can be set to true to connect to name server.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if on name server; otherwise, <c>false</c>.</value>//
|
||||
public static bool onNameServer {
|
||||
get {
|
||||
if (net == null) return false;
|
||||
|
||||
return net.State.Equals(ClientState.ConnectedToNameServer);
|
||||
} set {
|
||||
if (value) net.ConnectToNameServer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean to check if the network is in the master lobby, will be true after we've found a region.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if on master lobby; otherwise, <c>false</c>.</value>
|
||||
public static bool onMasterLobby {
|
||||
get {
|
||||
if (net == null) return false;
|
||||
|
||||
return net.State.Equals(ClientState.JoinedLobby);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean to check if we're in a room or not.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if in room; otherwise, <c>false</c>.</value>
|
||||
public static bool inRoom{
|
||||
get {
|
||||
if (net == null) return false;
|
||||
|
||||
return net.State.Equals(ClientState.Joined);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all players in the current room sorted by <see cref="Player.ID"/>.
|
||||
/// </summary>
|
||||
public static Player[] getSortedPlayers{
|
||||
get {
|
||||
if (!inRoom) return new Player[0];
|
||||
|
||||
var players = NetworkManager.net.CurrentRoom.Players.Values;
|
||||
var playersSorted = players.OrderBy(p => p.ID);
|
||||
return playersSorted.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue a network update to be sent. Network events are processed both on Update and LateUpdate timings.
|
||||
/// </summary>
|
||||
public static bool netMessage(byte eventCode, object eventContent, bool sendReliable = false, RaiseEventOptions options = null) {
|
||||
if (!inRoom || !isReady) return false; // Only actually send messages when in game and ready
|
||||
|
||||
if (options == null) options = RaiseEventOptions.Default;
|
||||
|
||||
return net.OpRaiseEvent(eventCode, eventContent, sendReliable, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the local player id. if isOnline isn't true, this will be -1
|
||||
/// </summary>
|
||||
public static int localID {
|
||||
get {
|
||||
if (net == null) return 0;
|
||||
|
||||
return net.LocalPlayer.ID;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static event System.Action<EventData> netHook;
|
||||
public static event System.Action<EventData> onPropChanged;
|
||||
public static event System.Action<EventData> onLeave;
|
||||
public static event System.Action<EventData> onJoin;
|
||||
|
||||
private static NetworkManager _instance;
|
||||
public static NetworkManager instance {
|
||||
get {
|
||||
if (_instance) return _instance;
|
||||
_instance = FindObjectOfType<NetworkManager>();
|
||||
if (_instance) return _instance;
|
||||
throw new System.Exception("Network manager not instanced in scene");
|
||||
}
|
||||
set {
|
||||
_instance = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Awake() {
|
||||
if (_instance != null) {
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.Log("Aweak")
|
||||
|
||||
instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
// Initialize network
|
||||
net = new NetLogic();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
if (net == null) return;
|
||||
|
||||
// Only do service
|
||||
if (_instance == this) {
|
||||
net.Service(); // Service before disconnect to clear any blockers
|
||||
net.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void Update() {
|
||||
net.Service();
|
||||
}
|
||||
|
||||
|
||||
void LateUpdate () {
|
||||
net.Service();
|
||||
}
|
||||
|
||||
public class NetLogic : LoadBalancingClient {
|
||||
public NetLogic() {
|
||||
// Setup and launch network service
|
||||
AppId = NetworkManager.instance.appID;
|
||||
AppVersion = NetworkManager.instance.gameVersion;
|
||||
|
||||
AutoJoinLobby = true;
|
||||
|
||||
// Register custom type handlers
|
||||
StreamCustomTypes.Register();
|
||||
|
||||
#if UNITY_WEBGL
|
||||
Debug.Log("Using secure websockets");
|
||||
this.TransportProtocol = ConnectionProtocol.WebSocketSecure;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public event System.Action GamelistRefresh;
|
||||
|
||||
public override void OnEvent(EventData photonEvent) {
|
||||
base.OnEvent(photonEvent);
|
||||
|
||||
switch(photonEvent.Code) {
|
||||
case EventCode.GameList:
|
||||
case EventCode.GameListUpdate:
|
||||
Debug.Log("Server List recieved");
|
||||
if (GamelistRefresh != null) GamelistRefresh();
|
||||
break;
|
||||
case EventCode.PropertiesChanged:
|
||||
onPropChanged?.Invoke(photonEvent);
|
||||
break;
|
||||
case EventCode.Join:
|
||||
onJoin?.Invoke(photonEvent);
|
||||
break;
|
||||
case EventCode.Leave:
|
||||
onLeave?.Invoke(photonEvent);
|
||||
break;
|
||||
}
|
||||
|
||||
netHook?.Invoke(photonEvent);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Joins a specific room by name. If the room doesn't exist (yet), it will be created implicitiy.
|
||||
/// Creates custom properties automatically for the room.
|
||||
/// </summary>
|
||||
/// <param name="roomName"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="lobby"></param>
|
||||
/// <param name="startingScene"></param>
|
||||
/// <returns></returns>
|
||||
public bool OpJoinOrCreateRoomWithProperties(string roomName, RoomOptions options, TypedLobby lobby) {
|
||||
PlayerProperties.CreatePlayerHashtable();
|
||||
options.CustomRoomProperties = RoomProperties.GetRoomHashtable();
|
||||
|
||||
return OpJoinOrCreateRoom(roomName, options, lobby);
|
||||
}
|
||||
|
||||
public bool OpCreateRoomWithProperties(string roomName, RoomOptions options, TypedLobby lobby) {
|
||||
PlayerProperties.CreatePlayerHashtable();
|
||||
options.CustomRoomProperties = RoomProperties.GetRoomHashtable();
|
||||
|
||||
return OpCreateRoom(roomName, options, lobby);
|
||||
}
|
||||
|
||||
public bool OpJoinRoomWithProperties(string roomName) {
|
||||
PlayerProperties.CreatePlayerHashtable();
|
||||
|
||||
return OpJoinRoom(roomName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ca2d3b1e793ff9643b979899bd8add03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,336 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &925392911
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 925392912}
|
||||
- component: {fileID: 925392914}
|
||||
- component: {fileID: 925392913}
|
||||
m_Layer: 0
|
||||
m_Name: Text
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &925392912
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 925392911}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2129360324}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0.9}
|
||||
m_AnchorMax: {x: 0.5, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &925392914
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 925392911}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!114 &925392913
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 925392911}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_text: In game
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 0}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_outlineColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4278190080
|
||||
m_fontSize: 96.65
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 300
|
||||
m_fontStyle: 0
|
||||
m_textAlignment: 257
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_firstOverflowCharacterIndex: -1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
m_isLinkedTextComponent: 0
|
||||
m_isTextTruncated: 0
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_ignoreRectMaskCulling: 0
|
||||
m_ignoreCulling: 1
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_VertexBufferAutoSizeReduction: 1
|
||||
m_firstVisibleCharacter: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_textInfo:
|
||||
textComponent: {fileID: 925392913}
|
||||
characterCount: 7
|
||||
spriteCount: 0
|
||||
spaceCount: 1
|
||||
wordCount: 2
|
||||
linkCount: 0
|
||||
lineCount: 1
|
||||
pageCount: 1
|
||||
materialCount: 1
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_spriteAnimator: {fileID: 0}
|
||||
m_hasFontAssetChanged: 0
|
||||
m_subTextObjects:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1 &2129360323
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2129360324}
|
||||
- component: {fileID: 2129360327}
|
||||
- component: {fileID: 2129360326}
|
||||
- component: {fileID: 2129360325}
|
||||
m_Layer: 0
|
||||
m_Name: Canvas
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &2129360324
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2129360323}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_Children:
|
||||
- {fileID: 925392912}
|
||||
m_Father: {fileID: 4214266351047742}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!223 &2129360327
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2129360323}
|
||||
m_Enabled: 0
|
||||
serializedVersion: 3
|
||||
m_RenderMode: 0
|
||||
m_Camera: {fileID: 0}
|
||||
m_PlaneDistance: 100
|
||||
m_PixelPerfect: 0
|
||||
m_ReceivesEvents: 1
|
||||
m_OverrideSorting: 0
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_AdditionalShaderChannelsFlag: 25
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 0
|
||||
m_TargetDisplay: 0
|
||||
--- !u!114 &2129360326
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2129360323}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1980459831, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UiScaleMode: 0
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
--- !u!114 &2129360325
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2129360323}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1301386320, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
--- !u!1 &1645469224734478
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4214266351047742}
|
||||
- component: {fileID: 114738351746576220}
|
||||
- component: {fileID: -8397950358197448299}
|
||||
- component: {fileID: 5367125707474810989}
|
||||
m_Layer: 0
|
||||
m_Name: NetworkManager
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4214266351047742
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 2129360324}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &114738351746576220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ca2d3b1e793ff9643b979899bd8add03, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
serverAddress:
|
||||
appID: f0c408f8-1fc7-43d9-8bfe-59156f86a6e8
|
||||
gameVersion: .1
|
||||
NetworkDebugParticles: {fileID: 0}
|
||||
expectedOnline: 1
|
||||
expectedMaxPlayers: 2
|
||||
--- !u!114 &-8397950358197448299
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a99da8b191429648a69535f4bf2e0d3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
currentState: 0
|
||||
--- !u!114 &5367125707474810989
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_Enabled: 0
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ce8aa006a722b1048adbee6308ab38ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
allowQuickJoin: 1
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ad49179d7bafd344d948ab9643c4fdad
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class NetworkManagerDebug : MonoBehaviour {
|
||||
// Debug out the current state for visibility
|
||||
[Header("Current state")]
|
||||
public ExitGames.Client.Photon.LoadBalancing.ClientState currentState;
|
||||
|
||||
private void Update() {
|
||||
if (NetworkManager.net != null) {
|
||||
currentState = NetworkManager.net.State;
|
||||
|
||||
GetComponentInChildren<TMPro.TextMeshPro>().text = currentState.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9a99da8b191429648a69535f4bf2e0d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,284 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1371084334
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1371084335}
|
||||
- component: {fileID: 1371084339}
|
||||
- component: {fileID: 1371084338}
|
||||
- component: {fileID: 1371084337}
|
||||
- component: {fileID: 1371084336}
|
||||
m_Layer: 0
|
||||
m_Name: GameObject
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1371084335
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1371084334}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4214266351047742}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 3}
|
||||
m_SizeDelta: {x: 20, y: 5}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!23 &1371084339
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1371084334}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
m_DynamicOccludee: 1
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
--- !u!33 &1371084338
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1371084334}
|
||||
m_Mesh: {fileID: 0}
|
||||
--- !u!222 &1371084337
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1371084334}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!114 &1371084336
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1371084334}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9541d86e2fd84c1d9990edf0852d74ab, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: State
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_outlineColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4278190080
|
||||
m_fontSize: 18
|
||||
m_fontSizeBase: 18
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_textAlignment: 260
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_firstOverflowCharacterIndex: -1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
m_isLinkedTextComponent: 0
|
||||
m_isTextTruncated: 0
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 0
|
||||
m_isCullingEnabled: 0
|
||||
m_ignoreRectMaskCulling: 0
|
||||
m_ignoreCulling: 1
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_VertexBufferAutoSizeReduction: 1
|
||||
m_firstVisibleCharacter: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_textInfo:
|
||||
textComponent: {fileID: 1371084336}
|
||||
characterCount: 5
|
||||
spriteCount: 0
|
||||
spaceCount: 0
|
||||
wordCount: 1
|
||||
linkCount: 0
|
||||
lineCount: 1
|
||||
pageCount: 1
|
||||
materialCount: 1
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_spriteAnimator: {fileID: 0}
|
||||
m_hasFontAssetChanged: 0
|
||||
m_renderer: {fileID: 1371084339}
|
||||
m_subTextObjects:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
m_maskType: 0
|
||||
--- !u!1 &1645469224734478
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4214266351047742}
|
||||
- component: {fileID: 114738351746576220}
|
||||
- component: {fileID: -8397950358197448299}
|
||||
- component: {fileID: 14614541}
|
||||
m_Layer: 0
|
||||
m_Name: NetworkManagerDummy
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4214266351047742
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 1371084335}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &114738351746576220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ca2d3b1e793ff9643b979899bd8add03, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
serverAddress:
|
||||
appID: 7c0120c9-54c3-4025-b11f-b7b153d681ed
|
||||
gameVersion: .1
|
||||
NetworkDebugParticles: {fileID: 0}
|
||||
expectedOnline: 0
|
||||
expectedMaxPlayers: 2
|
||||
--- !u!114 &-8397950358197448299
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9a99da8b191429648a69535f4bf2e0d3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
currentState: 0
|
||||
--- !u!114 &14614541
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1645469224734478}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ce8aa006a722b1048adbee6308ab38ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
allowQuickJoin: 1
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 41564653a5d28dc4491420399b220d74
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,63 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using TMPro;
|
||||
using ExitGames.Client.Photon.LoadBalancing;
|
||||
|
||||
public class QuickJoin : MonoBehaviour {
|
||||
// Update is called once per frame
|
||||
public bool allowQuickJoin = true;
|
||||
|
||||
void Update () {
|
||||
if (allowQuickJoin && (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) && Input.GetKeyDown(KeyCode.J)) {
|
||||
if (NetworkManager.inRoom) return;
|
||||
|
||||
var activeScene = "QUICKJOIN";
|
||||
|
||||
var ro = new RoomOptions();
|
||||
ro.IsVisible = false;
|
||||
ro.IsOpen = true;
|
||||
ro.MaxPlayers = NetworkManager.instance.expectedMaxPlayers;
|
||||
|
||||
NetworkManager.net.OpJoinOrCreateRoomWithProperties(activeScene, ro, null);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator Start() {
|
||||
while (!allowQuickJoin) yield return null;
|
||||
|
||||
if (NetworkManager.net.ConnectToNameServer()){
|
||||
Debug.Log("Connecting to name server");
|
||||
} else {
|
||||
Debug.Log("Name Server connection failed");
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (!NetworkManager.onNameServer || !NetworkManager.isReady) yield return null;
|
||||
Debug.Log("Connected to name server");
|
||||
|
||||
if (NetworkManager.net.OpGetRegions()){
|
||||
Debug.Log("Started region request");
|
||||
} else {
|
||||
Debug.Log("Failed region request");
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (NetworkManager.net.AvailableRegions == null) yield return null;
|
||||
Debug.Log("Received region list");
|
||||
|
||||
if(NetworkManager.net.ConnectToRegionMaster("usw")){
|
||||
Debug.Log("Connecting to region master 'usw'");
|
||||
} else {
|
||||
Debug.Log("Failed to connect to region master 'usw'");
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (!NetworkManager.onMasterLobby) yield return null;
|
||||
Debug.Log("Connected to region master");
|
||||
Debug.Log("You can quick join now");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ce8aa006a722b1048adbee6308ab38ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,351 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1108792484905544
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224112458976610374}
|
||||
- component: {fileID: 222721656503737066}
|
||||
- component: {fileID: 114096038084053498}
|
||||
m_Layer: 5
|
||||
m_Name: Image
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &224112458976610374
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1108792484905544}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 224945633132606452}
|
||||
m_Father: {fileID: 224249934373868544}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0.9}
|
||||
m_AnchorMax: {x: 0.15, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &222721656503737066
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1108792484905544}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!114 &114096038084053498
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1108792484905544}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
--- !u!1 &1569014553392932
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224945633132606452}
|
||||
- component: {fileID: 222745614857108624}
|
||||
- component: {fileID: 114116331299675328}
|
||||
m_Layer: 5
|
||||
m_Name: Title
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &224945633132606452
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1569014553392932}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 224112458976610374}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &222745614857108624
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1569014553392932}
|
||||
m_CullTransparentMesh: 0
|
||||
--- !u!114 &114116331299675328
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1569014553392932}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
|
||||
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_text: ABCD
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 0}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_outlineColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4278190080
|
||||
m_fontSize: 58
|
||||
m_fontSizeBase: 175
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 50
|
||||
m_fontSizeMax: 500
|
||||
m_fontStyle: 0
|
||||
m_textAlignment: 258
|
||||
m_isAlignmentEnumConverted: 1
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_firstOverflowCharacterIndex: -1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
m_isLinkedTextComponent: 0
|
||||
m_isTextTruncated: 0
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_ignoreRectMaskCulling: 0
|
||||
m_ignoreCulling: 1
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_firstVisibleCharacter: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_textInfo:
|
||||
textComponent: {fileID: 0}
|
||||
characterCount: 4
|
||||
spriteCount: 0
|
||||
spaceCount: 0
|
||||
wordCount: 1
|
||||
linkCount: 0
|
||||
lineCount: 1
|
||||
pageCount: 1
|
||||
materialCount: 1
|
||||
m_havePropertiesChanged: 0
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_spriteAnimator: {fileID: 0}
|
||||
m_isInputParsingRequired: 0
|
||||
m_inputSource: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_subTextObjects:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1 &1935942590280396
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224249934373868544}
|
||||
- component: {fileID: 223742609007803358}
|
||||
- component: {fileID: 114955499652235416}
|
||||
- component: {fileID: 114786950351237876}
|
||||
- component: {fileID: 6963261912793562539}
|
||||
m_Layer: 5
|
||||
m_Name: RoomCode
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &224249934373868544
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1935942590280396}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_Children:
|
||||
- {fileID: 224112458976610374}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!223 &223742609007803358
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1935942590280396}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_RenderMode: 0
|
||||
m_Camera: {fileID: 0}
|
||||
m_PlaneDistance: 100
|
||||
m_PixelPerfect: 0
|
||||
m_ReceivesEvents: 1
|
||||
m_OverrideSorting: 0
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_AdditionalShaderChannelsFlag: 25
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 6
|
||||
m_TargetDisplay: 0
|
||||
--- !u!114 &114955499652235416
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1935942590280396}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1980459831, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UiScaleMode: 0
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 800, y: 600}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
--- !u!114 &114786950351237876
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1935942590280396}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1301386320, guid: f70555f144d8491a825f0804e09c671c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
--- !u!114 &6963261912793562539
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1935942590280396}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 07421fd6321565a4e8aebdebfd61c79c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Display: {fileID: 1108792484905544}
|
||||
Text: {fileID: 114116331299675328}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7857daa21c365d749b825ce14546bc09
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using TMPro;
|
||||
|
||||
public class RoomCodeDisplay : MonoBehaviour {
|
||||
|
||||
public TextMeshProUGUI Text;
|
||||
|
||||
void Start() {
|
||||
if (NetworkManager.inRoom){
|
||||
gameObject.SetActive(true);
|
||||
Text.text = NetworkManager.net.CurrentRoom.Name;
|
||||
} else {
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07421fd6321565a4e8aebdebfd61c79c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b45dd90f2a7976d4ab982b1f5d5986ad
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8a4408e5f601f774eb5f3fb98b07f009
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,180 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Extensions.cs" company="Exit Games GmbH">
|
||||
// Photon Extensions - Copyright (C) 2017 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Provides some helpful methods and extensions for Hashtables, etc.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_4_7_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System.Collections;
|
||||
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
#endif
|
||||
#if UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This static class defines some useful extension methods for several existing classes (e.g. Vector3, float and others).
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges all keys from addHash into the target. Adds new keys and updates the values of existing keys in target.
|
||||
/// </summary>
|
||||
/// <param name="target">The IDictionary to update.</param>
|
||||
/// <param name="addHash">The IDictionary containing data to merge into target.</param>
|
||||
public static void Merge(this IDictionary target, IDictionary addHash)
|
||||
{
|
||||
if (addHash == null || target.Equals(addHash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object key in addHash.Keys)
|
||||
{
|
||||
target[key] = addHash[key];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges keys of type string to target Hashtable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not remove keys from target (so non-string keys CAN be in target if they were before).
|
||||
/// </remarks>
|
||||
/// <param name="target">The target IDicitionary passed in plus all string-typed keys from the addHash.</param>
|
||||
/// <param name="addHash">A IDictionary that should be merged partly into target to update it.</param>
|
||||
public static void MergeStringKeys(this IDictionary target, IDictionary addHash)
|
||||
{
|
||||
if (addHash == null || target.Equals(addHash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object key in addHash.Keys)
|
||||
{
|
||||
// only merge keys of type string
|
||||
if (key is string)
|
||||
{
|
||||
target[key] = addHash[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper method for debugging of IDictionary content, inlcuding type-information. Using this is not performant.</summary>
|
||||
/// <remarks>Should only be used for debugging as necessary.</remarks>
|
||||
/// <param name="origin">Some Dictionary or Hashtable.</param>
|
||||
/// <returns>String of the content of the IDictionary.</returns>
|
||||
public static string ToStringFull(this IDictionary origin)
|
||||
{
|
||||
return SupportClass.DictionaryToString(origin, false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Helper method for debugging of object[] content. Using this is not performant.</summary>
|
||||
/// <remarks>Should only be used for debugging as necessary.</remarks>
|
||||
/// <param name="data">Any object[].</param>
|
||||
/// <returns>A comma-separated string containing each value's ToString().</returns>
|
||||
public static string ToStringFull(this object[] data)
|
||||
{
|
||||
if (data == null) return "null";
|
||||
|
||||
string[] sb = new string[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
object o = data[i];
|
||||
sb[i] = (o != null) ? o.ToString() : "null";
|
||||
}
|
||||
|
||||
return string.Join(", ", sb);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This method copies all string-typed keys of the original into a new Hashtable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not recurse (!) into hashes that might be values in the root-hash.
|
||||
/// This does not modify the original.
|
||||
/// </remarks>
|
||||
/// <param name="original">The original IDictonary to get string-typed keys from.</param>
|
||||
/// <returns>New Hashtable containing only string-typed keys of the original.</returns>
|
||||
public static Hashtable StripToStringKeys(this IDictionary original)
|
||||
{
|
||||
Hashtable target = new Hashtable();
|
||||
if (original != null)
|
||||
{
|
||||
foreach (object key in original.Keys)
|
||||
{
|
||||
if (key is string)
|
||||
{
|
||||
target[key] = original[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This removes all key-value pairs that have a null-reference as value.
|
||||
/// Photon properties are removed by setting their value to null.
|
||||
/// Changes the original passed IDictionary!
|
||||
/// </summary>
|
||||
/// <param name="original">The IDictionary to strip of keys with null-values.</param>
|
||||
public static void StripKeysWithNullValues(this IDictionary original)
|
||||
{
|
||||
object[] keys = new object[original.Count];
|
||||
original.Keys.CopyTo(keys, 0);
|
||||
|
||||
for (int index = 0; index < keys.Length; index++)
|
||||
{
|
||||
var key = keys[index];
|
||||
if (original[key] == null)
|
||||
{
|
||||
original.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a particular integer value is in an int-array.
|
||||
/// </summary>
|
||||
/// <remarks>This might be useful to look up if a particular actorNumber is in the list of players of a room.</remarks>
|
||||
/// <param name="target">The array of ints to check.</param>
|
||||
/// <param name="nr">The number to lookup in target.</param>
|
||||
/// <returns>True if nr was found in target.</returns>
|
||||
public static bool Contains(this int[] target, int nr)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < target.Length; index++)
|
||||
{
|
||||
if (target[index] == nr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ee1c48ab6ff903f4c823e7cf48b423e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,45 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="FriendInfo.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2013 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Collection of values related to a user / friend.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
#if UNITY_4_7_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
#if UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to store info about a friend's online state and in which room he/she is.
|
||||
/// </summary>
|
||||
public class FriendInfo
|
||||
{
|
||||
public string Name { get; internal protected set; }
|
||||
public bool IsOnline { get; internal protected set; }
|
||||
public string Room { get; internal protected set; }
|
||||
|
||||
public bool IsInRoom
|
||||
{
|
||||
get { return this.IsOnline && !string.IsNullOrEmpty(this.Room); }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}\t is: {1}", this.Name, (!this.IsOnline) ? "offline" : this.IsInRoom ? "playing" : "on master");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 723f710efc79ee0469dbe94bf0cc9ddb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f9a8ceffad89d164e8b5ba037f5d34f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a3aba55d840ae78459c990a41ed84f82
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,430 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Player.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2011 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Per client in a room, a Player is created. This client's Player is also
|
||||
// known as PhotonClient.LocalPlayer and the only one you might change
|
||||
// properties for.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#define UNITY
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
#endif
|
||||
#if UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Summarizes a "player" within a room, identified (in that room) by ID (or "actorID").
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each player has a actorID, valid for that room. It's -1 until assigned by server (and client logic).
|
||||
/// </remarks>
|
||||
public class Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Used internally to identify the masterclient of a room.
|
||||
/// </summary>
|
||||
protected internal Room RoomReference { get; set; }
|
||||
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
private int actorID = -1;
|
||||
|
||||
/// <summary>Identifier of this player in current room. Also known as: actorNumber or actorID. It's -1 outside of rooms.</summary>
|
||||
/// <remarks>The ID is assigned per room and only valid in that context. It will change even on leave and re-join. IDs are never re-used per room.</remarks>
|
||||
public int ID
|
||||
{
|
||||
get { return this.actorID; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Only one player is controlled by each client. Others are not local.</summary>
|
||||
public readonly bool IsLocal;
|
||||
|
||||
|
||||
/// <summary>Background field for nickName.</summary>
|
||||
private string nickName;
|
||||
|
||||
/// <summary>Non-unique nickname of this player. Synced automatically in a room.</summary>
|
||||
/// <remarks>
|
||||
/// A player might change his own playername in a room (it's only a property).
|
||||
/// Setting this value updates the server and other players (using an operation).
|
||||
/// </remarks>
|
||||
public string NickName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.nickName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.nickName) && this.nickName.Equals(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.nickName = value;
|
||||
|
||||
// update a room, if we changed our nickName (locally, while being in a room)
|
||||
if (this.IsLocal && this.RoomReference != null && this.RoomReference.IsLocalClientInside)
|
||||
{
|
||||
this.SetPlayerNameProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>UserId of the player, available when the room got created with RoomOptions.PublishUserId = true.</summary>
|
||||
/// <remarks>Useful for PhotonNetwork.FindFriends and blocking slots in a room for expected players (e.g. in PhotonNetwork.CreateRoom).</remarks>
|
||||
public string UserId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this player is the Master Client of the current room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See also: PhotonNetwork.masterClient.
|
||||
/// </remarks>
|
||||
public bool IsMasterClient
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.RoomReference == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.ID == this.RoomReference.MasterClientId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>If this player is active in the room (and getting events which are currently being sent).</summary>
|
||||
/// <remarks>
|
||||
/// Inactive players keep their spot in a room but otherwise behave as if offline (no matter what their actual connection status is).
|
||||
/// The room needs a PlayerTTL > 0. If a player is inactive for longer than PlayerTTL, the server will remove this player from the room.
|
||||
/// For a client "rejoining" a room, is the same as joining it: It gets properties, cached events and then the live events.
|
||||
/// </remarks>
|
||||
public bool IsInactive { get; set; }
|
||||
|
||||
/// <summary>Read-only cache for custom properties of player. Set via Player.SetCustomProperties.</summary>
|
||||
/// <remarks>
|
||||
/// Don't modify the content of this Hashtable. Use SetCustomProperties and the
|
||||
/// properties of this class to modify values. When you use those, the client will
|
||||
/// sync values with the server.
|
||||
/// </remarks>
|
||||
/// <see cref="SetCustomProperties"/>
|
||||
public Hashtable CustomProperties { get; private set; }
|
||||
|
||||
/// <summary>Creates a Hashtable with all properties (custom and "well known" ones).</summary>
|
||||
/// <remarks>Creates new Hashtables each time used, so if used more often, cache this.</remarks>
|
||||
public Hashtable AllProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
Hashtable allProps = new Hashtable();
|
||||
allProps.Merge(this.CustomProperties);
|
||||
allProps[ActorProperties.PlayerName] = this.nickName;
|
||||
return allProps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Can be used to store a reference that's useful to know "by player".</summary>
|
||||
/// <remarks>Example: Set a player's character as Tag by assigning the GameObject on Instantiate.</remarks>
|
||||
public object TagObject;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a player instance.
|
||||
/// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer().
|
||||
/// </summary>
|
||||
/// <param name="nickName">NickName of the player (a "well known property").</param>
|
||||
/// <param name="actorID">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param>
|
||||
/// <param name="isLocal">If this is the local peer's player (or a remote one).</param>
|
||||
protected internal Player(string nickName, int actorID, bool isLocal) : this(nickName, actorID, isLocal, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a player instance.
|
||||
/// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer().
|
||||
/// </summary>
|
||||
/// <param name="nickName">NickName of the player (a "well known property").</param>
|
||||
/// <param name="actorID">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param>
|
||||
/// <param name="isLocal">If this is the local peer's player (or a remote one).</param>
|
||||
/// <param name="playerProperties">A Hashtable of custom properties to be synced. Must use String-typed keys and serializable datatypes as values.</param>
|
||||
protected internal Player(string nickName, int actorID, bool isLocal, Hashtable playerProperties)
|
||||
{
|
||||
this.IsLocal = isLocal;
|
||||
this.actorID = actorID;
|
||||
this.NickName = nickName;
|
||||
|
||||
this.CustomProperties = new Hashtable();
|
||||
this.InternalCacheProperties(playerProperties);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a Player by ActorNumber (Player.ID).
|
||||
/// </summary>
|
||||
/// <param name="id">ActorNumber of the a player in this room.</param>
|
||||
/// <returns>Player or null.</returns>
|
||||
public Player Get(int id)
|
||||
{
|
||||
if (this.RoomReference == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.RoomReference.GetPlayer(id);
|
||||
}
|
||||
|
||||
/// <summary>Gets this Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
|
||||
/// <returns>Player or null.</returns>
|
||||
public Player GetNext()
|
||||
{
|
||||
return GetNextFor(this.ID);
|
||||
}
|
||||
|
||||
/// <summary>Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
|
||||
/// <remarks>Useful when you pass something to the next player. For example: passing the turn to the next player.</remarks>
|
||||
/// <param name="currentPlayer">The Player for which the next is being needed.</param>
|
||||
/// <returns>Player or null.</returns>
|
||||
public Player GetNextFor(Player currentPlayer)
|
||||
{
|
||||
if (currentPlayer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return GetNextFor(currentPlayer.ID);
|
||||
}
|
||||
|
||||
/// <summary>Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
|
||||
/// <remarks>Useful when you pass something to the next player. For example: passing the turn to the next player.</remarks>
|
||||
/// <param name="currentPlayerId">The ActorNumber (Player.ID) for which the next is being needed.</param>
|
||||
/// <returns>Player or null.</returns>
|
||||
public Player GetNextFor(int currentPlayerId)
|
||||
{
|
||||
if (this.RoomReference == null || this.RoomReference.Players == null || this.RoomReference.Players.Count < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<int, Player> players = this.RoomReference.Players;
|
||||
int nextHigherId = int.MaxValue; // we look for the next higher ID
|
||||
int lowestId = currentPlayerId; // if we are the player with the highest ID, there is no higher and we return to the lowest player's id
|
||||
|
||||
foreach (int playerid in players.Keys)
|
||||
{
|
||||
if (playerid < lowestId)
|
||||
{
|
||||
lowestId = playerid; // less than any other ID (which must be at least less than this player's id).
|
||||
}
|
||||
else if (playerid > currentPlayerId && playerid < nextHigherId)
|
||||
{
|
||||
nextHigherId = playerid; // more than our ID and less than those found so far.
|
||||
}
|
||||
}
|
||||
|
||||
//UnityEngine.Debug.LogWarning("Debug. " + currentPlayerId + " lower: " + lowestId + " higher: " + nextHigherId + " ");
|
||||
//UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(currentPlayerId));
|
||||
//UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(lowestId));
|
||||
//if (nextHigherId != int.MaxValue) UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(nextHigherId));
|
||||
return (nextHigherId != int.MaxValue) ? players[nextHigherId] : players[lowestId];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Caches properties for new Players or when updates of remote players are received. Use SetCustomProperties() for a synced update.</summary>
|
||||
/// <remarks>
|
||||
/// This only updates the CustomProperties and doesn't send them to the server.
|
||||
/// Mostly used when creating new remote players, where the server sends their properties.
|
||||
/// </remarks>
|
||||
public virtual void InternalCacheProperties(Hashtable properties)
|
||||
{
|
||||
if (properties == null || properties.Count == 0 || this.CustomProperties.Equals(properties))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (properties.ContainsKey(ActorProperties.PlayerName))
|
||||
{
|
||||
string nameInServersProperties = (string)properties[ActorProperties.PlayerName];
|
||||
if (nameInServersProperties != null)
|
||||
{
|
||||
if (this.IsLocal)
|
||||
{
|
||||
// the local playername is different than in the properties coming from the server
|
||||
// so the local nickName was changed and the server is outdated -> update server
|
||||
// update property instead of using the outdated nickName coming from server
|
||||
if (!nameInServersProperties.Equals(this.nickName))
|
||||
{
|
||||
this.SetPlayerNameProperty();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.NickName = nameInServersProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (properties.ContainsKey(ActorProperties.UserId))
|
||||
{
|
||||
this.UserId = (string)properties[ActorProperties.UserId];
|
||||
}
|
||||
if (properties.ContainsKey(ActorProperties.IsInactive))
|
||||
{
|
||||
this.IsInactive = (bool)properties[ActorProperties.IsInactive]; //TURNBASED new well-known propery for players
|
||||
}
|
||||
|
||||
this.CustomProperties.MergeStringKeys(properties);
|
||||
this.CustomProperties.StripKeysWithNullValues();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Brief summary string of the Player. Includes name or player.ID and if it's the Master Client.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.NickName + " " + SupportClass.DictionaryToString(this.CustomProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String summary of the Player: player.ID, name and all custom properties of this user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use with care and not every frame!
|
||||
/// Converts the customProperties to a String on every single call.
|
||||
/// </remarks>
|
||||
public string ToStringFull()
|
||||
{
|
||||
return string.Format("#{0:00} '{1}'{2} {3}", this.ID, this.NickName, this.IsInactive ? " (inactive)" : "", this.CustomProperties.ToStringFull());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If players are equal (by GetHasCode, which returns this.ID).
|
||||
/// </summary>
|
||||
public override bool Equals(object p)
|
||||
{
|
||||
Player pp = p as Player;
|
||||
return (pp != null && this.GetHashCode() == pp.GetHashCode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accompanies Equals, using the ID (actorNumber) as HashCode to return.
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.ID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used internally, to update this client's playerID when assigned (doesn't change after assignment).
|
||||
/// </summary>
|
||||
protected internal void ChangeLocalID(int newID)
|
||||
{
|
||||
if (!this.IsLocal)
|
||||
{
|
||||
//Debug.LogError("ERROR You should never change Player IDs!");
|
||||
return;
|
||||
}
|
||||
|
||||
this.actorID = newID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates and synchronizes this Player's Custom Properties. Optionally, expectedProperties can be provided as condition.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Custom Properties are a set of string keys and arbitrary values which is synchronized
|
||||
/// for the players in a Room. They are available when the client enters the room, as
|
||||
/// they are in the response of OpJoin and OpCreate.
|
||||
///
|
||||
/// Custom Properties either relate to the (current) Room or a Player (in that Room).
|
||||
///
|
||||
/// Both classes locally cache the current key/values and make them available as
|
||||
/// property: CustomProperties. This is provided only to read them.
|
||||
/// You must use the method SetCustomProperties to set/modify them.
|
||||
///
|
||||
/// Any client can set any Custom Properties anytime (when in a room).
|
||||
/// It's up to the game logic to organize how they are best used.
|
||||
///
|
||||
/// You should call SetCustomProperties only with key/values that are new or changed. This reduces
|
||||
/// traffic and performance.
|
||||
///
|
||||
/// Unless you define some expectedProperties, setting key/values is always permitted.
|
||||
/// In this case, the property-setting client will not receive the new values from the server but
|
||||
/// instead update its local cache in SetCustomProperties.
|
||||
///
|
||||
/// If you define expectedProperties, the server will skip updates if the server property-cache
|
||||
/// does not contain all expectedProperties with the same values.
|
||||
/// In this case, the property-setting client will get an update from the server and update it's
|
||||
/// cached key/values at about the same time as everyone else.
|
||||
///
|
||||
/// The benefit of using expectedProperties can be only one client successfully sets a key from
|
||||
/// one known value to another.
|
||||
/// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
|
||||
/// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
|
||||
/// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
|
||||
/// take the item will have it (and the others fail to set the ownership).
|
||||
///
|
||||
/// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
|
||||
/// </remarks>
|
||||
/// <param name="propertiesToSet">Hashtable of Custom Properties to be set. </param>
|
||||
/// <param name="expectedValues">If non-null, these are the property-values the server will check as condition for this update.</param>
|
||||
/// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param>
|
||||
public void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, WebFlags webFlags = null)
|
||||
{
|
||||
if (propertiesToSet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||||
Hashtable customPropsToCheck = expectedValues.StripToStringKeys() as Hashtable;
|
||||
|
||||
|
||||
// no expected values -> set and callback
|
||||
bool noCas = customPropsToCheck == null || customPropsToCheck.Count == 0;
|
||||
|
||||
|
||||
if (noCas)
|
||||
{
|
||||
this.CustomProperties.Merge(customProps);
|
||||
this.CustomProperties.StripKeysWithNullValues();
|
||||
}
|
||||
|
||||
// send (sync) these new values if in room
|
||||
if (this.RoomReference != null && this.RoomReference.IsLocalClientInside)
|
||||
{
|
||||
this.RoomReference.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfActor(this.actorID, customProps, customPropsToCheck, webFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Uses OpSetPropertiesOfActor to sync this player's NickName (server is being updated with this.NickName).</summary>
|
||||
private void SetPlayerNameProperty()
|
||||
{
|
||||
if (this.RoomReference != null && this.RoomReference.IsLocalClientInside)
|
||||
{
|
||||
Hashtable properties = new Hashtable();
|
||||
properties[ActorProperties.PlayerName] = this.nickName;
|
||||
this.RoomReference.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfActor(this.ID, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0b0942472c9351047afc23b868b562f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,474 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Room.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2011 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// The Room class resembles the properties known about the room in which
|
||||
// a game/match happens.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#define UNITY
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a room a client joins/joined.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contains a list of current players, their properties and those of this room, too.
|
||||
/// A room instance has a number of "well known" properties like IsOpen, MaxPlayers which can be changed.
|
||||
/// Your own, custom properties can be set via SetCustomProperties() while being in the room.
|
||||
///
|
||||
/// Typically, this class should be extended by a game-specific implementation with logic and extra features.
|
||||
/// </remarks>
|
||||
public class Room : RoomInfo
|
||||
{
|
||||
protected internal int PlayerTTL;
|
||||
protected internal int RoomTTL;
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the LoadbalancingClient which is currently keeping the connection and state.
|
||||
/// </summary>
|
||||
protected internal LoadBalancingClient LoadBalancingClient { get; set; }
|
||||
|
||||
/// <summary>The name of a room. Unique identifier (per Loadbalancing group) for a room/match.</summary>
|
||||
/// <remarks>The name can't be changed once it's set by the server.</remarks>
|
||||
public new string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
this.name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room can be joined.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not affect listing in a lobby but joining the room will fail if not open.
|
||||
/// If not open, the room is excluded from random matchmaking.
|
||||
/// Due to racing conditions, found matches might become closed while users are trying to join.
|
||||
/// Simply re-connect to master and find another.
|
||||
/// Use property "IsVisible" to not list the room.
|
||||
///
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public new bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!this.IsLocalClientInside)
|
||||
{
|
||||
LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "Can't set room properties when not in that room.");
|
||||
}
|
||||
|
||||
if (value != this.isOpen)
|
||||
{
|
||||
LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsOpen, value } });
|
||||
}
|
||||
|
||||
this.isOpen = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room is listed in its lobby.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Rooms can be created invisible, or changed to invisible.
|
||||
/// To change if a room can be joined, use property: open.
|
||||
///
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public new bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!this.IsLocalClientInside)
|
||||
{
|
||||
LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "Can't set room properties when not in that room.");
|
||||
}
|
||||
|
||||
if (value != this.isVisible)
|
||||
{
|
||||
LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsVisible, value } });
|
||||
}
|
||||
|
||||
this.isVisible = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets a limit of players to this room. This property is synced and shown in lobby, too.
|
||||
/// If the room is full (players count == maxplayers), joining this room will fail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public new byte MaxPlayers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.maxPlayers;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!this.IsLocalClientInside)
|
||||
{
|
||||
LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "Can't set room properties when not in that room.");
|
||||
}
|
||||
|
||||
if (value != this.maxPlayers)
|
||||
{
|
||||
LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.MaxPlayers, value } });
|
||||
}
|
||||
|
||||
this.maxPlayers = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The count of players in this Room (using this.Players.Count).</summary>
|
||||
public new byte PlayerCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Players == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (byte)this.Players.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>While inside a Room, this is the list of players who are also in that room.</summary>
|
||||
private Dictionary<int, Player> players = new Dictionary<int, Player>();
|
||||
|
||||
/// <summary>While inside a Room, this is the list of players who are also in that room.</summary>
|
||||
public Dictionary<int, Player> Players
|
||||
{
|
||||
get
|
||||
{
|
||||
return players;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
players = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of users who are expected to join this room. In matchmaking, Photon blocks a slot for each of these UserIDs out of the MaxPlayers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
|
||||
/// Define expected players in the PhotonNetwork methods: CreateRoom, JoinRoom and JoinOrCreateRoom.
|
||||
/// </remarks>
|
||||
public string[] ExpectedUsers
|
||||
{
|
||||
get { return this.expectedUsers; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID (actorID, actorNumber) of the player who's the master of this Room.
|
||||
/// Note: This changes when the current master leaves the room.
|
||||
/// </summary>
|
||||
public int MasterClientId { get { return this.masterClientId; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of custom properties that are in the RoomInfo of the Lobby.
|
||||
/// This list is defined when creating the room and can't be changed afterwards. Compare: LoadBalancingClient.OpCreateRoom()
|
||||
/// </summary>
|
||||
/// <remarks>You could name properties that are not set from the beginning. Those will be synced with the lobby when added later on.</remarks>
|
||||
public string[] PropertiesListedInLobby
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.propertiesListedInLobby;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
this.propertiesListedInLobby = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this room uses autoCleanUp to remove all (buffered) RPCs and instantiated GameObjects when a player leaves.
|
||||
/// </summary>
|
||||
public bool AutoCleanUp
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.autoCleanUp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Creates a Room (representation) with given name and properties and the "listing options" as provided by parameters.</summary>
|
||||
/// <param name="roomName">Name of the room (can be null until it's actually created on server).</param>
|
||||
/// <param name="options">Room options.</param>
|
||||
protected internal Room(string roomName, RoomOptions options) : base(roomName, options != null ? options.CustomRoomProperties : null)
|
||||
{
|
||||
// base() sets name and (custom)properties. here we set "well known" properties
|
||||
if (options != null)
|
||||
{
|
||||
this.isVisible = options.IsVisible;
|
||||
this.isOpen = options.IsOpen;
|
||||
this.maxPlayers = options.MaxPlayers;
|
||||
this.propertiesListedInLobby = options.CustomRoomPropertiesForLobby;
|
||||
this.PlayerTTL = options.PlayerTtl;
|
||||
this.RoomTTL = options.EmptyRoomTtl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates and synchronizes this Room's Custom Properties. Optionally, expectedProperties can be provided as condition.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Custom Properties are a set of string keys and arbitrary values which is synchronized
|
||||
/// for the players in a Room. They are available when the client enters the room, as
|
||||
/// they are in the response of OpJoin and OpCreate.
|
||||
///
|
||||
/// Custom Properties either relate to the (current) Room or a Player (in that Room).
|
||||
///
|
||||
/// Both classes locally cache the current key/values and make them available as
|
||||
/// property: CustomProperties. This is provided only to read them.
|
||||
/// You must use the method SetCustomProperties to set/modify them.
|
||||
///
|
||||
/// Any client can set any Custom Properties anytime (when in a room).
|
||||
/// It's up to the game logic to organize how they are best used.
|
||||
///
|
||||
/// You should call SetCustomProperties only with key/values that are new or changed. This reduces
|
||||
/// traffic and performance.
|
||||
///
|
||||
/// Unless you define some expectedProperties, setting key/values is always permitted.
|
||||
/// In this case, the property-setting client will not receive the new values from the server but
|
||||
/// instead update its local cache in SetCustomProperties.
|
||||
///
|
||||
/// If you define expectedProperties, the server will skip updates if the server property-cache
|
||||
/// does not contain all expectedProperties with the same values.
|
||||
/// In this case, the property-setting client will get an update from the server and update it's
|
||||
/// cached key/values at about the same time as everyone else.
|
||||
///
|
||||
/// The benefit of using expectedProperties can be only one client successfully sets a key from
|
||||
/// one known value to another.
|
||||
/// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
|
||||
/// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
|
||||
/// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
|
||||
/// take the item will have it (and the others fail to set the ownership).
|
||||
///
|
||||
/// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
|
||||
/// </remarks>
|
||||
/// <param name="propertiesToSet">Hashtable of Custom Properties that changes.</param>
|
||||
/// <param name="expectedProperties">Provide some keys/values to use as condition for setting the new values. Client must be in room.</param>
|
||||
/// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param>
|
||||
public virtual void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null)
|
||||
{
|
||||
Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||||
|
||||
// merge (and delete null-values), unless we use CAS (expected props)
|
||||
if (expectedProperties == null || expectedProperties.Count == 0)
|
||||
{
|
||||
this.CustomProperties.Merge(customProps);
|
||||
this.CustomProperties.StripKeysWithNullValues();
|
||||
}
|
||||
|
||||
// send (sync) these new values if in room
|
||||
if (this.IsLocalClientInside)
|
||||
{
|
||||
this.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfRoom(customProps, expectedProperties, webFlags);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables you to define the properties available in the lobby if not all properties are needed to pick a room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Limit the amount of properties sent to users in the lobby to improve speed and stability.
|
||||
/// </remarks>
|
||||
/// <param name="propertiesListedInLobby">An array of custom room property names to forward to the lobby.</param>
|
||||
public void SetPropertiesListedInLobby(string[] propertiesListedInLobby)
|
||||
{
|
||||
Hashtable customProps = new Hashtable();
|
||||
customProps[GamePropertyKey.PropsListedInLobby] = propertiesListedInLobby;
|
||||
|
||||
bool sent = this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps);
|
||||
|
||||
if (sent)
|
||||
{
|
||||
this.propertiesListedInLobby = propertiesListedInLobby;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a player from this room's Players Dictionary.
|
||||
/// This is internally used by the LoadBalancing API. There is usually no need to remove players yourself.
|
||||
/// This is not a way to "kick" players.
|
||||
/// </summary>
|
||||
protected internal virtual void RemovePlayer(Player player)
|
||||
{
|
||||
this.Players.Remove(player.ID);
|
||||
player.RoomReference = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a player from this room's Players Dictionary.
|
||||
/// </summary>
|
||||
protected internal virtual void RemovePlayer(int id)
|
||||
{
|
||||
this.RemovePlayer(this.GetPlayer(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the server to assign another player as Master Client of your current room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// RaiseEvent has the option to send messages only to the Master Client of a room.
|
||||
/// SetMasterClient affects which client gets those messages.
|
||||
///
|
||||
/// This method calls an operation on the server to set a new Master Client, which takes a roundtrip.
|
||||
/// In case of success, this client and the others get the new Master Client from the server.
|
||||
///
|
||||
/// SetMasterClient tells the server which current Master Client should be replaced with the new one.
|
||||
/// It will fail, if anything switches the Master Client moments earlier. There is no callback for this
|
||||
/// error. All clients should get the new Master Client assigned by the server anyways.
|
||||
///
|
||||
/// See also: MasterClientId
|
||||
/// </remarks>
|
||||
/// <param name="masterClientPlayer">The player to become the next Master Client.</param>
|
||||
/// <returns>False when this operation couldn't be done currently. Requires a v4 Photon Server.</returns>
|
||||
public bool SetMasterClient(Player masterClientPlayer)
|
||||
{
|
||||
if (!this.IsLocalClientInside)
|
||||
{
|
||||
this.LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "SetMasterClient can only be called for the current room (being in one).");
|
||||
return false;
|
||||
}
|
||||
|
||||
Hashtable newProps = new Hashtable() { { GamePropertyKey.MasterClientId, masterClientPlayer.ID } };
|
||||
Hashtable prevProps = new Hashtable() { { GamePropertyKey.MasterClientId, this.MasterClientId} };
|
||||
return this.LoadBalancingClient.OpSetPropertiesOfRoom(newProps, prevProps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player is in the room's list already and calls StorePlayer() if not.
|
||||
/// </summary>
|
||||
/// <param name="player">The new player - identified by ID.</param>
|
||||
/// <returns>False if the player could not be added (cause it was in the list already).</returns>
|
||||
public virtual bool AddPlayer(Player player)
|
||||
{
|
||||
if (!this.Players.ContainsKey(player.ID))
|
||||
{
|
||||
this.StorePlayer(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a player reference in the Players dictionary (no matter if it existed before or not).
|
||||
/// </summary>
|
||||
/// <param name="player">The Player instance to insert into the room.</param>
|
||||
public virtual Player StorePlayer(Player player)
|
||||
{
|
||||
this.Players[player.ID] = player;
|
||||
player.RoomReference = this;
|
||||
|
||||
// while initializing the room, the players are not guaranteed to be added in-order
|
||||
if (this.MasterClientId == 0 || player.ID < this.MasterClientId)
|
||||
{
|
||||
this.masterClientId = player.ID;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the player with given actorNumber (a.k.a. ID).
|
||||
/// Only useful when in a Room, as IDs are only valid per Room.
|
||||
/// </summary>
|
||||
/// <param name="id">ID to look for.</param>
|
||||
/// <returns>The player with the ID or null.</returns>
|
||||
public virtual Player GetPlayer(int id)
|
||||
{
|
||||
Player result = null;
|
||||
this.Players.TryGetValue(id, out result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove all current expected users from the server's Slot Reservation list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this operation can conflict with new/other users joining. They might be
|
||||
/// adding users to the list of expected users before or after this client called ClearExpectedUsers.
|
||||
///
|
||||
/// This room's expectedUsers value will update, when the server sends a successful update.
|
||||
///
|
||||
/// Internals: This methods wraps up setting the ExpectedUsers property of a room.
|
||||
/// </remarks>
|
||||
public void ClearExpectedUsers()
|
||||
{
|
||||
Hashtable props = new Hashtable();
|
||||
props[GamePropertyKey.ExpectedUsers] = new string[0];
|
||||
Hashtable expected = new Hashtable();
|
||||
expected[GamePropertyKey.ExpectedUsers] = this.ExpectedUsers;
|
||||
this.LoadBalancingClient.OpSetPropertiesOfRoom(props, expected);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Returns a summary of this Room instance as string.</summary>
|
||||
/// <returns>Summary of this Room instance.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount);
|
||||
}
|
||||
|
||||
/// <summary>Returns a summary of this Room instance as longer string, including Custom Properties.</summary>
|
||||
/// <returns>Summary of this Room instance.</returns>
|
||||
public new string ToStringFull()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.\ncustomProps: {5}", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount, this.CustomProperties.ToStringFull());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7f05b351233593247979ece22db7a9f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,261 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="RoomInfo.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2011 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This class resembles info about available rooms, as sent by the Master
|
||||
// server's lobby. Consider all values as readonly.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_4_7_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System.Collections;
|
||||
|
||||
#if UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A simplified room with just the info required to list and join, used for the room listing in the lobby.
|
||||
/// The properties are not settable (IsOpen, MaxPlayers, etc).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class resembles info about available rooms, as sent by the Master server's lobby.
|
||||
/// Consider all values as readonly. None are synced (only updated by events by server).
|
||||
/// </remarks>
|
||||
public class RoomInfo
|
||||
{
|
||||
/// <summary>Used internally in lobby, to mark rooms that are no longer listed (for being full, closed or hidden).</summary>
|
||||
protected internal bool removedFromList;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
private Hashtable customProperties = new Hashtable();
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected byte maxPlayers = 0;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected string[] expectedUsers;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected bool isOpen = true;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected bool isVisible = true;
|
||||
|
||||
/// <summary>Backing field for property. False unless the GameProperty is set to true (else it's not sent).</summary>
|
||||
protected bool autoCleanUp = true;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected string name;
|
||||
|
||||
/// <summary>Backing field for master client id (actorNumber). defined by server in room props and ev leave.</summary>
|
||||
protected internal int masterClientId;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected string[] propertiesListedInLobby;
|
||||
|
||||
/// <summary>Read-only "cache" of custom properties of a room. Set via Room.SetCustomProperties (not available for RoomInfo class!).</summary>
|
||||
/// <remarks>All keys are string-typed and the values depend on the game/application.</remarks>
|
||||
/// <see cref="Room.SetCustomProperties"/>
|
||||
public Hashtable CustomProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.customProperties;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The name of a room. Unique identifier for a room/match (per AppId + game-Version).</summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count of players currently in room. This property is overwritten by the Room class (used when you're in a Room).
|
||||
/// </summary>
|
||||
public int PlayerCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// State if the local client is already in the game or still going to join it on gameserver (in lobby: false).
|
||||
/// </summary>
|
||||
public bool IsLocalClientInside { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The limit of players for this room. This property is shown in lobby, too.
|
||||
/// If the room is full (players count == maxplayers), joining this room will fail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public byte MaxPlayers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.maxPlayers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room can be joined.
|
||||
/// This does not affect listing in a lobby but joining the room will fail if not open.
|
||||
/// If not open, the room is excluded from random matchmaking.
|
||||
/// Due to racing conditions, found matches might become closed even while you join them.
|
||||
/// Simply re-connect to master and find another.
|
||||
/// Use property "IsVisible" to not list the room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isOpen;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room is listed in its lobby.
|
||||
/// Rooms can be created invisible, or changed to invisible.
|
||||
/// To change if a room can be joined, use property: open.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a RoomInfo to be used in room listings in lobby.
|
||||
/// </summary>
|
||||
/// <param name="roomName">Name of the room and unique ID at the same time.</param>
|
||||
/// <param name="roomProperties">Properties for this room.</param>
|
||||
protected internal RoomInfo(string roomName, Hashtable roomProperties)
|
||||
{
|
||||
this.InternalCacheProperties(roomProperties);
|
||||
|
||||
this.name = roomName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes RoomInfo comparable (by name).
|
||||
/// </summary>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
RoomInfo otherRoomInfo = other as RoomInfo;
|
||||
return (otherRoomInfo != null && this.Name.Equals(otherRoomInfo.name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accompanies Equals, using the name's HashCode as return.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.name.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Returns most interesting room values as string.</summary>
|
||||
/// <returns>Summary of this RoomInfo instance.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount);
|
||||
}
|
||||
|
||||
/// <summary>Returns most interesting room values as string, including custom properties.</summary>
|
||||
/// <returns>Summary of this RoomInfo instance.</returns>
|
||||
public string ToStringFull()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.\ncustomProps: {5}", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount, this.customProperties.ToStringFull());
|
||||
}
|
||||
|
||||
/// <summary>Copies "well known" properties to fields (IsVisible, etc) and caches the custom properties (string-keys only) in a local hashtable.</summary>
|
||||
/// <param name="propertiesToCache">New or updated properties to store in this RoomInfo.</param>
|
||||
protected internal virtual void InternalCacheProperties(Hashtable propertiesToCache)
|
||||
{
|
||||
if (propertiesToCache == null || propertiesToCache.Count == 0 || this.customProperties.Equals(propertiesToCache))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check of this game was removed from the list. in that case, we don't
|
||||
// need to read any further properties
|
||||
// list updates will remove this game from the game listing
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.Removed))
|
||||
{
|
||||
this.removedFromList = (bool)propertiesToCache[GamePropertyKey.Removed];
|
||||
if (this.removedFromList)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the "well known" properties of the room, if available
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.MaxPlayers))
|
||||
{
|
||||
this.maxPlayers = (byte)propertiesToCache[GamePropertyKey.MaxPlayers];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.IsOpen))
|
||||
{
|
||||
this.isOpen = (bool)propertiesToCache[GamePropertyKey.IsOpen];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.IsVisible))
|
||||
{
|
||||
this.isVisible = (bool)propertiesToCache[GamePropertyKey.IsVisible];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.PlayerCount))
|
||||
{
|
||||
this.PlayerCount = (int)((byte)propertiesToCache[GamePropertyKey.PlayerCount]);
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.CleanupCacheOnLeave))
|
||||
{
|
||||
this.autoCleanUp = (bool)propertiesToCache[GamePropertyKey.CleanupCacheOnLeave];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.MasterClientId))
|
||||
{
|
||||
this.masterClientId = (int)propertiesToCache[GamePropertyKey.MasterClientId];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.PropsListedInLobby))
|
||||
{
|
||||
this.propertiesListedInLobby = propertiesToCache[GamePropertyKey.PropsListedInLobby] as string[];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey((byte)GamePropertyKey.ExpectedUsers))
|
||||
{
|
||||
this.expectedUsers = (string[])propertiesToCache[GamePropertyKey.ExpectedUsers];
|
||||
}
|
||||
|
||||
// merge the custom properties (from your application) to the cache (only string-typed keys will be kept)
|
||||
this.customProperties.MergeStringKeys(propertiesToCache);
|
||||
this.customProperties.StripKeysWithNullValues();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34e9ca7b04eb3424c915e47461a9ca43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,167 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="WebRpc.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2016 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This class wraps responses of a Photon WebRPC call, coming from a
|
||||
// third party web service.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_4_7_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using ExitGames.Client.Photon;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>Reads an operation response of a WebRpc and provides convenient access to most common values.</summary>
|
||||
/// <remarks>
|
||||
/// See method PhotonNetwork.WebRpc.<br/>
|
||||
/// Create a WebRpcResponse to access common result values.<br/>
|
||||
/// The operationResponse.OperationCode should be: OperationCode.WebRpc.<br/>
|
||||
/// </remarks>
|
||||
public class WebRpcResponse
|
||||
{
|
||||
/// <summary>Name of the WebRpc that was called.</summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>ReturnCode of the WebService that answered the WebRpc.</summary>
|
||||
/// <remarks>
|
||||
/// 1 is: "OK" for WebRPCs.<br/>
|
||||
/// -1 is: No ReturnCode by WebRpc service (check OperationResponse.ReturnCode).<br/>
|
||||
/// Other ReturnCodes are defined by the individual WebRpc and service.
|
||||
/// </remarks>
|
||||
public int ReturnCode { get; private set; }
|
||||
|
||||
/// <summary>Might be empty or null.</summary>
|
||||
public string DebugMessage { get; private set; }
|
||||
|
||||
/// <summary>Other key/values returned by the webservice that answered the WebRpc.</summary>
|
||||
public Dictionary<string, object> Parameters { get; private set; }
|
||||
|
||||
/// <summary>An OperationResponse for a WebRpc is needed to read it's values.</summary>
|
||||
public WebRpcResponse(OperationResponse response)
|
||||
{
|
||||
object value;
|
||||
response.Parameters.TryGetValue(ParameterCode.UriPath, out value);
|
||||
this.Name = value as string;
|
||||
|
||||
response.Parameters.TryGetValue(ParameterCode.WebRpcReturnCode, out value);
|
||||
this.ReturnCode = (value != null) ? (byte)value : -1;
|
||||
|
||||
response.Parameters.TryGetValue(ParameterCode.WebRpcParameters, out value);
|
||||
this.Parameters = value as Dictionary<string, object>;
|
||||
|
||||
response.Parameters.TryGetValue(ParameterCode.WebRpcReturnMessage, out value);
|
||||
this.DebugMessage = value as string;
|
||||
}
|
||||
|
||||
/// <summary>Turns the response into an easier to read string.</summary>
|
||||
/// <returns>String resembling the result.</returns>
|
||||
public string ToStringFull()
|
||||
{
|
||||
return string.Format("{0}={2}: {1} \"{3}\"", this.Name, SupportClass.DictionaryToString(this.Parameters), this.ReturnCode, this.DebugMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Optional flags to be used in Photon client SDKs with Op RaiseEvent and Op SetProperties.
|
||||
/// Introduced mainly for webhooks 1.2 to control behavior of forwarded HTTP requests.
|
||||
/// </summary>
|
||||
public class WebFlags
|
||||
{
|
||||
|
||||
public readonly static WebFlags Default = new WebFlags(0);
|
||||
public byte WebhookFlags;
|
||||
/// <summary>
|
||||
/// Indicates whether to forward HTTP request to web service or not.
|
||||
/// </summary>
|
||||
public bool HttpForward
|
||||
{
|
||||
get { return (WebhookFlags & HttpForwardConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= HttpForwardConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte) (WebhookFlags & ~(1 << 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte HttpForwardConst = 0x01;
|
||||
/// <summary>
|
||||
/// Indicates whether to send AuthCookie of actor in the HTTP request to web service or not.
|
||||
/// </summary>
|
||||
public bool SendAuthCookie
|
||||
{
|
||||
get { return (WebhookFlags & SendAuthCookieConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= SendAuthCookieConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte)(WebhookFlags & ~(1 << 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte SendAuthCookieConst = 0x02;
|
||||
/// <summary>
|
||||
/// Indicates whether to send HTTP request synchronously or asynchronously to web service.
|
||||
/// </summary>
|
||||
public bool SendSync
|
||||
{
|
||||
get { return (WebhookFlags & SendSyncConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= SendSyncConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte)(WebhookFlags & ~(1 << 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte SendSyncConst = 0x04;
|
||||
/// <summary>
|
||||
/// Indicates whether to send serialized game state in HTTP request to web service or not.
|
||||
/// </summary>
|
||||
public bool SendState
|
||||
{
|
||||
get { return (WebhookFlags & SendStateConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= SendStateConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte)(WebhookFlags & ~(1 << 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte SendStateConst = 0x08;
|
||||
|
||||
public WebFlags(byte webhookFlags)
|
||||
{
|
||||
WebhookFlags = webhookFlags;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e82402aea03f000428f5ca11fec7ecfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64d9415f03804bb40b359b53619181be
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Binary file not shown.
|
@ -0,0 +1,113 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3124388131362e24dbb79c27c2fc0a89
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 0
|
||||
Exclude Editor: 0
|
||||
Exclude Linux: 0
|
||||
Exclude Linux64: 0
|
||||
Exclude LinuxUniversal: 0
|
||||
Exclude OSXUniversal: 0
|
||||
Exclude WebGL: 0
|
||||
Exclude Win: 0
|
||||
Exclude Win64: 0
|
||||
Exclude iOS: 0
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARMv7
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Facebook: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Facebook: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Linux
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Standalone: LinuxUniversal
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 16bfdffb4c4f75240b7dd4720c995413
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2139bd8d6da096942a30073374f7f0ab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,37 @@
|
|||
#if UNITY_WEBGL
|
||||
|
||||
namespace ExitGames.Client.Photon
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
public class PingHttp : PhotonPing
|
||||
{
|
||||
private WWW webRequest;
|
||||
|
||||
public override bool StartPing(string address)
|
||||
{
|
||||
address = "https://" + address + "/photon/m/?ping&r=" + UnityEngine.Random.Range(0, 10000);
|
||||
Debug.Log("StartPing: " + address);
|
||||
this.webRequest = new WWW(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Done()
|
||||
{
|
||||
if (this.webRequest.isDone)
|
||||
{
|
||||
Successful = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
this.webRequest.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 88e56dcdd2ef1f942b8f19c74c4cf805
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
To use WebSockets with the Photon C# library, you need to import this folder into your project.
|
||||
|
||||
SocketWebTcpThread can be used in all cases where the Thread class is available
|
||||
SocketWebTcpCoroutine must be used for WebGL exports and when the Thread class is unavailable
|
||||
|
||||
WebSocket.cs is used in all exports
|
||||
websocket-sharp.dll is used when not exporting to a browser (and in Unity Editor)
|
||||
WebSocket.jslib is used for WebGL exports by Unity (and must be setup accordingly)
|
||||
|
||||
|
||||
A WebGL export from Unity will find and use these files internally.
|
||||
Any other project will have to setup a few things in code:
|
||||
|
||||
Define "WEBSOCKET" for your project to make the SocketWebTcp classes available.
|
||||
To make a connection by WebSocket, setup the PhotonPeer (LoadBalancingPeer, ChatPeer, etc) similar to this:
|
||||
|
||||
Debug.Log("WSS Setup");
|
||||
PhotonPeer.TransportProtocol = ConnectionProtocol.WebSocket; // or WebSocketSecure for a release
|
||||
PhotonPeer.SocketImplementationConfig[ConnectionProtocol.WebSocket] = typeof(SocketWebTcpThread);
|
||||
PhotonPeer.SocketImplementationConfig[ConnectionProtocol.WebSocketSecure] = typeof(SocketWebTcpThread);
|
||||
|
||||
//PhotonPeer.DebugOut = DebugLevel.INFO; // this would show some logs from the SocketWebTcp implementation
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 81339ade5de3e2d419b360cfde8630c1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,312 @@
|
|||
#if UNITY_WEBGL || UNITY_XBOXONE || WEBSOCKET
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SocketWebTcpCoroutine.cs" company="Exit Games GmbH">
|
||||
// Copyright (c) Exit Games GmbH. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using SupportClassPun = ExitGames.Client.Photon.SupportClass;
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon
|
||||
{
|
||||
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
|
||||
/// <summary>
|
||||
/// Yield Instruction to Wait for real seconds. Very important to keep connection working if Time.TimeScale is altered, we still want accurate network events
|
||||
/// </summary>
|
||||
public sealed class WaitForRealSeconds : CustomYieldInstruction
|
||||
{
|
||||
private readonly float _endTime;
|
||||
|
||||
public override bool keepWaiting
|
||||
{
|
||||
get { return _endTime > Time.realtimeSinceStartup; }
|
||||
}
|
||||
|
||||
public WaitForRealSeconds(float seconds)
|
||||
{
|
||||
_endTime = Time.realtimeSinceStartup + seconds;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
/// </summary>
|
||||
public class SocketWebTcpCoroutine : IPhotonSocket, IDisposable
|
||||
{
|
||||
private WebSocket sock;
|
||||
|
||||
private GameObject websocketConnectionObject;
|
||||
|
||||
/// <summary>Constructor. Checks if "expected" protocol matches.</summary>
|
||||
public SocketWebTcpCoroutine(PeerBase npeer) : base(npeer)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.INFO, "new SocketWebTcpCoroutine(). Server: " + this.ConnectAddress + " protocol: " + this.Protocol);
|
||||
}
|
||||
|
||||
switch (this.Protocol)
|
||||
{
|
||||
case ConnectionProtocol.WebSocket:
|
||||
break;
|
||||
case ConnectionProtocol.WebSocketSecure:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Protocol '" + this.Protocol + "' not supported by WebSocket");
|
||||
}
|
||||
|
||||
this.PollReceive = false;
|
||||
}
|
||||
|
||||
/// <summary>Connect the websocket (base checks if this was already connected).</summary>
|
||||
public override bool Connect()
|
||||
{
|
||||
bool baseOk = base.Connect();
|
||||
if (!baseOk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Connecting;
|
||||
|
||||
|
||||
if (this.websocketConnectionObject != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(this.websocketConnectionObject);
|
||||
}
|
||||
|
||||
this.websocketConnectionObject = new GameObject("websocketConnectionObject");
|
||||
MonoBehaviour mb = this.websocketConnectionObject.AddComponent<MonoBehaviourExt>();
|
||||
this.websocketConnectionObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
UnityEngine.Object.DontDestroyOnLoad(this.websocketConnectionObject);
|
||||
|
||||
|
||||
this.sock = new WebSocket(new Uri(this.ConnectAddress));
|
||||
// connecting the socket is off-loaded into the coroutine which we start now
|
||||
|
||||
mb.StartCoroutine(this.ReceiveLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Disconnect the websocket (no matter what it does right now).</summary>
|
||||
public override bool Disconnect()
|
||||
{
|
||||
if (this.State == PhotonSocketState.Disconnecting || this.State == PhotonSocketState.Disconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcpCoroutine.Disconnect()");
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnecting;
|
||||
if (this.sock != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
this.sock = null;
|
||||
}
|
||||
|
||||
if (this.websocketConnectionObject != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(this.websocketConnectionObject);
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnected;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Calls Disconnect.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disconnect();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Used by TPeer to send.</summary>
|
||||
public override PhotonSocketError Send(byte[] data, int length)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Connected)
|
||||
{
|
||||
return PhotonSocketError.Skipped;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "Sending: " + SupportClassPun.ByteArrayToString(data));
|
||||
}
|
||||
|
||||
this.sock.Send(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ConnectAddress + ". " + e.Message);
|
||||
|
||||
if (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
this.HandleException(StatusCode.Exception);
|
||||
}
|
||||
return PhotonSocketError.Exception;
|
||||
}
|
||||
|
||||
return PhotonSocketError.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Not used currently.</summary>
|
||||
public override PhotonSocketError Receive(out byte[] data)
|
||||
{
|
||||
data = null;
|
||||
return PhotonSocketError.NoData;
|
||||
}
|
||||
|
||||
/// <summary>Used by TPeer to receive.</summary>
|
||||
public IEnumerator ReceiveLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
|
||||
while (this.State == PhotonSocketState.Connecting && this.sock != null && !this.sock.Connected && this.sock.Error == null)
|
||||
{
|
||||
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
|
||||
yield return new WaitForRealSeconds(0.02f);
|
||||
#else
|
||||
float waittime = Time.realtimeSinceStartup + 0.2f;
|
||||
while (Time.realtimeSinceStartup < waittime) yield return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (this.sock == null || this.sock.Error != null)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread. Server: " + this.ConnectAddress + " Error: " + ((this.sock!=null)?this.sock.Error:"socket==null"));
|
||||
this.HandleException(StatusCode.ExceptionOnConnect);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
// connected
|
||||
this.State = PhotonSocketState.Connected;
|
||||
this.peerBase.OnConnect();
|
||||
|
||||
|
||||
byte[] inBuff = null;
|
||||
|
||||
// receiving
|
||||
while (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.sock.Error != null)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread (inside loop). Server: " + this.ConnectAddress + " Error: " + this.sock.Error);
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
inBuff = this.sock.Recv();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (inBuff == null || inBuff.Length == 0)
|
||||
{
|
||||
// nothing received. wait a bit, try again
|
||||
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
|
||||
yield return new WaitForRealSeconds(0.02f);
|
||||
#else
|
||||
float waittime = Time.realtimeSinceStartup + 0.02f;
|
||||
while (Time.realtimeSinceStartup < waittime) yield return 0;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if (inBuff.Length < 0)
|
||||
{
|
||||
// got disconnected (from remote or net)
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.HandleException(StatusCode.DisconnectByServer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "TCP << " + inBuff.Length + " = " + SupportClassPun.ByteArrayToString(inBuff));
|
||||
}
|
||||
|
||||
this.HandleReceivedDatagram(inBuff, inBuff.Length, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonoBehaviourExt : MonoBehaviour { }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02e6576dec44f344eb5d58b9dfe5bdc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,268 @@
|
|||
#if WEBSOCKET
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SocketWebTcpThread.cs" company="Exit Games GmbH">
|
||||
// Copyright (c) Exit Games GmbH. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
/// </summary>
|
||||
public class SocketWebTcpThread : IPhotonSocket, IDisposable
|
||||
{
|
||||
private WebSocket sock;
|
||||
|
||||
|
||||
/// <summary>Constructor. Checks if "expected" protocol matches.</summary>
|
||||
public SocketWebTcpThread(PeerBase npeer) : base(npeer)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.INFO, "new SocketWebTcpThread(). Server: " + this.ConnectAddress + " protocol: " + this.Protocol+ " State: " + this.State);
|
||||
}
|
||||
|
||||
switch (this.Protocol)
|
||||
{
|
||||
case ConnectionProtocol.WebSocket:
|
||||
break;
|
||||
case ConnectionProtocol.WebSocketSecure:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Protocol '" + this.Protocol + "' not supported by WebSocket");
|
||||
}
|
||||
|
||||
this.PollReceive = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Connect the websocket (base checks if this was already connected).</summary>
|
||||
public override bool Connect()
|
||||
{
|
||||
bool baseOk = base.Connect();
|
||||
if (!baseOk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Connecting;
|
||||
|
||||
Thread dns = new Thread(this.DnsAndConnect);
|
||||
dns.Name = "photon dns thread";
|
||||
dns.IsBackground = true;
|
||||
dns.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Internally used by this class to resolve the hostname to IP.</summary>
|
||||
internal void DnsAndConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
IPAddress ipAddress = IPhotonSocket.GetIpAddress(this.ServerAddress);
|
||||
if (ipAddress == null)
|
||||
{
|
||||
throw new ArgumentException("DNS failed to resolve for address: " + this.ServerAddress);
|
||||
}
|
||||
|
||||
this.AddressResolvedAsIpv6 = ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
|
||||
|
||||
|
||||
if (this.State != PhotonSocketState.Connecting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.sock = new WebSocket(new Uri(this.ConnectAddress));
|
||||
this.sock.Connect();
|
||||
|
||||
while (this.sock != null && !this.sock.Connected && this.sock.Error == null)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
|
||||
if (this.sock.Error != null)
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Exiting receive thread. Server: " + this.ConnectAddress + " Error: " + this.sock.Error);
|
||||
this.HandleException(StatusCode.ExceptionOnConnect);
|
||||
return;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Connected;
|
||||
this.peerBase.OnConnect();
|
||||
}
|
||||
catch (SecurityException se)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Connect() to '" + this.ConnectAddress + "' failed: " + se.ToString());
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.SecurityExceptionOnConnect);
|
||||
return;
|
||||
}
|
||||
catch (Exception se)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Connect() to '" + this.ConnectAddress + "' failed: " + se.ToString());
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnConnect);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread run = new Thread(new ThreadStart(this.ReceiveLoop));
|
||||
run.Name = "photon receive thread";
|
||||
run.IsBackground = true;
|
||||
run.Start();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Disconnect the websocket (no matter what it does right now).</summary>
|
||||
public override bool Disconnect()
|
||||
{
|
||||
if (this.State == PhotonSocketState.Disconnecting || this.State == PhotonSocketState.Disconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcpThread.Disconnect()");
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnecting;
|
||||
if (this.sock != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
this.sock = null;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnected;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Calls Disconnect.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disconnect();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Used by TPeer to send.</summary>
|
||||
public override PhotonSocketError Send(byte[] data, int length)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Connected)
|
||||
{
|
||||
return PhotonSocketError.Skipped;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "Sending: " + SupportClass.ByteArrayToString(data));
|
||||
}
|
||||
|
||||
this.sock.Send(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ConnectAddress + ". " + e.Message);
|
||||
|
||||
if (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
this.HandleException(StatusCode.Exception);
|
||||
}
|
||||
return PhotonSocketError.Exception;
|
||||
}
|
||||
|
||||
return PhotonSocketError.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Not used currently.</summary>
|
||||
public override PhotonSocketError Receive(out byte[] data)
|
||||
{
|
||||
data = null;
|
||||
return PhotonSocketError.NoData;
|
||||
}
|
||||
|
||||
/// <summary>Used by TPeer to receive.</summary>
|
||||
public void ReceiveLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
if (this.sock.Error != null)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread (inside loop). Server: " + this.ConnectAddress + " Error: " + this.sock.Error);
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
byte[] inBuff = this.sock.Recv();
|
||||
if (inBuff == null || inBuff.Length == 0)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
if (inBuff.Length < 0)
|
||||
{
|
||||
// got disconnected (from remote or net)
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.HandleException(StatusCode.DisconnectByServer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "TCP << " + inBuff.Length + " = " + SupportClass.ByteArrayToString(inBuff));
|
||||
}
|
||||
|
||||
this.HandleReceivedDatagram(inBuff, inBuff.Length, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
|
||||
this.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f621e51e5c8aead49bd766c4b959a859
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ff7e276bdb10f1a4f9bdbfeaafadf2f2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,155 @@
|
|||
#if UNITY_WEBGL || UNITY_XBOXONE || WEBSOCKET
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Authentication;
|
||||
#endif
|
||||
|
||||
|
||||
public class WebSocket
|
||||
{
|
||||
private Uri mUrl;
|
||||
|
||||
public WebSocket(Uri url)
|
||||
{
|
||||
mUrl = url;
|
||||
|
||||
string protocol = mUrl.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public void SendString(string str)
|
||||
{
|
||||
Send(Encoding.UTF8.GetBytes (str));
|
||||
}
|
||||
|
||||
public string RecvString()
|
||||
{
|
||||
byte[] retval = Recv();
|
||||
if (retval == null)
|
||||
return null;
|
||||
return Encoding.UTF8.GetString (retval);
|
||||
}
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketCreate (string url);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketState (int socketInstance);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SocketSend (int socketInstance, byte[] ptr, int length);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SocketRecv (int socketInstance, byte[] ptr, int length);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketRecvLength (int socketInstance);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SocketClose (int socketInstance);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketError (int socketInstance, byte[] ptr, int length);
|
||||
|
||||
int m_NativeRef = 0;
|
||||
|
||||
public void Send(byte[] buffer)
|
||||
{
|
||||
SocketSend (m_NativeRef, buffer, buffer.Length);
|
||||
}
|
||||
|
||||
public byte[] Recv()
|
||||
{
|
||||
int length = SocketRecvLength (m_NativeRef);
|
||||
if (length == 0)
|
||||
return null;
|
||||
byte[] buffer = new byte[length];
|
||||
SocketRecv (m_NativeRef, buffer, length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
m_NativeRef = SocketCreate (mUrl.ToString());
|
||||
|
||||
//while (SocketState(m_NativeRef) == 0)
|
||||
// yield return 0;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
SocketClose(m_NativeRef);
|
||||
}
|
||||
|
||||
public bool Connected
|
||||
{
|
||||
get { return SocketState(m_NativeRef) != 0; }
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get {
|
||||
const int bufsize = 1024;
|
||||
byte[] buffer = new byte[bufsize];
|
||||
int result = SocketError (m_NativeRef, buffer, bufsize);
|
||||
|
||||
if (result == 0)
|
||||
return null;
|
||||
|
||||
return Encoding.UTF8.GetString (buffer);
|
||||
}
|
||||
}
|
||||
#else
|
||||
WebSocketSharp.WebSocket m_Socket;
|
||||
Queue<byte[]> m_Messages = new Queue<byte[]>();
|
||||
bool m_IsConnected = false;
|
||||
string m_Error = null;
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
m_Socket = new WebSocketSharp.WebSocket(mUrl.ToString(), new string[] { "GpBinaryV16" });// modified by TS
|
||||
m_Socket.SslConfiguration.EnabledSslProtocols = m_Socket.SslConfiguration.EnabledSslProtocols | (SslProtocols)(3072| 768);
|
||||
m_Socket.OnMessage += (sender, e) => m_Messages.Enqueue(e.RawData);
|
||||
m_Socket.OnOpen += (sender, e) => m_IsConnected = true;
|
||||
m_Socket.OnError += (sender, e) => m_Error = e.Message + (e.Exception == null ? "" : " / " + e.Exception);
|
||||
m_Socket.ConnectAsync();
|
||||
}
|
||||
|
||||
public bool Connected { get { return m_IsConnected; } }// added by TS
|
||||
|
||||
|
||||
public void Send(byte[] buffer)
|
||||
{
|
||||
m_Socket.Send(buffer);
|
||||
}
|
||||
|
||||
public byte[] Recv()
|
||||
{
|
||||
if (m_Messages.Count == 0)
|
||||
return null;
|
||||
return m_Messages.Dequeue();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
m_Socket.Close();
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Error;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cfe55c94da5ac634db8d4ca3d1891173
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,116 @@
|
|||
var LibraryWebSockets = {
|
||||
$webSocketInstances: [],
|
||||
|
||||
SocketCreate: function(url)
|
||||
{
|
||||
var str = Pointer_stringify(url);
|
||||
var socket = {
|
||||
socket: new WebSocket(str, ['GpBinaryV16']),
|
||||
buffer: new Uint8Array(0),
|
||||
error: null,
|
||||
messages: []
|
||||
}
|
||||
socket.socket.binaryType = 'arraybuffer';
|
||||
socket.socket.onmessage = function (e) {
|
||||
// if (e.data instanceof Blob)
|
||||
// {
|
||||
// var reader = new FileReader();
|
||||
// reader.addEventListener("loadend", function() {
|
||||
// var array = new Uint8Array(reader.result);
|
||||
// socket.messages.push(array);
|
||||
// });
|
||||
// reader.readAsArrayBuffer(e.data);
|
||||
// }
|
||||
if (e.data instanceof ArrayBuffer)
|
||||
{
|
||||
var array = new Uint8Array(e.data);
|
||||
socket.messages.push(array);
|
||||
}
|
||||
};
|
||||
socket.socket.onclose = function (e) {
|
||||
if (e.code != 1000)
|
||||
{
|
||||
if (e.reason != null && e.reason.length > 0)
|
||||
socket.error = e.reason;
|
||||
else
|
||||
{
|
||||
switch (e.code)
|
||||
{
|
||||
case 1001:
|
||||
socket.error = "Endpoint going away.";
|
||||
break;
|
||||
case 1002:
|
||||
socket.error = "Protocol error.";
|
||||
break;
|
||||
case 1003:
|
||||
socket.error = "Unsupported message.";
|
||||
break;
|
||||
case 1005:
|
||||
socket.error = "No status.";
|
||||
break;
|
||||
case 1006:
|
||||
socket.error = "Abnormal disconnection.";
|
||||
break;
|
||||
case 1009:
|
||||
socket.error = "Data frame too large.";
|
||||
break;
|
||||
default:
|
||||
socket.error = "Error "+e.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var instance = webSocketInstances.push(socket) - 1;
|
||||
return instance;
|
||||
},
|
||||
|
||||
SocketState: function (socketInstance)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
return socket.socket.readyState;
|
||||
},
|
||||
|
||||
SocketError: function (socketInstance, ptr, bufsize)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
if (socket.error == null)
|
||||
return 0;
|
||||
var str = socket.error.slice(0, Math.max(0, bufsize - 1));
|
||||
writeStringToMemory(str, ptr, false);
|
||||
return 1;
|
||||
},
|
||||
|
||||
SocketSend: function (socketInstance, ptr, length)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
socket.socket.send (HEAPU8.buffer.slice(ptr, ptr+length));
|
||||
},
|
||||
|
||||
SocketRecvLength: function(socketInstance)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
if (socket.messages.length == 0)
|
||||
return 0;
|
||||
return socket.messages[0].length;
|
||||
},
|
||||
|
||||
SocketRecv: function (socketInstance, ptr, length)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
if (socket.messages.length == 0)
|
||||
return 0;
|
||||
if (socket.messages[0].length > length)
|
||||
return 0;
|
||||
HEAPU8.set(socket.messages[0], ptr);
|
||||
socket.messages = socket.messages.slice(1);
|
||||
},
|
||||
|
||||
SocketClose: function (socketInstance)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
socket.socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(LibraryWebSockets, '$webSocketInstances');
|
||||
mergeInto(LibraryManager.library, LibraryWebSockets);
|
|
@ -0,0 +1,34 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 21d8d0d3e9f68ae43975534c13f23964
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Facebook: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
websocket-sharp.dll built from https://github.com/sta/websocket-sharp.git, commit 869dfb09778de51081b0ae64bd2c3217cffe0699 on Aug 24, 2016.
|
||||
|
||||
websocket-sharp is provided under The MIT License as mentioned here: https://github.com/sta/websocket-sharp#license
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 004b47cc08839884586e6b91d620b418
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Binary file not shown.
|
@ -0,0 +1,30 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0fb606431450aa343839e85c42dd9660
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 83aeb43ebfe53ea4285ca591ed80efe4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Binary file not shown.
|
@ -0,0 +1,114 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8b3cc63923895354ca37992f6c87ab63
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 1
|
||||
Exclude Editor: 1
|
||||
Exclude Linux: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude LinuxUniversal: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude WebGL: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
Exclude iOS: 1
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: ARMv7
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Facebook: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Facebook: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Linux
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Standalone: LinuxUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"com.unity.2d.animation": "3.2.16",
|
||||
"com.unity.2d.pixel-perfect": "2.1.0",
|
||||
"com.unity.2d.psdimporter": "2.1.10",
|
||||
"com.unity.2d.sprite": "1.0.0",
|
||||
"com.unity.2d.spriteshape": "3.0.18",
|
||||
"com.unity.2d.tilemap": "1.0.0",
|
||||
"com.unity.collab-proxy": "1.14.12",
|
||||
"com.unity.ide.rider": "1.2.1",
|
||||
"com.unity.ide.visualstudio": "2.0.14",
|
||||
"com.unity.ide.vscode": "1.2.4",
|
||||
"com.unity.test-framework": "1.1.30",
|
||||
"com.unity.textmeshpro": "2.1.6",
|
||||
"com.unity.timeline": "1.2.18",
|
||||
"com.unity.ugui": "1.0.0",
|
||||
"com.unity.modules.ai": "1.0.0",
|
||||
"com.unity.modules.androidjni": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.assetbundle": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.cloth": "1.0.0",
|
||||
"com.unity.modules.director": "1.0.0",
|
||||
"com.unity.modules.imageconversion": "1.0.0",
|
||||
"com.unity.modules.imgui": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0",
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.physics2d": "1.0.0",
|
||||
"com.unity.modules.screencapture": "1.0.0",
|
||||
"com.unity.modules.terrain": "1.0.0",
|
||||
"com.unity.modules.terrainphysics": "1.0.0",
|
||||
"com.unity.modules.tilemap": "1.0.0",
|
||||
"com.unity.modules.ui": "1.0.0",
|
||||
"com.unity.modules.uielements": "1.0.0",
|
||||
"com.unity.modules.umbra": "1.0.0",
|
||||
"com.unity.modules.unityanalytics": "1.0.0",
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestaudio": "1.0.0",
|
||||
"com.unity.modules.unitywebrequesttexture": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestwww": "1.0.0",
|
||||
"com.unity.modules.vehicles": "1.0.0",
|
||||
"com.unity.modules.video": "1.0.0",
|
||||
"com.unity.modules.vr": "1.0.0",
|
||||
"com.unity.modules.wind": "1.0.0",
|
||||
"com.unity.modules.xr": "1.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,404 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"com.unity.2d.animation": {
|
||||
"version": "3.2.16",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.2d.common": "2.1.1",
|
||||
"com.unity.mathematics": "1.1.0",
|
||||
"com.unity.2d.sprite": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.uielements": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.common": {
|
||||
"version": "2.1.1",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.2d.sprite": "1.0.0",
|
||||
"com.unity.modules.uielements": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.path": {
|
||||
"version": "2.1.1",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.pixel-perfect": {
|
||||
"version": "2.1.0",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.psdimporter": {
|
||||
"version": "2.1.10",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.2d.common": "2.1.0",
|
||||
"com.unity.2d.animation": "3.2.14",
|
||||
"com.unity.2d.sprite": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.sprite": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.2d.spriteshape": {
|
||||
"version": "3.0.18",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.mathematics": "1.1.0",
|
||||
"com.unity.2d.common": "2.1.0",
|
||||
"com.unity.2d.path": "2.1.1"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.2d.tilemap": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.collab-proxy": {
|
||||
"version": "1.14.12",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ext.nunit": {
|
||||
"version": "1.0.6",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.rider": {
|
||||
"version": "1.2.1",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.test-framework": "1.1.1"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.visualstudio": {
|
||||
"version": "2.0.14",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.test-framework": "1.1.9"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.vscode": {
|
||||
"version": "1.2.4",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.mathematics": {
|
||||
"version": "1.1.0",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.test-framework": {
|
||||
"version": "1.1.30",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.ext.nunit": "1.0.6",
|
||||
"com.unity.modules.imgui": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.textmeshpro": {
|
||||
"version": "2.1.6",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.ugui": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.timeline": {
|
||||
"version": "1.2.18",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.modules.director": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ugui": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.ui": "1.0.0",
|
||||
"com.unity.modules.imgui": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.ai": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.androidjni": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.animation": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.assetbundle": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.audio": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.cloth": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.physics": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.director": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.imageconversion": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.imgui": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.jsonserialize": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.particlesystem": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.physics": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.physics2d": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.screencapture": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.imageconversion": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.subsystems": {
|
||||
"version": "1.0.0",
|
||||
"depth": 1,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.jsonserialize": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.terrain": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.terrainphysics": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.terrain": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.tilemap": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.physics2d": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.ui": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.uielements": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.imgui": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.umbra": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.unityanalytics": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.unitywebrequest": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.unitywebrequestassetbundle": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.assetbundle": "1.0.0",
|
||||
"com.unity.modules.unitywebrequest": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.unitywebrequestaudio": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.unitywebrequesttexture": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.imageconversion": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.unitywebrequestwww": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestaudio": "1.0.0",
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.assetbundle": "1.0.0",
|
||||
"com.unity.modules.imageconversion": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.vehicles": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.physics": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.video": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.ui": "1.0.0",
|
||||
"com.unity.modules.unitywebrequest": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.vr": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.jsonserialize": "1.0.0",
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.xr": "1.0.0"
|
||||
}
|
||||
},
|
||||
"com.unity.modules.wind": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.xr": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
"source": "builtin",
|
||||
"dependencies": {
|
||||
"com.unity.modules.physics": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0",
|
||||
"com.unity.modules.subsystems": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!11 &1
|
||||
AudioManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Volume: 1
|
||||
Rolloff Scale: 1
|
||||
Doppler Factor: 1
|
||||
Default Speaker Mode: 2
|
||||
m_SampleRate: 0
|
||||
m_DSPBufferSize: 1024
|
||||
m_VirtualVoiceCount: 512
|
||||
m_RealVoiceCount: 32
|
||||
m_SpatializerPlugin:
|
||||
m_AmbisonicDecoderPlugin:
|
||||
m_DisableAudio: 0
|
||||
m_VirtualizeEffects: 1
|
||||
m_RequestedDSPBufferSize: 1024
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue