Posts Tagged design

Throwing potions III

Most of my work this week focused around getting the stone potion working.

One of the first things I had to do was split some of my existing Entity code out into smaller pieces. All the entities I’d written previously were sprites, but the stone potion spawns geometric shapes. I ended up pulling the sprite-related code out into a SpriteEntity class, derived from Entity, so that I could customize entity rendering behavior without having to duplicate the code for mantling, collision detection, etc.

Once that was done, I was able to spawn stone outcroppings at the point of impact of a stone potion, based on the surrounding geometry. The player could walk across them and destroy them. This quickly revealed that there were some issues with my algorithm for walking on surfaces: previously, all the surfaces in the game were simple geometric shapes – rectangles, triangles, circles. The stone outcroppings are irregular surfaces, which meant that some of the assumptions I made previously no longer hold.

Previously, to determine where on a surface the player should stand, I checked the left and right edges of the player along with his center. In order to account for irregular surfaces, I now have to scan across the entire width of the player in small increments to account for variations in height. It’s a slight performance hit, but it ensures that the player can walk smoothly over irregular surfaces cobbled together from multiple stones.

I also discovered that I’d made a mistake when implementing my damage effect code – the code in the XNA framework for maintaining a smooth framerate introduced timing inconsistencies between frames, such that the points where I sampled a damage effect’s curve didn’t match the points I sampled initially when computing the total damage (as described in the previous post). The result was that sometimes a potion would do more or less damage depending on the timing of its impact and the game’s current framerate. The solution to this was to maintain an internal clock for each damage effect so that I could walk along the curve at the same points in time.

The next thing I noticed was that the player had difficulty grabbing onto stone outcroppings and mantling up onto them. It turned out that there were two problems there:

The first problem was that the algorithm for finding a point to mantle onto assumed square surfaces. As a result, the uneven, rounded shape of the stone outcroppings caused it to sometimes select points below or above the surface of the stone. I rewrote the algorithm to work in two stages: First, sweep upward to find an obstruction, starting at the player’s feet, and then sweep upward further to find the end of the obstruction and a new place for the player’s feet to occupy.

Tags: , , ,

Throwing potions II

Not too much progress this week, been busy with other stuff. Most of my development effort went towards potions – there’s a simple HUD that shows the player’s selected potion and you can cycle through them using a button on the controller. I started working on the stone potion, which creates a short-lived rock outcropping when it impacts a surface. The basic functionality is there, but I still need to do lots of tweaking – the shape of the outcropping isn’t quite right, and it needs to decay after a little while instead of lasting forever. It also spawns at the wrong angle when hitting some surfaces, so I still need to figure that out as well.

Tags: , , ,

Damage effects

My goals for the damage effect code were as follows:

  1. I should be able to set a desired amount of damage for an effect to deal over its entire duration
  2. Effects should deal a portion of their damage every frame while an entity is within range
  3. Effects should not deal damage to an entity if it has moved out of range
  4. 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)
  5. 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)
            };
        }
    }
}

Tags: , , ,

Throwing potions

Most of my work this week was centered around adding the ability to throw potions of various types.

The first task was to implement basic collision detection for potions. I based this on the code I already wrote for drawing the trajectory of a potion when aiming, as shown in the previous post. The basic structure is that the potion has a position and a velocity, and has a gravitational pull applied every ‘step’. To compute the trajectory for drawing, I iteratively ‘step’ forward along the path of the potion, drawing dots onscreen every few steps, to show the predicted path of the potion if thrown in a given direction. Since I already had the code for that, it was easy to pull the step code out into a function of its own, and then call that from the Update function for the actual thrown potion. The end result looks like this:

internal static bool UpdateMotion (Sapphira sapphira, ref Vector2 position, ref Vector2 velocity) {
    Vector2 newPosition = position + velocity;

    if (sapphira.Game.ResolveMotion(position, newPosition, ref newPosition, sapphira)) {
        position = newPosition;
        velocity = new Vector2(0.0f, 0.0f);
        return false;
    } else {
        position = newPosition;

        velocity.Y = velocity.Y + GravitationRate;
        float dist = velocity.Length();
        if (dist > TravelVelocity)
            dist = Math.Max(TravelVelocity, dist - DecelerationRate);
        velocity = Vector2.Normalize(velocity) * dist;

        return true;
    }
}

internal static IEnumerable<Vector2> ComputeTrajectory (Sapphira sapphira, Vector2 startPosition, Vector2 direction) {
    var position = startPosition;
    var velocity = direction * InitialVelocity;

    for (int i = 0; i < 200; i++) {
        yield return position;

        UpdateMotion(sapphira, ref position, ref velocity);

        if (velocity.LengthSquared() <= 0)
            yield break;
    }
}

