From a6bd4ea7a55ce652b936d65b4e8d9e56f9cb8097 Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Fri, 6 Feb 2026 14:16:08 -0600 Subject: [PATCH] Added a basic event system over RPC. Added events OnPointerUp, OnPointerDown, OnPointerEnter, OnPointerExit, OnPointerClick, OnDestroy Added the ability to allow or block events on children from firing on the parent. --- CommunityEntity.UI.Events.cs | 189 +++++++++++++++++++++++++++++++++++ CommunityEntity.UI.cs | 47 ++++++++- Tests/EventsTest.json | 73 ++++++++++++++ 3 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 CommunityEntity.UI.Events.cs create mode 100644 Tests/EventsTest.json diff --git a/CommunityEntity.UI.Events.cs b/CommunityEntity.UI.Events.cs new file mode 100644 index 0000000..98729a6 --- /dev/null +++ b/CommunityEntity.UI.Events.cs @@ -0,0 +1,189 @@ +using UnityEngine; +using UnityEngine.EventSystems; +using Application = Rust.Application; + +public partial class CommunityEntity +{ + private enum CUiEventType + { + OnDestroy, + OnPointerUp, + OnPointerDown, + OnPointerEnter, + OnPointerExit, + OnPointerClick + } + +#if SERVER + [RPC_Server] + public void CUIEventRPC(RPCMessage rpc) + { + CUiEventType type = (CUiEventType)rpc.read.Int32(); + string name = rpc.read.String(); + switch (type) + { + case CUiEventType.OnDestroy: + Hook_OnDestroy(rpc.player, name); + break; + case CUiEventType.OnPointerEnter: + Hook_OnPointerEnter(rpc.player, name); + break; + case CUiEventType.OnPointerExit: + Hook_OnPointerExit(rpc.player, name); + break; + case CUiEventType.OnPointerUp: + { + PointerEventData.InputButton button = (PointerEventData.InputButton)rpc.read.Int32(); + Vector2 position = rpc.read.Vector3(); + Hook_OnPointerUp(rpc.player, name, button, position); + break; + } + case CUiEventType.OnPointerDown: + { + PointerEventData.InputButton button = (PointerEventData.InputButton)rpc.read.Int32(); + Vector2 position = rpc.read.Vector3(); + Hook_OnPointerDown(rpc.player, name, button, position); + break; + } + case CUiEventType.OnPointerClick: + { + PointerEventData.InputButton button = (PointerEventData.InputButton)rpc.read.Int32(); + Vector2 position = rpc.read.Vector3(); + Hook_OnPointerClick(rpc.player, name, button, position); + break; + } + } + } + + private void Hook_OnDestroy(BasePlayer player, string name) + { + + } + + private void Hook_OnPointerEnter(BasePlayer player, string name) + { + + } + + private void Hook_OnPointerExit(BasePlayer player, string name) + { + + } + + private void Hook_OnPointerUp(BasePlayer player, string name, PointerEventData.InputButton button, Vector2 position) + { + + } + + private void Hook_OnPointerDown(BasePlayer player, string name, PointerEventData.InputButton button, Vector2 position) + { + + } + + private void Hook_OnPointerClick(BasePlayer player, string name, PointerEventData.InputButton button, Vector2 position) + { + + } +#endif + +#if CLIENT + + private abstract class BaseEventHandler : MonoBehaviour + { + private RectTransform _rectTransform; + public bool allowChildren; + protected const string RPC = "CUIEventRPC"; + + private void Start() + { + _rectTransform = GetComponent(); + } + + public void SetAllowChildren(bool allowChildren) => this.allowChildren = allowChildren; + protected bool CanSendEvent(GameObject go) => allowChildren || go == gameObject; + + /// + /// Returns the local position of the event relative to the bottom left corner of the element + /// + protected Vector2 GetLocalPosition(PointerEventData eventData) + { + RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTransform, eventData.position, eventData.pressEventCamera, out Vector2 localPoint); + + // Subtract the rect's x and y (which are relative to the pivot) + // to get the position starting from (0,0) at the bottom-left. + float bottomLeftX = localPoint.x - _rectTransform.rect.x; + float bottomLeftY = localPoint.y - _rectTransform.rect.y; + + Vector2 finalPos = new Vector2(bottomLeftX, bottomLeftY); + return finalPos; + } + } + + private class OnDestroyEventHandler : BaseEventHandler + { + private void OnDestroy() + { + //Only fire the event if we are in-game and not quitting + if (Client.IsIngame && !Application.isQuitting) + { + ClientInstance?.ServerRPC(RPC, (int)CUiEventType.OnDestroy, gameObject.name); + } + } + } + + private class PointerUpEventHandler : BaseEventHandler, IPointerUpHandler + { + public void OnPointerUp(PointerEventData eventData) + { + if (CanSendEvent(eventData.rawPointerPress)) + { + ClientInstance.ServerRPC(RPC, (int)CUiEventType.OnPointerUp, gameObject.name, (int)eventData.button, (Vector3)GetLocalPosition(eventData)); + } + } + } + + private class PointerDownEventHandler : BaseEventHandler, IPointerDownHandler + { + public void OnPointerDown(PointerEventData eventData) + { + if (CanSendEvent(eventData.pointerPressRaycast.gameObject)) + { + ClientInstance.ServerRPC(RPC, (int)CUiEventType.OnPointerDown, gameObject.name, (int)eventData.button, (Vector3)GetLocalPosition(eventData)); + } + } + } + + private class PointerEnterEventHandler : BaseEventHandler, IPointerEnterHandler + { + public void OnPointerEnter(PointerEventData eventData) + { + if (CanSendEvent(eventData.pointerEnter)) + { + ClientInstance.ServerRPC(RPC, (int)CUiEventType.OnPointerEnter, gameObject.name); + } + } + } + + private class PointerExitEventHandler : BaseEventHandler, IPointerExitHandler + { + public void OnPointerExit(PointerEventData eventData) + { + if (CanSendEvent(eventData.pointerEnter)) + { + ClientInstance.ServerRPC(RPC, (int)CUiEventType.OnPointerExit, gameObject.name); + } + } + } + + private class PointerClickEventHandler : BaseEventHandler, IPointerClickHandler + { + public void OnPointerClick(PointerEventData eventData) + { + if (CanSendEvent(eventData.rawPointerPress)) + { + ClientInstance.ServerRPC(RPC, (int)CUiEventType.OnPointerClick, gameObject.name, (int)eventData.button, (Vector3)GetLocalPosition(eventData)); + } + } + } +#endif +} \ No newline at end of file diff --git a/CommunityEntity.UI.cs b/CommunityEntity.UI.cs index b49933c..2c4f237 100644 --- a/CommunityEntity.UI.cs +++ b/CommunityEntity.UI.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using UnityEngine.UI; using System; using System.Collections; @@ -813,6 +813,51 @@ T GetOrAddComponent() where T : Component scrollRect.verticalNormalizedPosition = obj.GetFloat("verticalNormalizedPosition", 0f); break; } + + case "UnityEngine.Events": + { + // Allow the event to be fired on children of this GameObject + bool allowChildren = obj.GetBoolean("allowChildren", false); + void AddOrRemoveEvent(bool add) where T : BaseEventHandler + { + if (add) + GetOrAddComponent().SetAllowChildren(allowChildren); + else if (go.TryGetComponent(out T comp)) + Destroy(comp); + } + + if (obj.TryGetBoolean("onPointerUp", out bool addEvent)) + { + AddOrRemoveEvent(addEvent); + } + + if (obj.TryGetBoolean("onPointerDown", out addEvent)) + { + AddOrRemoveEvent(addEvent); + } + + if (obj.TryGetBoolean("onPointerEnter", out addEvent)) + { + AddOrRemoveEvent(addEvent); + } + + if (obj.TryGetBoolean("onPointerExit", out addEvent)) + { + AddOrRemoveEvent(addEvent); + } + + if (obj.TryGetBoolean("onPointerClick", out addEvent)) + { + AddOrRemoveEvent(addEvent); + } + + if (obj.TryGetBoolean("onDestroy", out addEvent)) + { + AddOrRemoveEvent(addEvent); + } + + break; + } } } diff --git a/Tests/EventsTest.json b/Tests/EventsTest.json new file mode 100644 index 0000000..f87750b --- /dev/null +++ b/Tests/EventsTest.json @@ -0,0 +1,73 @@ +[ + { + "name": "Events_Panel1", + "parent": "Overlay", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "0 1 0 1" + }, + { + "type": "RectTransform", + "anchormin": "0 1", + "anchormax": "0 1", + "offsetmin": "20 -200", + "offsetmax": "500 -15" + }, + { + "type": "UnityEngine.Events", + "allowChildren": 0, + "onPointerUp": 1, + "onPointerDown": 1, + "onPointerEnter": 1, + "onPointerExit": 1, + "onPointerClick": 1, + "onDestroy": 0 + } + ] + }, + { + "name": "Events_Panel2", + "parent": "Events_Panel1", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "0 0 1 1" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "20 -200", + "offsetmax": "500 -15" + }, + { + "type": "UnityEngine.Events", + "allowChildren": 1, + "onPointerUp": 1, + "onPointerDown": 1, + "onPointerEnter": 1, + "onPointerExit": 1, + "onPointerClick": 1, + "onDestroy": 1 + } + ] + }, + { + "name": "Events_Panel3", + "parent": "Events_Panel2", + "components": [ + { + "type": "UnityEngine.UI.Image", + "color": "1 0 1 1" + }, + { + "type": "RectTransform", + "anchormin": "0.5 0.5", + "anchormax": "0.5 0.5", + "offsetmin": "20 -200", + "offsetmax": "500 -15" + } + ] + } +] \ No newline at end of file