Skip to content

Instantly share code, notes, and snippets.

@tomassedovic
Created November 17, 2013 20:33
Show Gist options
  • Save tomassedovic/7517915 to your computer and use it in GitHub Desktop.
Save tomassedovic/7517915 to your computer and use it in GitHub Desktop.
Dose Response components as of 2013/11/17.

These are the components I'm using in Dose Response so far. This is my first time using this pattern and the first time I'm writing a real game so it's been a learning experience and I'm probably doing a lot of things wrong. But hey, that's why I started this in the first place -- to figure out how this component stuff works in an actual game (rather than just reading about the PositionComponent and VelocityComponent for the hundredth time).

This is Rust, so apart from normal structs (records) you can have structs with no fields (that I use to declare a property -- Background meaning "is a background tile", Solid means "is a solid entity that you can bump into but cannot occupy the same space", etc.).

Then you have enums which are again like C enums except type-checked and they can contain structs, too. So the AttackType component can either be Kill (i.e. the attack instantly kills the target) or for example Stun{duration: int} which means the target will be stunned for n-turns where n is the duration property (so different monsters can stun for different ammount of turns).

Some of these could probably be merged or split, but they're working so far. Some more explanation/experiences below the list. Note that this is still a reasonably simple game ("1 hitpoint", no items, no levelling up, etc.), so I'm not sure it's actually worth it here.

struct AI{behaviour: ai::Behaviour, state: ai::State}
struct AcceptsUserInput
struct Addiction{tolerance: int, drop_per_turn: int, last_turn: int}
struct AnxietyKillCounter{count: int, threshold: int}
struct AttackTarget(ID)
enum   AttackType {Kill, Stun{duration: int}, Panic{duration: int}, ModifyAttributes}
struct AttributeModifier{state_of_mind: int, will: int}
struct Attributes{state_of_mind: int, will: int}
struct Exploration{radius: int}
struct FadeColor{from: Color, to: Color, duration_s: float, repetitions: Repetitions}
struct FadeOut{to: Color, duration_s: float}
struct FadingOut
struct ColorAnimation{color: Color, progress: float}
struct Background
struct Bump(ID)
struct DeathTile{glyph: char, color: Color}
struct Destination {x: int, y: int}
struct Dose{tolerance_modifier: int, resist_radius: int}
struct ExplosionEffect{radius: int}
struct Monster{kind: MonsterKind}
struct Panicking{turn: int, duration: int}
struct Position {x: int, y: int}
struct Solid
struct Stunned{turn: int, duration: int}
struct Tile{level: uint, glyph: char, color: Color}
struct Turn{side: Side, ap: int, max_ap: int, spent_this_tick: int}

ID is a type representing an entity in the component system. That's the int/uuid thing Adam Martin talks about in his blog. That's what all the components are tied into.

Here's a few examples of how things work:

Whenever you want to affect entity's movement, you set its Destination. So the user-input-system usually does this for the player and the ai-system for the monsters. The movement-system then runs a path-finding algorithm (if the destination isn't right next to the current position) and moves the entity one step forward while subtracting 1 action point from the Turn component.

There are three systems that get triggered between the movement-system and affect the Destination: stunned-system (will reset the Destination if the entity has the Stunned component), panicking-system (will set a random destination if the entity has the Panicking component) and irresistible-dose-system (if the entity has low willpower and a dose is near, this will set the destination towards the dose).

If the movement system finds out that the Destination would lead to a cell with a Solid entity, it doesn't move but sets the Bump component instead.

The Bump system then looks at the bumped entity: if it's a Monster, it sets the Attack component (which the attack-system deals with later on), otherwise it does nothing (i.e. "you bumped into a wall, stupid.").

Tile represents a renderable object and has the foreground colour, glyph (the ascii character, could easily be an actual image if I ever implemented proper graphics) and level (the display has multiple levels stacked on top of each others. If there are two tiles on the same position, the one of the higher level will be visible. So all the background is on the level 0, the player is on level 3, and the monsters and everything else are in-between.

ColorFade sets a fading animation between two colours. You can specify the number of times the fading should appear (or infinity) and the length it should take.

When you set it, the color-fade-system uses another component (ColorAnimation) to track the progress of the fading. This is like an internal state -- you would never set it directly, but it's gotta live somewhere. So I just created another component for it (think of it as a private property, though the privacy is not actually enforced). Not sure this is a good idea or not but it works.

There's a special case of the colour fading mechanism that I put into another component: FadeOut. You use that to do a one-time fade from the current colour to the desired one and hiding the entity once the fadeout is finished (useful e.g. for the death animation). If you set the FadeOut component on an entity, it will leverage the ColorFade component and system for the animation and remove the Tile component (making the entity disappear). FadingOut signals that this entity is being faded out -- so the fade-out-system shouldn't reset it or whatever (so this is like another private bit of state).

All the fading/colour animation stuff is terribly named -- I'll have to rethink that -- but the mechanism itself seems to be working great. You just set the desired outcome (fade between these two colours with a 1000ms period) and the systems will make it happen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment