using System; using System.Linq; using System.Reflection; using JetBrains.Annotations; using UnityEngine; using VRC.Udon.Common.Interfaces; using System.Diagnostics; using UnityEngine.Serialization; using VRC.Dynamics; #if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE using VRC.SDK3.Persistence; #endif using VRC.SDKBase; using VRC.SDK3.Data; using VRC.Udon.Serialization.OdinSerializer; namespace UdonSharp { public abstract class UdonSharpBehaviour : MonoBehaviour, ISerializationCallbackReceiver, ISupportsPrefabSerialization { // Stubs for the UdonBehaviour functions that emulate Udon behavior /// /// Gets a field from the target UdonSharpBehaviour /// [PublicAPI] public object GetProgramVariable(string name) { FieldInfo variableField = GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (variableField == null) return null; return variableField.GetValue(this); } /// /// Sets a field on the target UdonSharpBehaviour. /// Make sure you are setting a value on the behaviour with a compatible type to the given , or you may run into unexpected crashes. /// [PublicAPI] public void SetProgramVariable(string name, object value) { FieldInfo variableField = GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (variableField == null) return; FieldChangeCallbackAttribute fieldChangeCallback = variableField.GetCustomAttribute(); if (fieldChangeCallback != null) { PropertyInfo targetProperty = variableField.DeclaringType.GetProperty(fieldChangeCallback.CallbackPropertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (targetProperty == null) return; MethodInfo setMethod = targetProperty.GetSetMethod(true); if (setMethod == null) return; setMethod.Invoke(this, new object[] { value }); } else { variableField.SetValue(this, value); } } /// /// Calls the method with on the target UdonSharpBehaviour. The target method must be public and have no parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// /// Name of the method to call [PublicAPI] public void SendCustomEvent(string eventName) { #if UNITY_EDITOR if (_udonSharpBackingUdonBehaviour != null) // If this is a proxy, we need to check if this is a valid call to SendCustomEvent, since animation events can call it when they shouldn't { StackFrame frame = new StackFrame(1); // Get the frame of the calling method // If the calling method is null, this has been called from native code which indicates it was called by Unity, which we don't want on proxies if (frame.GetMethod() == null) return; } #endif MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 0); if (eventMethod != null) { eventMethod.Invoke(this, new object[] { }); } } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have no parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName) { SendCustomEvent(eventName); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have one parameter. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 1); eventMethod?.Invoke(this, new object[] { parameter0 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have two parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 2); eventMethod?.Invoke(this, new object[] { parameter0, parameter1 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have three parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 3); eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have four parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 4); eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 5); eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4, object parameter5) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 6); eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4, parameter5 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4, object parameter5, object parameter6) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 7); eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 }); } /// /// Sends a networked call to the method with on the target UdonSharpBehaviour. The target method must be public and have five parameters. /// The method is allowed to return a value, but the return value will not be accessible via this method. /// Methods with an underscore as their first character will not be callable via SendCustomNetworkEvent, unless they have a [NetworkCallable] attribute.. /// /// Whether to send this event to only the owner of the target behaviour's GameObject, or to everyone in the instance /// Name of the method to call [PublicAPI] public void SendCustomNetworkEvent(NetworkEventTarget target, string eventName, object parameter0, object parameter1, object parameter2, object parameter3, object parameter4, object parameter5, object parameter6, object parameter7) { MethodInfo eventMethod = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(e => e.Name == eventName && e.GetParameters().Length == 8); eventMethod?.Invoke(this, new object[] { parameter0, parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 }); } /// /// Executes target event after delaySeconds. If 0.0 delaySeconds is specified, will execute the following frame /// /// /// /// [PublicAPI] public void SendCustomEventDelayedSeconds(string eventName, float delaySeconds, VRC.Udon.Common.Enums.EventTiming eventTiming = VRC.Udon.Common.Enums.EventTiming.Update) { } /// /// Executes target event after delayFrames have passed. If 0 frames is specified, will execute the following frame. In effect 0 frame delay and 1 fame delay are the same on this method. /// /// /// /// [PublicAPI] public void SendCustomEventDelayedFrames(string eventName, int delayFrames, VRC.Udon.Common.Enums.EventTiming eventTiming = VRC.Udon.Common.Enums.EventTiming.Update) { } /// /// Disables Interact events on this UdonBehaviour and disables the interact outline on the object this is attached to /// [PublicAPI] public bool DisableInteractive { get; set; } /// /// Access the text that shows up on interactable tooltips /// [PublicAPI] public string InteractionText { get; set; } [Obsolete("This method is obsolete, use Object.Instantiate(gameObject) instead")] protected static GameObject VRCInstantiate(GameObject original) { return Instantiate(original); } /// /// Requests a network serialization of the target UdonSharpBehaviour. /// This will only function if the UdonSharpBehaviour is set to Manual sync mode and the person calling RequestSerialization() is the owner of the object. /// [PublicAPI] public void RequestSerialization() { } // Stubs for builtin UdonSharp methods to get type info private static long GetUdonTypeID(System.Type type) { return Internal.UdonSharpInternalUtility.GetTypeID(type); } /// /// Returns the unique ID of the UdonBehavior user type. Will return 0 if the UdonBehavior has no ID, which usually means that it's a graph program. /// /// [PublicAPI] public long GetUdonTypeID() { return GetUdonTypeID(GetType()); } /// /// Gets the type ID for the given T, usually used for checking UdonSharpBehaviour type equality /// [PublicAPI] public static long GetUdonTypeID() where T : UdonSharpBehaviour { return GetUdonTypeID(typeof(T)); } private static string GetUdonTypeName(System.Type type) { return Internal.UdonSharpInternalUtility.GetTypeName(type); } [PublicAPI] public string GetUdonTypeName() { return GetUdonTypeName(GetType()); } [PublicAPI] public static string GetUdonTypeName() where T : UdonSharpBehaviour { return GetUdonTypeName(typeof(T)); } // Method stubs for auto completion [PublicAPI] public virtual void PostLateUpdate() { } [PublicAPI] public virtual void Interact() { } [PublicAPI] public virtual void OnAvatarChanged(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnAvatarEyeHeightChanged(VRC.SDKBase.VRCPlayerApi player, float prevEyeHeightAsMeters) { } [PublicAPI] public virtual void OnDrop() { } [PublicAPI] public virtual void OnOwnershipTransferred(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnMasterTransferred(VRC.SDKBase.VRCPlayerApi newMaster) { } [PublicAPI] public virtual void OnPickup() { } [PublicAPI] public virtual void OnPickupUseDown() { } [PublicAPI] public virtual void OnPickupUseUp() { } [PublicAPI] public virtual void OnPlayerJoined(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerLeft(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnSpawn() { } [PublicAPI] public virtual void OnStationEntered(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnStationExited(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnVideoEnd() { } [PublicAPI] public virtual void OnVideoError(VRC.SDK3.Components.Video.VideoError videoError) { } [PublicAPI] public virtual void OnVideoLoop() { } [PublicAPI] public virtual void OnVideoPause() { } [PublicAPI] public virtual void OnVideoPlay() { } [PublicAPI] public virtual void OnVideoReady() { } [PublicAPI] public virtual void OnVideoStart() { } [PublicAPI] public virtual void OnPreSerialization() { } [PublicAPI] public virtual void OnDeserialization() { } [PublicAPI] public virtual void OnDeserialization(VRC.Udon.Common.DeserializationResult result) { } #if VRC_ENABLE_PLAYER_PERSISTENCE [PublicAPI] public virtual void OnPlayerDataUpdated(VRC.SDKBase.VRCPlayerApi player, VRC.SDK3.Persistence.PlayerData.Info[] infos) { } #endif [PublicAPI] public virtual void OnPlayerRestored(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerTriggerEnter(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerTriggerExit(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerTriggerStay(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerCollisionEnter(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerCollisionExit(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerCollisionStay(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPlayerParticleCollision(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnControllerColliderHitPlayer(VRC.SDK3.ControllerColliderPlayerHit hit) { } [PublicAPI] public virtual void OnPlayerRespawn(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnImageLoadSuccess(VRC.SDK3.Image.IVRCImageDownload result) { } [PublicAPI] public virtual void OnImageLoadError(VRC.SDK3.Image.IVRCImageDownload result) { } [PublicAPI] public virtual void OnStringLoadSuccess(VRC.SDK3.StringLoading.IVRCStringDownload result) { } [PublicAPI] public virtual void OnStringLoadError(VRC.SDK3.StringLoading.IVRCStringDownload result) { } [PublicAPI] public virtual void OnPlayerSuspendChanged(VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnDroneTriggerEnter(VRC.SDKBase.VRCDroneApi drone) { } [PublicAPI] public virtual void OnDroneTriggerExit(VRC.SDKBase.VRCDroneApi drone) { } [PublicAPI] public virtual void OnDroneTriggerStay(VRC.SDKBase.VRCDroneApi drone) { } [PublicAPI] public virtual void OnPostSerialization(VRC.Udon.Common.SerializationResult result) { } [PublicAPI] public virtual bool OnOwnershipRequest(VRC.SDKBase.VRCPlayerApi requestingPlayer, VRC.SDKBase.VRCPlayerApi requestedOwner) => true; #region Creator Economy [PublicAPI] public virtual void OnPurchaseConfirmed(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player, bool purchasedNow) { } [PublicAPI] public virtual void OnPurchaseConfirmedMultiple(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player, bool purchasedNow, int quantity) { } [PublicAPI] public virtual void OnPurchaseExpired(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnPurchasesLoaded(VRC.Economy.IProduct[] products, VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnProductEvent(VRC.Economy.IProduct product, VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnListPurchases(VRC.Economy.IProduct[] products, VRC.SDKBase.VRCPlayerApi player) { } [PublicAPI] public virtual void OnListAvailableProducts(VRC.Economy.IProduct[] products) { } [PublicAPI] public virtual void OnListProductOwners(VRC.Economy.IProduct product, string[] owners) { } #endregion [PublicAPI] public virtual void MidiNoteOn(int channel, int number, int velocity) { } [PublicAPI] public virtual void MidiNoteOff(int channel, int number, int velocity) { } [PublicAPI] public virtual void MidiControlChange(int channel, int number, int value) { } [PublicAPI] public virtual void InputJump(bool value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputUse(bool value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputGrab(bool value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputDrop(bool value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputMoveHorizontal(float value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputMoveVertical(float value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputLookHorizontal(float value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void InputLookVertical(float value, VRC.Udon.Common.UdonInputEventArgs args) { } [PublicAPI] public virtual void OnInputMethodChanged(VRC.SDKBase.VRCInputMethod inputMethod) { } [PublicAPI] public virtual void OnLanguageChanged(string language) { } [PublicAPI] public virtual void OnAsyncGpuReadbackComplete(VRC.SDK3.Rendering.VRCAsyncGPUReadbackRequest request) { } [PublicAPI] public virtual void OnVRCCameraSettingsChanged(VRC.SDK3.Rendering.VRCCameraSettings cameraSettings) { } [PublicAPI] public virtual void OnVRCQualitySettingsChanged() { } [PublicAPI] public virtual void OnScreenUpdate(VRC.SDK3.Platform.ScreenUpdateData data) {} [PublicAPI] public virtual void OnVRCPlusMassGift(VRC.SDKBase.VRCPlayerApi gifter, int numGifts) { } [PublicAPI] public virtual void OnPhysBoneGrabbed(PhysBoneGrabbedInfo physBoneInfo) { } [PublicAPI] public virtual void OnPhysBoneReleased(PhysBoneReleasedInfo physBoneInfo) { } [PublicAPI] public virtual void OnPhysBonePosed(PhysBonePosedInfo physBoneInfo) { } [PublicAPI] public virtual void OnPhysBoneUnPosed(PhysBoneUnPosedInfo physBoneInfo) { } [PublicAPI] public virtual void OnContactEnter(ContactEnterInfo contactInfo) { } [PublicAPI] public virtual void OnContactExit(ContactExitInfo contactInfo) { } #if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE [PublicAPI] public virtual void OnPersistenceUsageUpdated() {} #endif #if VRC_ENABLE_PLAYER_PERSISTENCE [PublicAPI] public virtual void OnPlayerDataStorageExceeded(VRCPlayerApi player) {} [PublicAPI] public virtual void OnPlayerDataStorageWarning(VRCPlayerApi player) {} [PublicAPI] public virtual void OnPlayerObjectStorageExceeded(VRCPlayerApi player) {} [PublicAPI] public virtual void OnPlayerObjectStorageWarning(VRCPlayerApi player) {} #endif [Obsolete("The OnStationEntered() event is deprecated use the OnStationEntered(VRCPlayerApi player) event instead", true)] public virtual void OnStationEntered() { } [Obsolete("The OnStationExited() event is deprecated use the OnStationExited(VRCPlayerApi player) event instead", true)] public virtual void OnStationExited() { } [Obsolete("The OnOwnershipTransferred() event is deprecated use the OnOwnershipTransferred(VRCPlayerApi player) event instead", true)] public virtual void OnOwnershipTransferred() { } // Used for tracking serialization data in editor // Also allows serializing user data on U# behaviours that is otherwise not supported by Unity, so stuff like jagged arrays and more complex collection types. [SerializeField, HideInInspector] private SerializationData serializationData; [SerializeField, HideInInspector, UsedImplicitly] private VRC.Udon.UdonBehaviour _udonSharpBackingUdonBehaviour; // Used to preserve backing behaviour on Reset, paste component value calls, and preset applications // http://answers.unity.com/answers/1754330/view.html private VRC.Udon.UdonBehaviour _backingUdonBehaviourDump; void ISerializationCallbackReceiver.OnBeforeSerialize() { UnitySerializationUtility.SerializeUnityObject(this, ref serializationData); _backingUdonBehaviourDump = _udonSharpBackingUdonBehaviour; } void ISerializationCallbackReceiver.OnAfterDeserialize() { UnitySerializationUtility.DeserializeUnityObject(this, ref serializationData); if (_backingUdonBehaviourDump) { _udonSharpBackingUdonBehaviour = _backingUdonBehaviourDump; } } SerializationData ISupportsPrefabSerialization.SerializationData { get => serializationData; set => serializationData = value; } #pragma warning disable CS0414 // Referenced via reflection [UsedImplicitly] private static bool _skipEvents; #pragma warning restore CS0414 [UsedImplicitly] private static bool ShouldSkipEvents() => _skipEvents; } }