My goals for the damage effect code were as follows:
- I should be able to set a desired amount of damage for an effect to deal over its entire duration
- Effects should deal a portion of their damage every frame while an entity is within range
- Effects should not deal damage to an entity if it has moved out of range
- I should be able to set a curve for a given effect so that I can sync the damage with the onscreen animation (pulsing, fading in, fading out, etc)
- Effects should be able to deal a different amount of damage based on how close an entity is to the effect
The first thing I did was to set a pair of values that specify the damage multiplier for the inside and outside edges of the damage effect – this allowed me to create effects with a smooth damage gradient from the inside to the outside, along with effects that have an inverted gradient or effects that deal the same amount of damage over their entire area of effect. I also added a pair of values that control the radius of the inner and outer parts of the damage ramp. The end result looks something like this:
Once I have the damage ramp figured out, it’s easy to map a given point within the circle to a damage value, and I can use that approach to figure out how much damage an entity should take based on its distance from the center.
The next step is to apply a second multiplier, based on a damage curve. This lets me control how long a damage effect is and the nature of the effect – pulsing, fading, etc. The curve takes the time elapsed since the start of the effect as an input, and outputs a damage multiplier (in the range 0 to 1) so that I can multiply it by the distance value from the damage ramp.
Once I have the two multipliers for a given entity, all that’s left is to determine how much damage to deal during the given frame. In order to get this value, I have to come up with a multiplier that will cause the effect’s total damage dealt to sum up to the desired amount.
As an example, if I want the effect to deal a total of 50 damage, and the damage curve I specified is 0.13 seconds long, that maps to around 8 frames. Given that, I can walk over the entire length of the curve, sampling it in single-frame steps. The sum of those values gives me the total value of the curve, which I can then map to the desired total damage value using simple arithmetic. Combining that with the other two multipliers (from the damage ramp and the damage curve) gives me the actual amount of damage to deal during the current frame.
The final implementation ends up looking like this:
public void Initialize () {
CurveSum = 0.0f;
float frameLength = 1.0f / 60.0f;
int numFrames = (int)Math.Ceiling(DamageCurve.End * 60);
float t = 0.0f;
for (int i = 0; i < numFrames; i++) {
CurveSum += DamageCurve[t];
t += frameLength;
}
}
public void Update (GameTime gameTime) {
var t = (float)Game.AnimationTimeProvider.Seconds - StartTime;
var curve = DamageCurve[t];
float frameDamage = (TotalDamage / CurveSum) * curve;
foreach (var hit in FindHits()) {
float rampPosition = MathHelper.Clamp((hit.Distance - InnerRadius) / (OuterRadius - InnerRadius), 0.0f, 1.0f);
float hitDamage = frameDamage * MathHelper.Lerp(InnerMultiplier, OuterMultiplier, rampPosition);
hit.Entity.Injure(hitDamage);
}
if (t > DamageCurve.End)
Game.Components.Remove(this);
}
internal IEnumerable<HitInfo> FindHits () {
var bounds = new Bounds(
new Vector2(Center.X - OuterRadius, Center.Y - OuterRadius),
new Vector2(Center.X + OuterRadius, Center.Y + OuterRadius)
);
RuntimeEntityList.ItemInfo itemInfo;
using (var e = Game.RuntimeLevel.Entities.GetItemsFromBounds(bounds))
while (e.GetNext(out itemInfo)) {
if (!bounds.Intersects(itemInfo.Bounds))
continue;
float minDistanceSquared = float.MaxValue;
foreach (var poly in itemInfo.Item.GetCollisionList()) {
int count = poly.Count;
for (int i = 0; i < count; i++) {
var edge = poly.GetEdge(i);
var closestPoint = Geometry.ClosestPointOnLine(Center, edge.Start, edge.End);
minDistanceSquared = Math.Min(minDistanceSquared, (closestPoint - Center).LengthSquared());
}
}
if (minDistanceSquared < float.MaxValue) {
yield return new HitInfo {
Entity = itemInfo.Item,
Distance = (float)Math.Sqrt(minDistanceSquared)
};
}
}
}




