Published on

Devlog #65 - PxPlayerMoveController

Previous: https://www.patreon.com/posts/devlog-63-151418457

This week was a big one. I finished porting over the actor spawn and movement system to the new engine and got it hooked up to the new SmartFox client. This is the first time we are taking real data from the server and turning it into actors moving around the world in PxEngine.

Spawning actors was the first piece to get working. The server sends appearance, attributes, destination, and location data. Instead of decoding everything into temporary objects, we now read only what we need straight from the raw WebSocket data and pass it directly into the actor system. The actor system creates the sprite, places it using the destination values, and sets its facing using the direction from the location data.

Movement is where things get really interesting. This is by far the most common packet sent in the game. In busy areas you can easily see over a hundred movement updates per second. Because of that I optimized this path heavily. The movement packet now reads direction and destination directly from the network buffer using a fixed float3 layout, so we avoid allocating arrays entirely. The handler receives raw numbers and feeds them straight into the actor system.

We also changed how movement timing works, and this ended up fixing a really annoying issue. The old system was based on delta time, which means movement depended on how often frames were updating. That works fine while the game is in focus, but browsers throttle or pause updates when a tab is in the background. When you come back, the accumulated delta time no longer lines up with real time, so actors end up in the wrong positions or snap to where they should be.

The new system is based on absolute time using performance.now(). Instead of advancing movements step by step each frame, we calculate where an actor should be based on when the movement started, how fast it is moving, and the current time. That means movement is tied to real time instead of frame updates. Even if the browser pauses the game in the background, everything stays consistent, and when you come back actors are exactly where they should be.

The result is a much cleaner and more reliable system. Actor spawning works the way it should, movement updates create almost no garbage, and timing issues from backgrounding the browser are gone. Since this system is constantly running, fixing it has a noticeable impact across the entire game, especially in busy areas.

This screenshot shows everything working, and Chrome’s Performance Recorder indicates an average frame time of 0.3 ms, which corresponds to roughly 3,300 FPS. In practice, browsers and web views cap requestAnimationFrame to the display’s refresh rate (commonly 60–144 Hz), so this number reflects headroom rather than what will actually be displayed. There’s still a lot more to add, which will naturally increase the frame time, but this is an excellent spot to be in.

Wow it has almost been exactly one year since I first added pathfinding to FO2. Looking back at that system now, it is kind of wild how much it carried. At the time the goal was just to get something working so actors could move around obstacles and reach a target reliably. It did that job, but it also ended up becoming a pretty large and tightly coupled system where pathfinding, collision, and movement were all deeply intertwined. A lot of logic lived in one place, and a lot of behavior was implicit rather than clearly defined.

https://www.patreon.com/posts/devlog-56-path-125421925

As the game grew, those tradeoffs started to show. Because pathfinding, collision, and movement were so tightly coupled, it became harder to reason about how a change in one area would affect the others. It also made it more difficult to keep behavior consistent between systems and between the client and server.

PxEngine is an opportunity to fix that properly.

Instead of building everything around pathfinding, movement is now split into clear layers with well defined responsibilities. At the top there is a single controller that decides intent, and underneath that are smaller focused controllers that handle direct movement and path movement separately. Each piece does one job, and the flow between them is explicit instead of implied.

This also made it much easier to support different input styles cleanly. Movement is no longer just click to move. WASD and analog stick input are now first class, and they plug into the same system without any special cases. Directional input feeds directly into the direct movement controller, while pointer input sets a world target that can either be reached directly or fall back to pathfinding if needed.

Direct movement is now the default. If there is a clear line to where you want to go, the system simply moves you there without ever touching the pathfinder. Pathfinding only comes into play when something is actually blocking the way. That shift alone removes a lot of unnecessary work and makes movement feel more immediate.

That shift only works because the underlying collision system is now much more reliable.

In PxEngine, collision is treated as a shared foundation instead of something embedded inside larger systems. The raw collision data is loaded once, and everything runs through the same query layer. Whether we are checking if a path is blocked, finding the last reachable point along a line, or generating a path, it all uses the same underlying rules. That keeps the results consistent no matter which part of the system is making the decision.

One of the biggest improvements from this is how edge cases are handled. Things like tight corners, diagonal movement, or navigating around small gaps are no longer special situations hidden inside pathfinding. They are handled directly by the collision queries themselves, which means both direct movement and pathfinding behave the same way without needing separate logic.

Pathfinding now fits into that system instead of driving it. It operates within a bounded area, reuses memory between searches, and focuses only on solving the path when it is actually needed. Because it relies on the same collision queries as everything else, the result matches what direct movement would do, instead of feeling like a separate system with slightly different rules.

The overall result is a system that is easier to reason about, easier to extend, and much more predictable in how it behaves. Movement, collision, and pathfinding are no longer tightly coupled pieces fighting each other. They are independent parts that work together through clear boundaries, which makes the entire foundation of the engine feel significantly more solid.

This is one of those changes that doesn’t just improve a single feature, it improves everything built on top of it. Movement feels better, behavior is more consistent, and future systems can plug into this without needing special cases or workarounds.

With that in place, the next step is getting this in front of players.

I will be adding a 5th character slot to the main site that will be used for testing the new PxEngine. The 5th slot will be launching this week.

Characters in this slot are strictly for testing and will be reset frequently as the engine continues to evolve. Your main characters will remain completely untouched and will never be reset.

This will let all Patrons experience PxEngine in pre-alpha and as it evolves!

Feedback and bugs for PxEngine will go here:

https://discord.com/channels/1076004705773310084/1486052018857705544

I can't wait to see what everyone thinks of the WASD/Left-Analog Stick movement!


Have Fun & Keep Gaming!

P.S. - The next PxEngine devlog will be in 2 weeks! See you soon for the 5th character slot launch later this week.