reorganized into a package, added a namespace, added an assemblydefinition, attempt at ci for upm branch creation

upm
Anton Franzluebbers 2022-01-06 16:02:50 -05:00
commit 887d310053
29 changed files with 5247 additions and 0 deletions

8
Editor.meta Normal file
View File

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

8
Runtime.meta Normal file
View File

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

522
Runtime/NetworkManager.cs Normal file
View File

@ -0,0 +1,522 @@
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using System.Net;
namespace VelNetUnity
{
public class NetworkManager : MonoBehaviour
{
public enum MessageType
{
OTHERS = 0,
ALL = 1,
OTHERS_ORDERED = 2,
ALL_ORDERED = 3
};
public string host;
public int port;
#region private members
private TcpClient socketConnection;
private Socket udpSocket;
public bool udpConnected = false;
IPEndPoint RemoteEndPoint;
private Thread clientReceiveThread;
private Thread clientReceiveThreadUDP;
public int userid = -1;
public string room;
int messagesReceived = 0;
public GameObject playerPrefab;
public Dictionary<int, NetworkPlayer> players = new Dictionary<int, NetworkPlayer>();
public Action<NetworkPlayer> onJoinedRoom = delegate { };
public Action<NetworkPlayer> onPlayerJoined = delegate { };
public Action<NetworkPlayer> onPlayerLeft = delegate { };
public List<NetworkObject> prefabs = new List<NetworkObject>();
public NetworkObject[] sceneObjects;
public List<string> deletedSceneObjects = new List<string>();
public Dictionary<string, NetworkObject> objects = new Dictionary<string, NetworkObject>(); //maintains a list of all known objects on the server (ones that have ids)
NetworkPlayer masterPlayer = null;
#endregion
// Use this for initialization
public class Message
{
public int type;
public string text;
public int sender;
}
public List<Message> receivedMessages = new List<Message>();
void Start()
{
ConnectToTcpServer();
sceneObjects = FindObjectsOfType<NetworkObject>(); //add all local network objects
}
private void addMessage(Message m)
{
lock (receivedMessages)
{
//Debug.Log(messagesReceived++);
receivedMessages.Add(m);
}
}
private void Update()
{
lock (receivedMessages)
{
//the main thread, which can do Unity stuff
foreach (Message m in receivedMessages)
{
if (m.type == 0) //when you join the server
{
userid = m.sender;
Debug.Log("joined server");
//start the udp thread
clientReceiveThreadUDP = new Thread(new ThreadStart(ListenForDataUDP));
clientReceiveThreadUDP.IsBackground = true;
clientReceiveThreadUDP.Start();
}
if (m.type == 2)
{
//if this message is for me, that means I joined a new room...
if (userid == m.sender)
{
foreach (KeyValuePair<int, NetworkPlayer> kvp in players)
{
Destroy(kvp.Value.gameObject);
}
players.Clear(); //we clear the list, but will recreate as we get messages from people in our room
if (m.text != "")
{
NetworkPlayer player = Instantiate<GameObject>(playerPrefab).GetComponent<NetworkPlayer>();
player.isLocal = true;
player.userid = m.sender;
players.Add(userid, player);
player.room = m.text;
player.manager = this;
onJoinedRoom(player);
}
}
else //not for me, a player is joining or leaving
{
NetworkPlayer me = players[userid];
if (me.room != m.text)
{
//we got a left message, kill it
//change ownership of all objects to master
foreach (KeyValuePair<string, NetworkObject> kvp in objects)
{
if (kvp.Value.owner == players[m.sender]) //the owner is the player that left
{
if (me.isLocal && me == masterPlayer) //I'm the local master player, so can take ownership immediately
{
me.takeOwnership(kvp.Key);
}
else if (players[m.sender] == masterPlayer) //the master player left, so everyone should set the owner null (we should get a new master shortly)
{
kvp.Value.owner = null;
}
}
}
Destroy(players[m.sender].gameObject);
players.Remove(m.sender);
}
else
{
//we got a join mesage, create it
NetworkPlayer player = Instantiate<GameObject>(playerPrefab).GetComponent<NetworkPlayer>();
player.isLocal = false;
player.room = m.text;
player.userid = m.sender;
player.manager = this;
players.Add(m.sender, player);
onPlayerJoined(player);
}
}
}
if (m.type == 3) //generic message
{
players[m.sender]?.handleMessage(m);
}
if (m.type == 4) //change master player (this should only happen when the first player joins or if the master player leaves)
{
if (masterPlayer == null)
{
masterPlayer = players[m.sender];
//no master player yet, add the scene objects
for (int i = 0; i < sceneObjects.Length; i++)
{
sceneObjects[i].networkId = -1 + "-" + i;
sceneObjects[i].owner = masterPlayer;
sceneObjects[i].isSceneObject = true; //needed for special handling when deleted
objects.Add(sceneObjects[i].networkId, sceneObjects[i]);
}
}
else
{
masterPlayer = players[m.sender];
}
masterPlayer.setAsMasterPlayer();
//master player should take over any objects that do not have an owner
foreach (KeyValuePair<string, NetworkObject> kvp in objects)
{
if (kvp.Value.owner == null)
{
kvp.Value.owner = masterPlayer;
}
}
}
messageReceived(m);
}
receivedMessages.Clear();
}
}
private void OnApplicationQuit()
{
socketConnection.Close();
}
public Action<string, int> joinedRoom = delegate { };
public Action<Message> messageReceived = delegate { };
public Action<string, int> loggedIn = delegate { };
public Action<string[], int> roomsReceived = delegate { };
public bool connected = false;
/// <summary>
/// Setup socket connection.
/// </summary>
private void ConnectToTcpServer()
{
try
{
clientReceiveThread = new Thread(new ThreadStart(ListenForData));
clientReceiveThread.IsBackground = true;
clientReceiveThread.Start();
}
catch (Exception e)
{
Debug.Log("On client connect exception " + e);
}
}
void handleMessage(string s) //this parses messages from the server, and adds them to a queue to be processed on the main thread
{
Message m = new Message();
string[] sections = s.Split(':');
if (sections.Length > 0)
{
int type = int.Parse(sections[0]);
switch (type)
{
case 0: //logged in message
{
if (sections.Length > 1)
{
m.type = type;
m.sender = int.Parse(sections[1]);
m.text = "";
addMessage(m);
}
break;
}
case 1: //room info message
{
break;
}
case 2: //joined room message
{
if (sections.Length > 2)
{
m.type = 2;
int user_id = int.Parse(sections[1]);
m.sender = user_id;
string new_room = sections[2];
m.text = new_room;
addMessage(m);
}
break;
}
case 3: //text message
{
if (sections.Length > 2)
{
m.type = 3;
m.sender = int.Parse(sections[1]);
m.text = sections[2];
addMessage(m);
}
break;
}
case 4: //change master client
{
if (sections.Length > 1)
{
m.type = 4;
m.sender = int.Parse(sections[1]);
addMessage(m);
}
break;
}
}
}
}
/// <summary>
/// Runs in background clientReceiveThread; Listens for incomming data.
/// </summary>
private void ListenForData()
{
connected = true;
try
{
socketConnection = new TcpClient(host, port);
socketConnection.NoDelay = true;
Byte[] bytes = new Byte[1024];
string partialMessage = "";
while (true)
{
// Get a stream object for reading
using (NetworkStream stream = socketConnection.GetStream())
{
int length;
// Read incomming stream into byte arrary.
while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
{
var incommingData = new byte[length];
Array.Copy(bytes, 0, incommingData, 0, length);
// Convert byte array to string message.
string serverMessage = Encoding.ASCII.GetString(incommingData);
string[] sections = serverMessage.Split('\n');
if (sections.Length > 1)
{
lock (receivedMessages)
{
for (int i = 0; i < sections.Length - 1; i++)
{
if (i == 0)
{
handleMessage(partialMessage + sections[0]);
partialMessage = "";
}
else
{
handleMessage(sections[i]);
}
}
}
}
partialMessage = partialMessage + sections[sections.Length - 1];
}
}
}
}
catch (Exception socketException)
{
Debug.Log("Socket exception: " + socketException);
}
connected = false;
}
private void ListenForDataUDP()
{
//I don't yet have a UDP connection
try
{
var addresses = Dns.GetHostAddresses(host);
Debug.Assert(addresses.Length > 0);
RemoteEndPoint = new IPEndPoint(addresses[0], port);
udpSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
udpConnected = false;
byte[] buffer = new byte[1024];
while (true)
{
string welcome = userid + ":0:Hello";
byte[] data = Encoding.ASCII.GetBytes(welcome);
udpSocket.SendTo(data, data.Length, SocketFlags.None, RemoteEndPoint);
if (udpSocket.Available == 0)
{
Thread.Sleep(100);
Debug.Log("Waiting for UDP response");
}
else
{
break;
}
}
udpConnected = true;
while (true)
{
int numReceived = udpSocket.Receive(buffer);
string message = Encoding.UTF8.GetString(buffer, 0, numReceived);
string[] sections = message.Split(':');
if (sections[0] == "0")
{
Debug.Log("UDP connected");
}
if (sections[0] == "3")
{
handleMessage(message);
}
}
}
catch (Exception socketException)
{
Debug.Log("Socket exception: " + socketException);
}
}
private void SendUdpMessage(string message)
{
if (udpSocket == null || !udpConnected)
{
return;
}
byte[] data = Encoding.UTF8.GetBytes(message);
//Debug.Log("Attempting to send: " + message);
udpSocket.SendTo(data, data.Length, SocketFlags.None, RemoteEndPoint);
}
/// <summary>
/// Send message to server using socket connection.
/// </summary>
private void SendNetworkMessage(string clientMessage)
{
if (socketConnection == null)
{
return;
}
try
{
// Get a stream object for writing.
NetworkStream stream = socketConnection.GetStream();
if (stream.CanWrite)
{
// Convert string message to byte array.
clientMessage = clientMessage + "\n"; //append a new line to delineate the message
byte[] clientMessageAsByteArray = Encoding.ASCII.GetBytes(clientMessage);
// Write byte array to socketConnection stream.
stream.Write(clientMessageAsByteArray, 0, clientMessageAsByteArray.Length);
}
}
catch (SocketException socketException)
{
Debug.Log("Socket exception: " + socketException);
}
}
public void login(string username, string password)
{
SendNetworkMessage("0:" + username + ":" + password);
}
public void join(string roomname)
{
SendNetworkMessage("2:" + roomname);
}
public void leave()
{
SendNetworkMessage("2:-1");
}
public void sendTo(MessageType type, string message, bool reliable = true)
{
if (reliable)
{
SendNetworkMessage("3:" + (int)type + ":" + message);
}
else
{
SendUdpMessage(userid + ":3:" + (int)type + ":" + message);
}
}
public void sendToGroup(string group, string message, bool reliable = true)
{
if (reliable)
{
SendNetworkMessage("4:" + group + ":" + message);
}
else
{
SendUdpMessage(userid + ":4:" + group + ":" + message);
}
}
//changes the designated group that sendto(4) will go to
public void setupMessageGroup(string groupname, int[] userids)
{
SendNetworkMessage("5:" + groupname + ":" + String.Join(":", userids));
}
public void deleteNetworkObject(string networkId)
{
if (objects.ContainsKey(networkId))
{
NetworkObject obj = objects[networkId];
if (obj.isSceneObject)
{
deletedSceneObjects.Add(networkId);
}
Destroy(obj.gameObject);
objects.Remove(networkId);
}
}
}
}

