Pixel Movement

by Forum_account
Pixel Movement
A pixel movement library for isometric and top-down maps.
ID:114345
 
Any game can have performance issues. You might think that BYOND games are so simple that it'd be hard to max out your CPU, but if you create a big world with lots of mobs it can be very easy. You might be familiar with some simple ways to identify performance issues and optimize your game, but when you use a library this can be more difficult. This article describes some performance problems you might run into when using my Pixel Movement library and some easy to implement solutions.

Note: The article explains things in terms of my Pixel Movement library, but almost everything here can also be applied to my Sidescroller library.

My pixel movement library provides you with a pixel-based movement system that any mob in your game can utilize. For this to work in as many situations as possible, it has to make this assumption:

Any mob may be moving (or begin moving) at any time.

The library is always prepared to handle pixel movement because it has a global loop that, every tick, executes every mob's movement behavior. This assumption is necessary for the library to work for any situation you might create, but this assumption can lead to problems. There's no way for the library to fix these problems, but it does provide you with simple ways to optimize and avoid these problems.

Here are the two general problems this assumption can lead to:

1. The default movement behavior performs some unnecessary actions.

The library allows for movement in the vertical direction. Players can jump, stand on top of walls, or walk off the edge of platforms. To ensure that players fall when they're supposed to, the default movement behavior enforces gravity. If there's not solid ground below a mob it'll accelerate downwards.

This sounds good, but here's the catch: Every tick the library checks whether or not every mob is standing on solid ground. Even if the mob is a stationary NPC that'll never move and never fall, the default movement behavior is still checking for this.

This is only one example of how the default movement behavior may be unnecessary. Here's the complete default behavior:

movement()
set_flags()
gravity()
action()
set_state()
pixel_move(vel_x, vel_y, vel_z)


If a mob is stationary, you don't need to do any of that. You can do this:

mob
var
stationary = 0

movement()
if(stationary) return
..()

npc
stationary = 1


That way all NPCs are flagged as being stationary and their movement proc won't waste CPU time.

If you don't make use of a mob's on_ground, on_left, on_right, etc. vars then you don't need to call set_flags(). Some of the default behavior of the library uses these flags (ex: you can only jump if on_ground is true), but if you don't make use of vertical movement (jumping/falling), then you may not need set_flags() at all, you can just do this:

mob
set_flags()
on_ground = 1


Even if you do use jumping, not all mobs will need this. Projectiles, for example, generally don't need this. As the optimization demo that comes with the library shows, you can greatly simplify the movement loop for projectiles:

mob
projectile
movement()
pixel_move(vel_x, vel_y, vel_z)


To create a bullet that just moves in a constant direction until it hits something, you don't need to call set_state(), action(), set_flags(), or gravity().


2. The movement behavior doesn't need to execute every tick.

The movement behavior is being run every tick for every mob in the game. Even when the behavior is correct (in other words, when it's not doing unnecessary actions as described above), you just might not want it to execute. You don't need movement loops to be running for mobs that are in one part of the world when the player is at the opposite side of the world.

You can override the global movement loop to have it run the movement() proc for some mobs in the world, not all:

var
list/active_mobs = list()

world
movement()
for(var/mob/m in active_mobs)
m.check_loc()
m.movement()


The default world.movement() proc is almost identical, it has "world" instead of "active_mobs" in the for loop. By keeping a list of active mobs you guarantee that you're not running movement loops for mobs that don't need it. If there aren't any players in a certain map, all the mobs on that map should be inactive so you don't waste time handling their movement.

This example doesn't include the conditions for adding or removing mobs from the active_mobs list. Check the demo called "performance-demo" in the Pixel Movement library for a complete example of this technique.

This can also be useful because I tend to implement a mob's AI in the action() proc. By automatically disabling a mob's movement loop when no players are nearby, its AI routine is disabled too.


Why Is This Necessary?

If you're familiar with other libraries on BYOND, you may be wondering why this is even necessary. For example, you can use the F_Damage library to make damage numbers appear on the screen and you don't have to worry about optimizing it for how you're using it.

My Pixel Movement library is entirely different. It's not a canned function that you call from your code whenever you want to do something, it's a completely new movement scheme that passively permeates your whole game. It's a comprehensive movement scheme that gives you completely functional default behavior.

Also, the pixel movement library increases the game's framerate from the default (10 fps) to 40 fps. When the server processes events 10 times per second, you can be a little careless and get away with not optimizing your code. By upping the framerate, performance problems will be amplified.


Conclusion

The Pixel Movement library creates movement loops that run every tick for every mob in the world. For most mobs this is not necessary but the library can't assume otherwise. It's up to you to optimize your usage of the library to fit your game, and hopefully this article has given you a few ideas about some easy ways to do that.

If you have any questions, don't hesitate to post on my forum.
Quite nice to see, that you point that out. With massive amount of movement, not only CPU usage increases, but also network traffic increases at least proportionally. I't like to read some thoughts about managing that traffic, because I think that is a BYOND specific bottleneck which is even harder to handle.
Most of the increased network traffic can't be avoided. If you want to update a mob's x, y, pixel_x, and pixel_y every tick it'll take more than updating just their x and y. Also, with pixel movement you'll generally have a higher framerate so these updates are being sent out more frequently.

I haven't given much thought specifically to optimizing network traffic. Its a lot harder to figure out because the inner workings are more mysterious, but I expect that the same techniques will help out. If the server notifies the client of changes to nearby objects (the range being slightly greater than your view size), if you minimize the amount of activity going on outside of the player's screen you'll minimize the amount of updates that are needed.

I imagine that CPU usage will be the bigger issue because you don't need a lot of players for CPU usage to be high. I have briefly looked into optimizing A Miner Adventure's network traffic, but I think a lot of the optimizations will be specific to individual cases. These CPU optimizations are more universal.