The ComputeTrajectory iterator is used by the drawing routines to show the predicted trajectory of a potion, and the UpdateMotion function is used by both the iterator and the actual thrown potion to update motion. Pretty simple, and it works fairly well. One thing that may need improvement is the length of ComputeTrajectory’s projection – right now it just steps 200 times, since that’s ‘about enough’ in all of my test cases to draw a full onscreen trajectory. I may need to come up with a better technique for that in the future if things change.

My initial implementation of UpdateMotion had a bug for a while where if you threw a potion directly upward, it would never fall – this was pretty sneaky, since it resulted from a minor mistake, where I was computing ‘dist’ based on the velocity of the potion before I applied gravity. I spent a little time messing around before I finally realized what was happening, because the visualization I was using to look at the results wasn’t making it clear what was going on. Luckily I caught the bug before writing too much code, otherwise I might have had to waste a lot of time trying to track it down.

Once I had the collision detection working, I spent some time implementing basic animation for the potions, so they leave a trail in the air as they fly and spray out some fluid when they collide with a surface and shatter. Tuning particle effects is a tough job, so I ended up having to make myself stop fiddling with it after a while and move onto something more productive, but it at least does the job.

My work-in-progress is the code for allowing potions to deal damage when shattered – right now, I’m modeling a potion’s impact as a circle, with a pair of radiuses that specify the two ends of a damage ramp. This allows me to create damage effects with a hard edge, damage effects with a softer damage ramp, and damage effects that combine the two (a hard-edged center with a ramp on the outside), which should be sufficient for the potions I have designed currently. To handle the other half of it – the fading in/out of the damage effect, based on the type of the potion – right now I’m just using a Curve that represents damage over time, as a multiplier based on the location of the target relative to the damage effect. I’m not totally certain this will work, but it seems to be effective so far.

public class FirePotion : ThrownPotion {
    public CircularDamageEffect DamageEffect = new CircularDamageEffect {
        InnerMultiplier = 1.0f,
        OuterMultiplier = 0.0f,
        InnerRadius = 16.0f,
        OuterRadius = 64.0f,
        DamageCurve = new Curve<float> {
            {0.0f, 0.25f, Interpolators<float>.Cubic},
            {0.25f, 1.0f, Interpolators<float>.Cubic},
            {0.75f, 0.0f, Interpolators<float>.Cubic},
        }
    };

    public FirePotion (Sapphira sapphira, Vector2 initialPosition, Vector2 direction)
        : base (sapphira, initialPosition, direction) {
        Color1 = new Color(0.9f, 0.4f, 0.2f, 1.0f);
        Color2 = new Color(0.825f, 0.225f, 0.125f, 1.0f);
    }

    public override void Impact () {
        base.Impact();
        Game.SpawnSparks(Position);
        DamageEffect.TriggerAt(Game, Position);
    }
}

Right now you can’t choose which kind of potion to throw, so the game just randomly selects a type every time you throw one. I plan to address this once damage effects are fully working. I’m also not totally happy with the way you aim potions right now – you just choose the initial trajectory of the potion, and you can’t adjust how hard you throw it, so it is a bit difficult to aim in certain directions. I’m thinking I might allow the player to aim at a position instead, and create a trajectory that passes through that point at a given distance, based on how hard you push the analog stick, in order to make it easier to aim.

In the video above, you can see the two different potion types have slightly different particle effects, and different damage ramps (drawn as solid white circles). While the damage effects detect collision with an entity (highlighted with a thin red box), they don’t actually deal damage to the entity yet, since I haven’t worked out the math for it. My goal is for an entity directly hit by a potion to take exactly N damage, since the ramp at the center is always exactly 1. If the entity is only somewhat near a potion, and the ramp’s value is 0.5, I want the entity to take a total of 0.5 * N damage, if he remains within the area of effect for the entire duration. Since damage is dealt over time, I have to determine the total length of the damage effect, and based on that compute how much of the target damage (N) to deal each frame while the entity is inside the area of effect.

Tags: , , ,

Trajectories

Spent some time this week tuning various physics parameters to make things fit with the new size of the player sprites. Things still aren’t quite perfect, but the feel of things is pretty close to what I want.

I also spent some time working on support for one of the player characters’ special abilities – throwing potions. It took a little effort to come up with a good set of constants to give the potion a convincing arc, but I’ve got the ability to aim a potion working and I’m pretty happy with how it feels. Next, I need to get some basic art for the potion into the game so you can see the player throw it, and implement the collision detection so that a potion can shatter when it hits a surface.

Tags: , , ,

Gruedorf mini-update #2

Another slow week. Most of my work went into moving to the new object model discussed previously – splitting bits of data and logic out into the Level and RuntimeLevel classes, fixing bugs as they cropped up, and so on. One thing I had to do in order for that to work that I hadn’t anticipated was change my object model so that every object in a given level had a unique name. Previously names were optional, but in the new object model it didn’t really make sense for objects to not have a name, or share the same name. After resolving that, some other issues cropped up – for example, bounding boxes weren’t updating correctly for some moving objects like doors and moving platforms, which meant that in some cases an entity could slip through an obstruction.