View File

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

16
Runtime/NetworkObject.cs Normal file
View File

@ -0,0 +1,16 @@
using UnityEngine;
namespace VelNetUnity
{
/// <summary>
/// This is a base class for all objects that a player can instantiated/owned
/// </summary>
public abstract class NetworkObject : MonoBehaviour
{
public NetworkPlayer owner;
public string networkId; //this is forged from the combination of the creator's id (-1 in the case of a scene object) and an object id, so it's always unique for a room
public string prefabName; //this may be empty if it's not a prefab (scene object)
public bool isSceneObject = false;
public abstract void handleMessage(string identifier, byte[] message);
}
}

View File

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

219
Runtime/NetworkPlayer.cs Normal file
View File

@ -0,0 +1,219 @@
using System.Collections.Generic;
using UnityEngine;
using System;
namespace VelNetUnity
{
[RequireComponent(typeof(NetworkObject))]
public class NetworkPlayer : MonoBehaviour
{
public NetworkObject myObject;
public int userid;
public string username;
public string room;
public NetworkManager manager;
public bool isLocal = false;
public int lastObjectId = 0; //for instantiation
private bool isMaster = false;
private void Start()
{
myObject.owner = this;
manager = FindObjectOfType<NetworkManager>();
manager.onPlayerJoined += handlePlayerJoined;
}
public void handlePlayerJoined(NetworkPlayer player)
{
//if this is the local player, go through the objects that I own, and send instantiation messages for the ones that have prefab names
if (isLocal)
{
foreach (KeyValuePair<string, NetworkObject> kvp in manager.objects)
{
if (kvp.Value.owner == this && kvp.Value.prefabName != "")
{
manager.sendTo(NetworkManager.MessageType.OTHERS, "7," + kvp.Value.networkId + "," + kvp.Value.prefabName);
}
}
if (isMaster)
{
//send a list of scene object ids when someone joins
sendSceneUpdate();
}
}
}
public void handleMessage(NetworkManager.Message m)
{
//these are generally things that come from the "owner" and should be enacted locally, where appropriate
//we need to parse the message
//types of messages
string[] messages = m.text.Split(';'); //messages are split by ;
for (int i = 0; i < messages.Length; i++)
{
//individual message parameters separated by comma
string[] sections = messages[i].Split(',');
switch (sections[0])
{
case "1": //update my object's data
{
string identifier = sections[1];
byte[] message = Convert.FromBase64String(sections[2]);
myObject.handleMessage(identifier, message);
break;
}
case "5": //sync update for an object I may own
{
string objectKey = sections[1];
string identifier = sections[2];
string syncMessage = sections[3];
byte[] messageBytes = Convert.FromBase64String(syncMessage);
if (manager.objects.ContainsKey(objectKey))
{
if (manager.objects[objectKey].owner == this)
{
manager.objects[objectKey].handleMessage(identifier, messageBytes);
}
}
break;
}
case "6": //I'm trying to take ownership of an object
{
string networkId = sections[1];
if (manager.objects.ContainsKey(networkId))
{
manager.objects[networkId].owner = this;
}
break;
}
case "7": //I'm trying to instantiate an object
{
string networkId = sections[1];
string prefabName = sections[2];
if (manager.objects.ContainsKey(networkId))
{
break; //we already have this one, ignore
}
NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName);
if (temp != null)
{
NetworkObject instance = Instantiate<NetworkObject>(temp);
instance.networkId = networkId;
instance.prefabName = prefabName;
instance.owner = this;
manager.objects.Add(instance.networkId, instance);
}
break;
}
case "8": //I'm trying to destroy a gameobject I own
{
string networkId = sections[1];
manager.deleteNetworkObject(networkId);
break;
}
case "9": //deleted scene objects
{
for (int k = 1; k < sections.Length; k++)
{
manager.deleteNetworkObject(sections[k]);
}
break;
}
}
}
}
public void setAsMasterPlayer()
{
isMaster = true;
//if I'm master, I'm now responsible for updating all scene objects
//FindObjectsOfType<NetworkObject>();
}
public void sendGroupMessage(NetworkObject obj, string group, string identifier, byte[] data, bool reliable = true)
{
if (obj == myObject)
{
manager.sendToGroup(group, "1," + identifier + "," + Convert.ToBase64String(data), reliable);
}
else
{
manager.sendToGroup(group, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable);
}
}
public void sendMessage(NetworkObject obj, string identifier, byte[] data, bool reliable = true)
{
if (obj == myObject)
{
manager.sendTo(NetworkManager.MessageType.OTHERS, "1," + identifier + "," + Convert.ToBase64String(data), reliable);
}
else
{
manager.sendTo(NetworkManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable);
}
}
public NetworkObject networkInstantiate(string prefabName)
{
if (!isLocal)
{
return null; //must be the local player to call instantiate
}
string networkId = userid + "-" + lastObjectId++;
NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName);
if (temp != null)
{
NetworkObject instance = Instantiate<NetworkObject>(temp);
instance.networkId = networkId;
instance.prefabName = prefabName;
instance.owner = this;
manager.objects.Add(instance.networkId, instance);
manager.sendTo(NetworkManager.MessageType.OTHERS, "7," + networkId + "," + prefabName); //only sent to others, as I already instantiated this. Nice that it happens immediately.
return instance;
}
return null;
}
public void networkDestroy(string networkId)
{
if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; //must be the local owner of the object to destroy it
manager.sendTo(NetworkManager.MessageType.ALL_ORDERED, "8," + networkId); //send to all, which will make me delete as well
}
public void takeOwnership(string networkId)
{
if (!manager.objects.ContainsKey(networkId) || !isLocal) return; //must exist and be the the local player
manager.objects[networkId].owner = this; //immediately successful
manager.sendTo(NetworkManager.MessageType.ALL_ORDERED, "6," + networkId); //must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome.
}
public void sendSceneUpdate()
{
manager.sendTo(NetworkManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects));
}
}
}

