Execute Behaviors

This commit is contained in:
Pedro Gomes 2025-06-22 22:31:01 +01:00
parent c4d084acb2
commit e4d35c0af6
9 changed files with 249 additions and 12 deletions

View File

@ -78,7 +78,7 @@ Material:
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Rotation: 6.1873918
- _Rotation: 6.1601553
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b8fc798226bf8b24e9bcefd5efa43c31
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,181 @@
using UnityEngine;
public class ProjectileExecutionBehavior : RuntimeBehavior
{
[Header("Projectile Settings")]
public GameObject projectilePrefab;
public float projectileSpeed = 15f;
public float lifeSpan = 1.5f;
public bool canPierce = false;
public bool canHitSelf = false;
[Header("Movement Behaviour")]
public AnimationCurve speedOverLifetime = AnimationCurve.Linear(0, 1, 1, 1);
public bool useSpeedCurve = false;
public bool enableCurving = false;
public Vector3 curveAxis = Vector3.up;
public float curveStrength = 1f;
public float curveAmplitude = 45f;
public bool enableRicochet = false;
public int maxRicochets = 1;
public float ricochetSpread = 10f;
public ProjectileExecutionBehavior()
{
Trigger = BehaviorTrigger.Execute; // This replaces your Execute() override
BehaviorName = "Projectile Execution";
}
public override void Execute(RuntimeAbilityInstance ability, Taggable user, Transform target, Vector3 point)
{
// This is your old ProjectileAbility.Execute() logic!
// Get spawn location
var spawnController = user.GetComponentInChildren<ProjectileSpawnLocationController>();
Vector3 spawnPos = spawnController?.transform.position ?? user.transform.position;
Quaternion spawnRot = spawnController?.transform.rotation ?? user.transform.rotation;
// Spawn projectile
var projectileGO = Object.Instantiate(projectilePrefab, spawnPos, spawnRot);
// Setup projectile
var networkedProjectile = projectileGO.GetComponent<NetworkedProjectile>();
if (networkedProjectile != null)
{
SetupProjectileInstance(ability, networkedProjectile, user);
networkedProjectile.Init();
}
}
protected virtual void SetupProjectileInstance(RuntimeAbilityInstance ability, NetworkedProjectile networkedProjectile, Taggable user)
{
networkedProjectile.speed = projectileSpeed;
networkedProjectile.ownerTag = user;
networkedProjectile.ability = ability.SourceAbility;
networkedProjectile.lifeSpan = lifeSpan;
networkedProjectile.canPierce = canPierce;
networkedProjectile.canHitSelf = canHitSelf;
networkedProjectile.speedOverLifetime = speedOverLifetime;
networkedProjectile.useSpeedCurve = useSpeedCurve;
networkedProjectile.enableCurving = enableCurving;
networkedProjectile.curveAxis = curveAxis;
networkedProjectile.curveStrength = curveStrength;
networkedProjectile.curveAmplitude = curveAmplitude;
networkedProjectile.enableRicochet = enableRicochet;
networkedProjectile.maxRicochets = maxRicochets;
networkedProjectile.ricochetSpread = ricochetSpread;
}
public override RuntimeBehavior Clone()
{
return new ProjectileExecutionBehavior
{
// Basic settings
projectilePrefab = this.projectilePrefab,
projectileSpeed = this.projectileSpeed,
lifeSpan = this.lifeSpan,
canPierce = this.canPierce,
canHitSelf = this.canHitSelf,
// Movement behavior
speedOverLifetime = this.speedOverLifetime,
useSpeedCurve = this.useSpeedCurve,
enableCurving = this.enableCurving,
curveAxis = this.curveAxis,
curveStrength = this.curveStrength,
curveAmplitude = this.curveAmplitude,
enableRicochet = this.enableRicochet,
maxRicochets = this.maxRicochets,
ricochetSpread = this.ricochetSpread
};
}
// ========================================================================
// FACTORY METHODS - Easy Creation of Common Projectile Types
// ========================================================================
/// <summary>
/// Create a simple straight projectile
/// </summary>
public static ProjectileExecutionBehavior CreateSimple(GameObject prefab, float speed = 15f, float lifeSpan = 5f)
{
return new ProjectileExecutionBehavior
{
projectilePrefab = prefab,
projectileSpeed = speed,
lifeSpan = lifeSpan
};
}
/// <summary>
/// Create a piercing projectile
/// </summary>
public static ProjectileExecutionBehavior CreatePiercing(GameObject prefab, float speed = 15f, float lifeSpan = 5f)
{
return new ProjectileExecutionBehavior
{
projectilePrefab = prefab,
projectileSpeed = speed,
lifeSpan = lifeSpan,
canPierce = true
};
}
/// <summary>
/// Create a curved projectile (like magic missile)
/// </summary>
public static ProjectileExecutionBehavior CreateCurved(GameObject prefab, float speed = 12f, float curveStrength = 2f)
{
return new ProjectileExecutionBehavior
{
projectilePrefab = prefab,
projectileSpeed = speed,
enableCurving = true,
curveStrength = curveStrength,
curveAmplitude = 30f
};
}
/// <summary>
/// Create a ricochet projectile
/// </summary>
public static ProjectileExecutionBehavior CreateRicochet(GameObject prefab, int maxBounces = 3, float speed = 15f)
{
return new ProjectileExecutionBehavior
{
projectilePrefab = prefab,
projectileSpeed = speed,
enableRicochet = true,
maxRicochets = maxBounces,
ricochetSpread = 15f
};
}
/// <summary>
/// Create a projectile with speed curve (accelerating/decelerating)
/// </summary>
public static ProjectileExecutionBehavior CreateWithSpeedCurve(GameObject prefab, AnimationCurve curve, float baseSpeed = 15f)
{
return new ProjectileExecutionBehavior
{
projectilePrefab = prefab,
projectileSpeed = baseSpeed,
useSpeedCurve = true,
speedOverLifetime = curve
};
}
public static ProjectileExecutionBehavior CreateIceshardLikeWithCurve(GameObject prefab, float baseSpeed = 15f)
{
return new ProjectileExecutionBehavior
{
projectilePrefab = prefab,
projectileSpeed = baseSpeed,
useSpeedCurve = true,
lifeSpan = 1f,
speedOverLifetime = GameConstants.BehaviorUtils.GetIceshardProjectileCurve()
};
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae360e58fae69584f9a3a850f466195f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -191,6 +191,8 @@ public class RuntimeAbilityInstance
Health userHealth;
public virtual void SpendResourcesNecessary(Taggable user)
{
if (userMana == null) userMana = user.GetComponent<Mana>();
if (userHealth == null) userHealth = user.GetComponent<Health>();
userMana.ChangeValue(-GetFinalManaCost(user));
userHealth.ChangeValue(-GetFinalHealthCost(user));
@ -202,8 +204,10 @@ public class RuntimeAbilityInstance
if (!CanExecute(user)) return;
ExecuteBehaviors(BehaviorTrigger.PreCast, user, null, Vector3.zero);
SpendResourcesNecessary(user);
sourceAbility.Execute(user);
ExecuteBehaviors(BehaviorTrigger.Execute, user, null, Vector3.zero);
ExecuteBehaviors(BehaviorTrigger.PostCast, user, null, Vector3.zero);
lastUsedTime = Time.time;
@ -214,8 +218,10 @@ public class RuntimeAbilityInstance
if (!CanExecute(user)) return;
ExecuteBehaviors(BehaviorTrigger.PreCast, user, null, point);
SpendResourcesNecessary(user);
sourceAbility.Execute(user, point);
ExecuteBehaviors(BehaviorTrigger.Execute, user, null, point);
ExecuteBehaviors(BehaviorTrigger.PostCast, user, null, point);
lastUsedTime = Time.time;
@ -226,8 +232,10 @@ public class RuntimeAbilityInstance
if (!CanExecute(user)) return;
ExecuteBehaviors(BehaviorTrigger.PreCast, user, target, target.position);
SpendResourcesNecessary(user);
sourceAbility.Execute(user, target);
ExecuteBehaviors(BehaviorTrigger.Execute, user, target, target.position);
ExecuteBehaviors(BehaviorTrigger.PostCast, user, target, target.position);
lastUsedTime = Time.time;

View File

@ -2,12 +2,15 @@ using UnityEngine;
public enum BehaviorTrigger
{
PreCast, // Before ability executes
Execute, // MAIN EXECUTION - replaces your override Execute()
PostCast, // After ability executes
OnHit, // When ability hits target
OnKill, // When ability kills target
OnCrit // When ability crits
PreCast, // Before ability executes
Execute, // MAIN EXECUTION - replaces your override Execute()
PostCast, // After ability executes
OnHit, // When ability hits target
OnKill, // When ability kills target
OnCrit, // When ability crits
OnMiss, // When ability misses
OnChannelTick, // During channeling
OnChannelEnd // When channeling ends
}
public abstract class RuntimeBehavior

View File

@ -1,4 +1,4 @@
using UnityEngine;
using UnityEngine;
using static GameConstants.EnemySpawning;
public static class GameConstants
@ -408,4 +408,24 @@ public static class GameConstants
return HuntersInn;
}
}
public static class BehaviorUtils
{
public static AnimationCurve GetIceshardProjectileCurve()
{
var keyframes = new Keyframe[]
{
// time, value, inTangent, outTangent
new Keyframe(0.0f, 0.0f, -0.0635979f, -0.0635979f),
new Keyframe(0.6008623f, 1.0058026f, 3.168166f, 3.168166f), // ← This steep tangent creates the sharp rise!
new Keyframe(0.9846547f, 1.3325472f, 0.3161671f, 0.3161671f)
};
var curve = new AnimationCurve(keyframes);
curve.preWrapMode = WrapMode.PingPong;
curve.postWrapMode = WrapMode.PingPong;
return curve;
}
}
}

View File

@ -11,7 +11,7 @@ public class NetworkedProjectile : MonoBehaviour
[Header("Set by code")]
public Taggable ownerTag;
public ProjectileAbility ability;
public BaseAbility ability;
public float speed;
public float lifeSpan;
public bool canPierce;
@ -79,6 +79,7 @@ public class NetworkedProjectile : MonoBehaviour
float lifetimeFraction = Mathf.Clamp01(timeAlive / lifeSpan);
float currentSpeed = useSpeedCurve ? speed * speedOverLifetime.Evaluate(lifetimeFraction) : speed;
// Curving logic
if (enableCurving)
{

View File

@ -185,6 +185,11 @@ public class AbilityKeyBinder : MonoBehaviour
[ContextMenu("debug Add manacost reduction")]
public void AddManaCostModifierDebug()
{
if(abilityInstance.SourceAbility is ProjectileAbility projectileAbility)
{
abilityInstance.AddBehavior(ProjectileExecutionBehavior.CreateIceshardLikeWithCurve(projectileAbility.projectilePrefab, 50f));
}
abilityInstance.AddModifier(AbilityModifier.CreateManaCostReduction(100f));
}