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.
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