View File

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

8
Runtime/Util.meta Normal file
View File

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

View File

@ -0,0 +1,80 @@
using System;
using System.Collections;
using UnityEngine;
namespace VelNetUnity
{
/// <summary>
/// A simple class that will sync the position and rotation of a network object
/// </summary>
public class SyncTransform : NetworkObject
{
public Vector3 targetPosition;
public Quaternion targetRotation;
public byte[] getSyncMessage()
{
float[] data = new float[7];
for (int i = 0; i < 3; i++)
{
data[i] = transform.position[i];
data[i + 3] = transform.rotation[i];
}
data[6] = transform.rotation[3];
byte[] toReturn = new byte[sizeof(float) * data.Length];
Buffer.BlockCopy(data, 0, toReturn, 0, toReturn.Length);
return toReturn;
}
public override void handleMessage(string identifier, byte[] message)
{
switch (identifier)
{
case "s":
float[] data = new float[7];
Buffer.BlockCopy(message, 0, data, 0, message.Length);
for (int i = 0; i < 3; i++)
{
targetPosition[i] = data[i];
targetRotation[i] = data[i + 3];
}
targetRotation[3] = data[6];
break;
}
}
// Start is called before the first frame update
void Start()
{
StartCoroutine(syncBehavior());
}
IEnumerator syncBehavior()
{
while (true)
{
if (owner != null && owner.isLocal)
{
owner.sendMessage(this, "s", getSyncMessage());
}
yield return new WaitForSeconds(.1f);
}
}
// Update is called once per frame
private void Update()
{
if (owner != null && !owner.isLocal)
{
transform.position = Vector3.Lerp(transform.position, targetPosition, .1f);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f);
}
}
}
}

View File

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

View File

@ -0,0 +1,83 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!244 &-1719467165466355418
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: 6362d3ceb713f4c3fa42448a3e29217e
m_EffectName: Dissonance Echo Cancellation
m_MixLevel: a240a2d1f057e4cf9bbc59f6cfbec367
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!241 &24100000
AudioMixerController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: VelAudioMixer
m_OutputGroup: {fileID: 0}
m_MasterGroup: {fileID: 24300002}
m_Snapshots:
- {fileID: 24500006}
m_StartSnapshot: {fileID: 24500006}
m_SuspendThreshold: -80
m_EnableSuspend: 1
m_UpdateMode: 1
m_ExposedParameters: []
m_AudioMixerGroupViews:
- guids:
- b8e40716c8c1442ab9ef14e149b2423c
name: View
m_CurrentViewIndex: 0
m_TargetSnapshot: {fileID: 24500006}
--- !u!243 &24300002
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Master
m_AudioMixer: {fileID: 24100000}
m_GroupID: b8e40716c8c1442ab9ef14e149b2423c
m_Children: []
m_Volume: 3d9590ea0314643bd94e1616b88508ef
m_Pitch: 31f1ddadf3a5d47caab0e64b81ced2ae
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: 24400004}
- {fileID: -1719467165466355418}
m_UserColorIndex: 0
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!244 &24400004
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: f79719d1f394d4b4783ede937d403602
m_EffectName: Attenuation
m_MixLevel: 8d97d9fd1e8d446eb9244b5dead78e90
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!245 &24500006
AudioMixerSnapshotController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Snapshot
m_AudioMixer: {fileID: 24100000}
m_SnapshotID: 86ea5b9fedb76448594a5ab2119e34a1
m_FloatValues: {}
m_TransitionOverrides: {}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fa1da19d935e241119cdd522ceae772c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 24100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,109 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dissonance;
using Dissonance.Extensions;
using Dissonance.Networking;
using UnityEngine;
public class VelCommsNetwork : MonoBehaviour, ICommsNetwork
{
public ConnectionStatus Status
{
get
{
return manager.connected?ConnectionStatus.Connected:ConnectionStatus.Disconnected;
}
}
public NetworkMode Mode
{
get
{
return NetworkMode.Client;
}
}
public event Action<NetworkMode> ModeChanged;
public event Action<string, CodecSettings> PlayerJoined;
public event Action<string> PlayerLeft;
public event Action<VoicePacket> VoicePacketReceived;
public event Action<TextMessage> TextPacketReceived;
public event Action<string> PlayerStartedSpeaking;
public event Action<string> PlayerStoppedSpeaking;
public event Action<RoomEvent> PlayerEnteredRoom;
public event Action<RoomEvent> PlayerExitedRoom;
ConnectionStatus _status = ConnectionStatus.Disconnected;
CodecSettings initSettings;
public string dissonanceId;
public DissonanceComms comms;
public NetworkManager manager;
public Action<ArraySegment<byte>> voiceQueued = delegate { }; //listen to this if you want to send voice
public void Initialize(string playerName, Rooms rooms, PlayerChannels playerChannels, RoomChannels roomChannels, CodecSettings codecSettings)
{
dissonanceId = playerName;
initSettings = codecSettings;
comms.ResetMicrophoneCapture();
}
public void voiceReceived(string sender,byte[] data)
{
uint sequenceNumber = BitConverter.ToUInt32(data, 0);
VoicePacket vp = new VoicePacket(sender, ChannelPriority.Default, 1, true, new ArraySegment<byte>(data,4,data.Length-4), sequenceNumber);
VoicePacketReceived(vp);
}
public void SendText(string data, ChannelType recipientType, string recipientId)
{
Debug.Log("sending text");
}
public void SendVoice(ArraySegment<byte> data)
{
voiceQueued(data);
}
// Start is called before the first frame update
void Start()
{
_status = ConnectionStatus.Connected;
comms = GetComponent<DissonanceComms>();
}
public void playerJoined(string id)
{
Debug.Log("dissonance player joined");
PlayerJoined(id, initSettings);
RoomEvent re = new RoomEvent();
re.Joined = true;
re.Room = "Global";
re.PlayerName = id;
PlayerEnteredRoom(re);
}
public void playerLeft(string id)
{
RoomEvent re = new RoomEvent();
re.Joined = false;
re.Room = "Global";
re.PlayerName = id;
PlayerExitedRoom(re);
PlayerLeft(id);
}
public void playerStartedSpeaking(string id)
{
PlayerStartedSpeaking(id);
}
public void playerStoppedSpeaking(string id)
{
PlayerStoppedSpeaking(id);
}
}

View File

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

View File

@ -0,0 +1,73 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class NetworkGUI : MonoBehaviour
{
public NetworkManager networkManager;
public InputField userInput;
public InputField sendInput;
public InputField roomInput;
public Text messages;
public List<string> messageBuffer;
public Dropdown microphones;
Dissonance.DissonanceComms comms;
public void handleSend()
{
if(sendInput.text != "")
{
networkManager.sendTo(NetworkManager.MessageType.OTHERS,sendInput.text);
}
}
public void handleLogin()
{
if(userInput.text != "")
{
networkManager.login(userInput.text, "nopass");
}
}
public void handleJoin()
{
if(roomInput.text != "")
{
networkManager.join(roomInput.text);
}
}
// Start is called before the first frame update
void Start()
{
comms = GameObject.FindObjectOfType<Dissonance.DissonanceComms>();
microphones.AddOptions(new List<string>(Microphone.devices));
networkManager.messageReceived += (m) => {
string s = m.type + ":" + m.sender +":" + m.text;
messageBuffer.Add(s);
messages.text = "";
if(messageBuffer.Count > 10)
{
messageBuffer.RemoveAt(0);
}
for(int i = 0; i < messageBuffer.Count; i++)
{
messages.text = messages.text + messageBuffer[i] + "\n";
}
};
}
public void handleMicrophoneSelection()
{
comms.MicrophoneName = microphones.options[microphones.value].text;
}
// Update is called once per frame
void Update()
{
}
}

View File

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

View File

@ -0,0 +1,244 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Dissonance;
using System.Text;
public class PlayerController : NetworkObject, Dissonance.IDissonancePlayer
{
VelCommsNetwork comms;
bool isSpeaking = false;
uint lastAudioId = 0;
public string dissonanceID="";
//required by dissonance for spatial audio
public string PlayerId => dissonanceID;
public Vector3 Position => transform.position;
public Quaternion Rotation => transform.rotation;
public NetworkPlayerType Type => this.owner.isLocal ? NetworkPlayerType.Local : NetworkPlayerType.Remote;
public bool IsTracking => true;
public Vector3 targetPosition;
public Quaternion targetRotation;
public List<int> closePlayers = new List<int>();
public byte[] getSyncMessage()
{
float[] data = new float[7];
for (int i = 0; i < 3; i++)
{
data[i] = transform.position[i];
data[i + 3] = transform.rotation[i];
}
data[6] = transform.rotation[3];
byte[] toReturn = new byte[sizeof(float) * data.Length];
Buffer.BlockCopy(data, 0, toReturn, 0, toReturn.Length);
return toReturn;
}
public override void handleMessage(string identifier, byte[] message)
{
switch (identifier)
{
case "s": //sync message
float[] data = new float[7];
Buffer.BlockCopy(message, 0, data, 0, message.Length);
for (int i = 0; i < 3; i++)
{
targetPosition[i] = data[i];
targetRotation[i] = data[i + 3];
}
targetRotation[3] = data[6];
break;
case "a": //audio data
{
if (isSpeaking)
{
comms.voiceReceived(dissonanceID, message);
}
break;
}
case "d": //dissonance id (player joined)
{
if (dissonanceID == "") //I don't have this yet
{
dissonanceID = Encoding.UTF8.GetString(message);
//tell the comms network that this player joined the channel
comms.playerJoined(dissonanceID); //tell dissonance
comms.comms.TrackPlayerPosition(this); //tell dissonance to track the remote player
}
break;
}
case "x": //speaking state
{
if (message[0]==0)
{
comms.playerStoppedSpeaking(dissonanceID);
isSpeaking = false;
}
else
{
comms.playerStartedSpeaking(dissonanceID);
isSpeaking = true;
}
break;
}
}
}
// Start is called before the first frame update
void Start()
{
//handle dissonance stuff
comms = GameObject.FindObjectOfType<VelCommsNetwork>();
if(comms != null)
{
if (owner.isLocal)
{
setDissonanceID(comms.dissonanceId);
comms.voiceQueued += sendVoiceData;
//we also need to know when other players join, so we can send the dissonance ID again
owner.manager.onPlayerJoined += (player) =>
{
byte[] b = Encoding.UTF8.GetBytes(dissonanceID);
owner.sendMessage(this, "d", b);
};
owner.manager.setupMessageGroup("close", closePlayers.ToArray());
}
}
if (owner.isLocal)
{
StartCoroutine(syncBehavior());
}
}
void sendVoiceData(ArraySegment<byte> data)
{
//need to send it
if(owner != null && owner.isLocal)
{
byte[] toSend = new byte[data.Count+4];
byte[] lastAudioIdBytes = BitConverter.GetBytes(lastAudioId++);
Buffer.BlockCopy(lastAudioIdBytes, 0, toSend, 0, 4);
Buffer.BlockCopy(data.Array, data.Offset, toSend, 4, data.Count);
owner.sendGroupMessage(this,"close", "a", toSend, false); //send voice data unreliably
}
}
public void setDissonanceID(string id) //this sort of all initializes dissonance
{
dissonanceID = id;
byte[] b = Encoding.UTF8.GetBytes(dissonanceID);
owner.sendMessage(this, "d", b);
comms.comms.TrackPlayerPosition(this);
}
void voiceInitialized(string id)
{
dissonanceID = id;
}
void OnDestroy()
{
comms.playerLeft(dissonanceID);
}
IEnumerator syncBehavior()
{
while (true)
{
owner.sendMessage(this, "s", getSyncMessage());
yield return new WaitForSeconds(.1f);
}
}
// Update is called once per frame
void Update()
{
if (owner != null && owner.isLocal) {
PlayerController[] players = GameObject.FindObjectsOfType<PlayerController>();
bool shouldUpdate = false;
for (int i = 0; i < players.Length; i++)
{
if (players[i] == this) { continue; }
float dist = Vector3.Distance(players[i].transform.position, this.transform.position);
if (dist < 2 && !closePlayers.Contains(players[i].owner.userid))
{
closePlayers.Add(players[i].owner.userid);
shouldUpdate = true;
}
else if(dist >=2 && closePlayers.Contains(players[i].owner.userid))
{
closePlayers.Remove(players[i].owner.userid);
shouldUpdate = true;
}
}
if (shouldUpdate)
{
owner.manager.setupMessageGroup("close", closePlayers.ToArray());
}
}
if (owner != null && !owner.isLocal)
{
transform.position = Vector3.Lerp(transform.position, targetPosition, .1f);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f);
}
else if(owner != null && owner.isLocal)
{
//handle dissonance comms
//if we're not speaking, and the comms say we are, send a speaking event, which will be received on other network players and sent to their comms accordingly
if (comms.comms.FindPlayer(dissonanceID).IsSpeaking != isSpeaking) //unfortunately, there does not seem to be an event for this
{
isSpeaking = !isSpeaking;
byte[] toSend = new byte[1];
toSend[0] = isSpeaking ? (byte)1 : (byte)0;
owner.sendMessage(this, "x", toSend);
if (!isSpeaking)
{
lastAudioId = 0;
}
}
Vector3 movement = new Vector3();
movement.x += Input.GetAxis("Horizontal");
movement.y += Input.GetAxis("Vertical");
movement.z = 0;
transform.Translate(movement * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Space))
{
owner.networkInstantiate("TestNetworkedGameObject");
}
if (Input.GetKeyDown(KeyCode.BackQuote))
{
foreach(KeyValuePair<string,NetworkObject> kvp in owner.manager.objects)
{
owner.takeOwnership(kvp.Key);
}
}
if (Input.GetKeyDown(KeyCode.Backspace))
{
foreach (KeyValuePair<string, NetworkObject> kvp in owner.manager.objects)
{
owner.networkDestroy(kvp.Key);
}
}
}
}
}

