【打包】提交部分代码
parent
75987c59d7
commit
2bb08348b7
|
|
@ -34,3 +34,9 @@ AppKey: "uUi_UI1oMKWo0VOSN1zme17Z70-FlAv3Lcx2mNhoYe4="
|
|||
CrashSight_Android:
|
||||
id: "669a94ce69" # 崩溃上报的 App ID
|
||||
key: "d9412717-2416-4270-b8bc-f7a0805f988f" # 崩溃上报的 Key
|
||||
|
||||
Packages:
|
||||
- Poco@latest
|
||||
- TalkingData@latest
|
||||
- AliPay@latest
|
||||
- WeichatPay@latest
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# 传入参数 0:SDK_PATH 1:ChannelPath
|
||||
import sys
|
||||
import os
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python PreHandle.py <SDK_PATH> <ChannelPath>")
|
||||
return
|
||||
|
||||
sdk_path = sys.argv[1]
|
||||
channel_path = sys.argv[2]
|
||||
|
||||
if not os.path.exists(sdk_path):
|
||||
print(f"Error: SDK path '{sdk_path}' does not exist.")
|
||||
return
|
||||
|
||||
if not os.path.exists(channel_path):
|
||||
print(f"Error: Channel path '{channel_path}' does not exist.")
|
||||
return
|
||||
|
||||
# 这里可以添加更多的处理逻辑
|
||||
print(f"SDK Path: {sdk_path}")
|
||||
print(f"Channel Path: {channel_path}")
|
||||
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e3dd1ab5744138f429f858e52c38d482
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
public static class PocoListenerUtils
|
||||
{
|
||||
public static void SubscribePocoListeners(RPCParser rpc, PocoListenersBase listeners)
|
||||
{
|
||||
var methods = listeners.GetType()
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
var uniqueListeners = new HashSet<string>();
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var attribute = method.GetCustomAttribute<PocoMethodAttribute>();
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
rpc.addListener(listeners, attribute.Name, method);
|
||||
|
||||
if (uniqueListeners.Add(attribute.Name) == false)
|
||||
{
|
||||
Debug.LogError($"Attempt to add non-unique Poco listener: `{attribute.Name}`, " +
|
||||
$"please check attributes of listeners at `{listeners.GetType().Name}`");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static object HandleInvocation(
|
||||
Dictionary<string, (object instance, MethodInfo method)> listeners,
|
||||
Dictionary<string, object> data)
|
||||
{
|
||||
if (listeners == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(listeners),
|
||||
"To use `poco.invoke()`, please assign object " +
|
||||
$"of class derived from {nameof(PocoListenersBase)} " +
|
||||
$"to field at `{nameof(PocoManager)}`");
|
||||
}
|
||||
|
||||
var paramsObject = (JObject)data["params"];
|
||||
|
||||
var listener = paramsObject["listener"].ToObject<string>();
|
||||
|
||||
if (listeners.TryGetValue(listener, out var listenerPair) == false)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
$"Listener method for `{listener}` " +
|
||||
$"marked with `{nameof(PocoMethodAttribute)}` was not found " +
|
||||
$"at `{listeners.GetType().Name}`");
|
||||
}
|
||||
|
||||
var (instance, method) = listenerPair;
|
||||
|
||||
var args = GetInvocationArgs(paramsObject, method);
|
||||
|
||||
var result = method.Invoke(instance, args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object[] GetInvocationArgs(JObject paramsObject, MethodInfo method)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
if (paramsObject.TryGetValue("data", out var data) == false)
|
||||
{
|
||||
if (parameters.Length > 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Signature mismatch of method `{method}`: " +
|
||||
"expected 0 arguments in listener, " +
|
||||
$"received {parameters.Length} arguments");
|
||||
}
|
||||
|
||||
return Array.Empty<object>();
|
||||
}
|
||||
|
||||
var args = new List<object>();
|
||||
|
||||
var remainingArgNames = new HashSet<string>(data.ToObject<Dictionary<string, object>>().Keys);
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var parameterName = parameter.Name;
|
||||
|
||||
var argToken = data[parameterName];
|
||||
|
||||
if (argToken == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Signature mismatch of method `{method}`: " +
|
||||
$"excess parameter `{parameterName}` in listener");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var argValue = argToken.ToObject(parameter.ParameterType);
|
||||
args.Add(argValue);
|
||||
remainingArgNames.Remove(parameterName);
|
||||
}
|
||||
catch (JsonReaderException exception)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Signature mismatch of method `{method}`: " +
|
||||
$"parameter `{parameterName}` type mismatch: " +
|
||||
$"tried to parse received value `{argToken}`, " +
|
||||
$"with type `{parameter.ParameterType.Name}` at listener",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingArgNames.Count > 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Signature mismatch of method `{method}`: " +
|
||||
$"missing parameters in listener: `{string.Join(", ", remainingArgNames)}`");
|
||||
}
|
||||
|
||||
return args.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fc41a3e97b2a6c24298dab601df56f5c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
using UnityEngine;
|
||||
|
||||
public class PocoListenersBase : MonoBehaviour { }
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2a6c7fe1602ea044591568fb38b100d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Poco;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using TcpServer;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
public class PocoManager : MonoBehaviour
|
||||
{
|
||||
public event Action<string> MessageReceived;
|
||||
|
||||
public const int versionCode = 6;
|
||||
public int port = 5001;
|
||||
private bool mRunning;
|
||||
public AsyncTcpServer server = null;
|
||||
public PocoListenersBase pocoListenersBase;
|
||||
private RPCParser rpc = null;
|
||||
private SimpleProtocolFilter prot = null;
|
||||
private UnityDumper dumper = new UnityDumper();
|
||||
private ConcurrentDictionary<string, TcpClientState> inbox = new ConcurrentDictionary<string, TcpClientState>();
|
||||
private VRSupport vr_support = new VRSupport();
|
||||
private Dictionary<string, long> debugProfilingData = new Dictionary<string, long>() {
|
||||
{ "dump", 0 },
|
||||
{ "screenshot", 0 },
|
||||
{ "handleRpcRequest", 0 },
|
||||
{ "packRpcResponse", 0 },
|
||||
{ "sendRpcResponse", 0 },
|
||||
};
|
||||
|
||||
class RPC : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Application.runInBackground = true;
|
||||
DontDestroyOnLoad(this);
|
||||
prot = new SimpleProtocolFilter();
|
||||
rpc = new RPCParser();
|
||||
rpc.addRpcMethod("isVRSupported", vr_support.isVRSupported);
|
||||
rpc.addRpcMethod("hasMovementFinished", vr_support.IsQueueEmpty);
|
||||
rpc.addRpcMethod("RotateObject", vr_support.RotateObject);
|
||||
rpc.addRpcMethod("ObjectLookAt", vr_support.ObjectLookAt);
|
||||
rpc.addRpcMethod("Screenshot", Screenshot);
|
||||
rpc.addRpcMethod("GetScreenSize", GetScreenSize);
|
||||
rpc.addRpcMethod("Dump", Dump);
|
||||
rpc.addRpcMethod("GetDebugProfilingData", GetDebugProfilingData);
|
||||
rpc.addRpcMethod("SetText", SetText);
|
||||
rpc.addRpcMethod("SendMessage", SendMessage);
|
||||
|
||||
rpc.addRpcMethod("GetSDKVersion", GetSDKVersion);
|
||||
|
||||
if (pocoListenersBase != null)
|
||||
{
|
||||
PocoListenerUtils.SubscribePocoListeners(rpc, pocoListenersBase);
|
||||
}
|
||||
|
||||
mRunning = true;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
this.server = new AsyncTcpServer(port + i);
|
||||
this.server.Encoding = Encoding.UTF8;
|
||||
this.server.ClientConnected +=
|
||||
new EventHandler<TcpClientConnectedEventArgs>(server_ClientConnected);
|
||||
this.server.ClientDisconnected +=
|
||||
new EventHandler<TcpClientDisconnectedEventArgs>(server_ClientDisconnected);
|
||||
this.server.DatagramReceived +=
|
||||
new EventHandler<TcpDatagramReceivedEventArgs<byte[]>>(server_Received);
|
||||
try
|
||||
{
|
||||
this.server.Start();
|
||||
Debug.Log(string.Format("Tcp server started and listening at {0}", server.Port));
|
||||
break;
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
Debug.Log(string.Format("Tcp server bind to port {0} Failed!", server.Port));
|
||||
Debug.Log("--- Failed Trace Begin ---");
|
||||
Debug.LogError(e);
|
||||
Debug.Log("--- Failed Trace End ---");
|
||||
// try next available port
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
if (this.server == null)
|
||||
{
|
||||
Debug.LogError(string.Format("Unable to find an unused port from {0} to {1}", port, port + 5));
|
||||
}
|
||||
vr_support.ClearCommands();
|
||||
}
|
||||
|
||||
static void server_ClientConnected(object sender, TcpClientConnectedEventArgs e)
|
||||
{
|
||||
Debug.Log(string.Format("TCP client {0} has connected.",
|
||||
e.TcpClient.Client.RemoteEndPoint.ToString()));
|
||||
}
|
||||
|
||||
static void server_ClientDisconnected(object sender, TcpClientDisconnectedEventArgs e)
|
||||
{
|
||||
Debug.Log(string.Format("TCP client {0} has disconnected.",
|
||||
e.TcpClient.Client.RemoteEndPoint.ToString()));
|
||||
}
|
||||
|
||||
private void server_Received(object sender, TcpDatagramReceivedEventArgs<byte[]> e)
|
||||
{
|
||||
Debug.Log(string.Format("Client : {0} --> {1}",
|
||||
e.Client.TcpClient.Client.RemoteEndPoint.ToString(), e.Datagram.Length));
|
||||
TcpClientState internalClient = e.Client;
|
||||
string tcpClientKey = internalClient.TcpClient.Client.RemoteEndPoint.ToString();
|
||||
inbox.AddOrUpdate(tcpClientKey, internalClient, (n, o) =>
|
||||
{
|
||||
return internalClient;
|
||||
});
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object Dump(List<object> param)
|
||||
{
|
||||
var onlyVisibleNode = true;
|
||||
if (param.Count > 0)
|
||||
{
|
||||
onlyVisibleNode = (bool)param[0];
|
||||
}
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
var h = dumper.dumpHierarchy(onlyVisibleNode);
|
||||
debugProfilingData["dump"] = sw.ElapsedMilliseconds;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object Screenshot(List<object> param)
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
var tex = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
|
||||
tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
||||
tex.Apply(false);
|
||||
byte[] fileBytes = tex.EncodeToJPG(80);
|
||||
var b64img = Convert.ToBase64String(fileBytes);
|
||||
debugProfilingData["screenshot"] = sw.ElapsedMilliseconds;
|
||||
return new object[] { b64img, "jpg" };
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object GetScreenSize(List<object> param)
|
||||
{
|
||||
return new float[] { Screen.width, Screen.height };
|
||||
}
|
||||
|
||||
public void stopListening()
|
||||
{
|
||||
mRunning = false;
|
||||
server?.Stop();
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object GetDebugProfilingData(List<object> param)
|
||||
{
|
||||
return debugProfilingData;
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object SetText(List<object> param)
|
||||
{
|
||||
var instanceId = Convert.ToInt32(param[0]);
|
||||
var textVal = param[1] as string;
|
||||
foreach (var go in GameObject.FindObjectsOfType<GameObject>())
|
||||
{
|
||||
if (go.GetInstanceID() == instanceId)
|
||||
{
|
||||
return UnityNode.SetText(go, textVal);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object SendMessage(List<object> param)
|
||||
{
|
||||
if (MessageReceived == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var textVal = param[0] as string;
|
||||
|
||||
MessageReceived.Invoke(textVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[RPC]
|
||||
private object GetSDKVersion(List<object> param)
|
||||
{
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
foreach (TcpClientState client in inbox.Values)
|
||||
{
|
||||
List<string> msgs = client.Prot.swap_msgs();
|
||||
msgs.ForEach(delegate (string msg)
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
var t0 = sw.ElapsedMilliseconds;
|
||||
string response = rpc.HandleMessage(msg);
|
||||
var t1 = sw.ElapsedMilliseconds;
|
||||
byte[] bytes = prot.pack(response);
|
||||
var t2 = sw.ElapsedMilliseconds;
|
||||
server.Send(client.TcpClient, bytes);
|
||||
var t3 = sw.ElapsedMilliseconds;
|
||||
debugProfilingData["handleRpcRequest"] = t1 - t0;
|
||||
debugProfilingData["packRpcResponse"] = t2 - t1;
|
||||
TcpClientState internalClientToBeThrowAway;
|
||||
string tcpClientKey = client.TcpClient.Client.RemoteEndPoint.ToString();
|
||||
inbox.TryRemove(tcpClientKey, out internalClientToBeThrowAway);
|
||||
});
|
||||
}
|
||||
|
||||
vr_support.PeekCommand();
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
// stop listening thread
|
||||
stopListening();
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
// stop listening thread
|
||||
stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
public class RPCParser
|
||||
{
|
||||
public delegate object RpcMethod(List<object> param);
|
||||
|
||||
protected Dictionary<string, RpcMethod> RPCHandler = new Dictionary<string, RpcMethod>();
|
||||
protected Dictionary<string, (object instance, MethodInfo method)> Listeners = new Dictionary<string, (object, MethodInfo)>();
|
||||
|
||||
private JsonSerializerSettings settings = new JsonSerializerSettings()
|
||||
{
|
||||
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii
|
||||
};
|
||||
|
||||
public string HandleMessage(string json)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(json, settings);
|
||||
|
||||
if (data.TryGetValue("method", out var methodObj) == false)
|
||||
{
|
||||
Debug.Log("ignore message without method");
|
||||
return null;
|
||||
}
|
||||
|
||||
var method = methodObj.ToString();
|
||||
var idAction = data.TryGetValue("id", out var id) ? id : null;
|
||||
|
||||
try
|
||||
{
|
||||
object result;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case "Invoke":
|
||||
result = PocoListenerUtils.HandleInvocation(Listeners, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
List<object> param = null;
|
||||
|
||||
if (data.TryGetValue("params", out var value))
|
||||
{
|
||||
param = ((JArray)value).ToObject<List<object>>();
|
||||
}
|
||||
|
||||
result = RPCHandler[method](param);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return formatResponse(idAction, result);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogError(exception);
|
||||
return formatResponseError(idAction, null, exception);
|
||||
}
|
||||
}
|
||||
|
||||
// Call a method in the server
|
||||
public string formatRequest(string method, object idAction, List<object> param = null)
|
||||
{
|
||||
Dictionary<string, object> data = new Dictionary<string, object>();
|
||||
data["jsonrpc"] = "2.0";
|
||||
data["method"] = method;
|
||||
if (param != null)
|
||||
{
|
||||
data["params"] = JsonConvert.SerializeObject(param, settings);
|
||||
}
|
||||
// if idAction is null, it is a notification
|
||||
if (idAction != null)
|
||||
{
|
||||
data["id"] = idAction;
|
||||
}
|
||||
return JsonConvert.SerializeObject(data, settings);
|
||||
}
|
||||
|
||||
// Send a response from a request the server made to this client
|
||||
public string formatResponse(object idAction, object result)
|
||||
{
|
||||
Dictionary<string, object> rpc = new Dictionary<string, object>();
|
||||
rpc["jsonrpc"] = "2.0";
|
||||
rpc["id"] = idAction;
|
||||
rpc["result"] = result;
|
||||
return JsonConvert.SerializeObject(rpc, settings);
|
||||
}
|
||||
|
||||
// Send a error to the server from a request it made to this client
|
||||
public string formatResponseError(object idAction, IDictionary<string, object> data, Exception e)
|
||||
{
|
||||
Dictionary<string, object> rpc = new Dictionary<string, object>();
|
||||
rpc["jsonrpc"] = "2.0";
|
||||
rpc["id"] = idAction;
|
||||
|
||||
Dictionary<string, object> errorDefinition = new Dictionary<string, object>();
|
||||
errorDefinition["code"] = 1;
|
||||
errorDefinition["message"] = e.ToString();
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
errorDefinition["data"] = data;
|
||||
}
|
||||
|
||||
rpc["error"] = errorDefinition;
|
||||
return JsonConvert.SerializeObject(rpc, settings);
|
||||
}
|
||||
|
||||
public void addRpcMethod(string name, RpcMethod method)
|
||||
{
|
||||
RPCHandler[name] = method;
|
||||
}
|
||||
|
||||
public void addListener(object instance, string name, MethodInfo method)
|
||||
{
|
||||
Listeners[name] = (instance, method);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8f4d95df30f3aed43b005fdacbeec3a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
public sealed class PocoMethodAttribute : Attribute
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public PocoMethodAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 52c4e3a313dae2f458aa48c582ec89bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Unity3D Integration Guide
|
||||
|
||||
PocoSDK supports Unity3D version 4 & 5 and above, ngui & ugui & fairygui, C# only for now.
|
||||
|
||||
1. Clone source code from [poco-sdk](https://github.com/AirtestProject/Poco-SDK) repo.
|
||||
|
||||
2. Copy the `Unity3D` folder to your unity project script folder.
|
||||
|
||||
3.
|
||||
- If you are using ngui, just delete the sub folder `Unity3D/ugui` and `Unity3D/fairygui`.
|
||||
- If you are using ugui, just delete the sub folder `Unity3D/ngui` and `Unity3D/fairygui`.
|
||||
- If you are using fairygui, please refer [fairygui guide](https://github.com/AirtestProject/Poco-SDK/tree/master/Unity3D/fairygui)
|
||||
|
||||
4. Add `Unity3D/PocoManager.cs` as script component on any GameObject, generally on main camera.
|
||||
|
||||
## SDK接入指南
|
||||
PocoSDK支持Unity3D版本4和5及更高版本,支持ngui、ugui与fairygui的C#版本,暂不支持lua调用。
|
||||
1. 从[poco-sdk](https://github.comAirtestProjectPoco-SDK) 仓库克隆源代码。
|
||||
2. 将`Unity3D`文件夹复制到您的unity项目的Scripts文件夹下。
|
||||
3.
|
||||
- 如果使用的是ngui,需要删除子文件夹`Unity3D/ugui`和`Unity3D/fairygui`
|
||||
- 如果您使用的是ugui,只需删除子文件夹`Unity3D/ngui`和`Unity3D/fairygui`
|
||||
- 如果您使用的是fairygui,请参阅[fairygui指南](https://github.comAirtestProjectPoco-SDKtreemasterUnity3Dfairygui)
|
||||
|
||||
4. 在任何GameObject上(通常在主摄像机上,或创建一个新的空GameObject)添加`Unity3DPocoManager.cs`作为脚本组件。
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0d77c44e79fe424408e4f0ab57c3732e
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TcpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 与客户端的连接已建立事件参数
|
||||
/// </summary>
|
||||
public class TcpClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 与客户端的连接已建立事件参数
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">客户端</param>
|
||||
public TcpClientConnectedEventArgs(TcpClient tcpClient)
|
||||
{
|
||||
if (tcpClient == null)
|
||||
throw new ArgumentNullException("tcpClient");
|
||||
|
||||
this.TcpClient = tcpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端
|
||||
/// </summary>
|
||||
public TcpClient TcpClient { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e0445c032c1c00409e9e24490782c5c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TcpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 与客户端的连接已断开事件参数
|
||||
/// </summary>
|
||||
public class TcpClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 与客户端的连接已断开事件参数
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">客户端状态</param>
|
||||
public TcpClientDisconnectedEventArgs(TcpClient tcpClient)
|
||||
{
|
||||
if (tcpClient == null)
|
||||
throw new ArgumentNullException("tcpClient");
|
||||
|
||||
this.TcpClient = tcpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端
|
||||
/// </summary>
|
||||
public TcpClient TcpClient { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e512939ef291f604b98b4859c8b82ed6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TcpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal class to join the TCP client and buffer together
|
||||
/// for easy management in the server
|
||||
/// </summary>
|
||||
public class TcpClientState
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for a new Client
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">The TCP client</param>
|
||||
/// <param name="buffer">The byte array buffer</param>
|
||||
/// <param name="prot">The protocol filter</param>
|
||||
public TcpClientState(TcpClient tcpClient, byte[] buffer, ProtoFilter prot)
|
||||
{
|
||||
if (tcpClient == null)
|
||||
throw new ArgumentNullException("tcpClient");
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
if (prot == null)
|
||||
throw new ArgumentNullException("prot");
|
||||
|
||||
this.TcpClient = tcpClient;
|
||||
this.Buffer = buffer;
|
||||
this.Prot = prot;
|
||||
// this.NetworkStream = tcpClient.GetStream ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TCP Client
|
||||
/// </summary>
|
||||
public TcpClient TcpClient { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Buffer.
|
||||
/// </summary>
|
||||
public byte[] Buffer { get; private set; }
|
||||
|
||||
public ProtoFilter Prot { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network stream
|
||||
/// </summary>
|
||||
public NetworkStream NetworkStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return TcpClient.GetStream();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c44a013bdfbc42248ad83e6b6ef7e677
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace TcpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 接收到数据报文事件参数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">报文类型</typeparam>
|
||||
public class TcpDatagramReceivedEventArgs<T> : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 接收到数据报文事件参数
|
||||
/// </summary>
|
||||
/// <param name="tcpClientState">客户端状态</param>
|
||||
/// <param name="datagram">报文</param>
|
||||
public TcpDatagramReceivedEventArgs(TcpClientState tcpClientState, T datagram)
|
||||
{
|
||||
this.Client = tcpClientState;
|
||||
this.TcpClient = tcpClientState.TcpClient;
|
||||
this.Datagram = datagram;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端状态
|
||||
/// </summary>
|
||||
public TcpClientState Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户端
|
||||
/// </summary>
|
||||
public TcpClient TcpClient { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报文
|
||||
/// </summary>
|
||||
public T Datagram { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8b315ab57e8d89a41be324a5bd5718af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcpServer
|
||||
{
|
||||
public interface ProtoFilter
|
||||
{
|
||||
void input(byte[] data);
|
||||
|
||||
List<string> swap_msgs();
|
||||
}
|
||||
|
||||
public class SimpleProtocolFilter : ProtoFilter
|
||||
{
|
||||
/* 简单协议过滤器
|
||||
协议按照 [有效数据字节数][有效数据] 这种协议包的格式进行打包和解包
|
||||
[有效数据字节数]长度HEADER_SIZE字节
|
||||
[有效数据]长度有效数据字节数字节
|
||||
本类按照这种方式,顺序从数据流中取出数据进行拼接,一旦接收完一个完整的协议包,就会将协议包返回
|
||||
[有效数据]字段接收到后会按照utf-8进行解码,因为在传输过程中是用utf-8进行编码的
|
||||
所有编解码的操作在该类中完成
|
||||
*/
|
||||
|
||||
private byte[] buf = new byte[0];
|
||||
private int HEADER_SIZE = 4;
|
||||
private List<string> msgs = new List<string>();
|
||||
|
||||
public void input(byte[] data)
|
||||
{
|
||||
buf = Combine(buf, data);
|
||||
|
||||
while (buf.Length > HEADER_SIZE)
|
||||
{
|
||||
int data_size = BitConverter.ToInt32(buf, 0);
|
||||
if (buf.Length >= data_size + HEADER_SIZE)
|
||||
{
|
||||
byte[] data_body = Slice(buf, HEADER_SIZE, data_size + HEADER_SIZE);
|
||||
string content = System.Text.Encoding.Default.GetString(data_body);
|
||||
msgs.Add(content);
|
||||
buf = Slice(buf, data_size + HEADER_SIZE, buf.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> swap_msgs()
|
||||
{
|
||||
List<string> ret = msgs;
|
||||
msgs = new List<string>();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public byte[] pack(String content)
|
||||
{
|
||||
int len = content.Length;
|
||||
byte[] size = BitConverter.GetBytes(len);
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
//reverse it so we get little endian.
|
||||
Array.Reverse(size);
|
||||
}
|
||||
byte[] body = System.Text.Encoding.Default.GetBytes(content);
|
||||
byte[] ret = Combine(size, body);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static byte[] Combine(byte[] first, byte[] second)
|
||||
{
|
||||
byte[] ret = new byte[first.Length + second.Length];
|
||||
Buffer.BlockCopy(first, 0, ret, 0, first.Length);
|
||||
Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public byte[] Slice(byte[] source, int start, int end)
|
||||
{
|
||||
int length = end - start;
|
||||
byte[] ret = new byte[length];
|
||||
Array.Copy(source, start, ret, 0, length);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 异步TCP服务器
|
||||
/// </summary>
|
||||
public class AsyncTcpServer : IDisposable
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private TcpListener _listener;
|
||||
private ConcurrentDictionary<string, TcpClientState> _clients;
|
||||
private bool _disposed = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ctors
|
||||
|
||||
/// <summary>
|
||||
/// 异步TCP服务器
|
||||
/// </summary>
|
||||
/// <param name="listenPort">监听的端口</param>
|
||||
public AsyncTcpServer(int listenPort)
|
||||
: this(IPAddress.Any, listenPort)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步TCP服务器
|
||||
/// </summary>
|
||||
/// <param name="localEP">监听的终结点</param>
|
||||
public AsyncTcpServer(IPEndPoint localEP)
|
||||
: this(localEP.Address, localEP.Port)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步TCP服务器
|
||||
/// </summary>
|
||||
/// <param name="localIPAddress">监听的IP地址</param>
|
||||
/// <param name="listenPort">监听的端口</param>
|
||||
public AsyncTcpServer(IPAddress localIPAddress, int listenPort)
|
||||
{
|
||||
this.Address = localIPAddress;
|
||||
this.Port = listenPort;
|
||||
this.Encoding = Encoding.Default;
|
||||
|
||||
_clients = new ConcurrentDictionary<string, TcpClientState>();
|
||||
|
||||
_listener = new TcpListener(Address, Port);
|
||||
// _listener.AllowNatTraversal(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 服务器是否正在运行
|
||||
/// </summary>
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 监听的IP地址
|
||||
/// </summary>
|
||||
public IPAddress Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 监听的端口
|
||||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通信使用的编码
|
||||
/// </summary>
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Server
|
||||
|
||||
/// <summary>
|
||||
/// 启动服务器
|
||||
/// </summary>
|
||||
/// <returns>异步TCP服务器</returns>
|
||||
public AsyncTcpServer Start()
|
||||
{
|
||||
Debug.Log("start server");
|
||||
return Start(10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动服务器
|
||||
/// </summary>
|
||||
/// <param name="backlog">服务器所允许的挂起连接序列的最大长度</param>
|
||||
/// <returns>异步TCP服务器</returns>
|
||||
public AsyncTcpServer Start(int backlog)
|
||||
{
|
||||
if (IsRunning)
|
||||
return this;
|
||||
|
||||
IsRunning = true;
|
||||
|
||||
_listener.Start(backlog);
|
||||
ContinueAcceptTcpClient(_listener);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止服务器
|
||||
/// </summary>
|
||||
/// <returns>异步TCP服务器</returns>
|
||||
public AsyncTcpServer Stop()
|
||||
{
|
||||
if (!IsRunning)
|
||||
return this;
|
||||
|
||||
try
|
||||
{
|
||||
_listener.Stop();
|
||||
|
||||
foreach (var client in _clients.Values)
|
||||
{
|
||||
client.TcpClient.Client.Disconnect(false);
|
||||
}
|
||||
_clients.Clear();
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
|
||||
IsRunning = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ContinueAcceptTcpClient(TcpListener tcpListener)
|
||||
{
|
||||
try
|
||||
{
|
||||
tcpListener.BeginAcceptTcpClient(new AsyncCallback(HandleTcpClientAccepted), tcpListener);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Receive
|
||||
|
||||
private void HandleTcpClientAccepted(IAsyncResult ar)
|
||||
{
|
||||
if (!IsRunning)
|
||||
return;
|
||||
|
||||
TcpListener tcpListener = (TcpListener)ar.AsyncState;
|
||||
|
||||
TcpClient tcpClient = tcpListener.EndAcceptTcpClient(ar);
|
||||
if (!tcpClient.Connected)
|
||||
return;
|
||||
|
||||
byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
|
||||
SimpleProtocolFilter prot = new SimpleProtocolFilter();
|
||||
TcpClientState internalClient = new TcpClientState(tcpClient, buffer, prot);
|
||||
|
||||
// add client connection to cache
|
||||
string tcpClientKey = internalClient.TcpClient.Client.RemoteEndPoint.ToString();
|
||||
_clients.AddOrUpdate(tcpClientKey, internalClient, (n, o) =>
|
||||
{
|
||||
return internalClient;
|
||||
});
|
||||
RaiseClientConnected(tcpClient);
|
||||
|
||||
// begin to read data
|
||||
NetworkStream networkStream = internalClient.NetworkStream;
|
||||
ContinueReadBuffer(internalClient, networkStream);
|
||||
|
||||
// keep listening to accept next connection
|
||||
ContinueAcceptTcpClient(tcpListener);
|
||||
}
|
||||
|
||||
private void HandleDatagramReceived(IAsyncResult ar)
|
||||
{
|
||||
if (!IsRunning)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
TcpClientState internalClient = (TcpClientState)ar.AsyncState;
|
||||
if (!internalClient.TcpClient.Connected)
|
||||
return;
|
||||
|
||||
NetworkStream networkStream = internalClient.NetworkStream;
|
||||
|
||||
int numberOfReadBytes = 0;
|
||||
try
|
||||
{
|
||||
// if the remote host has shutdown its connection,
|
||||
// read will immediately return with zero bytes.
|
||||
numberOfReadBytes = networkStream.EndRead(ar);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
numberOfReadBytes = 0;
|
||||
}
|
||||
|
||||
if (numberOfReadBytes == 0)
|
||||
{
|
||||
// connection has been closed
|
||||
TcpClientState internalClientToBeThrowAway;
|
||||
string tcpClientKey = internalClient.TcpClient.Client.RemoteEndPoint.ToString();
|
||||
_clients.TryRemove(tcpClientKey, out internalClientToBeThrowAway);
|
||||
RaiseClientDisconnected(internalClient.TcpClient);
|
||||
return;
|
||||
}
|
||||
|
||||
// received byte and trigger event notification
|
||||
var receivedBytes = new byte[numberOfReadBytes];
|
||||
Array.Copy(internalClient.Buffer, 0, receivedBytes, 0, numberOfReadBytes);
|
||||
// input bytes into protofilter
|
||||
internalClient.Prot.input(receivedBytes);
|
||||
RaiseDatagramReceived(internalClient, receivedBytes);
|
||||
// RaisePlaintextReceived(internalClient.TcpClient, receivedBytes);
|
||||
|
||||
// continue listening for tcp datagram packets
|
||||
ContinueReadBuffer(internalClient, networkStream);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ContinueReadBuffer(TcpClientState internalClient, NetworkStream networkStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
networkStream.BeginRead(internalClient.Buffer, 0, internalClient.Buffer.Length, HandleDatagramReceived, internalClient);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// 接收到数据报文事件
|
||||
/// </summary>
|
||||
public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;
|
||||
/// <summary>
|
||||
/// 接收到数据报文明文事件
|
||||
/// </summary>
|
||||
public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;
|
||||
|
||||
private void RaiseDatagramReceived(TcpClientState sender, byte[] datagram)
|
||||
{
|
||||
if (DatagramReceived != null)
|
||||
{
|
||||
DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
|
||||
}
|
||||
}
|
||||
|
||||
private void RaisePlaintextReceived(TcpClientState sender, byte[] datagram)
|
||||
{
|
||||
if (PlaintextReceived != null)
|
||||
{
|
||||
PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 与客户端的连接已建立事件
|
||||
/// </summary>
|
||||
public event EventHandler<TcpClientConnectedEventArgs> ClientConnected;
|
||||
/// <summary>
|
||||
/// 与客户端的连接已断开事件
|
||||
/// </summary>
|
||||
public event EventHandler<TcpClientDisconnectedEventArgs> ClientDisconnected;
|
||||
|
||||
private void RaiseClientConnected(TcpClient tcpClient)
|
||||
{
|
||||
if (ClientConnected != null)
|
||||
{
|
||||
ClientConnected(this, new TcpClientConnectedEventArgs(tcpClient));
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseClientDisconnected(TcpClient tcpClient)
|
||||
{
|
||||
if (ClientDisconnected != null)
|
||||
{
|
||||
ClientDisconnected(this, new TcpClientDisconnectedEventArgs(tcpClient));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
private void GuardRunning()
|
||||
{
|
||||
if (!IsRunning)
|
||||
throw new InvalidProgramException("This TCP server has not been started yet.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至指定的客户端
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">客户端</param>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void Send(TcpClient tcpClient, byte[] datagram)
|
||||
{
|
||||
GuardRunning();
|
||||
|
||||
if (tcpClient == null)
|
||||
throw new ArgumentNullException("tcpClient");
|
||||
|
||||
if (datagram == null)
|
||||
throw new ArgumentNullException("datagram");
|
||||
|
||||
try
|
||||
{
|
||||
NetworkStream stream = tcpClient.GetStream();
|
||||
if (stream.CanWrite)
|
||||
{
|
||||
stream.BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至指定的客户端
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">客户端</param>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void Send(TcpClient tcpClient, string datagram)
|
||||
{
|
||||
Send(tcpClient, this.Encoding.GetBytes(datagram));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至所有客户端
|
||||
/// </summary>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void SendToAll(byte[] datagram)
|
||||
{
|
||||
GuardRunning();
|
||||
|
||||
foreach (var client in _clients.Values)
|
||||
{
|
||||
Send(client.TcpClient, datagram);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至所有客户端
|
||||
/// </summary>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void SendToAll(string datagram)
|
||||
{
|
||||
GuardRunning();
|
||||
|
||||
SendToAll(this.Encoding.GetBytes(datagram));
|
||||
}
|
||||
|
||||
private void HandleDatagramWritten(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至指定的客户端
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">客户端</param>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void SyncSend(TcpClient tcpClient, byte[] datagram)
|
||||
{
|
||||
GuardRunning();
|
||||
|
||||
if (tcpClient == null)
|
||||
throw new ArgumentNullException("tcpClient");
|
||||
|
||||
if (datagram == null)
|
||||
throw new ArgumentNullException("datagram");
|
||||
|
||||
try
|
||||
{
|
||||
NetworkStream stream = tcpClient.GetStream();
|
||||
if (stream.CanWrite)
|
||||
{
|
||||
stream.Write(datagram, 0, datagram.Length);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至指定的客户端
|
||||
/// </summary>
|
||||
/// <param name="tcpClient">客户端</param>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void SyncSend(TcpClient tcpClient, string datagram)
|
||||
{
|
||||
SyncSend(tcpClient, this.Encoding.GetBytes(datagram));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至所有客户端
|
||||
/// </summary>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void SyncSendToAll(byte[] datagram)
|
||||
{
|
||||
GuardRunning();
|
||||
|
||||
foreach (var client in _clients.Values)
|
||||
{
|
||||
SyncSend(client.TcpClient, datagram);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文至所有客户端
|
||||
/// </summary>
|
||||
/// <param name="datagram">报文</param>
|
||||
public void SyncSendToAll(string datagram)
|
||||
{
|
||||
GuardRunning();
|
||||
|
||||
SyncSendToAll(this.Encoding.GetBytes(datagram));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources;
|
||||
/// <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this._disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener = null;
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 595bd7c94c4f1f1438804b3e71092b67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Poco
|
||||
{
|
||||
public class UnityDumper : AbstractDumper
|
||||
{
|
||||
public override AbstractNode getRoot()
|
||||
{
|
||||
return new RootNode();
|
||||
}
|
||||
}
|
||||
|
||||
public class RootNode : AbstractNode
|
||||
{
|
||||
private List<AbstractNode> children = null;
|
||||
|
||||
public RootNode()
|
||||
{
|
||||
children = new List<AbstractNode>();
|
||||
foreach (GameObject obj in Transform.FindObjectsOfType(typeof(GameObject)))
|
||||
{
|
||||
if (obj.transform.parent == null)
|
||||
{
|
||||
children.Add(new UnityNode(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override List<AbstractNode> getChildren() //<Modified>
|
||||
{
|
||||
return children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 65abe57f902b50b49a41d812b12f9e13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Poco;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using TcpServer;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
public class VRSupport
|
||||
{
|
||||
private static Queue<Action> commands = new Queue<Action>();
|
||||
|
||||
public VRSupport()
|
||||
{
|
||||
commands = new Queue<Action>();
|
||||
}
|
||||
|
||||
public void ClearCommands()
|
||||
{
|
||||
commands.Clear();
|
||||
}
|
||||
|
||||
public void PeekCommand()
|
||||
{
|
||||
if (null != commands && commands.Count > 0)
|
||||
{
|
||||
Debug.Log("command executed " + commands.Count);
|
||||
commands.Peek()();
|
||||
}
|
||||
}
|
||||
|
||||
public object isVRSupported(List<object> param)
|
||||
{
|
||||
#if UNITY_3 || UNITY_4
|
||||
return false;
|
||||
#elif UNITY_5 || UNITY_2017_1
|
||||
return UnityEngine.VR.VRSettings.loadedDeviceName.Equals("CARDBOARD");
|
||||
#else
|
||||
return UnityEngine.XR.XRSettings.loadedDeviceName.Equals("CARDBOARD");
|
||||
#endif
|
||||
}
|
||||
|
||||
public object IsQueueEmpty(List<object> param)
|
||||
{
|
||||
Debug.Log("Checking queue");
|
||||
if (commands != null && commands.Count > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(1000); // we wait a bit and check again just in case we run in between calls
|
||||
if (commands != null && commands.Count > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return commands.Count;
|
||||
}
|
||||
|
||||
public object RotateObject(List<object> param)
|
||||
{
|
||||
var xRotation = Convert.ToSingle(param[0]);
|
||||
var yRotation = Convert.ToSingle(param[1]);
|
||||
var zRotation = Convert.ToSingle(param[2]);
|
||||
float speed = 0f;
|
||||
if (param.Count > 5)
|
||||
speed = Convert.ToSingle(param[5]);
|
||||
Vector3 mousePosition = new Vector3(xRotation, yRotation, zRotation);
|
||||
foreach (GameObject cameraContainer in GameObject.FindObjectsOfType<GameObject>())
|
||||
{
|
||||
if (cameraContainer.name.Equals(param[3]))
|
||||
{
|
||||
foreach (GameObject cameraFollower in GameObject.FindObjectsOfType<GameObject>())
|
||||
{
|
||||
if (cameraFollower.name.Equals(param[4]))
|
||||
{
|
||||
lock (commands)
|
||||
{
|
||||
commands.Enqueue(() => recoverOffset(cameraFollower, cameraContainer, speed));
|
||||
}
|
||||
|
||||
lock (commands)
|
||||
{
|
||||
var currentRotation = cameraContainer.transform.rotation;
|
||||
commands.Enqueue(() => rotate(cameraContainer, currentRotation, mousePosition, speed));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object ObjectLookAt(List<object> param)
|
||||
{
|
||||
float speed = 0f;
|
||||
if (param.Count > 3)
|
||||
speed = Convert.ToSingle(param[3]);
|
||||
foreach (GameObject toLookAt in GameObject.FindObjectsOfType<GameObject>())
|
||||
{
|
||||
if (toLookAt.name.Equals(param[0]))
|
||||
{
|
||||
foreach (GameObject cameraContainer in GameObject.FindObjectsOfType<GameObject>())
|
||||
{
|
||||
if (cameraContainer.name.Equals(param[1]))
|
||||
{
|
||||
foreach (GameObject cameraFollower in GameObject.FindObjectsOfType<GameObject>())
|
||||
{
|
||||
if (cameraFollower.name.Equals(param[2]))
|
||||
{
|
||||
lock (commands)
|
||||
{
|
||||
commands.Enqueue(() => recoverOffset(cameraFollower, cameraContainer, speed));
|
||||
}
|
||||
|
||||
lock (commands)
|
||||
{
|
||||
commands.Enqueue(() => objectLookAt(cameraContainer, toLookAt, speed));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void rotate(GameObject go, Quaternion originalRotation, Vector3 mousePosition, float speed)
|
||||
{
|
||||
Debug.Log("rotating");
|
||||
if (!RotateObject(originalRotation, mousePosition, go, speed))
|
||||
{
|
||||
lock (commands)
|
||||
{
|
||||
commands.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void objectLookAt(GameObject go, GameObject toLookAt, float speed)
|
||||
{
|
||||
Debug.Log("looking at " + toLookAt.name);
|
||||
Debug.Log("from " + go.name);
|
||||
if (!ObjectLookAtObject(toLookAt, go, speed))
|
||||
{
|
||||
lock (commands)
|
||||
{
|
||||
commands.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void recoverOffset(GameObject subcontainter, GameObject cameraContainer, float speed)
|
||||
{
|
||||
Debug.Log("recovering " + cameraContainer.name);
|
||||
if (!ObjectRecoverOffset(subcontainter, cameraContainer, speed))
|
||||
{
|
||||
lock (commands)
|
||||
{
|
||||
commands.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected bool RotateObject(Quaternion originalPosition, Vector3 mousePosition, GameObject cameraContainer, float rotationSpeed = 0.125f)
|
||||
{
|
||||
if (null == cameraContainer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var expectedx = originalPosition.eulerAngles.x + mousePosition.x;
|
||||
var expectedy = originalPosition.eulerAngles.y + mousePosition.y;
|
||||
var expectedz = originalPosition.eulerAngles.z + mousePosition.z;
|
||||
|
||||
var toRotation = Quaternion.Euler(new Vector3(expectedx, expectedy, expectedz));
|
||||
cameraContainer.transform.rotation = Quaternion.Lerp(cameraContainer.transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
|
||||
var angle = Quaternion.Angle(cameraContainer.transform.rotation, toRotation);
|
||||
|
||||
if (angle == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool ObjectLookAtObject(GameObject go, GameObject cameraContainer, float rotationSpeed = 0.125f)
|
||||
{
|
||||
if (null == go || null == cameraContainer)
|
||||
{
|
||||
Debug.Log("exception - item null");
|
||||
return false;
|
||||
}
|
||||
|
||||
var toRotation = Quaternion.LookRotation(go.transform.position - (cameraContainer.transform.localPosition));
|
||||
cameraContainer.transform.rotation = Quaternion.Lerp(cameraContainer.transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
|
||||
// It should not be needed but sometimes the difference of eurlerAngles might be small and this would ensure it works fine
|
||||
if (Quaternion.Angle(cameraContainer.transform.rotation, toRotation) == 0)
|
||||
{
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool ObjectRecoverOffset(GameObject subcontainer, GameObject cameraContainer, float rotationSpeed = 0.125f)
|
||||
{
|
||||
if (null == cameraContainer)
|
||||
{
|
||||
Debug.Log("exception - item null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// add offset with the camera
|
||||
var cameraRotation = Camera.main.transform.localRotation;
|
||||
|
||||
var toRotate = new Quaternion(-cameraRotation.x, -cameraRotation.y, -cameraRotation.z, cameraRotation.w);
|
||||
subcontainer.transform.localRotation = Quaternion.Lerp(subcontainer.transform.localRotation, toRotate, rotationSpeed * Time.deltaTime);
|
||||
|
||||
// It should not be needed but sometimes the difference of eurlerAngles might be small and this would ensure it works fine
|
||||
if (Quaternion.Angle(subcontainer.transform.localRotation, toRotate) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 84a1358e5fce5a04d8e96e749c5777cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c6dd171143e15049a1cbf0f40a3a82e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace Poco
|
||||
{
|
||||
public class AbstractDumper : IDumper
|
||||
{
|
||||
public virtual AbstractNode getRoot()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> dumpHierarchy()
|
||||
{
|
||||
return dumpHierarchyImpl(getRoot(), true);
|
||||
}
|
||||
public Dictionary<string, object> dumpHierarchy(bool onlyVisibleNode)
|
||||
{
|
||||
return dumpHierarchyImpl(getRoot(), onlyVisibleNode);
|
||||
}
|
||||
|
||||
private Dictionary<string, object> dumpHierarchyImpl(AbstractNode node, bool onlyVisibleNode)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<string, object> payload = node.enumerateAttrs();
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
string name = (string)node.getAttr("name");
|
||||
result.Add("name", name);
|
||||
result.Add("payload", payload);
|
||||
|
||||
List<object> children = new List<object>();
|
||||
foreach (AbstractNode child in node.getChildren())
|
||||
{
|
||||
if (!onlyVisibleNode || (bool)child.getAttr("visible"))
|
||||
{
|
||||
children.Add(dumpHierarchyImpl(child, onlyVisibleNode));
|
||||
}
|
||||
}
|
||||
if (children.Count > 0)
|
||||
{
|
||||
result.Add("children", children);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual List<float> getPortSize()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDumper
|
||||
{
|
||||
AbstractNode getRoot();
|
||||
|
||||
Dictionary<string, object> dumpHierarchy();
|
||||
Dictionary<string, object> dumpHierarchy(bool onlyVisibleNode);
|
||||
|
||||
List<float> getPortSize();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b2eef00c02b7e0d4da3c2ba9e68f207c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace Poco
|
||||
{
|
||||
public class AbstractNode
|
||||
{
|
||||
private List<string> requiredAttrs = new List<string> {
|
||||
"name",
|
||||
"type",
|
||||
"visible",
|
||||
"pos",
|
||||
"size",
|
||||
"scale",
|
||||
"anchorPoint",
|
||||
"zOrders"
|
||||
};
|
||||
|
||||
public virtual AbstractNode getParent ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual List<AbstractNode> getChildren ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual object getAttr (string attrName)
|
||||
{
|
||||
Dictionary<string, object> defaultAttrs = new Dictionary<string, object> () {
|
||||
{ "name", "<Root>" },
|
||||
{ "type", "Root" },
|
||||
{ "visible", true },
|
||||
{ "pos", new List<float> (){ 0.0f, 0.0f } },
|
||||
{ "size", new List<float> (){ 0.0f, 0.0f } },
|
||||
{ "scale", new List<float> (){ 1.0f, 1.0f } },
|
||||
{ "anchorPoint", new List<float> (){ 0.5f, 0.5f } },
|
||||
{ "zOrders", new Dictionary<string, object> (){ { "local", 0 }, { "global", 0 } } }
|
||||
};
|
||||
return defaultAttrs.ContainsKey (attrName) ? defaultAttrs [attrName] : null;
|
||||
}
|
||||
|
||||
public virtual void setAttr (string attrName, object val)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, object> enumerateAttrs ()
|
||||
{
|
||||
Dictionary<string, object> ret = new Dictionary<string, object> ();
|
||||
foreach (string attr in requiredAttrs) {
|
||||
ret.Add (attr, getAttr (attr));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 80a9c1bd292d4184faf21890b350b53d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 856f0a97e1121c943ad31245cb0b6545
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,533 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
|
||||
|
||||
namespace Poco
|
||||
{
|
||||
public class UnityNode : AbstractNode
|
||||
{
|
||||
public static Dictionary<string, string> TypeNames = new Dictionary<string, string>() {
|
||||
{ "Text", "Text" },
|
||||
{ "Gradient Text", "Gradient.Text" },
|
||||
{ "Image", "Image" },
|
||||
{ "RawImage", "Raw.Image" },
|
||||
{ "Mask", "Mask" },
|
||||
{ "2DRectMask", "2D-Rect.Mask" },
|
||||
{ "Button", "Button" },
|
||||
{ "InputField", "InputField" },
|
||||
{ "Toggle", "Toggle" },
|
||||
{ "Toggle Group", "ToggleGroup" },
|
||||
{ "Slider", "Slider" },
|
||||
{ "ScrollBar", "ScrollBar" },
|
||||
{ "DropDown", "DropDown" },
|
||||
{ "ScrollRect", "ScrollRect" },
|
||||
{ "Selectable", "Selectable" },
|
||||
{ "Camera", "Camera" },
|
||||
{ "RectTransform", "Node" },
|
||||
{ "TextMeshProUGUI","TMPROUGUI" },
|
||||
{ "TMP_Text","TMPRO" },
|
||||
};
|
||||
public static string DefaultTypeName = "GameObject";
|
||||
private GameObject gameObject;
|
||||
private Renderer renderer;
|
||||
private RectTransform rectTransform;
|
||||
private Rect rect;
|
||||
private Vector2 objectPos;
|
||||
private List<string> components;
|
||||
private Camera camera;
|
||||
|
||||
|
||||
public UnityNode(GameObject obj)
|
||||
{
|
||||
gameObject = obj;
|
||||
camera = Camera.main;
|
||||
foreach (var cam in Camera.allCameras)
|
||||
{
|
||||
// skip the main camera
|
||||
// we want to use specified camera first then fallback to main camera if no other cameras
|
||||
// for further advanced cases, we could test whether the game object is visible within the camera
|
||||
if (cam == Camera.main)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((cam.cullingMask & (1 << gameObject.layer)) != 0)
|
||||
{
|
||||
camera = cam;
|
||||
}
|
||||
}
|
||||
|
||||
renderer = gameObject.GetComponent<Renderer>();
|
||||
rectTransform = gameObject.GetComponent<RectTransform>();
|
||||
rect = GameObjectRect(renderer, rectTransform);
|
||||
objectPos = renderer ? WorldToGUIPoint(camera, renderer.bounds.center) : Vector2.zero;
|
||||
components = GameObjectAllComponents();
|
||||
}
|
||||
|
||||
public override AbstractNode getParent()
|
||||
{
|
||||
GameObject parentObj = gameObject.transform.parent.gameObject;
|
||||
return new UnityNode(parentObj);
|
||||
}
|
||||
|
||||
public override List<AbstractNode> getChildren()
|
||||
{
|
||||
List<AbstractNode> children = new List<AbstractNode>();
|
||||
foreach (Transform child in gameObject.transform)
|
||||
{
|
||||
children.Add(new UnityNode(child.gameObject));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
public override object getAttr(string attrName)
|
||||
{
|
||||
switch (attrName)
|
||||
{
|
||||
case "name":
|
||||
return gameObject.name;
|
||||
case "type":
|
||||
return GuessObjectTypeFromComponentNames(components);
|
||||
case "visible":
|
||||
return GameObjectVisible(renderer, components);
|
||||
case "pos":
|
||||
return GameObjectPosInScreen(objectPos, renderer, rectTransform, rect);
|
||||
case "size":
|
||||
return GameObjectSizeInScreen(rect, rectTransform);
|
||||
case "scale":
|
||||
return new List<float>() { 1.0f, 1.0f };
|
||||
case "anchorPoint":
|
||||
return GameObjectAnchorInScreen(renderer, rect, objectPos);
|
||||
case "zOrders":
|
||||
return GameObjectzOrders();
|
||||
case "clickable":
|
||||
return GameObjectClickable(components);
|
||||
case "text":
|
||||
return GameObjectText();
|
||||
case "components":
|
||||
return components;
|
||||
case "texture":
|
||||
return GetImageSourceTexture();
|
||||
case "tag":
|
||||
return GameObjectTag();
|
||||
case "layer":
|
||||
return GameObjectLayerName();
|
||||
case "_ilayer":
|
||||
return GameObjectLayer();
|
||||
case "_instanceId":
|
||||
return gameObject.GetInstanceID();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override Dictionary<string, object> enumerateAttrs()
|
||||
{
|
||||
Dictionary<string, object> payload = GetPayload();
|
||||
Dictionary<string, object> ret = new Dictionary<string, object>();
|
||||
foreach (KeyValuePair<string, object> p in payload)
|
||||
{
|
||||
if (p.Value != null)
|
||||
{
|
||||
ret.Add(p.Key, p.Value);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private Dictionary<string, object> GetPayload()
|
||||
{
|
||||
Dictionary<string, object> payload = new Dictionary<string, object>() {
|
||||
{ "name", gameObject.name },
|
||||
{ "type", GuessObjectTypeFromComponentNames (components) },
|
||||
{ "visible", GameObjectVisible (renderer, components) },
|
||||
{ "pos", GameObjectPosInScreen (objectPos, renderer, rectTransform, rect) },
|
||||
{ "size", GameObjectSizeInScreen (rect, rectTransform) },
|
||||
{ "scale", new List<float> (){ 1.0f, 1.0f } },
|
||||
{ "anchorPoint", GameObjectAnchorInScreen (renderer, rect, objectPos) },
|
||||
{ "zOrders", GameObjectzOrders () },
|
||||
{ "clickable", GameObjectClickable (components) },
|
||||
{ "text", GameObjectText () },
|
||||
{ "components", components },
|
||||
{ "texture", GetImageSourceTexture () },
|
||||
{ "tag", GameObjectTag () },
|
||||
{ "_ilayer", GameObjectLayer() },
|
||||
{ "layer", GameObjectLayerName() },
|
||||
{ "_instanceId", gameObject.GetInstanceID () },
|
||||
};
|
||||
return payload;
|
||||
}
|
||||
|
||||
private string GuessObjectTypeFromComponentNames(List<string> components)
|
||||
{
|
||||
List<string> cns = new List<string>(components);
|
||||
cns.Reverse();
|
||||
foreach (string name in cns)
|
||||
{
|
||||
if (TypeNames.ContainsKey(name))
|
||||
{
|
||||
return TypeNames[name];
|
||||
}
|
||||
}
|
||||
return DefaultTypeName;
|
||||
}
|
||||
|
||||
private bool GameObjectVisible(Renderer renderer, List<string> components)
|
||||
{
|
||||
if (gameObject.activeInHierarchy)
|
||||
{
|
||||
bool light = components.Contains("Light");
|
||||
// bool mesh = components.Contains ("MeshRenderer") && components.Contains ("MeshFilter");
|
||||
bool particle = components.Contains("ParticleSystem") && components.Contains("ParticleSystemRenderer");
|
||||
if (light || particle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return renderer ? renderer.isVisible : true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int GameObjectLayer()
|
||||
{
|
||||
return gameObject.layer;
|
||||
}
|
||||
private string GameObjectLayerName()
|
||||
{
|
||||
return LayerMask.LayerToName(gameObject.layer);
|
||||
}
|
||||
|
||||
private bool GameObjectClickable(List<string> components)
|
||||
{
|
||||
Button button = gameObject.GetComponent<Button>();
|
||||
return button ? button.isActiveAndEnabled : false;
|
||||
}
|
||||
|
||||
private string GameObjectText()
|
||||
{
|
||||
TMP_Text tmpText = gameObject.GetComponent<TMP_Text>();
|
||||
if (tmpText)
|
||||
{
|
||||
return tmpText.GetParsedText();
|
||||
}
|
||||
TextMeshProUGUI tmpUIText = gameObject.GetComponent<TextMeshProUGUI>();
|
||||
if (tmpUIText)
|
||||
{
|
||||
return tmpUIText.GetParsedText();
|
||||
}
|
||||
Text text = gameObject.GetComponent<Text>();
|
||||
return text ? text.text : null;
|
||||
}
|
||||
|
||||
private string GameObjectTag()
|
||||
{
|
||||
string tag;
|
||||
try
|
||||
{
|
||||
tag = !gameObject.CompareTag("Untagged") ? gameObject.tag : null;
|
||||
}
|
||||
catch (UnityException)
|
||||
{
|
||||
tag = null;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
private List<string> GameObjectAllComponents()
|
||||
{
|
||||
List<string> components = new List<string>();
|
||||
Component[] allComponents = gameObject.GetComponents<Component>();
|
||||
if (allComponents != null)
|
||||
{
|
||||
foreach (Component ac in allComponents)
|
||||
{
|
||||
if (ac != null)
|
||||
{
|
||||
components.Add(ac.GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
private Dictionary<string, float> GameObjectzOrders()
|
||||
{
|
||||
float CameraViewportPoint = 0;
|
||||
if (camera != null)
|
||||
{
|
||||
CameraViewportPoint = Math.Abs(camera.WorldToViewportPoint(gameObject.transform.position).z);
|
||||
}
|
||||
Dictionary<string, float> zOrders = new Dictionary<string, float>() {
|
||||
{ "global", 0f },
|
||||
{ "local", -1 * CameraViewportPoint }
|
||||
};
|
||||
return zOrders;
|
||||
}
|
||||
|
||||
private Rect GameObjectRect(Renderer renderer, RectTransform rectTransform)
|
||||
{
|
||||
Rect rect = new Rect(0, 0, 0, 0);
|
||||
if (renderer)
|
||||
{
|
||||
rect = RendererToScreenSpace(camera, renderer);
|
||||
}
|
||||
else if (rectTransform)
|
||||
{
|
||||
rect = RectTransformToScreenSpace(rectTransform);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
private float[] GameObjectPosInScreen(Vector3 objectPos, Renderer renderer, RectTransform rectTransform, Rect rect)
|
||||
{
|
||||
float[] pos = { 0f, 0f };
|
||||
|
||||
if (renderer)
|
||||
{
|
||||
// 3d object
|
||||
pos[0] = objectPos.x / (float)Screen.width;
|
||||
pos[1] = objectPos.y / (float)Screen.height;
|
||||
}
|
||||
else if (rectTransform)
|
||||
{
|
||||
// ui object (rendered on screen space, other render modes may be different)
|
||||
// use center pos for now
|
||||
Canvas rootCanvas = GetRootCanvas(gameObject);
|
||||
RenderMode renderMode = rootCanvas != null ? rootCanvas.renderMode : new RenderMode();
|
||||
switch (renderMode)
|
||||
{
|
||||
case RenderMode.ScreenSpaceCamera:
|
||||
//上一个方案经过实际测试发现还有两个问题存在
|
||||
//1.在有Canvas Scaler修改了RootCanvas的Scale的情况下坐标的抓取仍然不对,影响到了ScreenSpaceCameram模式在不同分辨率和屏幕比例下识别的兼容性。
|
||||
//2.RectTransformUtility转的 rectTransform.transform.position本质上得到的是RectTransform.pivot中心轴在屏幕上的坐标,如果pivot不等于(0.5,0.5),
|
||||
//那么获取到的position就不等于图形的中心点。
|
||||
//试了一晚上,找到了解决办法。
|
||||
|
||||
//用MainCanvas转一次屏幕坐标
|
||||
Vector2 position = RectTransformUtility.WorldToScreenPoint(rootCanvas.worldCamera, rectTransform.transform.position);
|
||||
//注意: 这里的position其实是Pivot点在Screen上的坐标,并不是图形意义上的中心点,在经过下列玄学公式换算才是真的图形中心在屏幕的位置。
|
||||
//公式内算上了rootCanvas.scaleFactor 缩放因子,经测试至少在Canvas Scaler.Expand模式下,什么分辨率和屏幕比都抓的很准,兼容性很强,其他的有待测试。
|
||||
//由于得出来的坐标是左下角为原点,触控输入是左上角为原点,所以要上下反转一下Poco才能用,所以y坐标用Screen.height减去。
|
||||
position.Set(
|
||||
position.x - rectTransform.rect.width * rootCanvas.scaleFactor * (rectTransform.pivot.x - 0.5f),
|
||||
Screen.height - (position.y - rectTransform.rect.height * rootCanvas.scaleFactor * (rectTransform.pivot.y - 0.5f))
|
||||
);
|
||||
pos[0] = position.x / Screen.width;
|
||||
pos[1] = position.y / Screen.height;
|
||||
break;
|
||||
case RenderMode.WorldSpace:
|
||||
Vector2 _pos = RectTransformUtility.WorldToScreenPoint(rootCanvas.worldCamera, rectTransform.transform.position);
|
||||
pos[0] = _pos.x / Screen.width;
|
||||
pos[1] = (Screen.height - _pos.y) / Screen.height;
|
||||
break;
|
||||
default:
|
||||
pos[0] = rect.center.x / (float)Screen.width;
|
||||
pos[1] = rect.center.y / (float)Screen.height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private Canvas GetRootCanvas(GameObject gameObject)
|
||||
{
|
||||
Canvas canvas = gameObject.GetComponentInParent<Canvas>();
|
||||
// 如果unity版本小于unity5.5,就用递归的方式取吧,没法直接取rootCanvas
|
||||
// 如果有用到4.6以下版本的话就自己手动在这里添加条件吧
|
||||
#if UNITY_4_6 || UNITY_4_7 || UNITY_4_8 || UNITY_4_9 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_4
|
||||
if (canvas && canvas.isRootCanvas)
|
||||
{
|
||||
return canvas;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gameObject.transform.parent.gameObject != null)
|
||||
{
|
||||
return GetRootCanvas(gameObject.transform.parent.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (canvas && canvas.isRootCanvas)
|
||||
{
|
||||
return canvas;
|
||||
}
|
||||
else if (canvas)
|
||||
{
|
||||
return canvas.rootCanvas;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private float[] GameObjectSizeInScreen(Rect rect, RectTransform rectTransform)
|
||||
{
|
||||
float[] size = { 0f, 0f };
|
||||
if (rectTransform)
|
||||
{
|
||||
Canvas rootCanvas = GetRootCanvas(gameObject);
|
||||
RenderMode renderMode = rootCanvas != null ? rootCanvas.renderMode : new RenderMode();
|
||||
switch (renderMode)
|
||||
{
|
||||
case RenderMode.ScreenSpaceCamera:
|
||||
Rect _rect = RectTransformUtility.PixelAdjustRect(rectTransform, rootCanvas);
|
||||
size = new float[] {
|
||||
_rect.width * rootCanvas.scaleFactor / (float)Screen.width,
|
||||
_rect.height * rootCanvas.scaleFactor / (float)Screen.height
|
||||
};
|
||||
break;
|
||||
case RenderMode.WorldSpace:
|
||||
Rect rect_ = rectTransform.rect;
|
||||
RectTransform canvasTransform = rootCanvas.GetComponent<RectTransform>();
|
||||
size = new float[] { rect_.width / canvasTransform.rect.width, rect_.height / canvasTransform.rect.height };
|
||||
break;
|
||||
default:
|
||||
size = new float[] { rect.width / (float)Screen.width, rect.height / (float)Screen.height };
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size = new float[] { rect.width / (float)Screen.width, rect.height / (float)Screen.height };
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private float[] GameObjectAnchorInScreen(Renderer renderer, Rect rect, Vector3 objectPos)
|
||||
{
|
||||
float[] defaultValue = { 0.5f, 0.5f };
|
||||
if (rectTransform)
|
||||
{
|
||||
Vector2 data = rectTransform.pivot;
|
||||
defaultValue[0] = data[0];
|
||||
defaultValue[1] = 1 - data[1];
|
||||
return defaultValue;
|
||||
}
|
||||
if (!renderer)
|
||||
{
|
||||
//<Modified> some object do not have renderer
|
||||
return defaultValue;
|
||||
}
|
||||
float[] anchor = { (objectPos.x - rect.xMin) / rect.width, (objectPos.y - rect.yMin) / rect.height };
|
||||
if (Double.IsNaN(anchor[0]) || Double.IsNaN(anchor[1]))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else if (Double.IsPositiveInfinity(anchor[0]) || Double.IsPositiveInfinity(anchor[1]))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else if (Double.IsNegativeInfinity(anchor[0]) || Double.IsNegativeInfinity(anchor[1]))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return anchor;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetImageSourceTexture()
|
||||
{
|
||||
Image image = gameObject.GetComponent<Image>();
|
||||
if (image != null && image.sprite != null)
|
||||
{
|
||||
return image.sprite.name;
|
||||
}
|
||||
|
||||
RawImage rawImage = gameObject.GetComponent<RawImage>();
|
||||
if (rawImage != null && rawImage.texture != null)
|
||||
{
|
||||
return rawImage.texture.name;
|
||||
}
|
||||
|
||||
SpriteRenderer spriteRenderer = gameObject.GetComponent<SpriteRenderer>();
|
||||
if (spriteRenderer != null && spriteRenderer.sprite != null)
|
||||
{
|
||||
return spriteRenderer.sprite.name;
|
||||
}
|
||||
|
||||
Renderer render = gameObject.GetComponent<Renderer>();
|
||||
if (renderer != null && renderer.material != null)
|
||||
{
|
||||
return renderer.material.color.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static Vector2 WorldToGUIPoint(Camera camera, Vector3 world)
|
||||
{
|
||||
Vector2 screenPoint = Vector2.zero;
|
||||
if (camera != null)
|
||||
{
|
||||
screenPoint = camera.WorldToScreenPoint(world);
|
||||
screenPoint.y = (float)Screen.height - screenPoint.y;
|
||||
}
|
||||
return screenPoint;
|
||||
}
|
||||
|
||||
protected static Rect RendererToScreenSpace(Camera camera, Renderer renderer)
|
||||
{
|
||||
Vector3 cen = renderer.bounds.center;
|
||||
Vector3 ext = renderer.bounds.extents;
|
||||
Vector2[] extentPoints = new Vector2[8] {
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x - ext.x, cen.y - ext.y, cen.z - ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x + ext.x, cen.y - ext.y, cen.z - ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x - ext.x, cen.y - ext.y, cen.z + ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x + ext.x, cen.y - ext.y, cen.z + ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x - ext.x, cen.y + ext.y, cen.z - ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x + ext.x, cen.y + ext.y, cen.z - ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x - ext.x, cen.y + ext.y, cen.z + ext.z)),
|
||||
WorldToGUIPoint (camera, new Vector3 (cen.x + ext.x, cen.y + ext.y, cen.z + ext.z))
|
||||
};
|
||||
Vector2 min = extentPoints[0];
|
||||
Vector2 max = extentPoints[0];
|
||||
foreach (Vector2 v in extentPoints)
|
||||
{
|
||||
min = Vector2.Min(min, v);
|
||||
max = Vector2.Max(max, v);
|
||||
}
|
||||
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
|
||||
}
|
||||
|
||||
protected static Rect RectTransformToScreenSpace(RectTransform rectTransform)
|
||||
{
|
||||
Vector2 size = Vector2.Scale(rectTransform.rect.size, rectTransform.lossyScale);
|
||||
Rect rect = new Rect(rectTransform.position.x, Screen.height - rectTransform.position.y, size.x, size.y);
|
||||
rect.x -= (rectTransform.pivot.x * size.x);
|
||||
rect.y -= ((1.0f - rectTransform.pivot.y) * size.y);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static bool SetText(GameObject go, string textVal)
|
||||
{
|
||||
if (go != null)
|
||||
{
|
||||
var inputField = go.GetComponent<InputField>();
|
||||
if (inputField != null)
|
||||
{
|
||||
inputField.text = textVal;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ec219b907235df84eaba2f80047cf662
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Define_list:
|
||||
- SDK_POCO
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.0.1
|
||||
Loading…
Reference in New Issue