GrayScale – dev log #1
I’ve been posting couple of GIFs around the internet from my GrayScale game but never explained how each thing works. This post was originally written at Snowkit.org. It is a good place to start, if you’re reading this then you would find some luxe-related things interesting. In fact I think I managed to create/discover at least one interesting thing about luxe that I wanted to share with you (physiiiics).
I won’t call myself an expert in programming and this post is in no way a tutorial on ”how to do things”. With luxe you can find your own way of coding. I’m just sharing what I discovered and had fun with :)
Quick intro
GrayScale is a small game I made for GameBoy Jam last year. It was made in HaxeFlixel but I wanted more control over my code and be able to easily create lots of enemies with different abilities. After few hours of googling I stopped by a thing called ”Component Entity System” and finally found luxe engine (and so far I love it!). You can read more about the project itself on my blog.
Movement & ”fixed time rate”
Movement is the core gameplay in here, you can walk, run, dash and jump-attack. I organized each type to extend from one Mover
component so each kind of mechanics share the same base (which is really thin so far). With components it’s easy to make characters controlled by player or AI and make them move by similar rules. I can make new enemies that walk around and dash-attack the same way that player does. Movement can be basically used for moving any kind of objects around the world. This way I could use it to move projectiles, doors, platforms etc.
Problems
The first thing I noticed when testing web and native builds was that on native build updates happened much faster than on web. This caused any kind of movement look sluggish in the browser.
Lesson learned #1: Don’t move stuff on update()
.
Setting fixed_rate = 1/60
on every entity almost did the trick. Now my character moves at the same speed but when the scene becomes more complex, core updates take more time -> my movement calculations are delayed and in effect character moves slower again.
Lesson learned #2: Don’t move stuff onfixedupdate()
(unless it suits your needs!)
After consulting Sven on our Snowkit Gitter chat I was directed to take a look at luxe.PhysicsEngine
. Now I could directly control when everything is calculated, but I had to make some kind of connection between MyPhysics
engine and every mover component in the game. I’m not relying components update()
or onfixedupdate()
anymore…
Movers
Each mover should be then responsible to add itself to (or remove from) the list of components in PhysicsEngine. The engine then would call step(dt)
functions on every component it knows. So instead of using mover’s override function update(dt:Float)
, I’m calling function step(dt:Float)
from the engine.
Mover is basically a glue between character and physics engine. Example code below:
class Mover extends Component
{
// Mover can only be added to Actors
// This shall be direct and typed reference
var actor:Actor;
// Override this to add your own tricks
public function step(dt:Float) {}
override function onadded()
{
actor = cast entity;
if(actor == null) {
throw "Mover belongs on an Actor instance";
} //Actor test
// Add mover to the list, so it can receive `step(dt)` calls
Director.physics.add(actor);
// Notify Actor
actor.add_mover(this);
}
override function onremoved()
{
// Notify physics engine that mover is trying to drop from the list
Director.physics.remove(actor);
// Notify an Actor too
actor.remove_mover(this);
actor = null;
}
}
So in order to create for example a Walk
component:
- extend
Mover
- override
step()
- add all the other things like maxspeed, acceleration, depending on the movement type
Step function is also important because it directly modifies Actors velocity
and only that. One Actor can have many movers and each can be merged together. Actor itself also receives physics engine step()
call and after updating each mover it applies velocity to its position and sets it back to (zero,zero,zero). (I’ll talk about Actors below).
Physics engine timing
Standard PhysicsEngine
is using the same kind of timing you can find in Entity. Setting fixed_rate = 1/60
doesn’t mean it will always run each update after exactly 0.01(6) ms. Of course it would be enough for most of the time but again: more complex scenery (or slower PC, why not) -> frames drop -> movement calculation are delayed. I started testing it against Luxe.core.current_time
to see what I could do. I calculated how much time passed since the last physics update and if it was more than 1/60 ms then I just forced it to call mover.step(dt)
, multiple times.
Stress testing physics with maaaaany dummies running around. I’m spamming spawning tens walkers when frames drop ugly. Each walker runs a behavior tree of wait/walk sequence with collision testing. I can’t count how many of them are out there.
The other way of doing it would be calling mover.step(dt)
only once with much bigger delta, but that would cause characters to move through the walls… Probably? But still, we have more control over the code with luxe. I’ll just leave it like it is until (if) I find something ugly happening around it later.
Final lesson learned: luxe.PhysicsEngine! It’s yours to extend, you are its master and you may do with it as you will in Elohim voice cough.
Actor - my new entity type
Actor what? Let me explain.
Requirements for this game were:
- pixel style graphics - camera is zoomed 2 or 3 times. It’s possible for entities to appear as if they were placed in half-pixel location during movement. I hate when it happens (pixel world looses its pixeliness) so each actor needs to “snap” to the pixel grid.
- pseudo-3D location - in top-down view any character can be located in basic x/y position. Some characters can jump up to avoid being hit or attack from the air (ground pound!). This can be achieved by utilising unused Z axis in position vector. Adding shadow for each entity at base
position.z=0
will help player recognise if the thing is flying or standing on the ground near him.
Every entity placed in the world needs to have that functionality and properties so I thought that I could just create new class, extend luxe.Visual
and add those in.
Why not use components with all those additional functions/fields? I could just create SnapToGrid
and Location3D
components but I wanted every static, moveable, playable, visible entity to share the same characteristics without needing to remember to add each component every time I create new kind of actor.
So, each actor has properties like:
- pos:Vector - position of an actor snapped to pixel grid (It’s basically
pos.x = Math.round( realPos.x );
). - realPos:Vector - “true” position is still perserved in here so it can move very slowly, half pixel at the time. Without it, an actor wouldn’t move at all if velocity was really low.
- velocity:Vector - current directional velocity.
- acceleration:Vector - constant force to be added to velocity (local to the actor, gravity is set in physics engine).
- force:Vector - one-time push, will be added to velocity and reset back to zero each step
Additionally, a couple of functions to:
- make sure that it won’t go underground by keeping
z >= 0
(should probably move that to separate component, I like the idea of underground worms coming at you :D ) - update geometry vertices - in case of jumping, x and y of the actors
position
are kept the same. Position of vertices are in this case affected byposition.z
, so that actor’s square looks like flying up and down. - applyForce( v:Vector ) with event listeners in case if any other actor wants to push this one away.
BTW, The name ”Actor” came to me after playing around with Unreal Engine, which is also component-based game engine. Actor in UE is anything visible/movable/playable in the world that accepts components, so the name just stuck to me naturally. It can be a character, a tree or even bullet.
AI
Haxe Behavior Trees made by @whuop are working great! So far I managed to use it only once though:
- wait (random time)
- pick random direction
- walk straight (random time)
Each red guy is using player’s walk/run component for movement, and this again is why I love component-entity system!
Director - the Singleton
This is probably completely against the whole component-entity system and the whole OOP, but what the hell! This one is ba a cry for help or constructive criticism.
Director
knows everything about the game world and physics simulation. I couldn’t find any simple way of accessing tiles from a collider component. Tilemap data was placed in class Level
and colliders were attached on Sprites and I couldn’t figure out how to create any direct reference between them. First thing that came to my mind was a singleton. At the begining I made it contain a reference to the currently displayed level (with tilemapdata, layers, tilesets). Later I also added reference to the custom physics engine, so every component can add itself to the list if needed, but more on that later.
I still don’t know if having a singleton around is good idea, but so far It’s doing the job. I can only imagine that it’ll become one crowded piece of multi-purpose functions.
Shaders
I don’t really have anything useful to say about shaders yet… :D
End of transmission
I still didn’t say anything about collisions! That would find its place in the next dev log. Until then I still have some things to do on my list:
- enemies - something that would actually try to attack you
- damage/health - obviously every game needs an END state
- shaders - 2D black holes and wormholes are in my plans so that would probably require me to get some shader
whichcraftknowledge - world editor - a map that glues all the levels together into something traversable and fun to explore
Okay, I’m done for today. Thanks for reading, you’re awesome!