reorganized into a package, added a namespace, added an assemblydefinition, attempt at ci for upm branch creation
parent
18746b43d8
commit
480b9cd1ca
|
|
@ -0,0 +1,19 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
split-upm:
|
||||
name: split upm branch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: split upm branch
|
||||
run: |
|
||||
git subtree split -P "$PKG_ROOT" -b upm
|
||||
git push -u origin upm
|
||||
env:
|
||||
PKG_ROOT: TestVelGameServer\Packages\VelNetUnity
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# VelNetUnity
|
||||
Custom networking package for Unity
|
||||
|
||||

|
||||
---
|
||||
|
||||
See [VelNetServer](https://github.com/velaboratory/VelNetServer) for description of the server.
|
||||
|
||||
By contrast to the server, the Unity side is pretty complex.
|
||||
|
||||
### NetworkManager
|
||||
Deals with instantiating NetworkPlayers, and dealing with join, left, new master client events. Also manages the list of NetworkObjects. It maintains a list of prefabs that can be instantiated, as well as the prefab to use for players.
|
||||
|
||||
### NetworkPlayer
|
||||
One of these will be instantiated for each player that joins the "room". Most application communication happens by calling methods on this object. Voice comms are also handled within this object. Only one NetworkPlayer "isLocal". The others are remote. The local network player is the one that can modify it's "owned" network objects. It also has a special networkobject representing itself (setup within the prefab, not part of the networkmanager list). This is where most of the user behavior is going to go (e.g. an avatar).
|
||||
|
||||
### NetworkObject
|
||||
Something that can be owned by a network player, who is responsible for updating it. Only the owner network player can/should modify a network object, whether locally or in response to a received message from the local version. NetworkObjects are delineated by an session-unique id, which consists of the creator's userid + "-" + an increasing number designated by the creator. Only local NetworkPlayers should send instantiate message, which contain this id. This class should be extended to sync and send messages. Scenes are basically built from NetworkObjects. Scene's can also start with network objects. Those are known by all, and start with "-1-increasing". That should happen the same on all clients, and is done locally at the start of the networkmanager. An example of a NetworkObject is SyncTransform, which is a simple one that keeps an object's position and orientation synchronized.
|
||||
|
||||
### Ownership
|
||||
Ownership of objects changes regularly. Any local networkplayer can send a message to take ownership of a networkid. That client immediately assumes ownership. The message is sent to the network, and will be ordered with any other ownership messages. So, if two clients try to get ownership at the same time, both will assume ownership, but one will happen after the other. This means that the one who was first will quickly lose ownership.
|
||||
|
||||
### Joining
|
||||
An interesting problem is what to do when new clients join. This is partially left up to the developer. Any objects that are instantiated (or scene objects that are deleted) will be automatically handled.
|
||||
|
||||
### Message Groups
|
||||
Clients can manage network traffic using messaging groups. This is especially useful for audio, where you can maintain a list of people close enough to you to hear audio
|
||||
|
||||
|
||||
|
||||
18
README.txt
18
README.txt
|
|
@ -1,18 +0,0 @@
|
|||
See VelNet for description of the server.
|
||||
|
||||
By contrast to the server, the Unity side is pretty complex.
|
||||
|
||||
NetworkManager - Deals with instantiating NetworkPlayers, and dealing with join, left, new master client events. Also manages the list of NetworkObjects. It maintains a list of prefabs that can be instantiated, as well as the prefab to use for players.
|
||||
|
||||
NetworkPlayer - One of these will be instantiated for each player that joins the "room". Most application communication happens by calling methods on this object. Voice comms are also handled within this object. Only one NetworkPlayer "isLocal". The others are remote. The local network player is the one that can modify it's "owned" network objects. It also has a special networkobject representing itself (setup within the prefab, not part of the networkmanager list). This is where most of the user behavior is going to go (e.g. an avatar).
|
||||
|
||||
NetworkObject - Something that can be owned by a network player, who is responsible for updating it. Only the owner network player can/should modify a network object, whether locally or in response to a received message from the local version. NetworkObjects are delineated by an session-unique id, which consists of the creator's userid + "-" + an increasing number designated by the creator. Only local NetworkPlayers should send instantiate message, which contain this id. This class should be extended to sync and send messages. Scenes are basically built from NetworkObjects. Scene's can also start with network objects. Those are known by all, and start with "-1-increasing". That should happen the same on all clients, and is done locally at the start of the networkmanager. An example of a NetworkObject is SyncTransform, which is a simple one that keeps an object's position and orientation synchronized.
|
||||
|
||||
Ownership of objects changes regularly. Any local networkplayer can send a message to take ownership of a networkid. That client immediately assumes ownership. The message is sent to the network, and will be ordered with any other ownership messages. So, if two clients try to get ownership at the same time, both will assume ownership, but one will happen after the other. This means that the one who was first will quickly lose ownership.
|
||||
|
||||
An interesting problem is what to do when new clients join. This is partially left up to the developer. Any objects that are instantiated (or scene objects that are deleted) will be automatically handled.
|
||||
|
||||
Clients can manage network traffic using messaging groups. This is especially useful for audio, where you can maintain a list of people close enough to you to hear audio
|
||||
|
||||
|
||||
|
||||
|
|
@ -70,3 +70,5 @@ crashlytics-build.properties
|
|||
# Temporary auto-generated Android Assets
|
||||
/[Aa]ssets/[Ss]treamingAssets/aa.meta
|
||||
/[Aa]ssets/[Ss]treamingAssets/aa/*
|
||||
|
||||
.idea/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 867c77c74f84b41acb49da778488a75f
|
||||
guid: b52a74ceb5dcd9d498e95617f4247a27
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 09c6fdabe05be4c86917b1c585ed4a40
|
||||
guid: 8b822dd2b9e99144097e61694b8dab65
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 21c2212b420fd48a1b3552af8d219f80
|
||||
guid: 14a35ccfe945cc54292ca4e8940b53b9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c506b9118efc2de4ea5f2b2f26a52b9d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using Dissonance;
|
||||
using Dissonance.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace VelNetUnity
|
||||
{
|
||||
public class VelCommsNetwork : MonoBehaviour, ICommsNetwork
|
||||
{
|
||||
public ConnectionStatus Status => manager.connected ? ConnectionStatus.Connected : ConnectionStatus.Disconnected;
|
||||
public NetworkMode Mode => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2fc687ece57d64c7082cd32261ac438b
|
||||
guid: 07900994db5c50c4794e338da120f31b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace VelNetUnity
|
||||
{
|
||||
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 = 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Dissonance;
|
||||
using System.Text;
|
||||
|
||||
namespace VelNetUnity
|
||||
{
|
||||
public class PlayerController : NetworkObject, 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 => 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 = 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 = 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, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,505 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using Dissonance;
|
||||
using System.Net;
|
||||
|
||||
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 = GameObject.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
|
||||
{
|
||||
this.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 (this.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 = GameObject.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 = GameObject.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);
|
||||
}
|
||||
GameObject.Destroy(obj.gameObject);
|
||||
objects.Remove(networkId);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
|
||||
[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
|
||||
|
||||
|
||||
bool isMaster = false;
|
||||
|
||||
void Start()
|
||||
{
|
||||
myObject.owner = this;
|
||||
this.manager = GameObject.FindObjectOfType<NetworkManager>();
|
||||
manager.onPlayerJoined += handlePlayerJoined;
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
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 (this.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 = GameObject.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 = this.userid + "-" + lastObjectId++;
|
||||
|
||||
|
||||
NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName);
|
||||
if (temp != null)
|
||||
{
|
||||
NetworkObject instance = GameObject.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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using UnityEngine;
|
||||
|
||||
/// <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
|
||||
void Update()
|
||||
{
|
||||
if (owner != null && !owner.isLocal)
|
||||
{
|
||||
transform.position = Vector3.Lerp(transform.position, targetPosition, .1f);
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 453f243ca8f3d8b4db4a1db0d9f9d300
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 63ae5231117a1484198ba0a8f681612b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: feb473f59814842848dd4183ddeb6d9e
|
||||
guid: cd6bc3db2c19417498a737fbb79e8c7d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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: {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa1da19d935e241119cdd522ceae772c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 24100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5d2009d8e264649749c0315d48765749
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7a7db5bc792cd471dbd8039664359eee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 89e3af759df774692a566a166b4bf69b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4158ab9c4a204cdbba28d3273fc1fb3
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6e4a023f70e01405e8b249a4488fe319
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e4e43899246c941c78acfc59ce2f664a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "VelNetUnity",
|
||||
"rootNamespace": "VelNetUnity",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1e55e2c4387020247a1ae212bbcbd381
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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": {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 525b0bfd38079f24f86b406a8da0b3c7
|
||||
PackageManifestImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"com.unity.collab-proxy": "1.7.1",
|
||||
"com.unity.collab-proxy": "1.15.7",
|
||||
"com.unity.ide.rider": "2.0.7",
|
||||
"com.unity.ide.visualstudio": "2.0.11",
|
||||
"com.unity.ide.vscode": "1.2.3",
|
||||
"com.unity.test-framework": "1.1.27",
|
||||
"com.unity.ide.visualstudio": "2.0.12",
|
||||
"com.unity.ide.vscode": "1.2.4",
|
||||
"com.unity.test-framework": "1.1.30",
|
||||
"com.unity.textmeshpro": "3.0.6",
|
||||
"com.unity.timeline": "1.4.8",
|
||||
"com.unity.ugui": "1.0.0",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"com.unity.collab-proxy": {
|
||||
"version": "1.7.1",
|
||||
"version": "1.15.7",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
||||
"com.unity.nuget.newtonsoft-json": "2.0.0",
|
||||
"com.unity.services.core": "1.0.1"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
|
|
@ -26,7 +27,7 @@
|
|||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.visualstudio": {
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
|
|
@ -35,7 +36,7 @@
|
|||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.vscode": {
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.4",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
|
|
@ -48,8 +49,17 @@
|
|||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.services.core": {
|
||||
"version": "1.0.1",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.modules.unitywebrequest": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.test-framework": {
|
||||
"version": "1.1.27",
|
||||
"version": "1.1.30",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
|
|
@ -89,6 +99,12 @@
|
|||
"com.unity.modules.imgui": "1.0.0"
|
||||
}
|
||||
},
|
||||
"edu.uga.engr.vel.velnetunity": {
|
||||
"version": "file:VelNetUnity",
|
||||
"depth": 0,
|
||||
"source": "embedded",
|
||||
"dependencies": {}
|
||||
},
|
||||
"com.unity.modules.ai": {
|
||||
"version": "1.0.0",
|
||||
"depth": 0,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &1
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 61
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a287be6c49135cd4f9b2b8666c39d999, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
assetDefaultFramerate: 60
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Loading…
Reference in New Issue