Once the issues resulting from the switch were resolved, I started making some improvements to my moving platform code so it was easier to put to work. The first problem was that the use of cubic interpolation caused platforms to move at an inconsistent speed – slowing down as they approached waypoints on their path, and accelerating as they grew more distant. I spent a little time experimenting with various approaches to solving this, and ended up settling with a simple hack, where I do a binary search of the curve to find the next point along the curve that is a given distance away from the current one. While not mathematically pure, it was easy to build and solves the problem, and after implementing it I was surprised by how much better the motion of the platforms looked as a result of them moving consistently.

After this I spent some time improving the editor’s support for dealing with objects that move, making it possible to see a simple visualization of their path so you can get an idea of possible collisions and where it’s going to move.

Tags: , , ,

Gruedorf mini-update: Moving platforms and design changes

A nasty bug going around last weekend kicked my ass, so not too much to report:

Been making large architectural changes to clean up my object model, so that things like moving platforms can work correctly without too much hassle. In summary:

Previously, I had a Level class that contained all the important objects relevant to a game level. Some of these objects had ‘Runtime’ counterparts that you had to construct by calling a relevant method, for example:

Level.Entities contains LevelEntity objects, which have a GetRuntimeEntity method that returns a (Runtime) Entity object based on the LevelEntity. To call this method, you have to pass in a Game object.

There are some major problems with this approach – the need to call GetRuntime methods complicates iteration over these containers and means that Game objects have to get passed as arguments to basically any function that needs runtime objects, which is a tremendous hassle.

In order to address these problems, I’ve decided to split the Level class out into two objects: Level and RuntimeLevel. Level will contain only ‘design-time’ objects that have no dependencies on each other or on the Game object, while RuntimeLevel will be constructed from a Level by the Game and contain preconstructed run-time versions of all the objects from the Level. This will allow me to simplify lots of code by simply walking over the contents of the RuntimeLevel when I want run-time objects, and walking over the Level when I want design-time objects. It also nicely splits out some responsibilities – previously, Level had to subdivide objects like entities and geometry based on their boundaries, for efficiency purposes, which made the class far more complicated than was necessary for level editing. Now, that responsibility falls on the RuntimeLevel, so Level can just use a Dictionary to store its objects for easy lookup.

Regardless, I did get some additional functionality built since last week’s post. Here’s a video of two moving platforms following some simple paths I built using the editor’s node tools (you may notice some minor collision detection glitches; these are caused by the platforms having overlapping bounding boxes, and will be fixed by the move to a RuntimeLevel class.)

Tags: , , ,

Level editing and designery things

Over the past few days I’ve invested most of my energy in the design side of the platformer – writing up design docs for bosses and characters, roughing out the design of the game world, etc. Not a whole lot of insights to share in that area yet – most of it is in primordial first draft state, so I have yet to learn any lessons from my work, just apply previously learned ones. So far I’ve been taking a very iterative, loose approach to my design work – laying down extremely rough, simple drafts based on initial concepts and basic design principles, and then fleshing in detail and making adjustments or moving on to other parts of the design. This means that there are lots of gaps left in the design to fill in, but it also means that I can avoid having to set too many details in stone just to rework them later in response to design changes.

Here’s part of the current diagram that represents the layout of the game world:

As you can see, it’s very light on detail – the sizes and shapes of each area are not to any particular scale, and the finer details of each area are not filled in, like where walls and slopes and doors are placed. But it gives a general sense of the shape and flow of each area, along with describing each area, showing how they connect, and showing the general location of each boss.

I also started working on building level editing functionality into my game. I ended up deciding to use WinForms to build the editor UI, but still integrate it into the game itself. After a little hacking and experimenting, I was able to get controls like toolbars and listboxes hosted into the XNA game window without too much fuss. Right now tapping the ~ key opens the editing UI and pauses the game, and then tapping the key again closes it. I only implemented some of the basic UI elements – status bar showing the status of the editor (Whether it’s performing an operation like a load/save) and mouse coordinates, along with a tile picker and basic support for laying down tiles. I still have to figure out how to properly handle editing levels that are built out of variable-sized tiles, since the approach I’m using hasn’t been done very often.

I did encounter a couple problems getting it working, though – WinForms has no way to reparent a control onto another window directly, so I had to use P/Invoke to call SetParent. I also realized that since my tiles were Texture2D objects, there was no straightforward way to render them into a WinForms ListBox. I ended up building a simple tile picker myself instead, using XNA to render everything except the scrollbar. Other than those relatively minor issues, though, it was pretty easy to get up and running, and it builds nicely on top of the support I already had for reloading levels at runtime.

Tags: , , , ,