TDD and Gaming Physics

I play with a small game project, and since I am not very experienced in TDD, I would like to get some expert opinions on several things.

First of all, I realized that TDD does not seem ideal for game development. Opinions seem to vary greatly on this. My initial uneducated opinion was that TDD looked as if it worked very well for the whole logic of the game. I thought that everything related to video output and sound would be abstracted into classes that would be checked visually.

It all started well. The goal was to create a 2-dimensional space game (asteroids for those who care). I created a series of unit tests for the Ship class. Things like initialization, rotation can be easily tested in such series as GetRotation (), TurnRotateRightOn (), Update (1), GetRotation (), Expect_NE (rotation1, rotation2). Then I hit the first problem.

My understanding of TDD is that you should write a test, do you think you should use a class. I want the ship to be able to move, so I wrote a class that basically said. GetCoordinates (), ThrustOn (), Update (1), GetCoordinates (). It was perfect to make sure the ship was moving somewhere. However, I quickly realized that I had to make sure that the ship was moving in the right direction and at the right speed. This was followed by 75 lines of unit test, where I basically had to initialize the rotation, check the coordinates, initialize the thrust, update the ship, get new coordinates, check the new rotation. What else, I do not see the need to ever get the speed of the ship in the game (only the coordinates, the ship must update itself). Because of this, I had no direct way to get speed. Thus, the test basically had to recount what the speed should have been exactly so that I could make sure that it matches those with the codes that I received after the update. Overall it was very dirty and a lot of time, but it worked. The test failed, the test passed, etc.

This was good, as long as I realized that I wanted to reorganize the ship update code into the abstract class "Actor". I realized that although each subclass of the Actor should be able to correctly calculate a new position, not every subclass will necessarily update its speed the same way (some collide, some do not, some have static speeds). Now I mainly come across the prospect of duplicating and modifying this huge and massive test code, and I cannot help but think that there should be a better way.

Does anyone have any experience with unit testing this type of complex black box? It seems like I basically have to write exactly the same physical code in the test so that I know what the result should be. It seems that I am actually winning, and I am sure that I did not have the point of all this along the way. I would really appreciate any help or advice anyone could offer.

+7
source share
3 answers

I suggest starting with creating a component that calculates position and orientation, given the sequence of control inputs. This component then represents a “unit” for testing purposes. In the test examples for this component, all scenarios that you can imagine will be considered: zero acceleration, constant non-zero acceleration, pulse acceleration commands, etc. If the application does not need speed, then the component will not reveal any functionality related to speed.

When generating the expected results for inclusion in the tests, it is important to have high confidence in the correctness of the expected results. For this reason, it is necessary to minimize the amount of code needed to generate the expected results. In particular, if you find yourself writing test forests that are almost as complex as the component under test, then the problem of errors that occur in the tests becomes a serious problem.

In this case, I would generate test data directly from the equations of motion. I use Mathematica for this purpose, since I can directly enter equations, solve them, and then generate graphs and result tables. The graphs allow me to visualize the results and thereby have the confidence that they are reliably correct. Excel / OpenOffice / Google Apps can be used for the same purpose, as well as open source alternatives for Mathematica, such as Sage. No matter what one chooses, the main problem is to be able to solve equations of motion without having to write non-trivial code.

Once we have a good set of test cases along with the expected results, we can create unit test code. Please note that the test code is very simple without performing any calculations. It simply compares the component result with the hard-coded results we got earlier. Having test cases, we can write the component itself, adding code until all the tests pass. Of course, in strict TDD, these actions occur in that order. I admit that I personally do not stick to the waterfall and, as a rule, refuse to create test data, write tests and write component code.

Mathematica or Excel documents themselves have a useful life beyond the initial creation of tests. They can be used again when new functionality is added or (sky ban) if errors are found later. I would protect documents like source code.

At the end of this exercise, the result is a "bomb-resistant" component, which we have seen will calculate the correct position and orientation for the object under any given set of control inputs. From this foundation, we can build additional components that use this functionality, such as ships, asteroids, plates and snapshots. To avoid a combinatorial explosion of test cases for each component, I would abandon a rigorous approach to black box testing. So, for example, if we tested the ship component, we will write tests, knowing that it uses the position / orientation component. Using this knowledge of the white box, we can avoid re-testing all angular cases associated with movement. Ship unit tests can perform “smoke tests,” which confirm that ships actually respond to control inputs, but the focus will be on testing any functionality that is unique to the ship’s component itself.

So, we summarize:

  • make position / orientation calculation the responsibility of one component
  • generate data for a complete set of test cases using an external tool that gives high confidence in the data.
  • Avoid complex calculation logic in the tests themselves.
  • Avoid the combinatorial explosion of test cases in the component hierarchy using white box knowledge.
+5
source

You can put out your tests a little. Instead of checking the correct vector and acceleration, you can simply verify that the object under test moves at all. In most games, you have to introduce a small amount of randomness into the physical model so that everything is interesting.

+1
source

I think this will help if you take a more "episodic" approach. Instead of tracking coordinates and rotation along the continuum that you think you are doing, you can simply indicate where your spaceship should be and in which rotation, after 30 game steps. If this turns out to be true, then your ship probably also did all the right things. You will write much less test code this way.

The same idea works, focusing on the "episode" of the collision. If the billiard ball is designed to bounce off two walls and hit another ball, you can simply pick up the event when the final collision occurs and check the collision "angle of incidence". Once again you do not check all the steps between them. If the angle of incidence is correct, then the ball probably bounced off two walls correctly before it hit the last ball.

Of course, you must be prepared for the case when the collision does not occur. Your test can take into account game clicks per unit of time to achieve the final collision. You do a test run of the required number of clicks to achieve a collision. If the collision did not occur within a given number of clicks, then the test may fail.

All this is done in game clicks, and not in real time, so the test can happen almost instantly, and not wait for the expected result (as you would normally do if you really played the game).

+1
source

All Articles