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

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: