using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; [CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/Networking", order = 1)] public class Networking : ScriptableObject { public string host; public string path; public int port; public GameObject soulPrefab; public GameObject playerPrefab; public LoginInfo loginInfo; private ClientWebSocket sock; private Task writeTask; private Task readTask; private ArraySegment readBuffer; private int seq; async public void Connect() { readBuffer = new ArraySegment(new byte[32000]); if (sock != null) { if (sock.State == WebSocketState.Open) { return; } sock = null; } Application.quitting += autoDisconnect; sock = new ClientWebSocket(); UriBuilder b = new UriBuilder(); b.Scheme = "ws"; b.Host = host; b.Port = port; b.Path = path; Debug.LogFormat("Connecting to: {0}", b.Uri); await sock.ConnectAsync(b.Uri, CancellationToken.None); Debug.LogFormat("Finished connection task with status: {0}", sock.State); return; } public void SendCollectSoul(string playerName, Vector3 position) { seq++; CollectSoul info; info.seq = seq; info.cmd = "collect-soul"; info.playerName = playerName; info.position = position; string msg = JsonUtility.ToJson(info); ArraySegment buf = new ArraySegment(Encoding.UTF8.GetBytes(msg)); sock.SendAsync(buf, WebSocketMessageType.Text, true, CancellationToken.None); } public void SendDeath(Vector3 position) { seq++; Death death; death.seq = seq; death.cmd = "death"; death.position = position; string msg = JsonUtility.ToJson(death); ArraySegment buf = new ArraySegment(Encoding.UTF8.GetBytes(msg)); sock.SendAsync(buf, WebSocketMessageType.Text, true, CancellationToken.None); } public void SendLogin() { seq++; Login login; login.seq = seq; login.cmd = "login"; login.username = loginInfo.playerName; login.password = loginInfo.password; loginInfo.sentLogin = true; loginInfo.loginError = ""; string msg = JsonUtility.ToJson(login); ArraySegment buf = new ArraySegment(Encoding.UTF8.GetBytes(msg)); sock.SendAsync(buf, WebSocketMessageType.Text, true, CancellationToken.None); } public IEnumerator ReadMessages() { Task readTask = null; // = sock.ReceiveAsync(readBuffer, CancellationToken.None); while (true) { if (!isConnected()) { yield return null; continue; } if (readTask == null) { readTask = sock.ReceiveAsync(readBuffer, CancellationToken.None); } switch (readTask.Status) { case TaskStatus.Created: case TaskStatus.WaitingForActivation: case TaskStatus.WaitingToRun: case TaskStatus.Running: case TaskStatus.WaitingForChildrenToComplete: yield return null; continue; case TaskStatus.RanToCompletion: parseMessage(readTask.Result); break; case TaskStatus.Canceled: break; case TaskStatus.Faulted: break; } readTask = null; yield return null; } } private void parseMessage(WebSocketReceiveResult message) { string msg = Encoding.UTF8.GetString(readBuffer.Array, 0, message.Count); string[] parts = msg.Split(new char[]{' '}, 2); if (parts.Length != 2) { Debug.LogFormat("dunno how to handle this msg: {0}", msg); return; } switch (parts[0]) { case "spawn-soul": SpawnSoul spawned = JsonUtility.FromJson(parts[1]); onSpawnSoul(spawned); break; case "soul-collected": CollectSoul collected = JsonUtility.FromJson(parts[1]); onSoulCollected(collected); break; case "login-result": LoginResult login = JsonUtility.FromJson(parts[1]); onLoginResult(login); break; case "tick": break; default: Debug.LogFormat("also can't handle this one: {0} {1}", parts[0], parts[1]); break; } } private void onSpawnSoul(SpawnSoul spawn) { Debug.LogFormat("spawn a soul: {0} at {1}", spawn.playerName, spawn.position); GameObject soul = Instantiate(soulPrefab, spawn.position, Quaternion.identity); soul.name = spawn.playerName; GameObject allSouls = GameObject.Find("Souls"); if (allSouls == null) { Debug.LogError("unable to find souls container!"); } else { soul.transform.SetParent(allSouls.transform); SoulController sc = soul.GetComponent(); sc.playerName = spawn.playerName; } } private void onSoulCollected(CollectSoul collected) { Debug.LogFormat("a soul was collected: {0}", collected); GameObject soul = GameObject.Find("Souls/"+collected.playerName); Destroy(soul); if (collected.playerName == loginInfo.playerName) { GameObject currentPlayer = GameObject.Find("Player"); if (currentPlayer == null) { GameObject player = Instantiate(playerPrefab, loginInfo.startPosition, Quaternion.identity); Camera cam = Camera.main; CameraController cc = cam.GetComponent(); cc.player = player.transform; } } } private void onLoginResult(LoginResult result) { Debug.LogFormat("received login result: {0}", result); loginInfo.sentLogin = false; if (result.passed) { loginInfo.isLoggedIn = true; } else { loginInfo.loginError = result.error; Debug.LogErrorFormat("failed login: {0}", result.error); } } private void autoDisconnect() { if (isConnected()) { Debug.Log("disconnecting websocket via autoDisconnect"); Task task = sock.CloseAsync(WebSocketCloseStatus.NormalClosure, "closed", CancellationToken.None); task.Wait(); } } public bool isConnected() { return sock != null && sock.State == WebSocketState.Open; } private struct CollectSoul { public int seq; public string cmd; public string playerName; public Vector3 position; } private struct Death { public int seq; public string cmd; public Vector3 position; } private struct SpawnSoul { public string playerName; public Vector3 position; } private struct Login { public int seq; public string cmd; public string username; public string password; } private struct LoginResult { public bool passed; public string error; } }