From 322d986090b289155b353d7cf1a1c18a07b7ecd2 Mon Sep 17 00:00:00 2001 From: Bjoern Esswein <4-Bjoern@users.noreply.git.esswe.in> Date: Mon, 5 Feb 2024 19:18:30 +0100 Subject: [PATCH] added drag and drop capability --- Runtime/BrowserView.cs | 137 ++++++++++++++++-- Runtime/ChromeDevtools/Browser.cs | 23 +-- Runtime/ChromeDevtools/BrowserTab.cs | 63 ++++++-- Runtime/ChromeDevtools/DevtoolsWebsocket.cs | 1 - .../Protocol/DevtoolsCommand.cs | 4 +- .../Protocol/DevtoolsProtocolHandler.cs | 22 ++- .../ChromeDevtools/Protocol/Input/Input.cs | 88 ++++++++++- Runtime/JsonConverters.cs | 60 ++++++++ Runtime/JsonConverters.cs.meta | 11 ++ 9 files changed, 359 insertions(+), 50 deletions(-) create mode 100644 Runtime/JsonConverters.cs create mode 100644 Runtime/JsonConverters.cs.meta diff --git a/Runtime/BrowserView.cs b/Runtime/BrowserView.cs index 8a91b2d..e504004 100644 --- a/Runtime/BrowserView.cs +++ b/Runtime/BrowserView.cs @@ -1,12 +1,47 @@ using ChromeDevTools; +using ChromeDevTools.Protocol.Input; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; using System.Collections; +using UnityEditor.Experimental.GraphView; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; +using UnityEngine.UIElements; [RequireComponent(typeof(RawImage))] -public class BrowserView : MonoBehaviour, IPointerDownHandler, IPointerMoveHandler, IPointerUpHandler +public class BrowserView : MonoBehaviour, IPointerDownHandler, IPointerMoveHandler, IPointerUpHandler, IPointerEnterHandler, IDropHandler, IPointerExitHandler { + #region json serializer + /// <summary> + /// JsonSerializer for the user space objects (e.g. transfering objects that have been droped on the BrowserView) + /// Users are allowed to change serializer settings to their liking. + /// </summary> + public static JsonSerializer serializer; + /// <summary> + /// Json serializer settings for the user space objects (e.g. transfering objects that have been droped on the BrowserView) + /// Users are allowed to change serializer settings to their liking. + /// </summary> + public static JsonSerializerSettings serializerSettings; + static BrowserView() + { + // initialize the JsonSerializer + serializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new JsonConverter[] + { + new ColorConverter(), + new Vector2Converter(), + new Vector3Converter(), + new Vector4Converter() + } + }; + serializer = JsonSerializer.Create(serializerSettings); + } + #endregion json serializer + private Browser browser; private BrowserTab tab; @@ -37,8 +72,8 @@ public class BrowserView : MonoBehaviour, IPointerDownHandler, IPointerMoveHandl //StartCoroutine(createScreenshots()); StartCoroutine(tab.StartStream(900, 560, (frame) => { - Debug.Log("update texture"); rawImage.texture = frame; + rawImage.SetNativeSize(); })); })); @@ -59,36 +94,104 @@ public class BrowserView : MonoBehaviour, IPointerDownHandler, IPointerMoveHandl } + #region pointer event handlers + public void OnPointerDown(PointerEventData eventData) { - Vector2 pos; - if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out pos) - && rectTransform.rect.Contains(pos)) - { - tab.OnPointerDown(pos, eventData); - } + Vector2Int pos = toBrowserCoordinates(eventData.position); + tab.OnPointerDown(pos, eventData); } public void OnPointerMove(PointerEventData eventData) { - Vector2 pos; - if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.enterEventCamera, out pos) - && rectTransform.rect.Contains(pos)) + // TODO: transform eventData delta vector to browser coordinates + Vector2Int pos = toBrowserCoordinates(eventData.position); + tab.OnPointerMove(pos, eventData); + + // On drag over + if (eventData.dragging) { - tab.OnPointerMove(pos, eventData); + Debug.LogWarning($"OnDragOver: {eventData.position}"); + createDragEvent(DragEventType.dragOver, eventData); } } public void OnPointerUp(PointerEventData eventData) { - Vector2 pos; - if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out pos) - && rectTransform.rect.Contains(pos)) + Vector2Int pos = toBrowserCoordinates(eventData.position); + tab.OnPointerUp(pos, eventData); + } + #endregion pointer event handlers + + #region drag event handlers + public void OnPointerEnter(PointerEventData eventData) + { + if (eventData.dragging) { - tab.OnPointerUp(pos, eventData); + Debug.LogWarning($"OnDragEnter: {eventData.position}"); + createDragEvent(DragEventType.dragEnter, eventData); } } + // TODO: OnDragMove -> PointerMove + + public void OnDrop(PointerEventData eventData) + { + Debug.LogWarning($"OnDrop: {eventData.position}"); + createDragEvent(DragEventType.drop, eventData); + } + + public void OnPointerExit(PointerEventData eventData) + { + if (eventData.dragging) + { + Debug.LogWarning($"OnDragLeave: {eventData.position}"); + //createDragEvent(DragEventType.dragCancel, eventData); + tab.CancelDragging(); + // TODO: drag cancel seems to be ignored by the browser + } + } + + private void createDragEvent(DragEventType dragEventType, PointerEventData eventData) + { + if (eventData.pointerDrag.TryGetComponent(out BrowserDropable dropable)) + { + var position = toBrowserCoordinates(eventData.position); + var dragEvent = new dispatchDragEvent + { + type = dragEventType, + data = new DragData + { + items = new DragDataItem[] + { + new DragDataItem + { + mimeType = "application/json", + data = JsonConvert.SerializeObject(dropable, serializerSettings) + } + }, + dragOperationsMask = DragOperationsMask.Copy + }, + x = position.x, + y = position.y, + }; + Debug.LogWarning($"DragEvent: {dragEvent.type}, {eventData.position}, '{dragEvent.data.items[0].data}'"); + // send the DragEvent as drag event to the browser + tab.OnDragNDrop(dragEvent); + } + } + #endregion drag event handlers + + private Vector2Int toBrowserCoordinates(Vector2 eventPos) + { + // invert y because the browser has y=0 on the top + Vector2 invertedPos = new Vector2(eventPos.x, rectTransform.rect.size.y - eventPos.y); + // TODO: fix coordinate transformation, maybe use image size instead of rectTransform, if possible + Vector2 browserCoorinate = tab.size / rectTransform.rect.size * invertedPos; + Debug.Log($"eventPos: {eventPos}, invertedPos: {invertedPos}, browserCoordinate: {browserCoorinate}"); + return new Vector2Int((int) browserCoorinate.x, (int) browserCoorinate.y); + } + private void OnDisable() { // TODO: do we want to close the browser when not in use? @@ -110,3 +213,5 @@ public class BrowserView : MonoBehaviour, IPointerDownHandler, IPointerMoveHandl browser.Close(); } } + +public interface BrowserDropable { } diff --git a/Runtime/ChromeDevtools/Browser.cs b/Runtime/ChromeDevtools/Browser.cs index b57c646..3d2e563 100644 --- a/Runtime/ChromeDevtools/Browser.cs +++ b/Runtime/ChromeDevtools/Browser.cs @@ -21,9 +21,14 @@ namespace ChromeDevTools public static bool headless = true; private const int debugPort = 9222; - /* JsonSerializer */ - public static JsonSerializer serializer; - public static JsonSerializerSettings serializerSettings; + /// <summary> + /// Json serializer for the devtools connection + /// </summary> + public static JsonSerializer devtoolsSerializer; + /// <summary> + /// Json serializer settings for the devtools connection + /// </summary> + public static JsonSerializerSettings devtoolsSerializerSettings; public static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -46,11 +51,11 @@ namespace ChromeDevTools static Browser() { // initialize the JsonSerializer - var camelCasePropertyNamesContractResolver = new CamelCasePropertyNamesContractResolver(); - serializer = new JsonSerializer(); - serializer.ContractResolver = camelCasePropertyNamesContractResolver; - serializerSettings = new JsonSerializerSettings(); - serializerSettings.ContractResolver = camelCasePropertyNamesContractResolver; + devtoolsSerializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + }; + devtoolsSerializer = JsonSerializer.Create(devtoolsSerializerSettings); } /** @@ -113,7 +118,7 @@ namespace ChromeDevTools { yield return DevToolsApiRequest(true, $"/json/new?{targetUrl}", (response) => { - PageTargetInfo pageTarget = JsonConvert.DeserializeObject<PageTargetInfo>(response, serializerSettings); + PageTargetInfo pageTarget = JsonConvert.DeserializeObject<PageTargetInfo>(response, devtoolsSerializerSettings); callback(new BrowserTab(pageTarget)); }); diff --git a/Runtime/ChromeDevtools/BrowserTab.cs b/Runtime/ChromeDevtools/BrowserTab.cs index 589a535..b2787f8 100644 --- a/Runtime/ChromeDevtools/BrowserTab.cs +++ b/Runtime/ChromeDevtools/BrowserTab.cs @@ -13,6 +13,12 @@ namespace ChromeDevTools { private PageTargetInfo pageTarget; private DevtoolsProtocolHandler devtools; + + /// <summary> + /// width and height of the brower device + /// </summary> + public Vector2Int size { get; private set; } + public BrowserTab(PageTargetInfo pageTarget) { this.pageTarget = pageTarget; @@ -46,7 +52,7 @@ namespace ChromeDevTools }); } - public IEnumerator StartStream(int width, int height, Action<Texture2D> callback) + public IEnumerator StartStream(int maxWidth, int maxHeight, Action<Texture2D> callback) { // register screencast frame event handler // TODO: deregister on stop stream @@ -57,7 +63,10 @@ namespace ChromeDevTools frameAck.sessionId = frameEvent.sessionId; _ = devtools.SendCommandAsync(frameAck); - Debug.Log($"screencast frame, '{frameEvent.sessionId}'"); + size = new Vector2Int(frameEvent.metadata.deviceWidth, frameEvent.metadata.deviceHeight); + + Debug.Log($"screencast frame, '{frameEvent.sessionId}'; size: {size}, pageScaleFactor: {frameEvent.metadata.pageScaleFactor}"); + // parse the base64 encoded frame to a texture var frameTexture = new Texture2D(1, 1); // new Texture2D only works on the main thread @@ -68,40 +77,68 @@ namespace ChromeDevTools }; var startScreencast = new startScreencast(); - startScreencast.MaxWidth = width; - startScreencast.maxHeight = height; + startScreencast.MaxWidth = maxWidth; + startScreencast.maxHeight = maxHeight; startScreencast.everyNthFrame = 1; return devtools.SendCommand(startScreencast); } - public void OnPointerDown(Vector2 position, PointerEventData eventData) + public void OnPointerDown(Vector2Int position, PointerEventData eventData) { - Debug.Log($"PointerDown {position}:\n{eventData}"); + //Debug.Log($"PointerDown {position}:\n{eventData}"); // TODO: compensate stream and texture scaling - var mousePressedEvent = new dispatchMouseEvent(MouseEventType.MousePressed, (int)position.x, (int)position.y, eventData); + var mousePressedEvent = new dispatchMouseEvent(MouseEventType.MousePressed, position.x, position.y, eventData); + //var mousePressedEvent = new dispatchMouseEvent(MouseEventType.MousePressed, (int)eventData.position.x, size.y - (int)eventData.position.y, eventData); _ = devtools.SendCommandAsync(mousePressedEvent); } - internal void OnPointerMove(Vector2 position, PointerEventData eventData) + public void OnPointerMove(Vector2Int position, PointerEventData eventData) { - Debug.Log($"OnPointerMove {position}:\n{eventData}"); + //Debug.Log($"OnPointerMove {position}:\n{eventData}"); // TODO: compensate stream and texture scaling - var mousePressedEvent = new dispatchMouseEvent(MouseEventType.MouseMoved, (int)position.x, (int)position.y, eventData); + var mousePressedEvent = new dispatchMouseEvent(MouseEventType.MouseMoved, position.x, position.y, eventData); + //var mousePressedEvent = new dispatchMouseEvent(MouseEventType.MouseMoved, (int)eventData.position.x, size.y - (int)eventData.position.y, eventData); _ = devtools.SendCommandAsync(mousePressedEvent); } - public void OnPointerUp(Vector2 position, PointerEventData eventData) + public void OnPointerUp(Vector2Int position, PointerEventData eventData) { - Debug.Log($"OnPointerUp {position}:\n{eventData}"); + //Debug.Log($"OnPointerUp {position}:\n{eventData}"); // TODO: compensate stream and texture scaling - var mouseReleasedEvent = new dispatchMouseEvent(MouseEventType.MouseReleased, (int)position.x, (int)position.y, eventData); + var mouseReleasedEvent = new dispatchMouseEvent(MouseEventType.MouseReleased, position.x, position.y, eventData); + //var mouseReleasedEvent = new dispatchMouseEvent(MouseEventType.MouseReleased, (int)eventData.position.x, size.y - (int)eventData.position.y, eventData); _ = devtools.SendCommandAsync(mouseReleasedEvent); } + public void OnDrop(Vector2Int position, DragData dragData) + { + // TODO: compensate stream and texture scaling + var dropEvent = new dispatchDragEvent + { + type = DragEventType.drop, + data = dragData, + x = position.x, + y = position.y, + }; + + _ = devtools.SendCommandAsync(dropEvent); + } + + public void OnDragNDrop(dispatchDragEvent dragEvent) + { + // TODO: compensate stream and texture scaling + _ = devtools.SendCommandAsync(dragEvent); + } + + public void CancelDragging() + { + _ = devtools.SendCommandAsync(new cancelDragging()); + } + /** * close this tab */ diff --git a/Runtime/ChromeDevtools/DevtoolsWebsocket.cs b/Runtime/ChromeDevtools/DevtoolsWebsocket.cs index 83a7c02..f0f6183 100644 --- a/Runtime/ChromeDevtools/DevtoolsWebsocket.cs +++ b/Runtime/ChromeDevtools/DevtoolsWebsocket.cs @@ -95,7 +95,6 @@ namespace ChromeDevTools { throw new DevtoolsConnectionClosedException(); } - Debug.Log($"ws send: '{command}'"); return ws.SendAsync(Encoding.UTF8.GetBytes(command), WebSocketMessageType.Text, true, cancellationTokenSource.Token); } diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs index bddb4d0..47a25d8 100644 --- a/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsCommand.cs @@ -7,8 +7,8 @@ namespace ChromeDevTools /// public class DevtoolsCommandWrapper<T> where T: IDevtoolsCommand { - private static long LAST_ID = 0; - public long Id { get; } = ++LAST_ID; + //private static long LAST_ID = 0; + public long Id { get; set; } // = ++LAST_ID; public string Method { get diff --git a/Runtime/ChromeDevtools/Protocol/DevtoolsProtocolHandler.cs b/Runtime/ChromeDevtools/Protocol/DevtoolsProtocolHandler.cs index 9ff4aa5..ef7f3f7 100644 --- a/Runtime/ChromeDevtools/Protocol/DevtoolsProtocolHandler.cs +++ b/Runtime/ChromeDevtools/Protocol/DevtoolsProtocolHandler.cs @@ -15,6 +15,8 @@ namespace ChromeDevTools /// </summary> public class DevtoolsProtocolHandler { + private static long LAST_COMMAND_ID = 0; + private IDevtoolsConnection devtools; private ConcurrentDictionary<long, ResponseTypeAndCallback> commandResponseDict = new ConcurrentDictionary<long, ResponseTypeAndCallback>(); @@ -92,7 +94,7 @@ namespace ChromeDevTools } // deserialize the result - IDevtoolsResponse commandResponse = (IDevtoolsResponse) response.Result.ToObject(responseTypeAndCallback.responseType, Browser.serializer); + IDevtoolsResponse commandResponse = (IDevtoolsResponse) response.Result.ToObject(responseTypeAndCallback.responseType, Browser.devtoolsSerializer); // pass the response to the callback responseTypeAndCallback.callback(commandResponse); @@ -106,10 +108,10 @@ namespace ChromeDevTools switch (ev.Method) { case "Page.screencastFrame": - screencastFrameEventHandler?.Invoke( ev.Params.ToObject<screencastFrameEvent>(Browser.serializer) ); + screencastFrameEventHandler?.Invoke( ev.Params.ToObject<screencastFrameEvent>(Browser.devtoolsSerializer) ); break; case "Page.screencastVisibilityChanged": - screencastVisibilityChangedEventHandler?.Invoke( ev.Params.ToObject<screencastVisibilityChangedEvent>(Browser.serializer) ); + screencastVisibilityChangedEventHandler?.Invoke( ev.Params.ToObject<screencastVisibilityChangedEvent>(Browser.devtoolsSerializer) ); break; default: throw new UnexpectedMessageException($"Event of type '{ev}' is not implemented"); @@ -155,7 +157,10 @@ namespace ChromeDevTools public async Task SendCommandAsync<T>(T command, Action<IDevtoolsResponse> callback) where T : IDevtoolsCommandWithResponse { // apply the message wrapper - var wrappedCommand = new DevtoolsCommandWrapper<T>(command); + var wrappedCommand = new DevtoolsCommandWrapper<T>(command) + { + Id = ++LAST_COMMAND_ID + }; // get the response type from the commands attribute CommandResponseAttribute cra = (CommandResponseAttribute)Attribute.GetCustomAttribute( @@ -176,7 +181,7 @@ namespace ChromeDevTools } // json serialize the command and send it - var json = JsonConvert.SerializeObject(wrappedCommand, Browser.serializerSettings); + var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings); Debug.Log($"ws send: '{json}'"); await devtools.SendCommandAsync(json); } @@ -189,10 +194,13 @@ namespace ChromeDevTools public async Task SendCommandAsync<T>(T command) where T : IDevtoolsCommand { // apply the message wrapper - var wrappedCommand = new DevtoolsCommandWrapper<T>(command); + var wrappedCommand = new DevtoolsCommandWrapper<T>(command) + { + Id = ++LAST_COMMAND_ID + }; // json serialize the command and send it - var json = JsonConvert.SerializeObject(wrappedCommand, Browser.serializerSettings); + var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings); Debug.Log($"ws send: '{json}'"); await devtools.SendCommandAsync(json); } diff --git a/Runtime/ChromeDevtools/Protocol/Input/Input.cs b/Runtime/ChromeDevtools/Protocol/Input/Input.cs index f5bb061..08d5ec1 100644 --- a/Runtime/ChromeDevtools/Protocol/Input/Input.cs +++ b/Runtime/ChromeDevtools/Protocol/Input/Input.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json.Converters; using System; using Newtonsoft.Json.Serialization; using UnityEngine.EventSystems; -using UnityEditor.UI; using static UnityEngine.EventSystems.PointerEventData; namespace ChromeDevTools @@ -12,6 +11,7 @@ namespace ChromeDevTools { namespace Input { + #region dispatchMouseEvent /// <summary> /// Dispatches a mouse event to the page. /// </summary> @@ -58,7 +58,7 @@ namespace ChromeDevTools /// Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0). /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public int? modifiers { get; set; } + public ModifierKeyFlags? modifiers { get; set; } /// <summary> /// Time at which the event occurred. /// TimeSinceEpoch UTC time in seconds, counted from January 1, 1970. @@ -147,6 +147,90 @@ namespace ChromeDevTools { Mouse, Pen } + #endregion dispatchMouseEvent + + #region dispatchDragEvent + + /// <summary> + /// Cancels any active dragging in the page. + /// </summary> + public class cancelDragging : IDevtoolsCommand + { + + } + + /// <summary> + /// Dispatches a drag event into the page. + /// </summary> + public class dispatchDragEvent : IDevtoolsCommand + { + /// <summary> + /// Type of the drag event. + /// Allowed Values: dragEnter, dragOver, drop, dragCancel + /// </summary> + [JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy))] + public DragEventType type { get; set; } + public int x { get; set; } + public int y { get; set; } + public DragData data { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public ModifierKeyFlags? modifiers { get; set; } + + } + + public enum DragEventType + { + dragEnter, dragOver, drop, dragCancel + } + + public class DragData + { + public DragDataItem[] items { get; set; } + /// <summary> + /// List of filenames that should be included when dropping + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string[]? files { get; set; } + public DragOperationsMask dragOperationsMask { get; set; } + } + + [Flags] + public enum DragOperationsMask + { + Copy = 1, Link = 2, Move = 16 + } + + public class DragDataItem + { + /// <summary> + /// Mime type of the dragged data. + /// </summary> + public string mimeType { get; set; } = ""; + /// <summary> + /// Depending of the value of mimeType, it contains the dragged link, text, HTML markup or any other data. + /// </summary> + public string data { get; set; } = ""; + /// <summary> + /// Title associated with a link. Only valid when mimeType == "text/uri-list". + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? title { get; set; } = null; + /// <summary> + /// Stores the base URL for the contained markup. Only valid when mimeType == "text/html". + /// </summary> + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? baseURL { get; set; } = null; + } + #endregion dispatchDragEvent + + /// <summary> + /// Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0). + /// </summary> + [Flags] + public enum ModifierKeyFlags + { + None = 0, Alt = 1, Ctrl = 2, Meta_Command = 4, Shift = 8 + } } } } \ No newline at end of file diff --git a/Runtime/JsonConverters.cs b/Runtime/JsonConverters.cs new file mode 100644 index 0000000..988d394 --- /dev/null +++ b/Runtime/JsonConverters.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using UnityEngine; +// Solutions to prevent serialization errors. Seen in https://forum.unity.com/threads/jsonserializationexception-self-referencing-loop-detected.1264253/ +// Newtonsoft struggles serializing structs like Vector3 because it has a property .normalized +// that references Vector3, and thus entering a self-reference loop throwing circular reference error. +// Add the class to BootstrapJsonParser +public class ColorConverter : JsonConverter<Color> +{ + public override void WriteJson(JsonWriter writer, Color value, JsonSerializer serializer) + { + JObject obj = new JObject() { ["r"] = value.r, ["g"] = value.g, ["b"] = value.b, ["a"] = value.a }; + obj.WriteTo(writer); + } + public override Color ReadJson(JsonReader reader, Type objectType, Color existingValue, bool hasExistingValue, JsonSerializer serializer) + { + JObject obj = JObject.Load(reader); + return new Color((float)obj.GetValue("r"), (float)obj.GetValue("g"), (float)obj.GetValue("b"), (float)obj.GetValue("a")); + } +} +public class Vector2Converter : JsonConverter<Vector2> +{ + public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) + { + JObject obj = new JObject() { ["x"] = value.x, ["y"] = value.y }; + obj.WriteTo(writer); + } + public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) + { + JObject obj = JObject.Load(reader); + return new Vector2((float)obj.GetValue("x"), (float)obj.GetValue("y")); + } +} +public class Vector3Converter : JsonConverter<Vector3> +{ + public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer) + { + JObject obj = new JObject() { ["x"] = value.x, ["y"] = value.y, ["z"] = value.z }; + obj.WriteTo(writer); + } + public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer) + { + JObject obj = JObject.Load(reader); + return new Vector3((float)obj.GetValue("x"), (float)obj.GetValue("y"), (float)obj.GetValue("z")); + } +} +public class Vector4Converter : JsonConverter<Vector4> +{ + public override void WriteJson(JsonWriter writer, Vector4 value, JsonSerializer serializer) + { + JObject obj = new JObject() { ["x"] = value.x, ["y"] = value.y, ["z"] = value.z, ["w"] = value.w }; + obj.WriteTo(writer); + } + public override Vector4 ReadJson(JsonReader reader, Type objectType, Vector4 existingValue, bool hasExistingValue, JsonSerializer serializer) + { + JObject obj = JObject.Load(reader); + return new Vector4((float)obj.GetValue("x"), (float)obj.GetValue("y"), (float)obj.GetValue("z"), (float)obj.GetValue("w")); + } +} diff --git a/Runtime/JsonConverters.cs.meta b/Runtime/JsonConverters.cs.meta new file mode 100644 index 0000000..e03d094 --- /dev/null +++ b/Runtime/JsonConverters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 212e3789f1271d042bc798f031d45f58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: -- GitLab