Battle Dyzx is a 2.5D physics based action game prototype written in Lua and using the Love2D API. The project started of as an attempt to recreate spinning tops fighting games (e.g. Beyblade), where players have partial control over the movement of their spinning top. The concept eventually grew to involve some less physically correct abilities and mechanics.
- Dynamic fast paced gameplay that that tests reflexes and precision.
- Battles are based on flow prediction and timing, rather than button mashing
- Any number of players on a local machine
- 3D look and feel at the cost of 2D rendering
- The physical characteristics of each dyzk are determined entirely by its image, allowing battlers to craft the dyzk that suits their play style best
- Arenas can be constructed entirely from images (diffuse+heightmap) with no coding or extra tools needed, except for an image editor
- Data driven architecture and modability are the ultimate goals
The project is currently on-hold awaiting eventual restart. Due to wrong technological choices, the cost of development increases as new requirements are added as many features that should be available, need to be designed and implemented from scratch. The project will be restarted with a proper engine and tools.
First and foremost – physics. Love2D is equipped with the Box2D physics engine, but there were a few problems with it:
- there is a limit on how fast rigid bodies can rotate,
- there is no height-map collision detection/response,
- it doesn’t handle the third dimension
On the other hand the game required 3D logic, it only needed to handle axis aligned cylinder shapes and their interaction with a the height-map arena, and I needed fine control over how it worked internally, it didn’t have to be accurate as long as it played well, so I rolled my own simple dynamics engine embedded in the game logic. Collision detection was extremely easy to add, as there are only two types of objects spinning tops (circles) and arena (height-map) and collision response between tops was not hard either (and most of it was faked to add some exaggeration and custom gameplay rules). The collision reaction between the tops and the arena was however slightly more interesting problem (see this video showing an early implementation).
--# Reset the acceleration to g accel.x = g.x; accel.y = g.y; accel.z = g.z; --# The following bit comes from the expression: N x ( C x N ) --# Where N is the arena normal and C is the control vector --# This gives us the projection of C onto the plane with normal N --# We take advantage of the double cross product involving N --# and the fact that the control vector does not have a z component --# to reduce the number of operations (it is called every frame) local c = self._control; local n = self._arenaNormal; local cnx = c.x*n.x; local cny = c.y*n.y; local control3D_x = c.x*(n.y^2 + n.z^2) - n.x*cny; local control3D_y = c.y*(n.x^2 + n.z^2) - n.y*cnx; local control3D_z = -n.z*(cnx + cny); --# Now we apply dot product ( C . C3D ), and scale by speed local speed = self:GetSpeed(); local control3D_len = (control3D_x*c.x + control3D_y*c.y) * speed; --# Finally we apply the force accel.x = accel.x + control3D_x*control3D_len; accel.y = accel.y + control3D_y*control3D_len; accel.z = accel.z + control3D_z*control3D_len; --# Normal force is the force with which the surface counteracts to --# forces trying to push solid objects through it. It cancels out a bit --# of the gravity and the other forces that act in the direction --# opposite to the surface normal. This would usually leave the forces --# unbalanced and make the dyzk slide down the slope. local normalForce = self._arenaNormal * self._arenaNormal:Dot( accel ); accel:Sub( normalForce );
(original source here)
A related problem was the generation of a normal map from a gray-scale image. One of the original requirements was to allow users to add their own arenas in the easiest possible way – gray-scale height maps seemed like the obvious solution, but that approach had a few drawbacks:
- first and foremost getting and setting individual texture pixels in Love2D is exceptionally expensive, so all get operations had to be cached and worked around.
- more importantly, though, 8 bit did not provide enough precision for a smooth gradient and the resulting normal maps had visible steps (that didn’t hurt the gameplay, just the visuals). I tried a few things to tackle this – the most promising approach seemed to be 2D curve fitting using nurbs ( b-splines ) and sampled points from the height-map. I only ever implemented uniform sampling which did not yield the most impressive results, the correct way to do it would have been to chose key points at edges and other features around arena and fit the curves trough them.
At that point I realized it was becoming more of a problem that should be solved in C and not Lua, so I dropped it. Instead I went for a procedurally generated height-maps with floating point precision as an alternative and generated the normals from them. As for the actual normal generation, that was quite simple and I had already done it before. I used a 3×3 kernel with less weight on the diagonals (code here).
More on the visual side I experimented with various ways to make the game look more 3D.
- Fake perspective to make objects closer to the camera appear larger plus a fake transformation using a combination of non-uniform scaling and rotation to make dyzx appear tilted based on their orientation (Love2D does not have transform matrices nor 3D transformations of any sort).
- At one point I implemented a displacement shader using the heightmap to make the arena tilt based on the camera angle, but the lack of depth buffer meant that there would be no easy way to make the arena occlude moving objects. The idea had to be scrapped.
Although the project was fun to work on, the tools I was using to develop it would not allow it to scale well. Even though I realized early that I cannot create the complete game I envisioned in Love2D, on couple of occasions I made the mistake of sticking to it and taking it too seriously. Instead I should have “used the right tool for the job” and switched over to Unity or Unreal Engine early on. Another mistake I made was to tackle the most difficult problems first so that I can “get them out of the way” and get on with the fun bits after, that strategy backfired as the difficult problems took longer and often there was very little to show for the effort, at the same time I was gradually losing interest as the fun bit never came. It was a lesson learned – be lazy and take the easy route. Currently “Battle Dyzx” is postponed and awaiting eventual restart.
Leaving the negative behind I learned a lot about physics, more about GLSL, faced some tough problems and reinvented some wheels I wouldn’t have had to otherwise. I love the concept even more than I originally did and I came up with some really interesting ideas that I want to see happen in the future. As far as I’m concerned “Battle Dyzx” the love2d game is not the end of the road, it is just the start of an an even more amazing game.