View File

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

View File

@ -0,0 +1,136 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6139051692386484099
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3076416102083120807}
- component: {fileID: 8527011532923434593}
- component: {fileID: 6854617867369839}
- component: {fileID: 5845716565458182149}
- component: {fileID: 5713671764962751473}
- component: {fileID: -4404668399269848200}
m_Layer: 0
m_Name: PlayerPrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3076416102083120807
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6139051692386484099}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &8527011532923434593
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6139051692386484099}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &6854617867369839
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6139051692386484099}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
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
m_AdditionalVertexStreams: {fileID: 0}
--- !u!65 &5845716565458182149
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6139051692386484099}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 2
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!114 &5713671764962751473
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6139051692386484099}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d8d3b6de660834e3e898725928251405, type: 3}
m_Name:
m_EditorClassIdentifier:
myObject: {fileID: -4404668399269848200}
userid: 0
username:
room:
manager: {fileID: 0}
isLocal: 0
lastObjectId: 0
commsNetwork: {fileID: 0}
dissonanceID:
--- !u!114 &-4404668399269848200
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6139051692386484099}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 89e3af759df774692a566a166b4bf69b, type: 3}
m_Name:
m_EditorClassIdentifier:
owner: {fileID: 5713671764962751473}
networkId:
targetPosition: {x: 0, y: 0, z: 0}
targetRotation: {x: 0, y: 0, z: 0, w: 0}

