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:
Texel 2023-01-24 04:03:23 -05:00 committed by Touhexel
parent 8ec6828fa0
commit 9b69003715
119 changed files with 15444 additions and 0 deletions

6
.vsconfig Normal file
View File

@ -0,0 +1,6 @@
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Workload.ManagedGame"
]
}

8
Assets/Runtime.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0763d456e1ae157498c954ce5f728954
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c948a1a65858f7947baef36c7dc71069
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8550d86f434d784e98b16c3530703d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a0346cc8c59a10f4a966240dd9e440b3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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 "";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e22b2b5dabe661b4498b020007dbb838
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bde9bcb8cf46e7e4090257fbff464d17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e772dac1e610df54fbbf25f1073012dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
using UnityEngine;
public class EnumFlagAttribute : PropertyAttribute {
public string enumName;
public EnumFlagAttribute() { }
public EnumFlagAttribute(string name) {
enumName = name;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c03bbb4c7afaf746ab43c4fbeb3ae9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,5 @@
using UnityEngine;
public class ReadOnlyAttribute : PropertyAttribute {
public ReadOnlyAttribute() { }
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2175955395688374191c019f19c355be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9bbd61e2d44aca241a19686e7dbb6203
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f87146a5f00172e4c8bde3d6fb4918df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91365f7329a827044a590282cb8a77c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b45bb8cb359039458f36fee5d02af4a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 488818778f517a047ad888c9ccfb7b5a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c9b2b5002413374ca9dd3d2f5d26bcf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 118662652baff8841bb52034c1795789
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8c1f784b32aefb84cad6d24f7d2a37b5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91b1fc7f0e237bb4e8fc167e80d4b8e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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"}
};
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3090fc43b95be2244909c218fbf35512
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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];
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4402087c29fe6f4888bd19ae6c229d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d974f1ce0926d5e4e9e6b1d5bccacc0e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0acc2d84cbeb940498c3e42bcfa41d67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
*/
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98ad96430aae9e241a4dedc659a0fa4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca2d3b1e793ff9643b979899bd8add03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ad49179d7bafd344d948ab9643c4fdad
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a99da8b191429648a69535f4bf2e0d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 41564653a5d28dc4491420399b220d74
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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");
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce8aa006a722b1048adbee6308ab38ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7857daa21c365d749b825ce14546bc09
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07421fd6321565a4e8aebdebfd61c79c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b45dd90f2a7976d4ab982b1f5d5986ad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8a4408e5f601f774eb5f3fb98b07f009
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee1c48ab6ff903f4c823e7cf48b423e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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");
}
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3aba55d840ae78459c990a41ed84f82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b0942472c9351047afc23b868b562f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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());
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f05b351233593247979ece22db7a9f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34e9ca7b04eb3424c915e47461a9ca43
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e82402aea03f000428f5ca11fec7ecfc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 64d9415f03804bb40b359b53619181be
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 16bfdffb4c4f75240b7dd4720c995413
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2139bd8d6da096942a30073374f7f0ab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88e56dcdd2ef1f942b8f19c74c4cf805
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 81339ade5de3e2d419b360cfde8630c1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02e6576dec44f344eb5d58b9dfe5bdc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f621e51e5c8aead49bd766c4b959a859
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ff7e276bdb10f1a4f9bdbfeaafadf2f2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cfe55c94da5ac634db8d4ca3d1891173
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);

View File

@ -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:

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 004b47cc08839884586e6b91d620b418
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 83aeb43ebfe53ea4285ca591ed80efe4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -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:

49
Packages/manifest.json Normal file
View File

@ -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"
}
}

404
Packages/packages-lock.json Normal file
View File

@ -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"
}
}
}
}

View File

@ -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