XDR Programming #2: Proxy Input, Trick component, Boosting, Bullet Physics

In this entry, I will be covering some of the more minor features that I worked on. Firstly, I’ll cover adding support for AI input to the vehicle model, then I’ll discuss the Trick component and the Boosting functionality. Finally, I’ll touch upon the tweaking I did for the Bullet Physics engine to achieve the “feel” that we want in the game and solve some of the problems in the engine.

Proxy Input

By saying “Proxy Input” I really mean supporting input from someone other than a player’s control pad – the AI. In the last sprint of the prototype stage, we decided to try making the AI use the same movement model as the player does, instead of making the AI “cheat” and only appear like it moves the same way the player does. For this, I extended my vehicle model to support such functionality. Firstly, I added storage for Proxy input to the movement and rotation classes:

ProxyInput.PNG

This is a variable that an AI agent can write into. It takes the same format of input as the control pad (-1.0f to 1.0f for negative to positive input). Then, I modified the vehicle model to check if these values are set, and use them instead of player input if that’s the case:

VehicleProxyInput.PNG

This is an example condition when performing movement, and similar conditions are inserted for when a turn is performed, or when updating the VisualRotation component. At the end of each frame, these proxy inputs are cleared. This was a simple and quick implementation, mainly because we wanted to test the feasibility of using the vehicle model of the player on AI quickly.

However, it’s definitely a bit messy, and ideally we’d want to streamline this. In its current state, the vehicle model needs to explicitly know about the existence of Proxy Input. Instead, it would probably be better to make this completely invisible to the vehicle model, so no special code is required to support this. That could possibly be achieved by the AI directly writing into the input buffer, although that would mean that the AI vehicle would need a player ID assigned to it and the input buffer would need to be expanded considerably based on the number of AI agents in any given race.

Making the Proxy Input invisible to the player would hold a significant advantage that any future functionality added would be readily supported for Proxy Input. For example, currently the vehicle model does not support proxy input for Boosting and performing Tricks. All of that needs to be explicitly coded into the model. Deriving a system that supports this would be easier to extend, and it’s a good idea to suggest doing this when the need to add more functionality for input proxy-ing arises.

Trick component

As a proof of concept, I implemented the functionality of doing a single trick (which is a flip) to the game as a separate component. At the time of writing, doing tricks does not yield the player any gain, and is not tied to any gameplay system. Its purpose was simply to enable the design team to experiment with potential situations where the player might want to do tricks, help define level design and also design the actual gameplay reward system for doing tricks.

The trick would be invoked from the core vehicle model, but the execution would be done in the separate component. Here’s the function in the vehicle model that checks for doing tricks:

UpdateTrick.PNG

The trick component supports disabling player controls while a trick is being performed. If this is the case, the rest of the vehicle’s logic is ignored if doing a trick.

At the moment, the trick is performed programmatically, via quaternion rotation directly to the vehicle’s mesh. This does not look good at all, so we would likely want to do it via key framed animation in the future, to give it a more arcade “feel”, and it could also spark completely unrealistic tricks such as “looking around behind me” and so on (unrealistic being fine because after all, it’s an arcade game). The code for executing a flip is as follows:

TrickComponentUpdate.PNG

Here, the object is being rotated a certain amount each frame (I couldn’t command it to rotate more than 180 degrees at once, since quaternions don’t really care about rotation directions). Since I can’t just say “Rotate 360 degrees on this axis” and be done with it, I need other measures for how to detect the end of an animation. I do it by recording the initial pitch of the drone, and then comparing the current pitch with the initial one. If it’s closer than a given threshold, the trick is considered finished:

CheckTrickShouldFinish.PNG

Since this can lead to the trick being “finished” just as it starts, I include a small timer at the start to deal with this particular edge case.

Since this is just a placeholder implementation, I don’t feel there’s much value in discussing the improvements I could make to this component, and the current implementation fully fulfils its purpose (that’s not to say that the current implementation could not be improved significantly).

Boosting

Another implementation task that I was given was to implement player boosting. At this stage of development, the boost mechanic is not tied to any gameplay systems, and the player can boost any time they want, for as long as they want. This was done so that the design team can explore the boost mechanic, the situations it would be used in and the level design to enable it. They would later design a gameplay system that would balance the boost mechanic.

The implementation of the mechanic itself lies in the core vehicle model:

StartOfBoost.PNG

Here, when boosting, the player’s “Move forward” object is given a bonus to acceleration and top speed, while the “Yaw rotation” object can be penalised making steering more difficult to do when boosting. Once the boost button is released, the values are returned back to normal. All of these aspects are tweakable by the design team, of course:

BoostProperties.PNG

Furthermore, to give a nice visual effect for when the player boosts, the drone is tilted forwards more than normal, which is something tweakable in the VisualRotation component:

VisualRotationBoost.PNG

VisualRotationBoostParams.PNG

The main problem is that the boost mechanic propagates through several objects here, including the Movement/Rotation objects and the VisualRotation component. Everything needs to know about everything, and it becomes a tight dependency net. As argued before, I believe a message passing system would help relax the amount of dependencies present. Furthermore, it might be slightly tidier to extract the boost mechanic into a separate component.

Collision Responses and Tunnelling

In this last bit, I’ll quickly cover what I did to tune the Physics engine towards our requirements. The first problem we encountered was that when the drone would hit something, it would rotate in all possible axes, and gameplay would become near-impossible after that happens. Instead, we figured that it would be much nicer if the drone just bounced off the wall when it hit it, as that way the player can just get back to racing, without having their whole experience ruined.

At least that’s the desired starting point for collisions. Maybe later on we’ll want to add some damage model, but for now, we wanted to at least simplify the collision responses. After a couple of days digging through the internet for answers , I found a way to disable rotations on collision responses:

DisableCollisionRotation.PNG

I created a simple function call to the rigid body that any component can make in order to disable the rotations on responses.

While I don’t fully understand exactly what the meaning of the code is (admittedly), it effectively disables all rotations from collision responses, which can be seen at the start of this video:

Later on, we might want to enable Yaw on collision, since right now the player just bounces off the wall, but is still facing the wall after the bounce happens. In other arcade-y games, such as Need for Speed Underground, the collision response typically results in the player also aligning its forward direction with the wall on the side, so we might want that as well eventually, and we should be able to tweak the responses enough to support this.

The next bit I had to solve was collision tunnelling. Tunnelling is the occurrence of going straight through walls at high speeds, when the rigid body moves through the whole wall in a single step and the collision is left undetected. This is a big risk for racing games especially, since vehicles travel at particularly high speeds. One approach to counter this that I explored, but unfortunately couldn’t get to work reliably yet is Continuous Collision Detection (CCD). In discrete collision detection, the simulation of rigid bodies is advanced at discrete time steps, and this is why missed collisions can occur. In CCD, the engine checks for collisions between the current physics frame and the next, effectively sweeping the line between the physics frames. This helps alleviate tunnelling, but I couldn’t get it to work as expected. The Bullet community also states that CCD is a work-in-progress feature of Bullet at the version we are using, so we apparently can’t completely rely on CCD functionality to work.

http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Anti_tunneling_by_Motion_Clamping

http://bulletphysics.org/mediawiki-1.5.8/index.php/Collision_Detection_and_Physics_FAQ

https://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=313

http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=8390

The work-around I’ve found instead, is increasing the number of physics simulations per frame by four times, as such:

PhysicsSubSteps.PNG

Here, the original divisor for sub-steps at 60FPS was 2.0f, so I increased it to 8.0f. This most certainly increases the computational cost for simulating physics. However, my lecturer Zaf told me that it isn’t at all uncommon for racing games to have an increased physics simulation rate. Furthermore, in the game’s present state, it runs at 60FPS flawlessly, and we don’t have that many dynamic bodies to simulate (only the drivers at the moment). This means that the solution should be good enough up to the point when we need to get more performance out of the game. At that point, we might well consider re-investigating CCD further.

Related files

None due to NDA.