View File

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

View File

@ -0,0 +1,145 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &8565720275311462453
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8565720275311462455}
- component: {fileID: 8565720275311462452}
m_Layer: 0
m_Name: TestNetworkedGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8565720275311462455
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8565720275311462453}
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: 8565720276181857624}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8565720275311462452
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8565720275311462453}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3f1f9b0bbd93a484a987c51f1107ebe5, type: 3}
m_Name:
m_EditorClassIdentifier:
owner: {fileID: 0}
networkId:
targetPosition: {x: 0, y: 0, z: 0}
targetRotation: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &8565720276181857625
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8565720276181857624}
- component: {fileID: 8565720276181857605}
- component: {fileID: 8565720276181857626}
- component: {fileID: 8565720276181857627}
m_Layer: 0
m_Name: Sphere
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8565720276181857624
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8565720276181857625}
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: 8565720275311462455}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &8565720276181857605
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8565720276181857625}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8565720276181857626
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8565720276181857625}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
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
m_AdditionalVertexStreams: {fileID: 0}
--- !u!135 &8565720276181857627
SphereCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8565720276181857625}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Center: {x: 0, y: 0, z: 0}

View File

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

3431
Samples~/Example/test.unity Normal file

File diff suppressed because it is too large Load Diff

View File

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

14
VelNetUnity.asmdef Normal file
View File

@ -0,0 +1,14 @@
{
"name": "VelNetUnity",
"rootNamespace": "VelNetUnity",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

7
VelNetUnity.asmdef.meta Normal file
View File

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

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "edu.uga.engr.vel.velnetunity",
"displayName": "VelNetUnity",
"version": "1.0.0",
"unity": "2019.1",
"description": "A custom networking library for Unity.",
"keywords": [
"networking",
"self-hosted"
],
"author": {
"name": "Kyle Johnsen",
"email": "kjohnsen@uga.edu",
"url": "https://vel.engr.uga.edu"
},
"samples": [
{
"displayName": "Dissonance Integration",
"description": "Includes support for Dissonance Voice, available separately from the Unity Asset Store.",
"path": "Samples~/DissonanceIntegration"
},
{
"displayName": "Example",
"description": "Example Scene",
"path": "Samples~/Example"
}
],
"dependencies": {
}
}

7
package.json.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 525b0bfd38079f24f86b406a8da0b3c7
PackageManifestImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: