-
-
Save TigerHix/18e9f20152c0cfac38fd5528c7af16b6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Cysharp.Threading.Tasks; | |
using UnityEngine; | |
using Warudo.Core; | |
using Warudo.Core.Attributes; | |
using Warudo.Core.Graphs; | |
using Warudo.Core.Utils; | |
using Warudo.Plugins.Core.Assets; | |
using Warudo.Plugins.Core.Assets.Character; | |
using Warudo.Plugins.Interactions.Mixins; | |
using Object = UnityEngine.Object; | |
namespace Warudo.Plugins.Interactions.Nodes { | |
[NodeType(Id = "07594cda-42b6-48ee-ac27-e6700ac8c251", Title = "THROW_PROP_AT_CHARACTER", Category = "CATEGORY_INTERACTIONS")] | |
public class ThrowPropAtCharacterNode : Node { | |
[DataInput] | |
[Label("PROP_SOURCE")] | |
[PreviewGallery] | |
[AutoCompleteResource("Prop")] | |
public string PropSource; | |
[DataInput] | |
[Label("IMPACT_PARTICLE_SOURCE")] | |
[PreviewGallery] | |
[AutoCompleteResource("Particle")] | |
public string ImpactParticleSource; | |
[DataInput] | |
[Label("LAUNCH_SOUND_SOURCE")] | |
[AutoCompleteResource("Sound")] | |
public string LaunchSoundSource; | |
[DataInput] | |
[Label("IMPACT_SOUND_SOURCE")] | |
[AutoCompleteResource("Sound")] | |
public string ImpactSoundSource; | |
[DataInput] | |
[Label("TARGET_CHARACTER")] | |
public CharacterAsset Character; | |
[Mixin] | |
public FromTo FromTo; | |
[DataInput] | |
[Label("SCALE")] | |
[FloatSlider(0.1f, 3f)] | |
public float Scale = 1f; | |
[DataInput] | |
[FloatSlider(1f, 100f)] | |
[Label("MASS")] | |
public float Mass = 25f; | |
[DataInput] | |
[FloatSlider(1f, 10f)] | |
[Label("SPEED")] | |
public float Speed = 5f; | |
[DataInput] | |
[Label("ALIVE_TIME")] | |
[Description("ALIVE_TIME_DESCRIPTION")] | |
public float AliveTime = 5f; | |
[DataInput] | |
[Label("IMPACT_PARTICLE_SCALE")] | |
[FloatSlider(0.1f, 3f)] | |
public float ImpactParticleScale = 0.25f; | |
[DataInput] | |
[Label("SOUND_VOLUME")] | |
[FloatSlider(0f, 1f)] | |
public float SoundVolume = 0.1f; | |
private GameObject impactParticle; | |
private AudioClip launchSound; | |
private AudioClip impactSound; | |
private float particleAliveTime; | |
protected override void OnCreate() { | |
base.OnCreate(); | |
FromTo.CharacterGetter = () => Character; | |
Watch(nameof(ImpactParticleSource), () => { | |
if (impactParticle != null) { | |
Object.DestroyImmediate(impactParticle); | |
impactParticle = null; | |
} | |
if (ImpactParticleSource != null) { | |
impactParticle = Context.ResourceManager.ResolveResourceUri<GameObject>(ImpactParticleSource); | |
impactParticle.SetActive(false); | |
particleAliveTime = 0f; | |
foreach (var particleSystem in impactParticle.GetComponentsInChildren<ParticleSystem>()) { | |
particleAliveTime = Mathf.Max(particleAliveTime, particleSystem.main.duration); | |
} | |
particleAliveTime += 5f; | |
} | |
}); | |
Watch(nameof(LaunchSoundSource), () => { | |
launchSound = null; | |
if (LaunchSoundSource != null) { | |
launchSound = Context.ResourceManager.ResolveResourceUri<AudioClip>(LaunchSoundSource); | |
} | |
}); | |
Watch(nameof(ImpactSoundSource), () => { | |
impactSound = null; | |
if (ImpactSoundSource != null) { | |
impactSound = Context.ResourceManager.ResolveResourceUri<AudioClip>(ImpactSoundSource); | |
} | |
}); | |
} | |
public override void Destroy() { | |
base.Destroy(); | |
if (impactParticle != null) { | |
Object.DestroyImmediate(impactParticle); | |
impactParticle = null; | |
} | |
launchSound = null; | |
impactSound = null; | |
} | |
[FlowInput] | |
public Continuation Enter() { | |
if (PropSource.IsNullOrWhiteSpace() || Character.IsNullOrInactiveOrDisabled()) { | |
return Exit; | |
} | |
Throw(); | |
return Exit; | |
} | |
public async void Throw() { | |
var gameObject = Context.ResourceManager.ResolveResourceUri<GameObject>(PropSource); | |
if (gameObject == null) { | |
throw new Exception("Failed to load resource: " + PropSource); | |
} | |
gameObject.transform.localScale *= Scale; | |
gameObject.transform.SetLayerRecursively(LayerMask.NameToLayer("Prop")); | |
// TODO: This is necessary or not? | |
if (Application.isEditor) { | |
// Wait for shaders to compile | |
gameObject.SetActive(false); | |
await UniTask.DelayFrame(5); | |
gameObject.SetActive(true); | |
} | |
Character.DisableTemporaryRagdollTime = Time.realtimeSinceStartup + AliveTime; | |
var rigidbody = gameObject.GetComponent<Rigidbody>(); | |
if (rigidbody == null) { | |
rigidbody = gameObject.AddComponent<Rigidbody>(); | |
} | |
rigidbody.mass = Mass; | |
var startPos = FromTo.FromPosition; | |
var endPos = FromTo.ToPosition; | |
gameObject.transform.position = startPos; | |
var behavior = gameObject.AddComponent<ThrownPropBehavior>(); | |
behavior.Parent = this; | |
// Calculate the displacement vector and its horizontal distance | |
var deltaPosition = endPos - startPos; | |
var t = 1 / Speed; | |
// Calculate the horizontal and vertical distances | |
float horizontalDistance = new Vector2(deltaPosition.x, deltaPosition.z).magnitude; | |
float verticalDistance = deltaPosition.y; | |
// Calculate the initial horizontal and vertical velocities | |
float initialHorizontalVelocity = horizontalDistance / t; | |
float gravity = Physics.gravity.y; | |
float initialVerticalVelocity = verticalDistance / t + 0.5f * Mathf.Abs(gravity) * t; | |
// Calculate the initial velocity vector | |
Vector3 initialVelocity = new Vector3(deltaPosition.x, 0, deltaPosition.z).normalized * initialHorizontalVelocity; | |
initialVelocity.y = initialVerticalVelocity; | |
// Calculate the force vector | |
Vector3 force = Mass * initialVelocity; | |
// Apply the force to the rigidbody | |
rigidbody.AddForce(force, ForceMode.Impulse); | |
behavior.PlayLaunchSound(); | |
Object.Destroy(gameObject, AliveTime); | |
} | |
[FlowOutput] | |
public Continuation Exit; | |
[FlowOutput] | |
[Label("ON_COLLIDE")] | |
public Continuation OnCollide; | |
class ThrownPropBehavior : MonoBehaviour { | |
public ThrowPropAtCharacterNode Parent { get; set; } | |
public bool Collided { get; set; } | |
private AudioSource audioSource; | |
public void PlayLaunchSound() { | |
if (Parent.launchSound != null) { | |
if (audioSource == null) audioSource = gameObject.AddComponent<AudioSource>(); | |
audioSource.clip = Parent.launchSound; | |
audioSource.volume = Parent.SoundVolume; | |
audioSource.Play(); | |
} | |
} | |
void OnCollisionEnter(Collision collision) { | |
if (Collided || Parent.Character.IsNullOrInactiveOrDisabled() || !collision.collider.transform.IsChildOf(Parent.Character.PuppetMaster.transform)) return; | |
Collided = true; | |
Parent.InvokeFlow(nameof(Parent.OnCollide)); | |
if (Parent.impactParticle != null) { | |
var particle = Instantiate(Parent.impactParticle, collision.contacts[0].point, Parent.impactParticle.transform.rotation); | |
particle.SetActive(true); | |
particle.transform.localScale *= Parent.ImpactParticleScale; | |
particle.GetComponents<PSSizeControl>().ForEach(it => it.scaleNumber = Parent.ImpactParticleScale); | |
Destroy(particle, Parent.particleAliveTime); | |
} | |
if (Parent.impactSound != null) { | |
if (audioSource == null) audioSource = gameObject.AddComponent<AudioSource>(); | |
audioSource.clip = Parent.impactSound; | |
audioSource.volume = Parent.SoundVolume; | |
audioSource.Play(); | |
Destroy(audioSource, audioSource.clip.length); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FromTo
(source code here) is a mixin which is basically a reusable component across entity types (i.e. asset, node, plugin).You can define data inputs in a mixin just like doing it in a node. The data inputs in a mixin will be registered at their parent, in this case, the
ThrowPropAtCharacterNode
, which is why you can see the data inputs defined in the mixin on the node in editor. A mixin also has its ownOnCreate
,OnDestroy
lifecycle methods (andOnUpdate
etc. for aBehavioralMixin
), and they are invoked when their parents go through these lifecycle events.The reason of using a mixin is to avoid duplicating code. For example,
LaunchLiquidAtCharacterNode
also uses theFromTo
mixin as it requires data inputs from the user regarding the start and end position of the liquid. By simply writing[Mixin] public FromTo FromTo;
, the data inputs in the mixin can be "imported" along with any independent, reusable logic.