collinsmith / riiablo

Diablo II remade using Java and LibGDX
http://riiablo.com
Apache License 2.0
872 stars 99 forks source link

Improve Box2D implementation and memory usage #52

Open collinsmith opened 5 years ago

collinsmith commented 5 years ago

Entity update loop has become a bit messy since adding screen vector, which updates during Entity#act(float) but is actually occasionally required during Entity#update(float) (the names of these methods are also a bit confusing -- update(float) is supposed to update position/logic, act(float) is supposed to update animation frames).

This seems like a good opportunity to look into integrating a native physics solution like Box2D, which may improve performance and greatly simplify a lot of the problems I'm currently having with collision detection and movement. One specific optimization that may be required is wall aggregation -- walls are subdivided into 1yd squares which Box2D will prefer represented as a single body (these walls will be needed when I want to detect rooms to fade walls). This also will help add light ray casting.

One problem I've been having is that I originally assumed player movement was based with the underlying 1yd grid, but that assumption seems to be incorrect to a point. I tested this by using a character with extremely slow walk speed, walking and then casting a spell after a short enough distance where the next subtile could not have been reached. The characters stops instantly indicating that their position is not locked to the grid. I also noted that their networked position data seems to only update positions based on the grid -- so this may be a client-local behavior.

I also started looking into unit sizes, and assuming the information I was looking at was correct, they are not using axis-aligned boxes seem to be a selection of horizontal boxes (this would explain SizeX and SizeY columns in monstats2, but they are always same as far as I've seen). With Box2D, this might be better implemented as a circle with diameter of SizeX.

collinsmith commented 5 years ago

Initial tests implementing box2d look good -- I'm able to render out DS1s. I'm making an effort to keep the scale 1:1, so 1 unit in box2d will be 1 unit in-game (1 subtile) -- however this gets skewed with projection (physics are simulated without isometric), so I'm worried how this could effect physics simulation compared to the original game, so I might require some kind of scalar. Because of the large number of subtiles (act 1 town is 280x200 subtiles), I'm going to look into combining adjacent walls on a DS1 scale (initially I might test with individual tiles for simplicity) -- so a polygon will never be larger than its backing Map.Zone grid size.

It should be possible to clean this up eventually and do things like create better bounds for level boundaries using fewer rectangles since these will be well-defined once level generation is added.

collinsmith commented 5 years ago

I integrated MapRenderer onto my Box2D test module and it works good after scaling is adjusted. Performance for town zone is decent on desktop (41186 bodies, 300 fps), however once I add the additional zones the performance becomes quite bad (107522 bodies from only 3 zones) -- so I'm going to assume even the town zone would be too much for mobile. I think I either need to dynamically create/destroy static wall objects that are within a certain range of player or find a good wall aggregation strategy that's cheap.

The more I think about it, I really would like to find a way to make this work to leverage the advanced functionality of box2d, but I want to take advantage of the existing in-memory collision arrays which are O(1) access and makes the most logical sense (each cell is a subtile, array index is local x,y coordinate of subtile within zone) -- also, a 400x400 zone only needs 160KB to store world collision data. If I decide to take this route without finding appropriate optimizations, perhaps it's plausible to use both systems and let box2d handle entity collisions.

Box2d lighting works really well and looks good, except will eventually require customized shader for collision with walls to cast light on them properly (not designed for isometric games). It will also require drawing the shadows under some specific layers to make it look correct (e.g., roofs, walls in some cases).


I implemented a basic aggregation algorithm that combines subtiles in the same row with the same flags which has significantly reduced the number of bodies to 2209 for all 3 zones -- It's also really cheap. I'm betting including column will cut that down to ~200.


Further optimization with column aggregation has reduced the number of bodies for the first 3 zones to 4509 (roughly 4% of the starting amount) at the cost of 1MB of temp memory while the map is built which is reset for each zone. I think this is much more manageable for mobile -- remains to be seen how much of a hitch this will be for different zones (how many zones at once?). I think Body#setActive could help here, but the building of the box2d world could take some time on mobile -- benchmarking is needed here. From box2d manual

Note that activating a body is almost as expensive as creating the body from scratch. So you should not use activation for streaming worlds. Use creation/destruction for streaming worlds to save memory.