Saturday, September 29, 2012

We Still Exist

Hello everyone. I know we have not posted in a while. As I had eluded to, our activity will not be as frequent. Tonight, since I had some time, I decided to make a few changes to the game. Very small changes, and I will also post a video of the game since I never did that with the playable version. This post will start off technical, for those interested, and end with the playable video.

I made a couple performance changes. Nothing that would effect anything too much, and I imagine there are countless other areas that this could be applied. I wanted to point out some subtleties in the .NET language that can, without being aware of how things work underneath, cause some unintended loss of precious CPU cycles when running constantly for every game update. During event driven applications these generally don't matter much, but in the game world we have many complex computations where the CPU cycles are better spent.

The first one was the inventory. Previously, we did the logical idea in the loop by adding an if condition, and if the current inventory selection is the equipped selection, draw the selector icon behind the item image. You can see this in the video as we select items. However, we call this method on every update, and an if condition can do nefarious things to a CPU pipeline. That is, modern CPU's use a pipeline to optimize instruction execution per clock cycle. With a branch, the CPU doesn't know which instruction to execute until the branch is evaluated. I won't bore with the more technical details of instruction execution, but the inventory had a notable branch that wasn't required. Even though it wouldn't cause any serious penalties, this statement is only true once in the entire loop. This makes branch prediction somewhat useful, but the guess can be avoided entirely by doing the calculation ahead of time based on the selected index.

The next one was in the camera. We use a special algorithm for locking the map in place to the player isn't distracted by the nothingness in behind all the image drawing. That algorithm is responsible for moving the map when the player is in a specific boundary, but when the player moves to a corner, the map stops moving to a minimum of 0, 0, and a point based on the map width and height. This method made use of the player position, width, height, and its translation from the attack image (A translation of -64, -64). Originally, we used the properties in player to get the position and sprite width and height every time it was needed in the calculation. Because is is a property, this is creating a function call for every player.Position and player.CurrentSprite, and player.CurrentTranslation. That's a total of 6 function calls!

A function call requires for memory to be allocated on the stack (a memory data structure that is used as a pointer for the current executing method), all data on the CPU registers to be temporarily stored and new data allocated for the function call, and then deallocated off the stack with the return value. This is a lot of extra work, called a context switch (particularly brutal in database applications), that can be avoided. So, what I did was create local Vector2 variables for each of these, and used that. At the expense of a small amount of memory, we've reduced half the function calls. Using the X and Y values of a Vector2 is a value shift, because these are public variables rather than properties, and this invokes no additional functions.

The last one, and this currently only effects loading of the maps, is one of my previous changes to the AI component. I had mentioned that I used reflection to solve the problem of AI Components being referenced by all enemies. This was a solution, but as I looked back wasn't the ideal solution. Reflection is a lovely tool and makes most scripting needs not needed in the C# language due to its versatility. However, it can be slower and produce overhead, although not enough to be a major performance drain, is typically slower than direct access. There was another reason, once I thought of a different solution, I felt that it was a more reliable approach and fit into the AI Component paradigm in a way that I was comfortable with. Reflection added unnecessary logic to the create method which had made me a sad panda. So, in the end I added a clone method to the AI component, and I allow each individual component to clone itself and its required properties, rather than looping through all of them with reflection when they might not even need it (Looping includes branching). We add a function call, but the amount removed from using reflection and the cost of indirect access should be better since the components can have various functionality. This would also allow enemy spawns to be more efficient, should we ever include them.

I apologize to those who aren't here for the technical dribble, but this is part of our passion, and I know many struggle as I did. It took many years of dedication to have the understanding that I do, and I would probably still be considered an amateur. A happy one, though! Some people would roll around naked in money, but me? Ones and zeros, baby.

Well, now that you have a mental image that is none too exciting, I present to you the video!


As you can see, we have a playable dungeon with a "boss" at the end. The boss being a clone of the light bug but with a blood red glow, if you will. This is what I presented on the Friday before last. Enjoy!

No comments:

Post a Comment