Asteroid Creation with Working Contact Handling
When I wrote my last post, I had, theoretically, finished graphics and asteroids. In development, though, there is often a gap between when code “should” work and when it actually does. There’s an iterative process of testing and development. My project is no exception.
At the time of my last blog post, all of the code for working ECS-based graphics was in place, but when I ran my testing code, it didn’t work. My test was supposed to create a golden asteroid sprite, which would appear just south of the player’s position. When run, though, the asteroid didn’t show up. I delved deep into the way that libGDX handles rendering, hoping that a better understanding of the underlying mechanics would help me get my system working. Unfortunately, my searching was in vain. I could not figure out why my code wasn’t funtional. One of my mentors, Adrijaned, found the answer: in my test code, the order of the arguments was incorrect, which meant that I was putting the scaling information where the coordinates should have been, and vice versa. Whoops! Once I fixed that, my code worked perfectly.
SolApplication implements ApplicationListener, which is a libGDX interface for running and rendering applications. Every tick, a call is made to
render(), which calls SolApplication’s
draw() method, during which a RenderEvent is sent to each entity with a Renderable component.
Renderable components (renamed from
Graphics) work the way that I described when I wrote about them, although the individual elements now also contain width, height, and tint. The RenderingSystem draws each sprite unless the entity has an Invisibility component. The PR has been merged.
While I was working on that, a new Gestalt version was released. This new version contains the class EmptyComponent, which is an abstract class for components without any data fields. It also fixes a few minor bugs in entity creation. I made a PR to update DS to use the new version.
With the graphics working, It was time to tackle asteroids. When I started, I tried to use prefabs, which make entities from a pre-written recipe. However, I encountered several complications when using them, and after talking it over with my mentors, we decided that my time would be better spent working on developing new features.
When the AsteroidBodyCreationSystem receives a
GenerateBodyEvent for an entity with an AsteroidMesh component, it creates a new asteroid-shaped
Body for it. As of now, the way that the CollisionMeshLoader is structured, it would be very difficult to design a generic Body creation system, so for now it is non-modular. Impulse handling, on the other hand, is relatively generic. My current setup is asteroid-specific, but redesigning it is one of my next goals.
Once I wrote a non-prefab test, I realized that the contact handling in SolContactListener wasn’t fully set up. Specifically, I had designed entity-to-entity contact, but not entity-to-SolObject contact. When the player’s ship collided with the asteroid, it wouldn’t pass through (because libGDX still worked), but neither the asteroid nor the player would take any damage, no matter how fast they collided. I couldn’t just send the entity a collision event and call the SolObject’s
handleContact() method, though, because SolObject’s method requires another SolObject as a parameter. To solve this, I created a SolObjectEntityWrapper, which is what the the name implies: a wrapper for an entity that can be treated as a SolObject. It contains a reference to the entity it wraps, and doesn’t do anything when any of its methods are called. That way, entities can be handled normally, without concern for for how the wrapper is being used by the other method interacting with it.
Once I made that change, ramming into the asteroid caused it (and the player) to be damaged. However, I encountered another issue: when the asteroid was destroyed, the game would crash with a libGDX error with the short message of “
isLocked == false”. After some research, I determined that libGDX has specific requirements for when certain objects, such as Bodies, can be created or deleted. The Bodies were being deleted during the contact handling (if they took lethal damage), which is not a valid time for the deletion. I had to restructure my removal system to delay the deletion of the entities until a valid time.
Now, when an entity should be destroyed, the DestructionSystem doesn’t immedately get rid of it. Instead, a SlatedForDeletion component is added to the entity, which indicates that it is ready to be removed. Every tick, the DeletionUpdateSystem sends a DeletionEvent to entities with a
ShouldBeDeleted component, and the
DestructionSystem receives those events and deletes the entities. Similarly, when the BodyHandlerSystem needs to create an entity, it only does so when it receives a
All of that code has been merged, so now I’m on to my next goal: projectile handling. As of now, projectiles pass straight through entities. The way that projectiles currently work is tied directly to
SolObject, which is the source of the issue. Using my new SolObjectEntityWrapper, I will refactor it to work with entities as well, so that it can behave as expected.