Quantcast
Channel: GameDev.net
Viewing all 17625 articles
Browse latest View live

How to Design the Data Structure for a Turn Based Game

$
0
0
One of the recurring questions I get is how to exactly make a turn-based game that has a coherent data structure.

Because you're already great coding features for your games, all you may need is a little guidance on how to organize your design to make the things you want actually work.

When you see the following example, you’ll see how easy it is.

Stuff needed to build a turn-based game


To keep things simple, let’s say you want to build a classic tic-tac-toe game. What features should be expected from a game like this?
  • Multiple simultaneous games. Players should be able to have multiple games with different opponents taking place at the same time.
  • Different game status. Every game should have a status that indicates what to expect. Waiting, created, running, finished or cancelled.
  • Play with listed friends. You could have the option to challenge your friends to a game or add new friends to the list.
  • Play with random users. You may want to play with people you don’t know.
  • Play same skill users. You might want to play against random players that have a similar skill as you do.
Luckily, making a game with all these features is quite easy!

All you need to know is how to lay out the features to make them work like you want.

Here are a few questions you should be able to answer.

#1 How are you going to store data?


In this case, I assume a NoSQL database. There, game data is stored in collections, which is like a table in an SQL database.

There are some differences though. In a collection you store objects with a similar concept, but they don’t need to have the same number of “columns”. In fact, in a collection, objects have attributes instead of columns.
  • How many collections does the tic-tac-toe game need?
  • What information should we store in every object?
  • How does every process work inside the game?
To know these, first we have to determine the data structure of a game (match).

Designing the game structure


Our “games” will be objects stored inside a collection we can name GAMES.

Every “game” object has these features:
  • It is shared by two (or more) players
  • Allows players to make moves only on their turns
  • It has a winning condition
  • It has a winner
  • All players in it can update the “game” object
We’ll store all these features in GAMES collection, which must be readable and writeable by any player so they can work properly.

#2 How will the game structure look like?


Obviously it will depend on the kind of game you’d like to make, but in the tic-tac-toe example we’re doing you’ll need:
  • Users. Players involved in each game.
  • Status. Whether it is a waiting, created, running, finished or cancelled “game”.
  • Current turn. In tic-tac-toe, there will be a maximum of six turns between both players.
  • Current user. Which player has the active turn and can make a move.
  • Movements. List every move, which must be ordered by turn and has to contain the information about:
    • User who made the move
    • Position occupied on the board {x,y} when the move is made


how-design-data-structure-for-turn-based


This is how the structure of a turn based game looks like.

Most games will have a more elaborate board than we’re dealing with in this example, so you’ll need a complex matrix of coordinates, and so on. But for this example, the board can be represented by a simple array of positions.

Let’s see our board of coordinates so we can represent movements in the “game”.


0,21,22,2
0,11,12,1
0,01,02,0

The format of the objects used here is JSON, so every “game” will have this structure:

{
  'users':{
      // 'user1': id_of_user1
      '1': 55448343d3655,
      '2': 33129821c1233
  },
  'status': 'running',
  'currentturn': 3,
  'currentuser': '1',
  'movements': {
      '1': {'user': 55448343d3655, 'position':[0,0]},
      '2': {'user': 33129821c1233, 'position':[0,1]}
  },
}

#3 How will you manage the users?


Regardless of the method any user starts a session with (email, Facebook, silent) he or she will always need a profile. Every user has to be assigned a unique user id for the profile, where you can store any additional info you need.

Important notice. This profile is public for every other user that asks for it.

You should create a collection for "Users", and store there all their profile information for your game.

User public information


In the user profile we are going to store the following information that can be seen by the rest of users:
  • Nickname. The name the user wants to be listed as.
  • Avatar. The name of the image she is using as avatar. The fastest method is referencing an image already in the game package. The alternatives are URL of the file, or ID of the downloadable file in you storage.
  • Friend list. The list of user id’s that are in the friend list.

Adding new users to the friend list


We’ll have to create a screen flow that allows the players to search for other players in the game and add them to their friend list.

The best way to add a new user to the friend list would be to store the user id of the entity, not the nickname, not the avatar, or any other concepts.

Important notice. Every object stored should have been assigned a unique id, which is what you should use to look for the whole entity information when you need to challenge friends.

#4 How to play with random users of same skill?


Players will be able to play with random players around the world. Or, to be precise, players in your game database.

Now you know all these you’re ready to create any asynchronous game you want.

How can we make two players find each other and play a game?

The most common solution would be to create a collection named “random”, or “randomqueue” and make it readable and writeable by all users: Own(er) users and Other users.

When a user wants to play with a random opponent we will need him to create an object on that collection indicating he is “waiting” for another user to join. Besides, we’ll need to store specific data that lets the user who wants to join the game decipher whether she is an opponent of same skill.

This is what you should store for this tic-tac-toe example:
  • User id. Object id of the waiting user because the opponent must be able to download the whole profile if needed.
  • Nickname. So it can be shown on screen easily.
  • Avatar. To have the picture shown on screen easily.
  • Skill. To find the right opponent and offer a balanced gameplay.
Should we create a new object every time a user wants to play random opponents? Not really!

The algorithm to implement should be something like this:
  1. Make a search on the “random queue” collection looking for
    • a user that is not me, and
    • whose skill is close to my skill rating
  2. If the result is empty, create my own object on the queue for which I will be waiting
  3. If there are results:
    • pick one
    • create a game
    • send a notification to the opponent via server script

Calculate the user skill rating


In order to foster a balanced matchmaking system, it might be a good idea to have players of similar skill play each other. One way to calculate the skill level of a user is to design a system similar to an Elo rating system.

With a system like this, you can have a more balanced gameplay.

#5 How to notify users about their turn?


There are different ways to create a notification mechanism to alert users it is their turn to move or any other game event. Our preferred method are push notifications, though you may want to have an alternative mechanism just in case push are blocked by the user.

Push notifications


To let users know it’s their turn, we’ll create a server hook post-save script for the collection GAMES. This means every time a user creates or modifies a “game” object the script will run on the server side to send that notification.

The script we’ll add does a very simple thing:

  1. If the match status is waiting:
    • Pick the current user id
    • Send the user a push saying "It’s your turn"
  2. If the match status is created:
    • Pick the user who doesn’t Own (didn’t create) the match
    • Send the user a push saying "Mary is challenging you"

Alternatives to notify users


How can you notify users it’s their turn if they blocked push notifications?

One option you have is to create a pulling system. This is how it’d work: If your game detects push notifications are blocked, you can ask the server about your “games” status with an established frequency. You can do this by searching the GAMES collection or by creating a custom script that returns the information you need.

If changes to the “game” are found, you can update the scene, and if there aren’t the player can continue playing.

To sum up


You have to determine a few key things to build your turn-based game:
  • How to store data
  • How to structure a game
  • How to manage users
Now you know all these you’re ready to create any basic asynchronous game you want.

Are you using the same techniques to make your own turn-based games? Or something entirely different?

Please, post questions or your techniques in the comments section!


This was originally posted in Gamedonia blog.


A Rudimentary 3D Game Engine, Built with C++, OpenGL and GLSL

$
0
0
“What we’ve got here is failure to communicate. Some men you just can’t reach.” - The Captain, Cool Hand Luke

Introduction


In a way, this article is the continuation of the post I published about a year ago, on my little self inflicted course on game development, which I had embarked on despite all advice to the contrary. I had been told that using a ready-made game engine was the way to go for starters. At the time I had gotten down all the basics for rendering and animating a model of a goat I had created in Blender.

Stubbornness and the pain of it all


What I was doing until I reached that point, in order to motivate myself and not leave an another ambitious yet half-finished project somewhere on the web or a hard disk, was to keep up the habit of writing a series of blog posts on my personal website about progress made, once a month more or less.

That worked out pretty well. Each post helped me organise what I had learned during every iteration and have it somewhere written in my own way, so that I never forget. Publishing these progress notes also allowed me to have some feedback from time to time, as well as encouragement (I have no game developer friends so I have to rely on the kindness of strangers).

Of course, there was a lot of work to do. You see, even though I took about two semesters of C++ programming in University, towards the end of the 20th century, I had never worked with it professionally. My professional life revolved around C#, Java and PowerBuilder. If you are aware of this last piece of technology, I suppose you're also aware of Nirvana, not the state of mind, but the music band. But I digress.

My relationship with math was pretty much comparable to my relationship with C++. I wonder what the whole deal is with the focus on differentials, integrals, matrices and vectors in secondary and higher education. Especially in the case of Computer Science courses, at least in my time, there seemed to be this certainty ingrained in the system, that all of us who were to become software developers or engineers (whatever you want to call it) had to, of course, have a certain degree of mathematical expertise. Then, most of us proceeded to program ERPs and warehouse management systems. Some became web developers. I wonder if any of those mathematically adept people ever went beyond addition, subtraction, multiplication or division during the course their careers. I sure did not.

And finally, about 3D modelling skills, I guess they were elementary, as they still are. But hey, I know how to model a goat!

Of course, I had the choice of selecting a couple of these areas to focus on, and find solutions to save me time from the rest, like using something like Unity or acquiring a couple of ready made 3D models. As a matter of fact, a quick scanning of game development courses which I performed on line showed that that is the way the industry is going now and you have to specialise on something. But I wanted to have a sense of every aspect of making a game, coding, writing shaders, putting together the game loop, collision detection and the like. So even though I found no evidence that this was a good idea timewise I just went for it. The doubts never stopped. I always remember a tweet I received right after I had published a video of a rotating object that sort of looked like an animal. It was like “That's great man, you get it! Now download this engine and work like a pro!”. I did not listen. And time kept passing by.

I do not regret doing it. I now have this little engine put together and, indeed, I do have a sense of what it takes to make one. The problem while working like this, at least for me, is that many times you feel like you are fighting against your own brain. Just as you get comfortable modelling something and you feel like doing more of that and learning more, it is time to export your work and code a bit. Or right after you have finished this complicated model reading and rendering code which puts your goat on your scene you have to go back and model the bug chasing it. And while you are switching, you do not necessarily feel confident on what you have covered or learned already. The mind has an amazing capacity to forget what it senses it will not need in the immediate future. Ask me now about why I used a dot product somewhere and how it works exactly and I will need half an hour looking at my own code and maybe going through a few pages from one of my books before I can tell you (but I will, later on). In that respect, it is much harder to be a generalist than a specialist in my humble opinion, at least if you manage to become more than a jack of all traits.

The End Product


Anyway, somehow I have completed the game, making the goat controllable via the keyboard, adding a flying bug that chases it and developing the game logic, together with sound, collision detection and a tree, to make the 3D scene a bit more interesting. So as to be able to reuse a lot of the code I have written, I have reorganised the project, converting it from a one-off game codebase to a little game engine (I have named it small3d) which comes packaged with the game as its sample use case. So we now have a full game:




The engine abstracts away enough details for me to be able to play around with some effects, like rapid nightfall:




Just to see if the camera is robust or if I was just lucky positioning it in the right place, I have also tried sticking it on the bug, so as to see the scene through its eyes, as it chases the goat:




I suppose it can be said that small3d is not really a game engine but a renderer packed with some sound and collision detection facilities. This is the current list of features:
  • Developed in C++
  • Using OpenGL (v3.3 if available, falling back to v2.1 if not)
  • Using GLSL (no fixed pipeline)
  • Plays sounds
  • Offers bounding box collision detection
  • Reads models from Wavefront files and renders them
  • Provides animation out of a series of models
  • Textures can be read from PNG files and mapped to the models
  • Alternatively the models can be assigned a single colour
  • PNG files can also be rendered as independent rectangles
  • Provides text rendering
  • Provides basic lighting
  • Provides camera positioning
  • It has been released with a permissive license (3 – clause BSD) and only libraries with the same or similar licenses are referenced
  • Allows for cross-platform compilation. It has been tested on Windows 7, 8 and 10, OSX and Debian.
  • It is available via a dependency manager

Design & Architecture


These are the main classes that make up the engine:


Attached Image: design.png


A SceneObject is any solid body that appears on the screen, be that a character (like the goat) or an inanimate object, like the tree. The SceneObject is represented visually by Models, which are loaded from WaveFront files by the WaveFrontLoader. ModelLoader is a generalisation of WaveFrontLoader, which provides the option of developing loaders for other file formats in the future, always conforming to the same interface. The SceneObject can also accept an Image to be mapped on the Model. Finally, if some boxes are created in a tool like Blender, properly positioned over a model and exported to a separate Wavefront file, the SceneObject can pick them up using the BoundingBoxes class and provide some basic collision detection.

The Renderer can render Models provided by the SceneObjects. It uses the Image class, either for holding textures to be mapped to the Models, or to be rendered as separate rectangles. These rectangles work as objects of the scene themselves and can be used for representing the ground, the sky, splash screens, etc.

The Text class can be used to load text and display it on the screen, via the Renderer.

The Sound class works as a sound library, loading sounds into SoundData objects and playing them when given the relevant instruction.

Finally, the Exception and Logger classes are used throughout the engine for reporting errors and logging, as their names imply. They can also be used by the code of each game being developed with the engine.

Even though I have avoided utilising a lot of pre-developed game facilities, some library dependencies were necessary. This is what a typical game would look like in relation to the engine and these referenced components:


Attached Image: components.png


There is no limitation for the game code to only go through the engine for everything it is developed to do. This allows for flexibility and, as a matter of fact, sometimes it is necessary to use some of the features from the libraries directly. For example, the engine does not provide user input facilities. The referenced SDL2 library is very good at that so it is left to the developer to use it directly.

I would not want to bore you with every little detail, but there are a couple elements that would be interesting to discuss at this point.

First of all, about my design choices, I did not base them on any literature and therein may lie the reason for any potential imperfections. I was coding each piece of functionality while learning how to do it and then, after it worked, I tried to organise the code into some classes or structures that made sense.

Initially, I was only experimenting with rendering and I can tell you that that is probably the hardest thing I have done for this project. It may be that something else like physics or AI is harder to do for a larger game. But for the purposes of this project, the first year went into rendering and animation. Once that was done, it just took me a few months to work on user input (super easy), add a splash screen (a bit less easy), develop the bug's “AI” and add collision detection.

The problem with rendering is that there are a lot of things to know about OpenGL and GLSL itself before you can even write code that actually does something. And then, once you have put together some instructions for the CPU and the GPU that are supposed to work, many things can go wrong like off-by-one errors, wrong datatypes used for pushing vertices to the GPU, wrong positioning or wrong matrices used, etc. And the only way to find out what is wrong in many cases, is to have also written code that picks up errors from the GPU, because those are not just going to get output to your screen.

I will not discuss rendering further because it will make this article a bit too long and anyway, you can figure out a lot of things by reading the literature I mention in the previous article and looking through my code.

I can mention a couple of things about collision detection and “AI” where, rather than following existing literature to the letter, I have tried to think up solutions myself, without believing of course that what I have come up with is novel in any way.

Leniently, I suppose it can be said that the bug uses some elements of AI. It does not really think. What happens is that it always detects whether or not it is moving towards the goat. The program basically calculates the dot product of the normalised horizontal component of the vector connecting the bug to the goat and the bug’s direction. That is equal to the cosine of the angle between the two. If the angle is not close to zero, the bug starts turning. This way it always tries to be moving towards the goat on the horizontal plane. On the vertical one, things are much simpler. When the bug is kind of close to the goat, it takes a dive and hopes to touch it.

But how do we know when the bug has touched the goat? Well, for that I have just manually placed a couple of bounding boxes over the goat in Blender, to be used for collision detection.


Attached Image: GoatBoundingBoxes.png


An instance of the BoundingBoxes structure loads these and, when the bug is diving, it checks whether or not the two game characters are touching each other. The bug has no bounding box. It is small enough to be considered to be a point, without affecting gameplay much. A little shortcut that I have taken is that I only have the bounding boxes rotate around the Y axis, since the goat is only moving horizontally.


Attached Image: CollisionDetection.png



Dependency Management


An interesting feature I was able to experiment with and provide for this project, is dependency management. I have discovered a service called Biicode, which allowed me to do that.

Biicode can receive projects that support CMake, with minor and (if done well) non-intrusive modifications to their CMakeFile.txt. Each project can reference other projects (library source code in effect) hosted on the service, and Biicode will analyse the dependencies and automatically download and compile them during builds. All the developer has to do is add an #include statement with the address of a desired .h file from a project hosted on the service and Biicode will do the rest. I suppose it can be said that it is an equivalent of Nuget or Maven, but for C++.

The reason I have chosen to use this service, even though it is relatively new, was speed of development. CMake is fantastic on its own as well, but setting up and linking libraries is a time-consuming procedure especially when working cross-platform or switching between debug and release builds. Since Biicode will detect the files needed from each library and download and compile them on the fly, the developer is spared the relevant intricacies of project setup.

I am not mentioning all of this to advertise the service. I find it very useful but my first commitment is to the game engine. Biicode is open source, so even if the service in its present form were to become unavailable at some point, I would either figure out how to set it up locally, go back to plain vanilla CMake (maybe with ExternalProject_Add, which would still be more limited feature-wise) or look for another dependency manager. But the way things stand right now, it is the best solution for my little project.

One difficulty I had not mentioned earlier is actually starting up OpenGL from a single codebase on various platforms. There are different libraries to link to. Moreover, on Windows and Linux it is kind of easy to check which version is available and select it. On the Mac however, you have to make an assumption about the version because there are some detection capabilities missing, at least as far as I have been able to find out. I suppose having all of these things preconfigured and offered via a library from a dependency manager is one of the awesomest things about this project. It may be silly to say that but, if you experience how nice it is to just add an #include statement pointing to some hosted rendering code and be ready to program on three operating systems without doing much else, you may see my point.

The other thing I like about the dependency manager is separation of concerns. In the same way rendering functionality can be covered and set up by one person, others can be maintaining other useful libraries. Each new project can do more things, saving time by reusing what is there and, if the project itself is a new library, adding more useful features developers can use. By keeping the libraries small and focused, a pool of ever increasing possibilities of no fuss code reuse gets created.

For example, I am planning to improve small3d but I am wondering whether or not I will add more features to it. If I want to make a platformer game, instead of adding its reusable elements to small3d itself, I can create another library called small3d_platformer. Another developer can make a small3d_shooter. This is not novel, in the sense that library reuse works that way anyway, but having it online with a dependency manager for C++ is the advantage. It makes code reuse much faster and it is also a guarantee that the various libraries will always interoperate, since a record is always kept of the relationships between specific versions. Every time someone uses one part of the "chain", it is a verification that it works, or it gets communicated that it needs to be fixed.

Conclusion


This article does not contain any step-by-step instructions on using the engine because, looking through the code which I have uploaded, I believe that a lot of things will be made very clear. Also, see the references below for further documentation. You will need to get started with Biicode in order for the code to compile (or convert the project to a simple CMake project, even though that will take more time).

I hope that the provided code and information will help some developers who have chosen to do things the slow way move faster in their learning than I had to. There is a lot of information available today about how to set up OpenGL, use shaders and the like. The problem is that it might be too much to absorb on one go and little technical details can take a long time to sort out.

Using my code, you can either develop your own little game quickly, help me improve this engine, or keep going on your own learning path, referring here from time to time when something you read in a book or tutorial does not work out exactly the way it is supposed to. I am using this engine to develop my own games so, whatever its disadvantages, I am putting a lot of effort into maintaining it operational at all times.

You may be wondering if I now believe that it is worth doing things the way I did or selecting a more pragmatic approach. It is really hard to say. The first thing that leaps to mind is that, just because everyone is saying that something should be done in a certain manner, it does not necessarily have to be so. Of course there is always a risk involved. You may end up stubbornly completing your self-assigned project and showing the world that you have done it your way. Or you may spend the rest of your life watching a goat walking around and wonder in old age what the big deal with it was :)

The outcome depends on many things that cannot all be known in advance. One is your background. If you are more familiar than I was with a lot of the concepts I have discussed, it will most certainly be easier for you and you will finish faster. Then there is commitment and perseverance. Just because you want to do something, it does not mean that the whole process will be fun. And finally, there is life itself. Even if you do everything right, heading towards one direction, a sort of "storm" can come and pick you up and throw you at a place where you never thought you would be.

Personally I will just keep going on this track. I will improve my code (one person commented that I really need to) and then I will see what else I can learn and hopefully make another game before long. I am doing this as a hobby so there is no rush. My career has gone too far in a certain direction for me to hope that I will ever become a professional game developer, even though I wanted to, when I was a kid.

References


[1] small3d.org
[2] Version of small3d, corresponding to this article, on Biicode

Changes


[2015-09-10] Updated article with some corrections and more information, as requested by reader comments.
[2015-09-15] Uploaded a new version of the source code with many dynamic allocations removed and some other minor improvements.
[2015-09-22] Uploaded a new version of the source code, corresponding to the latest stable release (v1.0.2).

Code for Game Developers: Optimization

$
0
0
Code for Game Developers is another take on Math for Game Developers - a weekly instructional YouTube series starting from the basics of a concept and working up towards more complex topics. In the case of this video series, after laying out the foundation of optimization you will learn about:
  • Amdahl's Law
  • Big O notation
  • Cache Levels
  • Binary Search
  • Hash Tables
  • CPU optimizations
If you have questions about the topics covered or requests for future topics, I would love to hear them! Leave a comment, or ask me on my Twitter, @VinoBS

Note:  
The video below contains the playlist for all the videos in this series, which can be accessed via the playlist icon at the top of the embedded video frame. The first video in the series is loaded automatically




Creating your first game with Game Maker: Studio

$
0
0

Introduction


So you want to start creating your own games? Then you've come at the right place. This article will get you started on creating simple 2D games.

Note: After you've read this article, I suggest you go for this course: http://www.udemy.com/gamedeveloper/?couponCode=GAMEDEV
Following this link you can buy my Game Maker: Studio course for just $3 (95% off) and learn making 2 games in just 2 hours.


Game Maker: Studio


What is that?


Game Maker: Studio is a popular game development software used by many Indie Game Developers all over the world. It's easy; yet powerful.

It has a free version which is capable of making good games for Windows. Further, the professional version can be bought for more features and can be extended by buying packages to be able to make games for different platforms such as Android, Mac, iOS, etc.

There are mainly two ways of creating your game: Using the Drag&Drop actions, or by coding. In this article, we'll make our game using the coding language of Game Maker: Studio, which is called Game Maker Language and often abbreviated as GML. Don't worry, it's very easy. Let's proceed.

Understanding the basics


Before starting, understanding the basics is a must. Let's see how a game is created in GM:S.

Sprites


Sprites are the images created/imported in the software to be used for different things such as the character, a block, the wall, or the boss. A sprite can be a single image or a series of images (or sub-images) which results in an animated sprite.

Sprites can be created using the sprite-editor in GM:S or can be imported from any file.

Objects


Objects signify different elements of a game, such as a character, a block, a power-up, an enemy, or anything. So every different element, or simply object of a game needs a different object.

Sprites can be assigned to objects. Further, you can add events, actions and code in the object which define its actions, and then can be placed in a room, which will be shown when you play your game.

Note: If you understand this completely, then, good for you. If not, or if you're confused, don't worry - just keep reading. You'll get it eventually.

Rooms


Rooms can be defined as levels in your game. A room is a rectangular space - its size is defined by its width and height in number of pixels. (Example: A width of 1024 and a height of 768 pixels will result in a room of size 1024x768)

After a room is created, objects can be put in the space of the room, and a level can be designed in this way. This way, many rooms, or levels, can be created. When you start your game, the first room is shown first.

Our first game!


So now that we're done with the basics, we'll start by creating a simple game. We'll be making a simple character who needs to collect coins to win, avoiding the enemies.

Start Game Maker: Studio. You'll see a small window with many tabs. Open the New Tab, enter your project name and click on Create. You'll see an empty window with many folders in the left pane. That's where all of the Sprites, Objects, Rooms, Sounds and everything is sorted. Quite neat, isn't it? ;)

Sprites


In that left pane, the first folder will be Sprites. Right-click on it and select Create Sprite. You'll see sprite0 under the folder Sprites - that's your first sprite! A small window will open - that's the sprite manager. It shows your sprite with many options. It's empty for now, because you haven't created or imported any sprite.

Name it spr_player, because it will be our player's sprite.

Click on Load Sprite to load any image file for the player, or click on Edit Sprite to create your own.

Creating your sprite


Now that you've clicked on Edit Sprite, another window will open: This is where all of the subimages of your sprite are created. From the menus on the top, open the File menu and select New.... Enter the dimensions of the sprite in the window that opens. In our case, we'll use width: 32, height: 32. Hit enter, and in that window, a small sprite will be created, with the name image 0.

Wow. You just created the first subimage of your sprite! Double-click on the sub-image you just created. Another window will open, and this one is the sprite editor. You'll see it's mostly like Paint. Now - use your creativity! Create your player, as you like. Remember: We're creating a Top-Down game, which means the player must be created as seen from the top. And, it must face right: or else it'll cause problems. Now, create! :D

...

Done with the sprite? Click on the Green Tick on the top-left in the window. It'll close, and in the sub-image editor you'll see your player image. Again, click on the Green Tick. There's your sprite! Now click OK to save the sprite.

Now, in the same way, create these sprites: a wall block, a coin, and an enemy - and remember, they too must be from the top and the enemy also should be facing right. Don't forget to name them after "spr_"(Like spr_player, spr_coin). Use the size 32x32 for all of these sprites.

Objects


Done with creating the sprites? Let's move on to creating our objects. Find the objects folder from the left pane, right-click on it and choose Create Object. Your object (object0) will be created and the object manager window will open.

First of all, change the name from object0 to obj_player. Yes, obj_ will be used as the object name prefix.

Prefixes: Name prefixes such as spr_ (for sprite names), obj_ (for object names), room_ (for room names) aren't compulsory but they're used so that it's easier to reference them in code. For example: A coin is a coin but while coding, you'll know what you want to reference and it will be easier: spr_coin for the sprite and obj_coin for the object.


Now, under the place where you typed the name of the object will be a menu where you can select the sprite you want to use with the object. Click on it and select spr_player. Click on OK to save the object.

Now, in the same way, create objects for the coin, the wall block and the enemy.

Wow! Do you realise that you're creating your own game? You're not far away from getting it running. Just keep reading!

Done with creating the objects, and assigning sprites to them? Good. Now let's start the next step.

Coding


Double-click on obj_player. In object manager, you'll see two empty panes: the one on the left is the event pane, and the one on the left is the action pane.

Events are the specific events which trigger the actions inside them. For example, actions inside the 'left arrow key' event will be executed when the left arrow key on the keyboard is pressed. The 'Create' event works when the object is created first, and that's the only time the actions inside the create event are executed. Actions inside the 'Step' event will be executed every step: or every frame. By default, there are 30 steps in a game, so it means actions inside the Step event will be executed 30 times a second. Woah! Similarly, there are many events.

Room Speed, which tells how many steps there will be in the room, can be changed from the room settings. Default is 30.

Now, right-click in the event pane and select "Add" or just click on "Add Event" below the pane. A small window will open, which contains all of the existing events. Click on Keyboard, and select Left Arrow Key. Similarly, add events for Right Arrow Key, Up Arrow Key and Down Arrow Key. Now we'll add the code for these events.

Click on the Left Arrow Key Event. Now look at the action pane - in the rightmost pane, there are many events which can be dragged into the action pane. They're called Drag & Drop actions. We'll not use them; we'll use an action which is used to add code. See the many tabs on the right side? Open the control tab. From the actions there, choose the first action under the name "Code". (There will be total 3 actions under 'Code') Drag it into the action pane. A window will open - it's the text editor for entering the code. Enter this code in the window:

x-=3

Let me explain what this does.

x is the horizontal property, so it defines the horizontal position of the object in the room in the number of pixels. Similarly, y is the vertical property - it defines the vertical position of the object. So, x=254 will change the horizontal position of the object to 254 pixels in the room.

If x increases, the object will move right. If it decreases, it'll go left.
If y increases, the object will go down, and if it decreases, it'll move up.

What we're doing is telling the object to move left when Left Arrow Key is pressed - so we're decreasing its x property, by using x-=3 - which means subtract 3 from x.

Now click on the Green Tick at the Top-Left. You'll see that your code action has been added in the action pane. You can open and edit the code any time just by double-clicking on the action.

Now, in the same way, add codes for the other Arrow Key actions. Open the next event. Drag the Code action from the control tab of D&D (Drag and Drop) menu. Here are the codes for the arrow keys: (I suggest you first yourself guess what the code will be based on what I told you in the previous paragraph about reducing and increasing x and y to move)

Right Arrow Key:

x+=3

Up Arrow Key:

y-=3

Down Arrow Key:

y+=3

Added the code? Press OK to save your object. Let's move on to creating the room.

Rooms


Find the Rooms folder in the left pane and... I think you know what to do. Right Click > Create Room. Your room, namely room0, will be created. We'll let this be the name. Another window opens - this one will be the room manager. You'll see an empty space along with a left pane and some options on the top - that empty space is your room, basically what you'll see when you start your game. In the left pane, there will be many tabs. Open the Settings tab, and change the room width and height to 800 and 600, respectively.

Now, open the Objects tab. Before adding objects, change both x snap and y snap to 32, which are found on the top in the room manager window. Now, in the left pane, click on the empty pane under the tabs. A menu will open with all of your objects. Click on the wall object (obj_wall). You'll see its sprite there - it means that the object has been selected. Now, use your mouse to add the wall blocks in the room (that empty space on the right). Left-click to add one or hold Ctrl+Shift while Left-clicking to continuously add them. What you want to do here is create your level - design it. Add the wall blocks so that the player has to navigate through the level to find the coins.

If you misplace any object, they can just be dragged and re-placed, or deleted using Right Click > Delete.

Done with adding the wall blocks? Now select the coin object (obj_coin) from the left pane and add 10 coins in your level. After adding the coins, add a few enemies. Add them at a place where they can move up - where a wall block is not blocking their way. After adding the enemies, select obj_player and add it into the room. That's where our player will start, so choose a nice place.

Now save the room using the same green tick you see in every window.

Now let's add more code. We've just made the player move. Double-click open obj_coin. Click on Add Event and choose Collision and select obj_player. This is the event that triggers when the object (obj_coin) collides with the object you just selected (obj_player). Let's add code in it. Select the event, open the control tab, and drag the Code action into the Action Pane.

Add this code:

score+=1
instance_destroy()

score+=1 will increase the score by one everytime the player gets the coin.
instance_destroy() will delete the object instance of the coin. That's to show that the player had taken the coin.

Click on the Green Tick. Press OK to save the object.

Now, open obj_player. Add a collision event with obj_wall.
Add Event > Collision > obj_wall
Add this code in it:

x=xprevious
y=yprevious

This code restricts the player from walking through the wall block.
xprevious is the previous x property of the object.
yprevious is the previous y property of the object.
When the player collides with the wall, its position is immediately changed to its previous position (before the collision), stopping it there.

Click on the green tick.

Add another collision event, this one with obj_enemy. Add this code:

room_restart()

This will restart the room whenever the player collides with the enemy. Click on the green tick.

Add Step event. It executes 30 times a second. In its code, add:

if x>xprevious image_angle=0
if x<xprevious image_angle=180
if y>yprevious image_angle=270
if y<yprevious image_angle=90

This code rotates the player based on where it's going. Copy this code because we'll need it again. Click on OK.

Now.. It's time to open obj_enemy. Add create event. In its code, add:

vspeed=-1

This code will set its vertical speed to -1, which means it'll go up. Now add step event, and add the code I told you to copy.(the code of obj_player's step event). Now add a collision event with obj_wall and add this code:

direction-=180

It'll reverse its direction and the object (enemy) will move down instead of up. Now if a collision happens again, it'll go up.

Click on OK to save the object.

Now let's test the game!


Press F5 to start the game. Test it. Move your player using the Arrow Keys. Collect the coins. Run into enemies.
Isn't it cool? Your own game!
Now close the game window.

Let's make winning possible.

Open obj_player and in the Step event, add this code.

if score=10 room_goto_next()

This one is simple: it checks if the score is 10, and then opens the next room. This is how it works: after if, comes the condition (score=10, here) and then the action which must be performed if the condition (score=10) is true. Now green tick, OK.

Now, create a new sprite. Name it spr_play. Click on Edit Sprite. Open File menu, select New. Enter the size as width = 600, height = 400 and press OK. Double-click on the sub-image that is created. From the left pane, use the Text tool to write this there:

"You Win!" or "You completed the game!" or whatever you like - it's your game.

There's an option to change the font and size in the left pane, and to change the color in the right pane. Click in the empty area to add the text. After adding it, add another text:

"Click here to play again"
The text must be big enough for the player to click on it.

Now save the sprite. Create an object for it, namely obj_play. Click on Add Event. There'll be a Mouse event. Click on it, and select Left Button. Open the event and add this code:

room_goto(room0)

This will take the user to the first room (room0) when they click on the sprite so that they can play the game again.

So let's create a room for it!

Right-click on the Rooms folder and select Create Room. When the Room Manager for the new room opens, open the settings tab and rename it room_play and change its size to 800 and 600 (like the previous room). Add obj_play in the room, wherever you like.

Testing time!


Press F5. Play the game, collect all of the 10 coins and win. It'll take you to the next room (room_play), and when you click on the text, it'll take you back to your game.

Fun, isn't it?

Adding more levels


You can create as many levels as you want. Just do this: Create a new room, change its size (800,600), add the wall blocks, coins, enemies, and the player, as you did in the first room, but differently: because this is a new level. Make sure the snap x and snap y are 32. After clicking on the green tick, take a look at the Rooms folder in the left pane. It'll be like this:

room0
room_play
room2


This means that you can't access room2, because it comes after room_play. So, what you need to do here is drag room2 where room_play is, so that after the first level, the next level comes, and in the end, room_play.

This way, you can create as many levels as you want.

Press Ctrl+S to save your project.

Creating an executable


Open the File Menu, and select Create Executable. A window will open. Select the folder where you want to save your game as an *.exe. Enter the name at the text field at the bottom. Select Executable from the drop-down list below it, and hit enter. You can now share the exe file you just created to share your game!

Conclusion


So creating a game wasn't that hard after all? Game Maker: Studio really does a great job at helping you create your game. There are many things to learn about this great software - so never stop learning!


If you found this article helpful, please consider sharing it so that it can help other people also. Have some suggestions, need help or did I make a mistake? Comment below or mail me at gurpreetsingh793@gmail.com. Have a great day!


Article Update Log


16 Dec 2015: Updated about the price.
12 Dec 2015: Initial release

Not All is Fine in the Morrowind Universe

$
0
0

I have checked the OpenMW project by PVS-Studio and written this tiny article. Very few bugs were found, so OpenMW team can be proud of their code.

OpenMW


OpenMW is an attempt to reconstruct the popular RPG Morrowind, a full-blown implementation of all of the game's specifics with open source code. To run OpenMW, you will need an original Morrowind disk.

The source code can be downloaded from https://code.google.com/p/openmw/

Suspicious fragments found


Fragment No. 1

std::string getUtf8(unsigned char c,
  ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding)
{
  ....
  conv[0xa2] = 0xf3;
  conv[0xa3] = 0xbf;
  conv[0xa4] = 0x0;
  conv[0xe1] = 0x8c;
  conv[0xe1] = 0x8c;   <<<<====
  conv[0xe3] = 0x0;
  ....
}

PVS-Studio diagnostic message: V519 The 'conv[0xe1]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 103, 104. openmw fontloader.cpp 104

I guess it is a typo. It is the 0xe2 index that should be probably used in the marked line.

Fragment No. 2

enum Flags
{
  ....
  NoDuration = 0x4,
  ....
}

bool CastSpell::cast (const ESM::Ingredient* ingredient)
{
  ....
  if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
  ....
}

PVS-Studio diagnostic message: V564 The '&' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' operator. openmw spellcasting.cpp 717

Here we deal with a mistake related to operation precedence. At first, the (!magicEffect->mData.mFlags) statement is executed which evaluates either to 0 or 1. Then the statement 0 & 4 or 1 & 4 is executed. But it doesn't make any sense, and the code should most likely look as follows:

if ( ! (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) )

Fragment No. 3

void Clothing::blank()
{
  mData.mType = 0;
  mData.mWeight = 0;
  mData.mValue = 0;
  mData.mEnchant = 0;
  mParts.mParts.clear();
  mName.clear();
  mModel.clear();
  mIcon.clear();
  mIcon.clear();
  mEnchant.clear();
  mScript.clear();
}

PVS-Studio diagnostic message: V586 The 'clear' function is called twice for deallocation of the same resource. Check lines: 48, 49. components loadclot.cpp 49

The mIcon object is cleared twice. The second clearing is redundant or something else should have been cleared instead.

Fragment No. 4

void Storage::loadDataFromStream(
  ContainerType& container, std::istream& stream)
{
  std::string line;
  while (!stream.eof())
  {
    std::getline( stream, line );
    ....
  }
  ....
}

PVS-Studio diagnostic message: V663 Infinite loop is possible. The 'cin.eof()' condition is insufficient to break from the loop. Consider adding the 'cin.fail()' function call to the conditional expression. components translation.cpp 45

When working with the std::istream class, calling the eof() function to terminate the loop is not enough. If a failure occurs when reading data, the call of the eof() function will return false all the time. To terminate the loop in this case, you need an additional check of the value returned by fail().

Fragment No. 5

class Factory
{
  ....
  bool getReadSourceCache() { return mReadSourceCache; }
  bool getWriteSourceCache() { return mReadSourceCache; }
  ....
  bool mReadSourceCache;
  bool mWriteSourceCache;
  ....
};

PVS-Studio diagnostic message: V524 It is odd that the body of 'getWriteSourceCache' function is fully equivalent to the body of 'getReadSourceCache' function. components factory.hpp 209

I guess the getWriteSourceCache() function should look like this:

bool getWriteSourceCache() { return mWriteSourceCache; }

Fragments No. 6, 7, 8

std::string rangeTypeLabel(int idx)
{
  const char* rangeTypeLabels [] = {
    "Self",
    "Touch",
    "Target"
  };
  if (idx >= 0 && idx <= 3)
    return rangeTypeLabels[idx];
  else
    return "Invalid";
}

PVS-Studio diagnostic message: V557 Array overrun is possible. The value of 'idx' index could reach 3. esmtool labels.cpp 502

Here we see an incorrect check of an array index. If the idx variable equals 3, an array overrun will occur.

The correct code:

if (idx >= 0 && idx < 3)

A similar defect was found in two other fragments:
  • V557 Array overrun is possible. The value of 'idx' index could reach 143. esmtool labels.cpp 391
  • V557 Array overrun is possible. The value of 'idx' index could reach 27. esmtool labels.cpp 475
Fragment No. 9

enum UpperBodyCharacterState
{
  UpperCharState_Nothing,
  UpperCharState_EquipingWeap,
  UpperCharState_UnEquipingWeap,
  ....
};

bool CharacterController::updateWeaponState()
{
  ....
  if((weaptype != WeapType_None ||
      UpperCharState_UnEquipingWeap) && animPlaying)
  ....
}

PVS-Studio diagnostic message: V560 A part of conditional expression is always true: UpperCharState_UnEquipingWeap. openmw character.cpp 949

This condition is very strange. In its current form, it can be reduced to if (animPlaying). Something is obviously wrong with it.

Fragments No. 10, 11

void World::clear()
{
  mLocalScripts.clear();
  mPlayer->clear();
  ....
  if (mPlayer)
  ....
}

PVS-Studio diagnostic message: V595 The 'mPlayer' pointer was utilized before it was verified against nullptr. Check lines: 234, 245. openmw worldimp.cpp 234

Similar defect: V595 The mBody pointer was utilized before it was verified against nullptr. Check lines: 95, 99. openmw physic.cpp 95

Fragment No. 12

void ExprParser::replaceBinaryOperands()
{
  ....
  if (t1==t2)
    mOperands.push_back (t1);
  else if (t1=='f' || t2=='f')
    mOperands.push_back ('f');
  else
    std::logic_error ("failed to determine result operand type");
}

PVS-Studio diagnostic message: V596 The object was created but it is not being used. The 'throw' keyword could be missing: throw logic_error(FOO); components exprparser.cpp 101

The keyword throw is missing. The fixed code should look like this:

throw std::logic_error ("failed to determine result operand type");

Conclusion


Purchase a PVS-Studio for in your team, and you will save huge amount of time usually spent on eliminating typos and diverse bugs.

Brain Dead Simple Game States

$
0
0

Lately, I've realized that game state management is always vastly overcomplicated. Here's a brain dead simple system that does everything you probably need in a straightforward way.

Just what the heck is a game state?


Well, what happens when you boot up a game? You probably see some credit to the engine, get shown "the way it's meant to be played", and maybe watch a sweet FMV cutscene. Then you get launched into the menu, where you can tighten up the graphics on level 3 and switch the controls to accommodate your DVORAK keyboard. Then you pick your favourite level, and start playing. A half an hour later, you've had too much Mountain Dew, so you have to pause the game for a few minutes to stop the action to be resumed later.

That's about 4 game states right there: introduction, menu, gameplay, pause screen.

Alright, how do we start coding?


The job of a state is pretty simple. Generally, it needs to update something, and then draw something. Sounds like an interface to me.

public interface State {
  public void update(float dt);
  public void draw();
}

You'd then have concrete states like Menu or Play that implement this interface. Now, I'm going to put a little spin on it, by changing the type of the update method.

public interface State {
  public State update(float dt);
  public void draw();
}

Why did I do that? Well, one of the important parts about game states is the ability to change between them. A game wouldn't be very fun if all you could do was watch the intro FMV over and over again. So the update method now returns whichever state should be used next. If there's no change, it should just return itself.

public class Menu implements State {
  public State update(float dt) {
    if(newGameButton.clicked()) {
      return new Play("Level 1");
    }
      
    return this;
  }
    
  public void draw() {
    drawSomeButtons();
  }
}

Now, the state management code becomes extremely simple, and doesn't require any separate manager class or anything. Just stick it in your main method or whatever holds the game loop.

State current = new Intro();

while(isRunning) {
  handleInput();
  current = current.update(calculateDeltaTime());
  current.draw();
  presentAndClear();
}

Wait, that's it?


Yup.

For real?


Nah, just kidding. Here's something really cool about this method.

Take the pause state. You have to be able to unpause and return to what you were doing, unchanged, right? Usually, a stack is advocated. You push the pause state on to the stack, and pop it off when you're done to get back to the play state. You would then only update and draw the topmost state.

I say, screw the stack. Have the pause state take a State in its constructor, which is stored, and then returned instead of the pause state itself when the update method detects that the game should be unpaused. If the pause screen needs to be an overlay over whatever was going on before the game was paused, that's really easy, too!

public class Pause implements State {
  private State previous;

  public Pause(State previous) {
    this.previous = previous;
  }

  public State update(float dt) {
    if(resumeButton.clicked()) {
      return previous;
    }

    return this;
  }

  public State draw() {
    previous.draw();
    applyFancyBlurEffect();
    drawThePauseMenu();
  }
}

Closing Remarks


Although it may seem like this method requires garbage collection, it really doesn't. You might have to slightly complicate the barely logical "management logic" to accomodate languages without automatic destruction, but in general, the idea of handling transitions by returning different states will work just fine.

I'm sure there are many more ways to use/abuse this system that I can't think of right now, and I appreciate all feedback! Thanks for reading, and I hope it helped!

Decoding Audio for XAudio2 with Microsoft Media Foundation

$
0
0

As I was learning XAudio2 I came across countless tutorials showing how to read in uncompressed .wav files and feed them into an XAudio2 source voice. What was even worse was most of these tutorials reinvented the wheel on parsing and validating a .wav file (even the sample on MSDN “How to: Load Audio Data Files in XAudio2” performs such manual parsing). While reinventing the wheel is never a good thing you also might not want to utilize uncompressed audio files in your game because, well... they are just to big! The .mp3 compression format reduces audio file size by about 10x and provides no inherently noticeable degradation in sound quality. This would certainly be great for the music your games play!

Microsoft Media Foundation


Microsoft Media Foundation, as described by Microsoft, is the next generation multimedia platform for Windows. It was introduced as a replacement for DirectShow and offers capabilities such as the following

  1. Playing Media
  2. Transcoding Media
  3. Decoding Media
  4. Encoding Media

NOTE: I use Media to represent audio, video, or a combination of both

The Pipeline Architecture


Media Foundation is well architectured and consists of many various components. These components are designed to connect together like Lego pieces to produce what is known as a Media Foundation Pipeline. A full Media Foundation pipeline consists of reading a media file from some location, such as the file system, to sending the it to one or more optional components that can transform the audio in someway and then finally sending it to a renderer that forwards the media to some output device.

The Media Foundation Source Reader


The Source reader was introduced to allow applications to utilize features of Media Foundation without having to build a full MF Pipeline. For Example, you might want to read and possibly decode an audio file and then pass it to the XAudio2 engine for playback.

Source Readers can be thought of as a component that can read an audio file and produce media samples to be consumed by your application in any way you see fit.

Media Types


Media Types are used in MF to describe the format of a particular media stream that came from possibly a file system. Your applications generally use media types to determine the format and the type of media in the stream. Objects within Media Foundation, such as the source reader, use these as well such as for loading the correct decoder for the media type output you are wanting.

Parts of a Media Type


Media Types consist of 2 parts that provide information about the type of media in a data stream. The 2 parts are described below:

  1. A Major Type
    1. The Major Type indicates the type of data (audio or video)

  2. A Sub Type
    1. The Sub Type indicates the format of the data (compressed mp3, uncompressed wav, etc)


Getting our hands dirty


With the basics out of the way, let’s now see how we can utilize Media Foundation’s Source Reader to read in any type of audio file (compressed or uncompressed) and extract the bytes to be sent to XAudio2 for playback.

First Things First, before we can begin using Media Foundation we must load and initialize the framework within our application. This is done with a call to MSStartup(MF_VERSION). We should also be good citizens and be sure to unload it once we are done using it with MSShutdown(). This seems like a great opportunity to use the RAII idiom to create a class that handles all of this for us.

struct MediaFoundationInitialize
{
  MediaFoundationInitialize()
  {
    HR(MFStartup(MF_VERSION));
  }
  ~MediaFoundationInitialize()
  {
    HR(MFShutdown());
  }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
 MediaFoundationInitialize mf{};
 return 0;
}

Once Media Foundation has been initialized the next thing we need to do is create the source reader. This is done using the MFCreateSourceReaderFromURL() factory method that accepts the following 3 arguments.

  1. Location to the media file on disk
  2. Optional list of attributes that will configure settings that affect how the source reader operates
  3. The output parameter of the newly allocated source reader

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
 MediaFoundationInitialize mf{};
 // Create Attribute Store
 ComPtr<IMFAttributes> sourceReaderConfiguration;
 HR(MFCreateAttributes(sourceReaderConfiguration.GetAddressOf(), 1));
 HR(sourceReaderConfiguration->SetUINT32(MF_LOW_LATENCY, true));
 // Create Source Reader
 ComPtr<IMFSourceReader> sourceReader;
 HR(MFCreateSourceReaderFromURL(L"C:\\Users\\TraGicCode\\Desktop\\394506-n-a--1450673416.mp3", sourceReaderConfiguration.Get(), sourceReader.GetAddressOf()));
 return 0;
}

Notice we set 1 attribute for our source reader

  1. MF_LOW_LATENCY – This attribute informs the source reader we want data as quick as possible for in near real time operations

With the source reader created and attached to our media file we can query the source reader for the native media type of the file. This will allow us to do some validation such as verifying that the file is indeed an audio file and also if its compressed so that we can branch off and perform extra work needed by MF to uncompress it.

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  MediaFoundationInitialize mf{};
  // Create Attribute Store
  ComPtr<IMFAttributes> sourceReaderConfiguration;
  HR(MFCreateAttributes(sourceReaderConfiguration.GetAddressOf(), 1));
  HR(sourceReaderConfiguration->SetUINT32(MF_LOW_LATENCY, true));
  // Create Source Reader
  ComPtr<IMFSourceReader> sourceReader;
  HR(MFCreateSourceReaderFromURL(L"C:\\Users\\TraGicCode\\Desktop\\394506-n-a--1450673416.mp3", sourceReaderConfiguration.Get(), sourceReader.GetAddressOf()));
  // Query information about the media file
  ComPtr<IMFMediaType> nativeMediaType;
  HR(sourceReader->GetNativeMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nativeMediaType.GetAddressOf()));
  
  // Check if media file is indeed an audio file
  GUID majorType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType));
  if (MFMediaType_Audio != majorType)
  {
    throw NotAudioFileException{};
  }
  // Check if media file is compressed or uncompressed
  GUID subType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &subType));
  if (MFAudioFormat_Float == subType || MFAudioFormat_PCM == subType)
  {
    // Audio File is uncompressed
  }
  else
  {
    // Audio file is compressed
  }
  return 0;
}

If the audio file happens to be compressed (such as if we were reading in an .mp3 file) then we need to inform the source reader we would like it to decode the audio file so that it can be sent to our audio device. This is done by creating a Partial Media Type object and setting the MAJOR and SUBTYPE options for the type of output we would like. When passed to the source reader it will look throughout the system for registered decoders that can perform such requested conversion. Calling IMFSourceReader::SetCurrentMediaType() will pass if a decoder exists or fail otherwise

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  MediaFoundationInitialize mf{};
  // Create Attribute Store
  ComPtr<IMFAttributes> sourceReaderConfiguration;
  HR(MFCreateAttributes(sourceReaderConfiguration.GetAddressOf(), 1));
  HR(sourceReaderConfiguration->SetUINT32(MF_LOW_LATENCY, true));
  // Create Source Reader
  ComPtr<IMFSourceReader> sourceReader;
  HR(MFCreateSourceReaderFromURL(L"C:\\Users\\TraGicCode\\Desktop\\394506-n-a--1450673416.mp3", sourceReaderConfiguration.Get(), sourceReader.GetAddressOf()));
  // Query information about the media file
  ComPtr<IMFMediaType> nativeMediaType;
  HR(sourceReader->GetNativeMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nativeMediaType.GetAddressOf()));
  
  // Check if media file is indeed an audio file
  GUID majorType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType));
  if (MFMediaType_Audio != majorType)
  {
    throw NotAudioFileException{};
  }
  // Check if media file is compressed or uncompressed
  GUID subType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &subType));
  if (MFAudioFormat_Float == subType || MFAudioFormat_PCM == subType)
  {
    // Audio File is uncompressed
  }
  else
  {
    // Audio file is compressed
    // Inform the SourceReader we want uncompressed data
    // This causes it to look for decoders to perform the request we are making
    ComPtr<IMFMediaType> partialType = nullptr;
    HR(MFCreateMediaType(partialType.GetAddressOf()));
    // We want Audio
    HR(partialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
    // We want uncompressed data
    HR(partialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
    HR(sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, partialType.Get()));
  }
  return 0;
}

Now that we have the source reader configured we must next create a WAVEFORMATEX object from the source reader. This data structure essentially represent the fmt chunk in a RIFF file. This is needed so that XAudio2 or more generally anything that wants to play the audio knows the speed at which playback should happen. This is done by Calling IMFSourceReader::MFCreateWaveFormatExFromMFMediaType(). This function takes the following 3 parameters

  1. The Current Media Type of the Source Reader
  2. The address to a WAVEFORMATEX struct that will be filled in by the function
  3. The address of an unsigned int that will be filled in with the size of the above struct

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  MediaFoundationInitialize mf{};
  // Create Attribute Store
  ComPtr<IMFAttributes> sourceReaderConfiguration;
  HR(MFCreateAttributes(sourceReaderConfiguration.GetAddressOf(), 1));
  HR(sourceReaderConfiguration->SetUINT32(MF_LOW_LATENCY, true));
  // Create Source Reader
  ComPtr<IMFSourceReader> sourceReader;
  HR(MFCreateSourceReaderFromURL(L"C:\\Users\\TraGicCode\\Desktop\\394506-n-a--1450673416.mp3", sourceReaderConfiguration.Get(), sourceReader.GetAddressOf()));
  // Query information about the media file
  ComPtr<IMFMediaType> nativeMediaType;
  HR(sourceReader->GetNativeMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nativeMediaType.GetAddressOf()));
  
  // Check if media file is indeed an audio file
  GUID majorType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType));
  if (MFMediaType_Audio != majorType)
  {
    throw NotAudioFileException{};
  }
  // Check if media file is compressed or uncompressed
  GUID subType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &subType));
  if (MFAudioFormat_Float == subType || MFAudioFormat_PCM == subType)
  {
    // Audio File is uncompressed
  }
  else
  {
    // Audio file is compressed
    // Inform the SourceReader we want uncompressed data
    // This causes it to look for decoders to perform the request we are making
    ComPtr<IMFMediaType> partialType = nullptr;
    HR(MFCreateMediaType(partialType.GetAddressOf()));
    // We want Audio
    HR(partialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
    // We want uncompressed data
    HR(partialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
    HR(sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, partialType.Get()));
  }
  ComPtr<IMFMediaType> uncompressedAudioType = nullptr;
  HR(sourceReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, uncompressedAudioType.GetAddressOf()));
  WAVEFORMATEXTENSIBLE d;
  WAVEFORMATEX * waveformatex;
  unsigned int waveformatlength;
  HR(MFCreateWaveFormatExFromMFMediaType(uncompressedAudioType.Get(), &waveformatex, &waveformatlength));
  return 0;
}

lastly we synchronously read all the audio from the file and store them in a vector<byte>.

NOTE: In production software you would definitely not want to synchronously read bytes into memory. This is only meant for this example

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  MediaFoundationInitialize mf{};
  // Create Attribute Store
  ComPtr<IMFAttributes> sourceReaderConfiguration;
  HR(MFCreateAttributes(sourceReaderConfiguration.GetAddressOf(), 1));
  HR(sourceReaderConfiguration->SetUINT32(MF_LOW_LATENCY, true));
  // Create Source Reader
  ComPtr<IMFSourceReader> sourceReader;
  HR(MFCreateSourceReaderFromURL(L"C:\\Users\\TraGicCode\\Desktop\\394506-n-a--1450673416.mp3", sourceReaderConfiguration.Get(), sourceReader.GetAddressOf()));
  // Query information about the media file
  ComPtr<IMFMediaType> nativeMediaType;
  HR(sourceReader->GetNativeMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nativeMediaType.GetAddressOf()));
  
  // Check if media file is indeed an audio file
  GUID majorType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType));
  if (MFMediaType_Audio != majorType)
  {
    throw NotAudioFileException{};
  }
  // Check if media file is compressed or uncompressed
  GUID subType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &subType));
  if (MFAudioFormat_Float == subType || MFAudioFormat_PCM == subType)
  {
    // Audio File is uncompressed
  }
  else
  {
    // Audio file is compressed
    // Inform the SourceReader we want uncompressed data
    // This causes it to look for decoders to perform the request we are making
    ComPtr<IMFMediaType> partialType = nullptr;
    HR(MFCreateMediaType(partialType.GetAddressOf()));
    // We want Audio
    HR(partialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
    // We want uncompressed data
    HR(partialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
    HR(sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, partialType.Get()));
  }
  ComPtr<IMFMediaType> uncompressedAudioType = nullptr;
  HR(sourceReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, uncompressedAudioType.GetAddressOf()));
  WAVEFORMATEXTENSIBLE d;
  WAVEFORMATEX * waveformatex;
  unsigned int waveformatlength;
  HR(MFCreateWaveFormatExFromMFMediaType(uncompressedAudioType.Get(), &waveformatex, &waveformatlength));
  std::vector<BYTE> bytes;
  // Get Sample
  ComPtr<IMFSample> sample;
  while (true)
  {
    DWORD flags{};
    HR(sourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, sample.GetAddressOf()));
    // Check for eof
    if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
    {
      break;
    }
    // Convert data to contiguous buffer
    ComPtr<IMFMediaBuffer> buffer;
    HR(sample->ConvertToContiguousBuffer(buffer.GetAddressOf()));
    // Lock Buffer & copy to local memory
    BYTE* audioData = nullptr;
    DWORD audioDataLength{};
    HR(buffer->Lock(&audioData, nullptr, &audioDataLength));
    for (size_t i = 0; i < audioDataLength; i++)
    {
      bytes.push_back(*(audioData + i));
    }
    // Unlock Buffer
    HR(buffer->Unlock());
  }
  return 0;
}

Now that we have the WAVEFORMATEX object and vector<byte> of our audio file we are reading to send it to XAudio2 for playback!

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
  MediaFoundationInitialize mf{};
  // Create Attribute Store
  ComPtr<IMFAttributes> sourceReaderConfiguration;
  HR(MFCreateAttributes(sourceReaderConfiguration.GetAddressOf(), 1));
  HR(sourceReaderConfiguration->SetUINT32(MF_LOW_LATENCY, true));
  // Create Source Reader
  ComPtr<IMFSourceReader> sourceReader;
  HR(MFCreateSourceReaderFromURL(L"C:\\Users\\TraGicCode\\Desktop\\394506-n-a--1450673416.mp3", sourceReaderConfiguration.Get(), sourceReader.GetAddressOf()));
  // Query information about the media file
  ComPtr<IMFMediaType> nativeMediaType;
  HR(sourceReader->GetNativeMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nativeMediaType.GetAddressOf()));
  
  // Check if media file is indeed an audio file
  GUID majorType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType));
  if (MFMediaType_Audio != majorType)
  {
    throw NotAudioFileException{};
  }
  // Check if media file is compressed or uncompressed
  GUID subType{};
  HR(nativeMediaType->GetGUID(MF_MT_MAJOR_TYPE, &subType));
  if (MFAudioFormat_Float == subType || MFAudioFormat_PCM == subType)
  {
    // Audio File is uncompressed
  }
  else
  {
    // Audio file is compressed
    // Inform the SourceReader we want uncompressed data
    // This causes it to look for decoders to perform the request we are making
    ComPtr<IMFMediaType> partialType = nullptr;
    HR(MFCreateMediaType(partialType.GetAddressOf()));
    // We want Audio
    HR(partialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
    // We want uncompressed data
    HR(partialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
    HR(sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, partialType.Get()));
  }
  ComPtr<IMFMediaType> uncompressedAudioType = nullptr;
  HR(sourceReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, uncompressedAudioType.GetAddressOf()));
  WAVEFORMATEXTENSIBLE d;
  WAVEFORMATEX * waveformatex;
  unsigned int waveformatlength;
  HR(MFCreateWaveFormatExFromMFMediaType(uncompressedAudioType.Get(), &waveformatex, &waveformatlength));
  std::vector<BYTE> bytes;
  // Get Sample
  ComPtr<IMFSample> sample;
  while (true)
  {
    DWORD flags{};
    HR(sourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, sample.GetAddressOf()));
    // Check for eof
    if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
    {
      break;
    }
    // Convert data to contiguous buffer
    ComPtr<IMFMediaBuffer> buffer;
    HR(sample->ConvertToContiguousBuffer(buffer.GetAddressOf()));
    // Lock Buffer & copy to local memory
    BYTE* audioData = nullptr;
    DWORD audioDataLength{};
    HR(buffer->Lock(&audioData, nullptr, &audioDataLength));
    for (size_t i = 0; i < audioDataLength; i++)
    {
      bytes.push_back(*(audioData + i));
    }
    // Unlock Buffer
    HR(buffer->Unlock());
  }
  // Create XAudio2 stuff
  auto xAudioEngine = CreateXAudioEngine();
  auto masteringVoice = CreateMasteringVoice(xAudioEngine);
  auto sourceVoice = CreateSourceVoice(xAudioEngine, *waveformatex);
  XAUDIO2_BUFFER xAudioBuffer{};
  xAudioBuffer.AudioBytes = bytes.size();
  xAudioBuffer.pAudioData = (BYTE* const)&bytes[0];
  xAudioBuffer.pContext = nullptr;
  sourceVoice->Start();
  HR(sourceVoice->SubmitSourceBuffer(&xAudioBuffer));
  // Sleep for some time to hear to song by preventing the main thread from sleep
  // XAudio2 plays the sound on a seperate audio thread <img src="http://tragiccode.com/wp-includes/images/smilies/simple-smile.png" alt=":)" class="wp-smiley" style="height: 1em; max-height: 1em;" />
  Sleep(1000000);
  return 0;
}

And There you have it. Not too bad if you ask me!

Unreal Engine 4 C++ Quest Framework

$
0
0

Lately, I have been working on a simple horror game in UE4 that has a very simple Objective system that drives the gameplay. After looking at the code, I realized it could serve as the basis of a framework for a generic questing system. Today I will share all of that code and explain each class as it pertains to the framework.

The following classes to get started on a simple quest framework are AQuest and AObjective, using the UE4 naming conventions for classes. AObjective is metadata about the quest as well the actual worker when it comes to completing parts of a quest. AQuest is a container of objectives and does group management of objectives. Both classes are derived from AInfo as they are purely classes of information and do not need to have a transform or collision within the world.

Objectives:

Since it is the foundation for a quest, I will first layout and explain AObjective. The header of AObjective goes as follows:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Info.h"
#include "Objective.generated.h"

UCLASS()
class QUESTGAME_API AObjective : public AInfo
{
 	GENERATED_BODY()
 
public: 
 	// Sets default values for this actor's properties
 	AObjective();

 	// Called when the game starts or when spawned
 	virtual void BeginPlay() override;
 
 	// Called every frame
 	virtual void Tick( float DeltaSeconds ) override;

 	UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "O" )
    FText Description;

 	UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "O" )
    FName ObjectiveName;

 	UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "O" )
    bool MustBeCompletedToAdvance;

 	UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "O" )
    int32 TotalProgressNeeded;

 	UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "O" )
    int32 CurrentProgress;

	UFUNCTION( BlueprintCallable, Category = "O" )
    void Update( int32 Progress );

 	UFUNCTION( BlueprintCallable, Category = "O" )
    virtual bool IsComplete( ) const;

	UFUNCTION( BlueprintCallable, Category = "O" )
    virtual float GetProgress( ) const;
};

Not that bad of a deal. The only responsibilities of an AObjective is to track the completion of the sub-portion of an AQuest and offer some idea of what the player must do.

The objective is tracked by the CurrentProgress and TotalProgressNeeded properties. Added by the supplied helper functions, Update, IsComplete, and GetProgress, we can get a reasonable amount of data about this tiny portion of a quest. These functions give you all the functionality needed to start a questing framework for your UE4 game.

There is one boolean property that has not been mentioned: MustBeCompletedToAdvance. Depending on the use case, this could be used to ensure a sequential order in objectives or having required and optional objectives. I will implement it as the first in this tutorial. Only minor changes later on are needed to use it as an indicator of optional or required quests. Or, you could just add a new property to support both.

There are two more properties that help us out with AObjective management: ObjectiveName and Description. ObjectiveName can be thought of as a unique identifier for the implemented AObjective. The ObjectiveName's purpose is for player feedback. For instance, the FText value could be (in simple string terms) "Get a rock". It is nothing specific to the game, it is only something to be used as a hint in either a UI or other visual element to let the player know that they need to do something in order to complete the objective.

Next, we can look at the small amount of code that is used to define AObjective.

// Fill out your copyright notice in the Description page of Project Settings.

#include "QuestGame.h"
#include "Objective.h"


// Sets default values
AObjective::AObjective( ) :
 Description( ),
 ObjectiveName( NAME_None ),
 TotalProgressNeeded( 1 ),
 CurrentProgress( 0 ),
 MustBeCompletedToAdvance( true )
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
 	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AObjective::BeginPlay()
{
 	Super::BeginPlay();
}

// Called every frame
void AObjective::Tick( float DeltaTime )
{
 	Super::Tick( DeltaTime );

}

void AObjective::Update( int32 Progress )
{
 	CurrentProgress += Progress;
}

bool AObjective::IsComplete( ) const
{
 	return CurrentProgress >= TotalProgressNeeded;
}

float AObjective::GetProgress( ) const
{
 	check( TotalProgressNeeded != 0 )
 	return (float)CurrentProgress / (float)TotalProgressNeeded;
}

Again, you will be hard pressed to say "that is a lot of code". Indeed, the most complex code is the division in the GetProgress function.

Wait, why do we call/override BeginPlay or Tick? Well, that is an extreme implementation detail. For instance, what if, while an AObjective is active, you want to tick a countdown for a time trialed AObjective.

For BeginPlay we could implement various other details such as activating certain items in the world, spawning enemies, and so on and so forth. You are only limited by your code skills and imagination.

Right, so how do we manage all of these objectives and make sure only relevant AObjectives are available? Well, we implement an AQuest class in which it acts as an AObjective manager.

Quests:

Here is the declaration of an AQuest to get you started:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Info.h"
#include "Quest.generated.h"

UCLASS()
class QUESTGAME_API AQuest : public AInfo
{
  GENERATED_BODY()
 
public: 
  // Sets default values for this actor's properties
  AQuest();

  // Called when the game starts or when spawned
  virtual void BeginPlay() override;

  // Called every frame
  virtual void Tick( float DeltaSeconds ) override;

public:
  UPROPERTY( EditDefaultsOnly, BlueprintReadWrite, Category = "Q" )
    TArray<class AObjective*> CurrentObjectives;

  UPROPERTY( EditDefaultsOnly, BlueprintReadWrite, Category = "Q" )
    TArray<TSubclassOf<AObjective>> Objectives;
  
  UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "Q" )
    USoundCue* QuestStartSoundCue;

  UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "Q" )
    FName QuestName;

  UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "Q" )
    FText QuestStartDescription;

  UPROPERTY( EditDefaultsOnly, BlueprintReadOnly, Category = "Q" )
    FText QuestEndDescription;

  UFUNCTION( BlueprintCallable, Category = "Q" )
    bool IsQuestComplete( ) const;

  UFUNCTION( BlueprintCallable, Category = "Q" )
    bool CanUpdate( FName Objective );

  UFUNCTION( BlueprintCallable, Category = "Q" )
    void Update( FName Objective, int32 Progress );

  UFUNCTION( BlueprintCallable, Category = "Q" )
    bool TryUpdate( FName Objective, int32 Progress );

  UFUNCTION( BlueprintCallable, Category = "Q" )
    float QuestCompletion( ) const; 
};

Not much bigger than the AObjective class is it? This is because all AQuest does is wrap around a collection of AObjective's and provides some utility functions to help manage them.

The Objectives property is a simple way to configure an AQuest's objectives via the Blueprints Editor, and the CurrentObjectives is a collection of all live AObjective's that are configured for the given AQuest.

There are several user-friendly properties such as a USoundCue, FName, and FText types that help give audio visual feedback to the player. For instance, when a player starts a quest, a nice sound plays - like a chime - and the QuestStartDescription text is written to the player's HUD and a journal implementation. Then, when a player completes a quest, a get is called for the QuestEndDescription property and writes it to a journal implementation. But those are all specific implementation details related to your game and is limited only by coding skills and imagination.

All of the functions for AQuest are wrappers to operate on collections of AObjectives to update and query for completion. All AObjectives in the AQuest are referenced and found by FName property types. This allows for updating different instances of AObjectives that are essentially the same, but differ at the data level. It also allows the removal of managing pointers. As another argument, it decouples knowledge of what an AObjective object is from other classes, so completing quests via other class implementations only requires the knowledge of an AQuest - or the container for the AQuest - object and default types supplied by the engine such as int32 and FName.

How does this all work? Well, just like before, here is the definition of AQuest:

// Fill out your copyright notice in the Description page of Project Settings.

#include "QuestGame.h"
#include "Objective.h"
#include "Quest.h"


AQuest::AQuest() :
  QuestName( NAME_None ),
  CurrentObjectives( ),
  QuestStartDescription( ),
  QuestEndDescription( )
{
}

void AQuest::BeginPlay()
{
	Super::BeginPlay();
    UWorld* World = GetWorld();
    if ( World )
    {
     for ( auto Objective : Objectives )
     {
       CurrentObjectives.Add(World->SpawnActor(Objective));
     }
    }
}

// Called every frame
void AQuest::Tick( float DeltaTime )
{
  Super::Tick( DeltaTime );
}

bool AQuest::IsQuestComplete() const
{
  bool result = true;
  for ( auto Objective : CurrentObjectives )
  {
    result &= Objective->IsComplete();
  }
  return result;
}

bool AQuest::CanUpdate( FName Objective )
{
  bool PreviousIsComplete = true;
  for ( auto Obj : CurrentObjectives )
  {
    if ( PreviousIsComplete )
    {
      if ( Objective == Obj->ObjectiveName )
        return true;
      else
        PreviousIsComplete = Obj->IsComplete() |
        !Obj->MustBeCompletedToAdvance;
    }
    else
    {
      return false;
    }
  }
  return true;
}

void AQuest::Update( FName Objective, int32 Progress )
{
  for ( auto Obj : CurrentObjectives )
  {
    if ( Obj->ObjectiveName == Objective )
    {
      Obj->Update( Progress );
      return;
    }
  }
}

bool AQuest::TryUpdate( FName Objective, int32 Progress )
{
  bool result = CanUpdate( Objective );
  if ( result )
  {
    Update( Objective, Progress );
  }
  return result;
}

float AQuest::QuestCompletion( ) const 
{
  int32 NumObjectives = CurrentObjectives.Num( );
  if( NumObjectives == 0 ) return 1.0f;

  float AggregateCompletion = 0.0f;
  for( auto Objective : CurrentObjectives )
  {
    AggregateCompletion += Objective->GetProgress( );
  }
  return AggregateCompletion / (float)NumObjectives;
}

Probably the most complex code out of all of this is the CanUpdate method. It checks to see, sequentially (so order of AObjective configuration matters), if an AObjective is completed and if it is required to complete any other AObjectives after it. This is where the bitwise OR comes in. So basicly, we cannot advance to the requested AObjective if any of the previous AObjectives are not complete and are set to MustBeCompletedToAdvance (or as the listeral code says you CAN advance if the previous AObjective IS complete OR it does not required to be completed in order to advance).

The IsComplete function is just and aggregate check to see if all AObjectives are complete - defining a completed AQuest. The QuestCompletion method is a simple averaging of all AObjective completion percentages.

Also, the AQuest class has a simple function to wrap up the CanUpdate and Update calls into one neat little function called TryUpdate. This allows a check for the ability to update before applying the requested progress update and returns an indicator of success or failure. This is useful when code outside of AQuest wants to attempt AObjective updates without caring about much else.

Finally, for the same reason of AObjective's BeginPlay and Tick functions, AQuest also overrides these to allow your coding skills and imagination to fly.

Hopefully, this was a good introduction into the groundwork of designing a questing framework for your Unreal Engine 4 game. If you did enjoy it, comment or like it. If there is enough interest I will continue onwards with Part II: Nesting Quests and Objectives. That part will be a tutorial just like this, with full code samples, explaining how to structure the framework to nest multiple AObjectives into an AObjective to create a structure of sub-objectives as well as the same pattern applied to AQuest to supply sub-quests.

Originally posted on Blogspot

A Brain Dump of What I Worked on for Uncharted 4

$
0
0

Posted Image
 
Original blog post
 
This post is part of My Career Series.
 
Now that Uncharted 4 is released, I am able to talk about what I worked on for the project. I mostly worked on AI for single-player buddies and multiplayer sidekicks, as well as some gameplay logic. I'm leaving out things that never went in to the final game and some minor things that are too verbose to elaborate on. So here it goes:
 
 
The Post System
 
Before I start, I'd like to mention the post system we used for NPCs. I did not work on the core logic of the system; I helped writing some client code that makes use of this system.Posts are discrete positions within navigable space, mostly generated from tools and some hand-placed by designers. Based on our needs, we created various post selectors that rate posts differently (e.g. stealth post selector, combat post selector), and we pick the highest-rated post to tell an NPC to go to.
 
Posted Image
 
 
Buddy Follow
 
The buddy follow system was derived from The Last of Us.The basic idea is that buddies pick positions around the player to follow. These potential positions are fanned out from the player, and must satisfy the following linear path clearance tests: player to position, position to a forward-projected position, forward-projected position to the player.
 
Posted Image
 
Climbing is something present in Uncharted 4 that is not in The Last of Us. To incorporate climbing into the follow system, we added the climb follow post selector that picks climb posts for buddies to move to when the player is climbing.
 
Posted Image
 
It turned out to be trickier than we thought. Simply telling buddies to use regular follow logic when the player is not climbing, and telling them to use climb posts when the player is climbing, is not enough. If the player quickly switch between climbing and non-climbing states, buddies would oscillate pretty badly between the two states. So we added some hysteresis, where the buddies only switch states when the player has switched states and moved far enough while maintaining in that state. In general, hysteresis is a good idea to avoid behavioral flickering.
 
 
Buddy Lead
 
In some scenarios in the game, we wanted buddies to lead the way for the player. The lead system is ported over from The Last of Us and updated, where designers used splines to mark down the general paths we wanted buddies to follow while leading the player.
 
Posted Image
 
In case of multiple lead paths through a level, designers would place multiple splines and turned them on and off via script.
 
Posted Image
 
The player's position is projected onto the spline, and a lead reference point is placed ahead by a distance adjustable by designers. When this lead reference point passes a spline control point marked as a wait point, the buddy would go to the next wait point. If the player backtracks, the buddy would only backtrack when the lead reference point gets too far away from the furthest wait point passed during last advancement. This, again, is hysteresis added to avoid behavioral flickering.
 
We also incorporated dynamic movement speed into the lead system. "Speed planes" are placed along the spline, based on the distance between the buddy and the player along the spline. There are three motion types NPCs can move in: walk, run, and sprint. Depending on which speed plane the player hits, the buddy picks an appropriate motion type to maintain distance away from the player. Designers can turn on and off speed planes as they see fit. Also, the buddy's locomotion animation speed is slightly scaled up or down based on the player's distance to minimize abrupt movement speed change when switching motion types.
 
Posted Image
 
 
Buddy Cover Share
 
In The Last of Us, the player is able to move past a buddy while both remain in cover. This is called cover share.
 
Posted Image
 
In The Last of Us, it makes sense for Joel to reach out to the cover wall over Ellie and Tess, who have smaller profile than Joel. But we thought that it wouldn't look as good for Nate, Sam, Sully, and Elena, as they all have similar profiles. Plus, Uncharted 4 is much faster-paced, and having Nate reach out his arms while moving in cover would break the fluidity of the movement. So instead, we decided to simply make buddies hunker against the cover wall and have Nate steer slightly around them.
 
Posted Image
 
The logic we used is very simple. If the projected player position based on velocity lands within a rectangular boundary around the buddy's cover post, the buddy aborts current in-cover behavior and quickly hunkers against the cover wall.
 
Posted Image
 
 
Medic Sidekicks
 
 
Medic sidekicks in multiplayer required a whole new behavior that is not present in single-player: reviving downed allies and mirroring the player's cover behaviors.
 
Posted Image
 
Medics try to mimic the player's cover behavior, and stay as close to the player as possible, so when the player is downed, they are close by to revive the player. If a nearby ally is downed, they would also revive the ally, given that the player is not already downed. If the player is equipped with the RevivePak mod for medics, they would try to throw RevivePaks at revive targets before running to the targets for revival; throwing RevivePaks reuses the grenade logic for trajectory clearance test and animation playback, except that grenades were swapped out with RevivePaks.
 
Posted Image
 
 
Stealth Grass
 
 
Crouch-moving in stealth grass is also something new in Uncharted 4. For it to work, we need to somehow mark the environment, so that the player gameplay logic knows whether the player is in stealth grass. Originally, we thought about making the background artists responsible of marking collision surfaces as stealth grass in Maya, but found out that necessary communication between artists and designers made iteration time too long. So we arrived at a different approach to mark down stealth grass regions. An extra stealth grass tag is added for designers in the editor, so they could mark the nav polys that they'd like the player to treat as stealth grass, with high precision. With this extra information, we can also rate stealth posts based on whether they are in stealth grass or not. This is useful for buddies moving with the player in stealth.
 
Posted Image
 
 
Perception
 
 
Since we don't have listen mode in Uncharted 4 like The Last of Us, we needed to do something to make the player aware of imminent threats, so the player doesn't feel overwhelmed by unknown enemy locations. Using the enemy perception data, we added the colored threat indicators that inform the player when an enemy is about to notice him/her as a distraction (white), to perceive a distraction (yellow), and to acquire full awareness (orange). We also made the threat indicator raise a buzzing background noise to build up tension and set off a loud stinger when an enemy becomes fully aware of the player, similar to The Last of Us.
 
Posted Image
 
 
Investigation
 
This is the last major gameplay feature I took part in on before going gold. I don't usually go to formal meetings at Naughty Dog, but for the last few months before gold, we had a at least one meeting per week driven by Bruce Straley or Neil Druckmann, focusing on the AI aspect of the game. Almost after every one of these meetings, there was something to be changed and iterated for the investigation system. I went through many iterations before arriving at what we shipped with the final game.
 
There are two things that create distractions and would cause enemies to investigate: player presence and dead bodies. When an enemy registers a distraction (distraction spotter), he would try to get a nearby ally to investigate with him as a pair. The closer one to the distraction becomes the investigator, and the other becomes the watcher. The distraction spotter can become an investigator or a watcher, and we set up different dialog sets for both scenarios ("There's something over there. I'll check it out." versus "There's something over there. You go check it out.").
 
In order to make the start and end of investigation look more natural, we staggered the timing of enemy movement and the fading of threat indicators, so the investigation pair don't perform the exact same action at the same time in a mechanical fashion.
 
Posted Image
 
If the distraction is a dead body, the investigator would be alerted of player presence and tell everyone else to start searching for the player, irreversibly leaving ambient/unaware state. The dead body discovered would also be highlighted, so the player gets a chance to know what gave him/her away.
 
Posted Image
 
Under certain difficulties, consecutive investigations would make enemies investigate more aggressively, having a better chance of spotting the player hidden in stealth grass. In crushing difficulty, enemies always investigate aggressively.
 
 
Dialog Looks
 

This is also among the last few things I helped out with for this project.

Dialog looks refers to the logic that makes characters react to conversations, such as looking at the other people and hand gestures. Previously in The Last of Us, people spent months annotating all in-game scripted dialogs with looks and gestures by hand. This was something we didn't want to do again. We had some scripted dialogs that are already annotated by hand, but we needed a default system that handles dialogs that are not annotated. The animators are given parameters to adjust the head turn speed, max head turn angle, look duration, cool down time, etc.
 
Posted Image
 
 
Jeep Momentum Maintenance
 
One of the problems we had early on regarding the jeep driving section in the Madagascar city level, is that the player's jeep can easily spin out and lose momentum after hitting a wall or an enemy vehicle, throwing the player far behind the convoy and failing the level.
 
My solution was to temporarily cap the angular velocity and change of linear velocity direction upon impact against walls and enemy vehicles. This easy solution turns out pretty effective, making it much harder for players to fail the level due to spin-outs.
 
Posted Image
 
 
Vehicle Deaths
 
Driveable vehicles are first introduced in Uncharted 4. Previously, only NPCs can drive vehicles, and those vehicles are constrained to spline rails. I helped handling vehicle deaths.

There are multiple ways to kill enemy vehicles: kill the driver, shoot the vehicle enough times, bump into an enemy bike with your jeep, and ram your jeep into an enemy jeep to cause a spin-out. Based on various causes of death, a death animation is picked to play for the dead vehicle and all its passengers. The animation blends into physics-controlled ragdolls, so the death animation smoothly transitions into physically simulated wreckage.
 
Posted Image
 
For bumped deaths of enemy bikes, we used the bike's bounding box on the XZ plane and the contact position to determine which one of the four directional bump death animations to play.
 
Posted Image
 
As for jeep spin-outs, the jeep's rotational deviation from desired driving direction is tested against a spin-out threshold.
 
Posted Image
 

When playing death animations, there's a chance that the dead vehicle can penetrate walls. A sphere cast is used, from the vehicle's ideal position along the rail if it weren't dead, to where the vehicle's body actually is. If a contact is generated from the sphere cast, the vehicle is shifted in the direction of the contact normal by a fraction of penetration amount, so the de-penetration happens gradually across multiple frames, avoiding positional pops.
 
Posted Image
 
We made a special type of vehicle death, called vehicle death hint. They are context-sensitive death animations that interact with environments. Animators and designers place these hints along the spline rail, and specify entry windows on the splines. If a vehicle is killed within an entry window, it starts playing the corresponding special death animation. This feature started off as a tool to implement the specific epic jeep kill in the 2015 E3 demo.
 
 
Bayer Matrix for Dithering
 
We wanted to eliminate geometry clipping the camera when the camera gets too close to environmental objects, mostly foliage. So we decided to fade out pixels in pixel shaders based on how close the pixels are to the camera. Using transparency was not an option, because transparency is not cheap, and there's just too much foliage. Instead, we went with dithering, combining a pixel's distance from the camera and a patterned Bayer matrix, some portion of the pixels are fully discarded, creating an illusion of transparency.
 
Posted Image
 
Our original Bayer matrix was an 8x8 matrix shown on this Wikipedia page. I thought it was too small and resulted in banding artifacts. I wanted to use a 16x16 Bayer matrix, but it was no where to be found on the internet. So I tried to reverse engineer the pattern of the 8x8 Bayer matrix and noticed a recursive pattern. I would have been able to just use pure inspection to write out a 16x16 matrix by hand, but I wanted to have more fun and wrote a tool that can generate Bayer matrices sized any powers of 2.
 
Posted Image
 
After switching to the 16x16 Bayer matrix, there was a noticeable improvement on banding artifacts.
 
Posted Image
 
 
Explosion Sound Delay
 
This is a really minor contribution, but I'd still like to mention it. A couple weeks before the 2015 E3 demo, I pointed out that the tower explosion was seen and heard simultaneously and that didn't make sense. Nate and Sully are very far away from the tower, they should have seen and explosion first and then heard it shortly after. The art team added a slight delay to the explosion sound into the final demo.
 
 
Traditional Chinese Localization
 
I didn't switch to Traditional Chinese text and subtitles until two weeks before we were locking down for gold, and I found some translation errors. Most of the errors were literal translations from English to Traditional Chinese and just did't work in the contexts. I did not think I would have time to play through the entire game myself and look out for translation errors simultaneously. So I asked multiple people from QA to play through different chapters of the game in Traditional Chinese, and I went over the recorded gameplay videos as they became available. This proved pretty efficient; I managed to log all the translation errors I found, and the localization team was able to correct them before the deadline.
 
 
That's It
 
These are pretty much the things I worked on for Uncharted 4 that are worth mentioning. I hope you enjoyed reading it. :)

Building tools for Unity to improve your workflow

$
0
0

I think we all consider Editor Tools a great and really useful aspect of Unity. Working with it has allowed us to create prototypes really fast and makes a lot easier to build prototypes, to add new objects easily and to assign variables. It’s a huge improvement in our work flow. The thing is, as Immortal Redneck grew bigger, we started to needing our own tools and we could use Unity editor to build them fast.


We’ve already written about what assets we’be bought to make our development a little more straight-forwarded, but sometimes there’s none for your needs. So, why not making it ourselves? It’s not the first time we’ve built our own tools to improve our workflow, and Immortal Redneck wouldn’t be different. So let me show you what we have.



Game Design Tool


Immortal Redneck is a big game – at least for us. In addition to the amount of development hours, it would require a lot of balancing: there’s guns, enemies and skills, so we wanted this task to be as easy and simple as possible.


Attached Image: GameDesignTool_Weapons.png

That’s why we built this tool, to change things almost on the fly. Right now, we can alter:


  • Global parameters like drop rates and spawn times between enemies
  • Each class statistics (HP, Attack, Defense, etc.)
  • Enemies’ stats. In the future, we want to change their global behavior and change when they attack, when the hold, at what distance they start moving…
  • Weapons’ damage, range, spread, ammo, recoil…
  • The skill tree’s levels, gold rates, statistics it changes…

Attached Image: GameDesignTool_Skills.png
Attached Image: GameDesignTool_Enemies.png

Room Utils


We want our game to have a lot of rooms, so we needed to have the capacity to create them fast and well. We coded a few tools to this intention.


In first place, we created a menu that looks like ProGrids so we can mix everything. We can duplicate objects in each axys really fast because that’s what took us most time in the first days of development. Also, it was excruciatingly boring and repetitive thing for the team to do.


Attached Image: RoomUtils_Duplicate.gif

There’s another tool that allow us to check each asset we’ve created on a catalogue, and paste what we want into the scene. It’s almost automatic and works really well. We can place the asset in the place we are looking at or select something in the scene and replace it with something from the catalogue. This is a really, really fast way to replace a whole floor or wall.



Attached Image: RoomUtils_Catalog.png

RoomScene Inspector

Each one of our rooms is a scene due to a optimization requirements. Even though Unity 5.3 has evolved a lot in this matter, it’s still kind of hard to work with them in comparison with using prefabs, for example.


Attached Image: RoomSceneInspector.png

We decided to work that way, creating various scenes and using a ScriptableObject as a reference for each room. This ScriptableObject has the data of each scene, but we can also use it to open them, add them to Build Settings and more stuff.


We work constantly with Custom Inspectors in Unity because of this, and that’s why we’ve extended the default editor and added more options. We’ve even added a room preview because Unity is not allowing that at the moment and it’s something that we need to quickly identify what we are changing in the scene.



Create PBR Material


Attached Image: CreatePBRMaterial.png

Working with PBR (Physically Based Rendering), as we are doing in each asset of our game, requires three textures (Albedo, Mask1 and Normal) every time. This implies that each time we import something into Unity, we have to create a Material for that object and assign the three texture each one at a time.


It’s not a very taxing process, but when you start adding textures of everything in the game, it takes sometime and gets boring. We coded a little tool that automatically create the material by selecting the three texture on the project. It ended up being one of the most useful tools in our development.



MultiCamera Screenshot


Most screenshot plugins only let you use one game camera. Since Immortal Redneck uses two – one for the player, another for the weapon – we created our own tool to capture all the cameras we wanted.


Attached Image: MulticameraScreenshot.png

Once we had it, we made it better with overlay options, so we could add our logo and insert the always necessary ‘pre-alpha’ title without opening Photoshop.


Every promotional screenshot you’ve seen has been capture with MultiCamera Screenshot.



Combine Utils


Meshes that use the same materials can be combined in Unity to improve its performance. There’s various plugins to do this, but we are using our own tool because it looks better.


Again, we took ProGrid’s style and created our own window. It looks great and it has a lot of options: creating a group of objects to combine, another one to erase a group, another to combine them and the last one to separate the group.



Object Randomizer


In order to build the three pyramids of Immortal Redneck, our artists made different rock blocks that they would have to pile to create the pyramids sides.


Attached Image: ObjectRandomizerGif.gif

To do this manually would be hard, so we coded a little tool that would take rock lines with little variations. This way, our artists could create as many lines as they wanted and build the pyramid faster without repeating patterns. Later, they made some little changes, but the pyramids were done in a jiffy.



Attached Image: ObjectRandomizer.png

Gizmos


Unity has a very interesting feature: it merges Gizmos with scripts. Gizmos are conceived to show graphics over the scene so some stuff is easily seen on it.



Attached Image: Gizmos_1.png

For example, in the room above, we’ve got gizmos showing spawn positions and interest points for their AI.


Red spheres show the enemies avatar so we can see where they’ll spawn, while red squares do the same with walking creatures.


In a similar fashion, blue spheres represent the flying enemies’ interest points and the orange squares on the floor are interest points for the walking ones.


At first, it might seem crowded and confusing, but it really helps a lot once you get used to it.



Components Clipboard


Attached Image: ComponentsClipboard.png

Anyone that’s worked with Unity has experienced this: you test the game, you change something, you stop it and then you cry when you realize nothing has been saved.


Sometimes, you need to change a value while playing so you see on real time how that affects the game. We coded a script that worked as a clipboard so you copy your values so you can paste them after stopping the game. It’s simple and easy to use, and it saved our artists a lot of time.

Serious Sam shooter anniversary - finding bugs in the code of the Serious Engine v.1.10

$
0
0

The first-person shooter 'Serious Sam' celebrated its release anniversary on March, 2016. In honor of this, the game developers form the Croatian company Croteam decided to open the source code for the game engine, Serious Engine 1 v.1.10. It provoked the interest of a large number of developers, who got an opportunity to have a look at the code and improve it. I have also decided to participate in the code improvement, and wrote an article reviewing the bugs that were found by PVS-Studio analyzer.


Introduction


Serious Engine is a game engine developed by a Croatian company Croteam. V 1.1o, and was used in the games 'Serious Sam Classic: The First Encounter', and 'Serious Sam Classic: The Second Encounter'. Later on, the Croteam Company released more advanced game engines - Serious Engine 2, Serious Engine 3, and Serious Engine 4; the source code of Serious Engine version 1.10 was officially made open and available under the license GNU General Public License v.2

The project is easily built in Visual Studio 2013, and checked by PVS-Studio 6.02 static analyzer.

Typos!


image2.png
V501 There are identical sub-expressions to the left and to the right of the '==' operator: tp_iAnisotropy == tp_iAnisotropy gfx_wrapper.h 180

class CTexParams {
public:

  inline BOOL IsEqual( CTexParams tp) {
    return tp_iFilter     == tp.tp_iFilter &&
           tp_iAnisotropy == tp_iAnisotropy && // <=
           tp_eWrapU      == tp.tp_eWrapU &&
           tp_eWrapV      == tp.tp_eWrapV; };
  ....
};


I have changed the formatting of this code fragment to make it more visual. The defect, found by the analyzer became more evident - the variable is compared with itself. The object with the name 'tp' has a field 'tp_iAnisotropy', so, by the analogy with the neighboring part of the code, a part of the condition should be 'tp_iAnisotropy'.

V501 There are identical sub-expressions 'GetShadingMapWidth() < 32' to the left and to the right of the '||' operator. terrain.cpp 561

void CTerrain::SetShadowMapsSize(....)
{
  ....
  if(GetShadowMapWidth()<32 || GetShadingMapHeight()<32) {
    ....
  }

  if(GetShadingMapWidth()<32 || GetShadingMapWidth()<32) { // <=
    tr_iShadingMapSizeAspect = 0;
  }
  ....
  PIX pixShadingMapWidth  = GetShadingMapWidth();
  PIX pixShadingMapHeight = GetShadingMapHeight();
  ....
}


The analyzer found a suspicious code fragment which checks the width and height of a map, of the width, to be more exact, because we can see two similar checks "GetShadingMapWidth()<32" in the code. Most probably, the conditions should be:

if(GetShadingMapWidth()<32 || GetShadingMapHeight()<32) {
  tr_iShadingMapSizeAspect = 0;
}


V501 There are identical sub-expressions '(vfp_ptPrimitiveType == vfpToCompare.vfp_ptPrimitiveType)' to the left and to the right of the '&&' operator. worldeditor.h 580

inline BOOL CValuesForPrimitive::operator==(....)
{
  return (
 (....) &&
 (vfp_ptPrimitiveType == vfpToCompare.vfp_ptPrimitiveType) &&//<=
 (vfp_plPrimitive == vfpToCompare.vfp_plPrimitive) &&
 ....
 (vfp_bDummy == vfpToCompare.vfp_bDummy) &&
 (vfp_ptPrimitiveType == vfpToCompare.vfp_ptPrimitiveType) &&//<=
 ....
 (vfp_fXMin == vfpToCompare.vfp_fXMin) &&
 (vfp_fXMax == vfpToCompare.vfp_fXMax) &&
 (vfp_fYMin == vfpToCompare.vfp_fYMin) &&
 (vfp_fYMax == vfpToCompare.vfp_fYMax) &&
 (vfp_fZMin == vfpToCompare.vfp_fZMin) &&
 (vfp_fZMax == vfpToCompare.vfp_fZMax) &&
 ....
);


The condition in the overloaded comparison operator takes 35 lines. No wonder the author was copying the strings to write faster, but it's very easy to make an error coding in such a way. Perhaps there is an extra check here, or the copied string was not renamed, and the comparison operator doesn't always return a correct result.

Strange comparisons


V559 Suspicious assignment inside the condition expression of 'if' operator: pwndView = 0. mainfrm.cpp 697

void CMainFrame::OnCancelMode()
{
  // switches out of eventual direct screen mode
  CWorldEditorView *pwndView = (....)GetActiveView();
  if (pwndView = NULL) {                             // <=
    // get the MDIChildFrame of active window
    CChildFrame *pfrChild = (....)pwndView->GetParentFrame();
    ASSERT(pfrChild!=NULL);
  }
  CMDIFrameWnd::OnCancelMode();
}


There is quite a number of strange comparisons in the code of the engine. For example, in this code fragment we get a pointer "pwndView", which is then assigned with NULL, making the condition always false.

Most likely the programmer meant to write the inequality operator '!=' and the code should have been like this:

if (pwndView != NULL) {
  // get the MDIChildFrame of active window
  CChildFrame *pfrChild = (....)pwndView->GetParentFrame();
  ASSERT(pfrChild!=NULL);
}


Two more similar code fragments:V559 Suspicious assignment inside the condition expression of 'if' operator: pwndView = 0. mainfrm.cpp 710
V547 Expression is always false. Probably the '||' operator should be used here. entity.cpp 3537

enum RenderType {
  ....
  RT_BRUSH       = 4,
  RT_FIELDBRUSH  = 8,
  ....
};

void
CEntity::DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck)
{
  ....
  if( en_pciCollisionInfo == NULL) {
    strm.FPrintF_t("Collision info NULL\n");
  } else if (en_RenderType==RT_BRUSH &&       // <=
             en_RenderType==RT_FIELDBRUSH) {  // <=
    strm.FPrintF_t("Collision info: Brush entity\n");
  } else {
  ....
  }
  ....
}


One variable with the name "en_RenderType" is compared with two different constants. The error is in the usage of '&&' logical and operator. A variable can never be equal to two constants at the same time, that's why the condition is always false. The '||' operator should be used in this fragment.

V559 Suspicious assignment inside the condition expression of 'if' operator: _strModURLSelected = "". menu.cpp 1188

CTString _strModURLSelected;

void JoinNetworkGame(void)
{
  ....
  char strModURL[256] = {0};
  _pNetwork->ga_strRequiredMod.ScanF(...., &strModURL);
  _fnmModSelected = CTString(strModName);
  _strModURLSelected = strModURL; // <=
  if (_strModURLSelected="") {    // <=
    _strModURLSelected = "http://www.croteam.com/mods/Old";
  }
  ....
}


An interesting bug. A request is performed in this function, and the result with the name "strModURL" is written in the buffer (url to "mod"). Later this result is saved in the object under the name "_strModURLSelected". This is its own class implementation that works with strings. Because of a typo, in the condition "if (_strModURLSelected="")" the url that was received earlier will be replaced with an empty string, instead of comparison. Then the operator, casting the string to the 'const char*' type takes action. As a result we'll have verification against null of the pointer which contains a link to the empty string. Such a pointer can never be equal to zero. Therefore, the condition will always be true. So, the program will always use the link that is hard coded, although it was meant to be used as a default value.

V547 Expression is always true. Probably the '&&' operator should be used here. propertycombobar.cpp 1853

CEntity *CPropertyComboBar::GetSelectedEntityPtr(void) 
{
 // obtain selected property ID ptr
 CPropertyID *ppidProperty = GetSelectedProperty();
 // if there is valid property selected
 if( (ppidProperty == NULL) || 
 (ppidProperty->pid_eptType != CEntityProperty::EPT_ENTITYPTR) ||
 (ppidProperty->pid_eptType != CEntityProperty::EPT_PARENT) )
 {
   return NULL;
 }
 ....
}


The analyzer detected a bug that is totally different from the previous one. Two checks of the "pid_eptType" variable are always true because of the '||' operator. Thus, the function always returns, regardless of the value of the "ppidProperty" pointer value and "ppidProperty->pid_eptType" variable.

V547 Expression 'ulUsedShadowMemory >= 0' is always true. Unsigned type value is always >= 0. gfxlibrary.cpp 1693

void CGfxLibrary::ReduceShadows(void)
{
  ULONG ulUsedShadowMemory = ....;
  ....
  ulUsedShadowMemory -= sm.Uncache();  // <=
  ASSERT( ulUsedShadowMemory>=0);      // <=
  ....
}


An unsafe decrement of an unsigned variable is executed in this code fragment, as the variable "ulUsedShadowMemory" may overflow, at the same time there is Assert() that never issues a warning. It is a very suspicious code fragment, the developers should recheck it.

V704 'this != 0' expression should be avoided - this expression is always true on newer compilers, because 'this' pointer can never be NULL. entity.h 697

inline void CEntity::AddReference(void) { 
  if (this!=NULL) { // <=
    ASSERT(en_ctReferences>=0);
    en_ctReferences++; 
  }
};


There are 28 comparisons of 'this' with null in the code of the engine. The code was written a long time ago, but according to the latest standard of C++ language, 'this' pointer can never be null, and therefore the compiler can do the optimization and delete the check. This can lead to unexpected errors in the case of more complicated conditions. Examples can be found in the documentation for this diagnostic.

At this point Visual C++ doesn't work like that, but it's just a matter of time. This code is outlawed from now on.

V547 Expression 'achrLine != ""' is always true. To compare strings you should use strcmp() function. worldeditor.cpp 2254

void CWorldEditorApp::OnConvertWorlds()
{
  ....
  char achrLine[256];                // <=
  CTFileStream fsFileList;

  // count lines in list file
  try {
    fsFileList.Open_t( fnFileList);
    while( !fsFileList.AtEOF()) {
      fsFileList.GetLine_t( achrLine, 256);
      // increase counter only for lines that are not blank
      if( achrLine != "") ctLines++; // <=
    }
    fsFileList.Close();
  }
  ....
}


The analyzer detected wrong comparison of a string with an empty string. The error is that the (achrLine != "") check is always true, and the increment of the "ctLines" is always executed, although the comments say that it should execute only for non-empty strings.

This behavior is caused by the fact that two pointers are compared in this condition: "achrLine" and a pointer to the temporary empty string. These pointers will never be equal.

Correct code, using the strcmp() function:

if(strcmp(achrLine, "") != 0) ctLines++;


Two more wrong comparisons:V547 Expression is always true. To compare strings you should use strcmp() function. propertycombobar.cpp 965
V547 Expression 'achrLine == ""' is always false. To compare strings you should use strcmp() function. worldeditor.cpp 2293

Miscellaneous errors


V541 It is dangerous to print the string 'achrDefaultScript' into itself. dlgcreateanimatedtexture.cpp 359

BOOL CDlgCreateAnimatedTexture::OnInitDialog() 
{
  ....
  // allocate 16k for script
  char achrDefaultScript[ 16384];
  // default script into edit control
  sprintf( achrDefaultScript, ....); // <=
  ....
  // add finishing part of script
  sprintf( achrDefaultScript,        // <=
           "%sANIM_END\r\nEND\r\n",  // <=
           achrDefaultScript);       // <=
  ....
}


A string is formed in the buffer, then the programmer wants to get a new string, saving the previous string value and add two more words. It seems really simple.

To explain why an unexpected result can manifest here, I will quote a simple and clear example from the documentation for this diagnostic:

char s[100] = "test";
sprintf(s, "N = %d, S = %s", 123, s);


As a result we would want to have a string:

N = 123, S = test


But in practice, we will have the following string in the buffer:

N = 123, S = N = 123, S =


In similar situations, the same code can lead not only to incorrect text, but also to program abortion. The code can be fixed if you use a new buffer to store the result. A safe option:

char s1[100] = "test";
char s2[100];
sprintf(s2, "N = %d, S = %s", 123, s1);


The same should be done in the Serious Engine code. Due to pure luck, the code may work correctly, but it would be much safer to use an additional buffer to form the string.

V579 The qsort function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. mesh.cpp 224

// optimize lod of mesh
void CMesh::OptimizeLod(MeshLOD &mLod)
{
  ....
  // sort array
  qsort(&_aiSortedIndex[0]           // <=
        ctVertices
        sizeof(&_aiSortedIndex[0]),  // <=
        qsort_CompareArray);
  ....
}


The function qsort() takes the size of the element of array to be sorted as the third argument. It is very suspicious that the pointer size is always passed there. Perhaps the programmer copied the first argument of the function to the third one, and forgot to delete the ampersand.

V607 Ownerless expression 'pdecDLLClass->dec_ctProperties'. entityproperties.cpp 107

void CEntity::ReadProperties_t(CTStream &istrm) // throw char *
{
  ....
  CDLLEntityClass *pdecDLLClass = en_pecClass->ec_pdecDLLClass;
  ....
  // for all saved properties
  for(INDEX iProperty=0; iProperty<ctProperties; iProperty++) {
    pdecDLLClass->dec_ctProperties;  // <=
    ....
  }
  ....
}


It's unclear, what the highlighted string does. Well, it's clear that it does nothing. The class field is not used in any way, perhaps this error got here after refactoring or the string was left unchanged after debugging.

V610 Undefined behavior. Check the shift operator '<'. The left operand '(- 2)' is negative. layermaker.cpp 363

void CLayerMaker::SpreadShadowMaskOutwards(void)
{
  #define ADDNEIGHBOUR(du, dv)                                  \
  if ((pixLayerU+(du)>=0)                                       \
    &&(pixLayerU+(du)<pixLayerSizeU)                            \
    &&(pixLayerV+(dv)>=0)                                       \
    &&(pixLayerV+(dv)<pixLayerSizeV)                            \
    &&(pubPolygonMask[slOffsetMap+(du)+((dv)<<pixSizeULog2)])) {\
    ....                                                        \
    }

  ADDNEIGHBOUR(-2, -2); // <=
  ADDNEIGHBOUR(-1, -2); // <=
  ....                  // <=
}


The macro "ADDNEIGHBOUR" is declared in the body of the function, and is used 28 times in a row. Negative numbers are passed to this macro, where they are shifted. According to the latest standards of the C++ language, the shift of a negative number results in undefined behavior.

V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. sessionstate.cpp 1191

void CSessionState::ProcessGameStream(void)
{
  ....
  if (res==CNetworkStream::R_OK) {
    ....
  } if (res==CNetworkStream::R_BLOCKNOTRECEIVEDYET) { // <=
    ....
  } else if (res==CNetworkStream::R_BLOCKMISSING) {
    ....
  }
  ....
}


Looking at the code formatting, we may assume that the keyword 'else' is missing in the cascade of conditions.

One more similar fragment: V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. terrain.cpp 759

V595 The 'pAD' pointer was utilized before it was verified against nullptr. Check lines: 791, 796. anim.cpp 791

void CAnimObject::SetData(CAnimData *pAD) {
  // mark new data as referenced once more
  pAD->AddReference();                      // <=
  // mark old data as referenced once less
  ao_AnimData->RemReference();
  // remember new data
  ao_AnimData = pAD;
  if( pAD != NULL) StartAnim( 0);           // <=
  // mark that something has changed
  MarkChanged();
}


In the end I would like to give an example of an error with potential dereference of a null pointer. If you read the analyzer warning, you will see how dangerous the pointer "pAD" is in this small function. Almost immediately after the call of "pAD->AddReference()", the check "pAD != NULL"is executed, which denotes a possible passing of a pointer to this function.

Here is a full list of dangerous fragments that contain pointers:
V595 The '_ppenPlayer' pointer was utilized before it was verified against nullptr. Check lines: 851, 854. computer.cpp 851
V595 The '_meshEditOperations' pointer was utilized before it was verified against nullptr. Check lines: 416, 418. modelermeshexporter.cpp 416
V595 The '_fpOutput' pointer was utilized before it was verified against nullptr. Check lines: 654, 664. modelermeshexporter.cpp 654
V595 The '_appPolPnts' pointer was utilized before it was verified against nullptr. Check lines: 647, 676. modelermeshexporter.cpp 647
V595 The 'pModelerView' pointer was utilized before it was verified against nullptr. Check lines: 60, 63. dlginfopgglobal.cpp 60
V595 The 'pNewWT' pointer was utilized before it was verified against nullptr. Check lines: 736, 744. modeler.cpp 736
V595 The 'pvpViewPort' pointer was utilized before it was verified against nullptr. Check lines: 1327, 1353. serioussam.cpp 1327
V595 The 'pDC' pointer was utilized before it was verified against nullptr. Check lines: 138, 139. tooltipwnd.cpp 138
V595 The 'm_pDrawPort' pointer was utilized before it was verified against nullptr. Check lines: 94, 97. wndanimationframes.cpp 94
V595 The 'penBrush' pointer was utilized before it was verified against nullptr. Check lines: 9033, 9035. worldeditorview.cpp 9033

Conclusion


The analysis of Serious Engine 1 v.1.10 showed that bugs can live in the program for a very long time, and even celebrate anniversaries! This article contains only some of the most interesting examples from the analyzer report. Several warnings were given as a list. But the whole report has quite a good number of warnings, taking into account that the project is not very large. The Croteam Company have more advanced game engines - Serious Engine 2, Serious Engine 3 and Serious Engine 4. I hate to think, how much of the unsafe code could get into the new versions of the engine. I hope that the developers will use a static code analyzer, and make the users happy, producing high-quality games. Especially knowing that the analyzer is easy to download, easy to run in Visual Studio, and for other systems there is a Standalone utility.

Load Testing with Locust.io

$
0
0

This article was originally posted on Kongregate's Developer Blog.


Overview


With more and more developers utilizing cloud services for hosting, it is critical to understand the performance metrics and limits of your application. First and foremost, this knowledge helps you keep your infrastructure up and running. Just as important, however, is the fact that this will ensure you are not allocating too many resources and wasting money in the process.


This post will describe how Kongregate utilizes Locust.io for internal load testing of our infrastructure on AWS, and give you an idea of how you can do similar instrumentation within your organization in order to gain a deeper knowledge of how your system will hold up under various loads.


What Is Locust.io?


Locust is a code-driven, distributed load testing suite built in Python. Locust makes it very simple to create customizable clients, and gives you plenty of options to allow them to emulate real users and traffic.


The fact that Locust is distributed means it is easy to test your system with hundreds of thousands of concurrent users, and the intuitive web-based UI makes it trivial to manage starting and stopping of tests.


For more information on Locust, see their documentation and GitHub repository.


How We Defined Our Tests


We wanted to create a test that would allow us to determine how many more web and mobile users we could support on our current production stack before we needed to either add more web servers or upgrade to a larger database instance.


In order to create a useful test suite for this purpose, we took a look at both our highest throughput and slowest requests in NewRelic. We split those out into web and mobile requests, and started adding Locust tasks for each one until we were satisfied that we had an acceptable test suite.


We configured the weights for each task so that the relative frequencies were correct, and tweaked the rate at which tasks were performed until the number of requests per second for a given number of concurrent users was similar to what we see in production. We also set weights for web vs. mobile traffic so that we could predict what might happen if a mobile game goes viral and starts generating a lot of load on those specific endpoints.


Our application also has vastly different performance metrics for authenticated users (they are more expensive), so we added a configurable random chance for users to create an authenticated session in our on_start function. The Locust HTTP client persists cookies across requests, so maintaining a session is quite simple.


How We Ran Our Tests


We have all of our infrastructure represented as code, mostly via CloudFormation. With this methodology we were able to bring up a mirror of our production stack with a recent database snapshot to test against. Once we had this stack running we created several thousand test users with known usernames and passwords so that we could initiate authenticated sessions as needed.


Initially, we just ran Locust locally with several hundred clients to ensure that we had the correct behavior. After we were convinced that our tests were working properly, we created a pool of EC2 Spot Instances for running Locust on Amazon Linux. We knew we would need a fairly large pool of machines to run the test suite, and we wanted use more powerful instance types for their robust networking capabilities. There is a cost associated with load testing a lot of users, and using spot instances helped us mitigate that.


In order to ensure we had everything we needed on the nodes, we simply used the following user data script for instance creation on our stock Amazon Linux AMI:


#!/bin/bash
sudo yum -y update
sudo yum -y install python
sudo yum -y groupinstall 'Development Tools'
sudo pip install pyzmq
sudo pip install locustio

To start the Locust instances on these nodes we used Capistrano along with cap-ec2 to orchestrate starting the master node and getting the slaves to attach to it. Capistrano also allowed us to easily upload our test scripts on every run so we could rapidly iterate.


Note: If you use EC2 for your test instances, you’ll need to ensure your security group is set up properly to allow traffic between the master and slave nodes. By default, Locust needs to communicate on ports 5557 and 5558.


Test Iteration


While attempting to hit our target number of concurrent users, we ran into a few snags. Here are some of the problems we ran into, along with some potential solutions:

  • Locust Web UI would lock up when requests had dynamic URLs
    • Make sure to group these requests to keep the UI manageable.

  • Ramping up too aggressively:
    • Don’t open the flood gates full force, unless that’s what you’re testing.
    • If you’re on AWS this can overload your ELB and push requests into the surge queue, where they are in danger of being discarded.
    • This can overload your backend instances with high overhead setup tasks (session, authentication, etc.).

  • High memory usage (~1MB per active client):
    • We did not find a solution for this, but instead just brute-forced the problem by adding nodes with more memory.

  • Errors due to too many open files:
    • Increase the open files limit for the user running Locust.
    • Alternatively, you can run your Locust processes as root, and increase the limit directly from your script:
      try:resource.setrlimit(resource.RLIMIT_NOFILE, (1000000, 1000000))except:print "Couldn't raise resource limit"


Miscellaneous Tips and Tricks


Share a session cookie between HTTP/HTTPS requests:

r = self.client.post('https://k.io/session', {'username': u, 'password': p})
self.cookies = { '_session': r.cookies['_session'] }
self.client.get('http://k.io/account', cookies=self.cookies)

Debug request output to verify correctness locally before ramping up:

r = self.client.get('/some_request')
print r.text

Outcome


After all was said and done, we ended up running a test with roughly 450,000 concurrent users. This allowed us to discover some Linux Kernel settings that were improperly tuned and causing <code>502 Bad Gateway</code> errors, and also let us discover breaking points for both our web servers and our database. The test also helped us confirm that we made correct choices in regard to the number of web server processes per instance, and instance types.


We now have a better idea how our system will respond to viral game launches and other events, we can perform regression tests to ensure that large features don’t slow things down unexpectedly, and we can use the information gathered to further optimize our architecture and reduce overall costs.

Tools for developing a RPG like Cossacks

$
0
0

Hello, my name is Gabriel and I'm currently studying Computer Science,but as I like very much history and strategy games,I really want to develop my own game in the next comming years.</h1>


What tools should I master for developing a RPG like Cossacks(closest example of what I want to create)</h1>


I know,that I'm a noob and I dont have any real idea about how hard it would be,but I'm willing to make sacrifices and do whatever it takes to achieve my goal,so bare with me.What programming language would you use and which graphic library,game engine,etc.?What about Vulkan?

Article Update Log</h1>

8 Jul 2016: Initial release

Throwing a Winning Pass

$
0
0
In a previous article, the problem of hitting a target with a fixed velocity was discussed and a solution described. In that case, the shooter had the ability to launch the projectile immediately at the direction where the target would eventually intercept it. While this is true for many cases, it is also true that the effect of having an AI turn and shoot at a moving target, and hit it, is more realistic and just plain awesome. It raises the bar significantly above "shoot at stuff" to "I can pick which target I want to hit, turn to shoot at it before it knows what's going on, and move on to the next target with ease." As you would expect, this does raise the bar a bit in the algorithm area.

Approaching the Problem


Looking at the picture below, it can be seen that the dynamics of the problem are very similar to the case from before.


Attached Image: post_images_hitting_targets_with_bullets2.png


The equations derived in the previous article still describe the situation:

(1) \(\vec{P_T^1} = \vec{P_T^0} + \vec{v_T} *(t_B+t_R)\)

(2) \((P_{Tx}^1 - P_{Sx})^2 +(P_{Ty}^1 - P_{Sy})^2 = S_b^2 * (t_B+t_R)^2\)

However, now instead of the time being just \(t_B\), the term includes \(t_R\), which is the amount of time needed to rotate through \(\theta_R\) radians.

Defining a few new variables:

  1. The unit vector for the "facing" direction of the shooter when the calculation begins: \(\hat{P_{ST}^0}\)
  2. The unit vector for the "facing" direction of the shooter when the shot is fired; this points towards \(\vec{P_T^1}\): \(\hat{P_{ST}^1}\)
  3. The rate at which the shooter rotates its body: \(\omega_R\)

When the body rotates at a rate of \(\omega_R\) for \(t_R\) seconds, it rotates through \(\theta_R\) radians.

\(\omega_R * t_R =\theta_R\)

Given the unit vectors defined above and using the definition of the dot product:

(3) \(\omega_R * t_R = acos(\hat{P_{ST}^0}\cdot\hat{P_{ST}^1})\)

In the previous case, the situation was "static". You fire the projectile and it heads towards a location. The only thing you need to know is the time of flight. But now you need to know how long you have to wait before you can shoot. But the longer you wait, the further your target will have traveled. So the solution "moves" as the rotation time increases.

That is a fairly "ugly" set of equations to try and solve. The static case's quadratic solution and evaluation of its descriminant gave us a good picture of the number of solutions possible. In this case, because of the quadratic/transcendental nature of the formuals, there may or may not be a closed form solution to it. So what do we do? Instead of asking ourselves how can we find the answer directly, ask ourselves how we would know if we found an answer that worked at all.

Pick A Number Between...


If we were to pick a random number for the total time, \(t_{impact} = t_R + t_B\), we could calculate the intercept position because that is how far the target would have traveled in that time, equation (1). Since we know the final position, we can calculate how far the projectile must have traveled to hit the target and also the time to rotate to that position from (2) and (3). If the value we chose for \(t_{impact}\) is a solution, then:

\(t_{impact} = t_R + t_B\)

But, if it is not, then \(t_{impact}\) will either be greater than or less than the sum. Using this, we can propose an answer, test it, and decide if that answer lies to the "left" or "right" of the proposed solution. Then propose (read: guess) again, using the answer we just got to get a little closer. Using this approach, we can iterate towards a solution in a (hopefully) bounded number of steps. Not as clean as a simple "plug and chug" formula, but very serviceable.

Binary Search


It is tempting to use a fast-converging numerical technique like Newton's Method to try and solve this. But the shape of the space that the solution lies in is unknown. We haven't even proven that the "left" or "right" decision process won't stick us in some thorny cyclic patch of non-convergence. Shooting off towards infinity on a small derivative estimate is also something that would be undesirable and hard to bound. We want this to be executed in an AI for a game that is running out in the field, not in a lab.

So, we're going to trade something that *might* converge faster for a search algorithm that will guarantee cutting the search space in half each time, the binary search. Here is how it will work:

  1. Define the minimum value, \(t_{min}\) that will be the smallest value for \(t_{impact}\) that will be allowed.
  2. Define the maximum value, \(t_{max}\) that will be the largest value for \(t_{impact}\) that will be allowed.
  3. Start with the value that is between the minimum and maximum as the first proposed value.
  4. Loop:
    1. Calculate the final impact location.
    2. Calculate the rotation time necessary to face the impact location, \(t_{rot}\).
    3. Calculate the flight time from the shooter to the final impact location, \(t_{flight}\).
    4. \(t_{shot} = t_{impact} - (t_{rot} + t_{flight})\)
    5. if \(t_{shot} > 0\), then set the upper limit to the proposed value and propose a new value between the upper and lower limits.
    6. if \(t_{shot} < 0\), then set the lower limit to the proposed value and propose a new value between the upper and lower limits.
    7. If the value of value of \(t_{impact}\) is changing within less than a specified tolerance, the algorithm has converged.
    8. If the number of loops gets too high, fail.
  5. Return success and the final position or failure.

The Code


The following function calculates whether or not the target can be hit and then returns the result. If the target could not be hit, the return value is "false". If it could, the return value is "true" and the solution, the position vector of the impact.

/* Calculate the future position of a moving target so that 
 * a turret can turn to face the position and fire a projectile.
 *
 * This algorithm works by "guessing" an intial time of impact
 * for the projectile 0.5*(tMin + tMax).  It then calculates
 * the position of the target at that time and computes what the 
 * time for the turret to rotate to that position (tRot0) and
 * the flight time of the projectile (tFlight).  The algorithms
 * drives the difference between tImpact and (tFlight + tRot) to 
 * zero using a binary search. 
 *
 * The "solution" returned by the algorithm is the impact 
 * location.  The shooter should rotate towards this 
 * position and fire immediately.
 *
 * The algorithm will fail (and return false) under the 
 * following conditions:
 * 1. The target is out of range.  It is possible that the 
 *    target is out of range only for a short time but in
 *    range the rest of the time, but this seems like an 
 *    unnecessary edge case.  The turret is assumed to 
 *    "react" by checking range first, then plot to shoot.
 * 2. The target is heading away from the shooter too fast
 *    for the projectile to reach it before tMax.
 * 3. The solution cannot be reached in the number of steps
 *    allocated to the algorithm.  This seems very unlikely
 *    since the default value is 40 steps.
 *
 *  This algorithm uses a call to sqrt and atan2, so it 
 *  should NOT be run continuously.
 *
 *  On the other hand, nominal runs show convergence usually
 *  in about 7 steps, so this may be a good 'do a step per
 *  frame' calculation target.
 *
 */
bool CalculateInterceptShotPosition(const Vec2& pShooter,
                                    const Vec2& vShooter,
                                    const Vec2& pSFacing0,
                                    const Vec2& pTarget0,
                                    const Vec2& vTarget,
                                    float64 sProjectile,
                                    float64 wShooter,
                                    float64 maxDist,
                                    Vec2& solution,
                                    float64 tMax = 4.0,
                                    float64 tMin = 0.0
                                    )
{
   cout << "----------------------------------------------" << endl;
   cout << " Starting Calculation [" << tMin << "," << tMax << "]" << endl;
   cout << "----------------------------------------------" << endl;
   
   float64 tImpact = (tMin + tMax)/2;
   float64 tImpactLast = tImpact;
   // Tolerance in seconds
   float64 SOLUTION_TOLERANCE_SECONDS = 0.01;
   const int MAX_STEPS = 40;
   for(int idx = 0; idx < MAX_STEPS; idx++)
   {
      // Calculate the position of the target at time tImpact.
      Vec2 pTarget = pTarget0 + tImpact*vTarget;
      // Calulate the angle between the shooter and the target
      // when the impact occurs.
      Vec2 toTarget = pTarget - pShooter;
      float64 dist = toTarget.Length();
      Vec2 pSFacing = (pTarget - pShooter);
      float64 pShootRots = pSFacing.AngleRads();
      float64 tRot = fabs(pShootRots)/wShooter;
      float64 tFlight = dist/sProjectile;
      float64 tShot = tImpact - (tRot + tFlight);
      cout << "Iteration: " << idx
      << " tMin: " << tMin
      << " tMax: " << tMax
      << " tShot: " << tShot
      << " tImpact: " << tImpact
      << " tRot: " << tRot
      << " tFlight: " << tFlight
      << " Impact: " << pTarget.ToString()
      << endl;
      if(dist >= maxDist)
      {
         cout << "FAIL:  TARGET OUT OF RANGE (" << dist << "m >= " << maxDist << "m)" << endl;
         return false;
      }
      tImpactLast = tImpact;
      if(tShot > 0.0)
      {
         tMax = tImpact;
         tImpact = (tMin + tMax)/2;
      }
      else
      {
         tMin = tImpact;
         tImpact = (tMin + tMax)/2;
      }
      if(fabs(tImpact - tImpactLast) < SOLUTION_TOLERANCE_SECONDS)
      {  // WE HAVE A WINNER!!!
         solution = pTarget;
         return true;
      }
   }
   return false;
}

Note:  The algorithm takes not only the position of the shooter, but its velocity as well. This is provision for a small modification where the shooter could be moving. In development of Star Crossing thus far, it has not been necessary to put in this modification. Feel free to let us know via feedback if you work it in (and it works for you).



The Video


Instead of cooking up a video just to demonstrate the basic use of the algorithm, it is going to be more effective to let you see it in action. The video below is a clip from a game we are actively working on called Star Crossing. In the clip, the ship pulls the Defense Drone behind it like a tail gunner. The Defense Drone turns to shoot at the Snakes as the ship drags it around. Go about a minute into the video and you'll see it.

Note:  This game is in work and the art is all drawn by hand to have something to look at while the mechanics are worked out. It looks pretty...well...crayolaish...that's not even a word but it probably has the right feel. If you would like to help the project with some art skill, feel free to contact us.





The Demo


I put together a small console application as a test bed to develop the algorithm initially. The simulation allows you to tinker with the parameters and see the running of the algorithm. You can download the source code for it using the link below. It is written in C++ and should compile on any modern compiler. We used XCode on a Macbook Pro, but the code has no graphics or special libraries associated with it.

The (Rest of the) Code


Get the Source Code for this article hosted on GitHub by clicking here.

Interesting Points

  • While there is a bound on the algorithm, it usually converges in less than 10 steps in our testing.
  • (Proposed...not proven) Knowing your turn rate in radians/sec, you can modify the SOLUTION_TOLERANCE_SECONDS value so that it converges to a resoluion in terms of arc seconds from the target. That is to say, you don't have to shoot dead at the target positiion to hit it, you just have to be really close. This gives you a good way to set your tolerance and save some loops. You could change the algorithm to take a tolerance in degrees or radians to set the convergence limit.
  • You need to handle the case where the target is heading right at you. We use the dot product for this and just "fire now". This is done before the algorithm is even called.
  • Our initial algorithm shot at the location of the target instead of leading it. When we put the new algorithm into effect at first, it was not too exciting, but much better. When we put in the "shoot if it is in front of you" addition, the combo pack was devastating and we had to turn down the Defense Drone weapon power to stop it from decimating the snakes too fast. Be careful what you wish for.
  • While I haven't tried it, it seems reasonable you could pick a "better" start value for \(t_{impact}\) by using the non-rotating solution plus a little slop time (maybe the amount of time to rotate to face that position). This has the right "feel" for an initial estimate and seems worth exploring in the future.

Conclusion


This article presented an approach for predicting the future position of a moving target based on having to rotate to shoot it. A simulation of using the algorithm was also created for you to tinker with. The solution appears to work well for our current needs.

Article Update Log

30 October 2014: Initial release

Long-Awaited Check of CryEngine V

$
0
0

image1.png


In May 2016, German game-development company Crytek made a decision to upload the source code of their game engine CryEngine V to Github. The engine is written in C++ and has immediately attracted attention of both the open-source developer community and the team of developers of PVS-Studio static analyzer who regularly scan the code of open-source projects to estimate its quality. A lot of great games were created by a number of video-game development studios using various versions of CryEngine, and now the engine has become available to even more developers. This article gives an overview of errors found in the project by PVS-Studio static analyzer.


Introduction


CryEngine is a game engine developed by German company Crytek in 2002 and originally used in first-person shooter Far Cry. A lot of great games were created by a number of video-game development studios using various licensed versions of CryEngine: Far Cry, Crysis, Entropia Universe, Blue Mars, Warface, Homefront: The Revolution, Sniper: Ghost Warrior, Armored Warfare, Evolve, and many others. In March 2016, Crytek announced a release date for their new engine CryEngine V and uploaded its source code to Github soon after.


The project's source code was checked by PVS-Studio static analyzer, version 6.05. This is a tool designed for detecting software errors in program source code in C, C++, and C#. The only true way of using static analysis is to regularly scan code on developers' computers and build-servers. However, in order to demonstrate PVS-Studio's diagnostic capabilities, we run single-time checks of open-source projects and then write articles about errors found. If we like a project, we might scan it again a couple of years later. Such recurring checks are in fact the same as single-time checks since the code accumulates a lot of changes during that time.


For our checks, we pick projects that are simply popular and wide-known as well as projects suggested by our readers via e-mail. That's why CryEngine V was by no means the first game engine among those scanned by our analyzer. Other engines that we have already checked include:



We also checked CryEngine 3 SDK once.


I'd like to elaborate on the check of Unreal Engine 4 engine in particular. Using that project as an example allowed us to demonstrate in every detail what the right way of using static analysis on a real project should look like, covering the whole process from the phase of integrating the analyzer into the project to the phase of cutting warnings to zero with subsequent control over bug elimination in new code. Our work on Unreal Engine 4 project developed into collaboration with Epic Games company, in terms of which our team fixed all the defects found in the engine's source code and wrote a joint article with Epic Games on the accomplished work (it was posted on Unreal Engine Blog). Epic Games also purchased a PVS-Studio license to be able to maintain the quality of their code on their own. Collaboration of this kind is something that we would like to try with Crytek, too.


Analyzer-report structure


In this article, I'd like to answer a few frequently asked questions concerning the number of warnings and false positives, for example, "What is the ratio of false positives?" or "Why are there so few bugs in so large a project?"


To begin with, all PVS-Studio warnings are classified into three severity levels: High, Medium, and Low. The High level holds the most critical warnings, which are almost surely real errors, while the Low level contains the least critical warnings or warnings that are very likely to be false positives. Keep in mind that the codes of errors do not tie them firmly to particular severity levels: distribution of warnings across the levels very much depends on the context.


This is how the warnings of the General Analysis module are distributed across the severity levels for CryEngine V project:

  • High: 576 warnings;
  • Medium: 814 warnings,
  • Low: 2942 warnings.

Figure 1 shows distribution of the warnings across the levels in the form of a pie chart.


image2.png


Figure 1 - Percentage distribution of warnings across severity levels


It is impossible to include all the warning descriptions and associated code fragments in an article. Our articles typically discuss 10-40 commented cases; some warnings are given as a list; and most have to be left unexamined. In the best-case scenario, project authors, after we inform them, ask for a complete analysis report for close study. The bitter truth is that in most cases the number of High-level warnings alone is more than enough for an article, and CryEngine V is no exception. Figure 2 shows the structure of the High-level warnings issued for this project.


image3.png


Figure 2 - Structure of High-level warnings


Let's take a closer look at the sectors of this chart:

  • Described in the article (6%) - warnings cited in the article and accompanied by code fragments and commentary.
  • Presented as a list (46%) - warnings cited as a list. These warnings refer to the same pattern as some of the errors already discussed, so only the warning text is given.
  • False Positives (8%) - a certain ratio of false positives we have taken into account for future improvement of the analyzer.
  • Other (40%) - all the other warnings issued. These include warnings that we had to leave out so that the article wouldn't grow too large, non-critical warnings, or warnings whose validity could be estimated only by a member of the developer team. As our experience of working on Unreal Engine 4 has shown, such code still "smells" and those warnings get fixed anyway.

Analysis results


Annoying copy-paste

image4.png


V501 There are identical sub-expressions to the left and to the right of the '-' operator: q2.v.z - q2.v.z entitynode.cpp 93


bool
CompareRotation(const Quat& q1, const Quat& q2, float epsilon)
{
  return (fabs_tpl(q1.v.x - q2.v.x) <= epsilon)
      && (fabs_tpl(q1.v.y - q2.v.y) <= epsilon)
      && (fabs_tpl(q2.v.z - q2.v.z) <= epsilon) // <=
      && (fabs_tpl(q1.w - q2.w) <= epsilon);
}

A mistyped digit is probably one of the most annoying typos one can make. In the function above, the analyzer detected a suspicious expression, (q2.v.z - q2.v.z), where variables q1 and q2 seem to have been mixed up.


V501 There are identical sub-expressions '(m_eTFSrc == eTF_BC6UH)' to the left and to the right of the '||' operator. texturestreaming.cpp 919


//! Texture formats.
enum ETEX_Format : uint8
{
  ....
  eTF_BC4U,     //!< 3Dc+.
  eTF_BC4S,
  eTF_BC5U,     //!< 3Dc.
  eTF_BC5S,
  eTF_BC6UH,
  eTF_BC6SH,
  eTF_BC7,
  eTF_R9G9B9E5,
  ....
};

bool CTexture::StreamPrepare(CImageFile* pIM)
{
  ....
  if ((m_eTFSrc == eTF_R9G9B9E5) ||
      (m_eTFSrc == eTF_BC6UH) ||     // <=
      (m_eTFSrc == eTF_BC6UH))       // <=
  {
    m_cMinColor /= m_cMaxColor.a;
    m_cMaxColor /= m_cMaxColor.a;
  }
  ....
}

Another kind of typos deals with copying of constants. In this case, the m_eTFSrc variable is compared twice with the eTF_BC6UH constant. The second of these checks must compare the variable with some other constant whose name differs from the copied one in just one character, for example, eTF_BC6SH.


Two more similar issues:


  • V501 There are identical sub-expressions '(td.m_eTF == eTF_BC6UH)' to the left and to the right of the '||' operator. texture.cpp 1214
  • V501 There are identical sub-expressions 'geom_colltype_solid' to the left and to the right of the '|' operator. attachmentmanager.cpp 1004

V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 266, 268. d3dhwshader.cpp 266


int SD3DShader::Release(EHWShaderClass eSHClass, int nSize)
{
  ....
  if (eSHClass == eHWSC_Pixel)
    return ((ID3D11PixelShader*)pHandle)->Release();
  else if (eSHClass == eHWSC_Vertex)
    return ((ID3D11VertexShader*)pHandle)->Release();
  else if (eSHClass == eHWSC_Geometry)                   // <=
    return ((ID3D11GeometryShader*)pHandle)->Release();  // <=
  else if (eSHClass == eHWSC_Geometry)                   // <=
    return ((ID3D11GeometryShader*)pHandle)->Release();  // <=
  else if (eSHClass == eHWSC_Hull)
    return ((ID3D11HullShader*)pHandle)->Release();
  else if (eSHClass == eHWSC_Compute)
    return ((ID3D11ComputeShader*)pHandle)->Release();
  else if (eSHClass == eHWSC_Domain)
    return ((ID3D11DomainShader*)pHandle)->Release()
  ....
}

This is an example of lazy copying of a cascade of conditional statements, one of which was left unchanged.


V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 970, 974. environmentalweapon.cpp 970


void CEnvironmentalWeapon::UpdateDebugOutput() const
{
  ....
  const char* attackStateName = "None";
  if(m_currentAttackState &                       // <=
     EAttackStateType_EnactingPrimaryAttack)      // <=
  {
    attackStateName = "Primary Attack";
  }
  else if(m_currentAttackState &                  // <=
          EAttackStateType_EnactingPrimaryAttack) // <=
  {
    attackStateName = "Charged Throw";
  }
  ....
}

In the previous example, there was at least a small chance that an extra condition resulted from making too many copies of a code fragment, while the programmer simply forgot to remove one of the checks. In this code, however, the attackStateName variable will never take the value "Charged Throw" because of identical conditional expressions.


V519 The 'BlendFactor[2]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1265, 1266. ccrydxgldevicecontext.cpp 1266


void CCryDXGLDeviceContext::
OMGetBlendState(...., FLOAT BlendFactor[4], ....)
{
  CCryDXGLBlendState::ToInterface(ppBlendState, m_spBlendState);
  if ((*ppBlendState) != NULL)
    (*ppBlendState)->AddRef();
  BlendFactor[0] = m_auBlendFactor[0];
  BlendFactor[1] = m_auBlendFactor[1];
  BlendFactor[2] = m_auBlendFactor[2]; // <=
  BlendFactor[2] = m_auBlendFactor[3]; // <=
  *pSampleMask = m_uSampleMask;
}

In this function, a typo in the element index prevents the element with index '3', BlendFactor[3], from being filled with a value. This fragment would have remained just one of the many interesting examples of typos, had not the analyzer found two more copies of the same incorrect fragment:


V519 The 'm_auBlendFactor[2]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 904, 905. ccrydxgldevicecontext.cpp 905


void CCryDXGLDeviceContext::
  OMSetBlendState(....const FLOAT BlendFactor[4], ....)
{
  ....
  m_uSampleMask = SampleMask;
  if (BlendFactor == NULL)
  {
    m_auBlendFactor[0] = 1.0f;
    m_auBlendFactor[1] = 1.0f;
    m_auBlendFactor[2] = 1.0f;                   // <=
    m_auBlendFactor[2] = 1.0f;                   // <=
  }
  else
  {
    m_auBlendFactor[0] = BlendFactor[0];
    m_auBlendFactor[1] = BlendFactor[1];
    m_auBlendFactor[2] = BlendFactor[2];         // <=
    m_auBlendFactor[2] = BlendFactor[3];         // <=
  }

  m_pContext->SetBlendColor(m_auBlendFactor[0],
                            m_auBlendFactor[1],
                            m_auBlendFactor[2],
                            m_auBlendFactor[3]);
  m_pContext->SetSampleMask(m_uSampleMask);
  ....
}

Here's that fragment where the element with index '3' is skipped again. I even thought for a moment that there was some intentional pattern to it, but this thought quickly vanished as I saw that the programmer attempted to access all the four elements of the m_auBlendFactor array at the end of the function. It looks like the same code with a typo was simply copied several times in the file ccrydxgldevicecontext.cpp.


V523 The 'then' statement is equivalent to the 'else' statement. d3dshadows.cpp 1410


void CD3D9Renderer::ConfigShadowTexgen(....)
{
  ....
  if ((pFr->m_Flags & DLF_DIRECTIONAL) ||
    (!(pFr->bUseHWShadowMap) && !(pFr->bHWPCFCompare)))
  {
    //linearized shadows are used for any kind of directional
    //lights and for non-hw point lights
    m_cEF.m_TempVecs[2][Num] = 1.f / (pFr->fFarDist);
  }
  else
  {
    //hw point lights sources have non-linear depth for now
    m_cEF.m_TempVecs[2][Num] = 1.f / (pFr->fFarDist);
  }
  ....
}

To finish the section on copy-paste, here is one more interesting error. No matter what result the conditional expression produces, the value m_cEF.m_TempVecs[2][Num] is always computed by the same formula. Judging by the surrounding code, the index is correct: it's exactly the element with index '2' that must be filled with a value. It's just that the formula itself was meant to be different in each case, and the programmer forgot to change the copied code.


Troubles with initialization

image5.png


V546 Member of a class is initialized by itself: 'eConfigMax(eConfigMax)'. particleparams.h 1013


ParticleParams() :
  ....
  fSphericalApproximation(1.f),
  fVolumeThickness(1.0f),
  fSoundFXParam(1.f),
  eConfigMax(eConfigMax.VeryHigh), // <=
  fFadeAtViewCosAngle(0.f)
{}

The analyzer detected a potential typo that causes a class field to be initialized to its own value.


V603 The object was created but it is not being used. If you wish to call constructor, 'this->SRenderingPassInfo::SRenderingPassInfo(....)' should be used. i3dengine.h 2589

SRenderingPassInfo()
  : pShadowGenMask(NULL)
  , nShadowSide(0)
  , nShadowLod(0)
  , nShadowFrustumId(0)
  , m_bAuxWindow(0)
  , m_nRenderStackLevel(0)
  , m_eShadowMapRendering(static_cast<uint8>(SHADOW_MAP_NONE))
  , m_bCameraUnderWater(0)
  , m_nRenderingFlags(0)
  , m_fZoomFactor(0.0f)
  , m_pCamera(NULL)
  , m_nZoomInProgress(0)
  , m_nZoomMode(0)
  , m_pJobState(nullptr)
{
  threadID nThreadID = 0;
  gEnv->pRenderer->EF_Query(EFQ_MainThreadList, nThreadID);
  m_nThreadID = static_cast<uint8>(nThreadID);
  m_nRenderFrameID = gEnv->pRenderer->GetFrameID();
  m_nRenderMainFrameID = gEnv->pRenderer->GetFrameID(false);
}
  
SRenderingPassInfo(threadID id)
{
  SRenderingPassInfo(); // <=
  SetThreadID(id);
}

In this code, incorrect use of constructor was detected. The programmer probably assumed that calling a constructor in a way like that - without parameters - inside another constructor would initialize the class fields, but this assumption was wrong.


Instead, a new unnamed object of type SRenderingPassInfo will be created and immediately destroyed. The class fields, therefore, will remain uninitialized. One way to fix this error is to create a separate initialization function and call it from different constructors.


V688 The 'm_cNewGeomMML' local variable possesses the same name as one of the class members, which can result in a confusion. terrain_node.cpp 344


void CTerrainNode::Init(....)
{
  ....
  m_nOriginX = m_nOriginY = 0; // sector origin
  m_nLastTimeUsed = 0;         // basically last time rendered

  uint8 m_cNewGeomMML = m_cCurrGeomMML = m_cNewGeomMML_Min ....

  m_pLeafData = 0;

  m_nTreeLevel = 0;
  ....
}

The name of the local variable cNewGeomMML coincides with that of a class field. It's usually not an error, but in this particular case it does look strange in comparison to how the other class fields are initialized.


V575 The 'memset' function processes '0' elements. Inspect the third argument. crythreadutil_win32.h 294

void EnableFloatExceptions(....)
{
  ....
  CONTEXT ctx;
  memset(&ctx, sizeof(ctx), 0);  // <=
  ....
}

This error is a very interesting one. When calling the memset() function, two arguments were swapped by mistake, which resulted in calling the function to fill 0 bytes. This is the function prototype:

void * memset ( void * ptr, int value, size_t num );

The function expects to receive the buffer size as the third argument and the value the buffer is to be filled with as the second.


The fixed version:


void EnableFloatExceptions(....)
{
  ....
  CONTEXT ctx;
  memset(&ctx, 0, sizeof(ctx));
  ....
}

V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. command_buffer.cpp 62

void CBuffer::Execute()
{
  ....
  QuatT * pJointsTemp = static_cast<QuatT*>(
    alloca(m_state.m_jointCount * sizeof(QuatT)));
  ....
}

In some parts of the project's code, the alloca() function is used to allocate memory for an array of objects. In the example above, with memory allocated in such a way, neither the constructor, nor the destructor will be called for objects of class QuatT. This defect may result in handling uninitialized variables, and other errors.


Here's a complete list of other defects of this type:


  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. command_buffer.cpp 67
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. posematching.cpp 144
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. characterinstance.cpp 280
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. characterinstance.cpp 282
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. scriptbind_entity.cpp 6252
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. jobmanager.cpp 1016
  • V630 The '_alloca' function is used to allocate memory for an array of objects which are classes containing constructors. driverd3d.cpp 5859

V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: -1.8f. posealignerc3.cpp 330


ILINE bool InitializePoseAlignerPinger(....)
{
  ....
  chainDesc.offsetMin = Vec3(0.0f, 0.0f, bIsMP ? -1.8f : -1.8f);
  chainDesc.offsetMax = Vec3(0.0f, 0.0f, bIsMP ? +0.75f : +1.f);
  ....
}

A few fragments were found where the ternary operator ?: returns one and the same value. While in the previous example it could have been done for aesthetic reasons, the reason for doing so in the following fragment is unclear.

float predictDelta = inputSpeed &lt; 0.0f ? 0.1f : 0.1f; // &lt;=
float dict = angle + predictDelta * ( angle - m_prevAngle) / dt ;

A complete list of other defects of this type:

  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: -1.8f. posealignerc3.cpp 313
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: -2.f. posealignerc3.cpp 347
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: D3D11_RTV_DIMENSION_TEXTURE2DARRAY. d3dtexture.cpp 2277
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: 255U. renderer.cpp 3389
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: D3D12_RESOURCE_STATE_GENERIC_READ. dx12device.cpp 151
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: 0.1f. vehiclemovementstdboat.cpp 720

V570 The 'runtimeData.entityId' variable is assigned to itself. behaviortreenodes_ai.cpp 1771


void ExecuteEnterScript(RuntimeData&amp; runtimeData)
{
  ExecuteScript(m_enterScriptFunction, runtimeData.entityId);

  runtimeData.entityId = runtimeData.entityId; // <=
  runtimeData.executeExitScriptIfDestructed = true;
}

A variable is assigned to itself, which doesn't look right. The authors should check this code.


Operation precedence

image6.png


V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '+' operator. gpuparticlefeaturespawn.cpp 79

bool HasDuration() { return m_useDuration; }

void CFeatureSpawnRate::SpawnParticles(....)
{
  ....
  SSpawnData& spawn = pRuntime->GetSpawnData(i);
  const float amount = spawn.amount;
  const int spawnedBefore = int(spawn.spawned);
  const float endTime = spawn.delay +
                        HasDuration() ? spawn.duration : fHUGE;
  ....
}

The function above seems to measure time in a wrong way. The precedence of the addition operator is higher than that of the ternary operator :?, so the value 0 or 1 is added to spawn.delay first, and then the value spawn.duration or fHUGE is written into the endTime variable. This error is quite a common one. To learn more about interesting patterns of errors involving operation precedence collected from the PVS-Studio bug database, see my article: Logical Expressions in C/C++. Mistakes Made by Professionals.


V634 The priority of the '*' operation is higher than that of the '<' operation. It's possible that parentheses should be used in the expression. model.cpp 336

enum joint_flags
{
  angle0_locked = 1,
  ....
};

bool CDefaultSkeleton::SetupPhysicalProxies(....)
{
  ....
  for (int j = 0; .... ; j++)
  {
    // lock axes with 0 limits range
    m_arrModelJoints[i]....flags |= (....) * angle0_locked << j;
  }
  ....
}

This is another very interesting error that has to do with the precedence of the multiplication and bitwise shift operations. The latter has lower precedence, so the whole expression is multiplied by one at each iteration (as the angle0_locked constant has the value one), which looks very strange.


This is what the programmer must have wanted that code to look like:

m_arrModelJoints[i]....flags |= (....) * (angle0_locked &lt;&lt; j);

The following file contains a list of 35 suspicious fragments involving precedence of shift operations: CryEngine5_V634.txt.


Undefined behavior

Undefined behavior is the result of executing computer code written in a certain programming language that depends on a number of random factors such as memory state or triggered interrupts. In other words, this result is not prescribed by the language specification. It is considered to be an error to let such a situation occur in your program. Even if it can successfully execute on some compiler, it is not guaranteed to be cross-platform and may fail on another machine, operating system, and even other settings of the same compiler.


image7.png


V610 Undefined behavior. Check the shift operator '<'. The left operand '-1' is negative. physicalplaceholder.h 25

#ifndef physicalplaceholder_h
#define physicalplaceholder_h
#pragma once
....
const int NO_GRID_REG = -1<<14;
const int GRID_REG_PENDING = NO_GRID_REG+1;
....

Under the modern C++ standard, a left shift of a negative value is undefined behavior. The analyzer found a few more similar issues in CryEngine V's code:

  • V610 Undefined behavior. Check the shift operator '<'. The left operand '~(TFragSeqStorage(0))' is negative. udpdatagramsocket.cpp 757
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '-1' is negative. tetrlattice.cpp 324
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '-1' is negative. tetrlattice.cpp 350
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '-1' is negative. tetrlattice.cpp 617
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '-1' is negative. tetrlattice.cpp 622
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '(~(0xF))' is negative. d3ddeferredrender.cpp 876
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '(~(0xF))' is negative. d3ddeferredshading.cpp 791
  • V610 Undefined behavior. Check the shift operator '<'. The left operand '(~(1 < 0))' is negative. d3dsprites.cpp 1038

V567 Undefined behavior. The 'm_current' variable is modified while being used twice between sequence points. operatorqueue.cpp 105

bool COperatorQueue::Prepare(....)
{
  ++m_current &= 1;
  m_ops[m_current].clear();
  return true;
}

The analyzer detected an expression that causes undefined behavior. A variable is used multiple times between two sequence points, while its value changes. The result of executing such an expression, therefore, can't be determined.


Other similar issues:

  • V567 Undefined behavior. The 'itail' variable is modified while being used twice between sequence points. trimesh.cpp 3101
  • V567 Undefined behavior. The 'ihead' variable is modified while being used twice between sequence points. trimesh.cpp 3108
  • V567 Undefined behavior. The 'ivtx' variable is modified while being used twice between sequence points. boolean3d.cpp 1194
  • V567 Undefined behavior. The 'ivtx' variable is modified while being used twice between sequence points. boolean3d.cpp 1202
  • V567 Undefined behavior. The 'ivtx' variable is modified while being used twice between sequence points. boolean3d.cpp 1220
  • V567 Undefined behavior. The 'm_commandBufferIndex' variable is modified while being used twice between sequence points. xconsole.cpp 180
  • V567 Undefined behavior. The 'm_FrameFenceCursor' variable is modified while being used twice between sequence points. ccrydx12devicecontext.cpp 952
  • V567 Undefined behavior. The 'm_iNextAnimIndex' variable is modified while being used twice between sequence points. hitdeathreactionsdefs.cpp 192

Errors in conditions

image8.png


V579 The memcmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. graphicspipelinestateset.h 58

bool
operator==(const SComputePipelineStateDescription& other) const
{
  return 0 == memcmp(this, &other, sizeof(this)); // <=
}

The programmer made a mistake in the equality operation in the call to the memcmp() function, which leads to passing the pointer size instead of the object size as a function argument. As a result, only the first several bytes of the objects are compared.


The fixed version:

memcmp(this, &amp;other, sizeof(*this));

Unfortunately, three more similar issues were found in the project:

  • V579 The memcpy function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. geomcacherendernode.cpp 286
  • V579 The AddObject function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the second argument. clipvolumemanager.cpp 145
  • V579 The memcmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. graphicspipelinestateset.h 34

V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. livingentity.cpp 181

CLivingEntity::~CLivingEntity()
{
  for(int i=0;i<m_nParts;i++) {
    if (!m_parts[i].pPhysGeom || ....)
      delete[] m_parts[i].pMatMapping; m_parts[i].pMatMapping=0;
  }
  ....
}

I spotted a huge number of code blocks with statements written in one line. These include not only ordinary assignments, but rather loops, conditions, function calls, and sometimes a mixture of all of these (see Figure 3).


image9.png


Figure 3 - Poor code formatting


In code of size like that, this programming style almost inevitably leads to errors. In the example above, the memory block occupied by an array of objects was to be freed and the pointer was to be cleared when a certain condition was met. However, incorrect code formatting causes the m_parts[i].pMatMapping pointer to be cleared at every loop iteration. The implications of this problem can't be predicted, but the code does look strange.


Other fragments with strange formatting:

  • V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. physicalworld.cpp 2449
  • V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. articulatedentity.cpp 1723
  • V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. articulatedentity.cpp 1726

V695 Range intersections are possible within conditional expressions. Example: if (A < 5) { ... } else if (A < 2) { ... }. Check lines: 538, 540. statobjrend.cpp 540


bool CStatObj::RenderDebugInfo(....)
{
  ....
  ColorB clr(0, 0, 0, 0);
  if (nRenderMats == 1)
    clr = ColorB(0, 0, 255, 255);
  else if (nRenderMats == 2)
    clr = ColorB(0, 255, 255, 255);
  else if (nRenderMats == 3)
    clr = ColorB(0, 255, 0, 255);
  else if (nRenderMats == 4)
    clr = ColorB(255, 0, 255, 255);
  else if (nRenderMats == 5)
    clr = ColorB(255, 255, 0, 255);
  else if (nRenderMats >= 6)          // <=
    clr = ColorB(255, 0, 0, 255);
  else if (nRenderMats >= 11)         // <=
    clr = ColorB(255, 255, 255, 255);
  ....
}

The programmer made a mistake that prevents the color ColorB(255, 255, 255, 255) from ever being selected. The values nRenderMats are first compared one by one with the numbers from 1 to 5, but when comparing them with value ranges, the programmer didn't take into account that values larger than 11 already belong to the range of values larger than 6, so the last condition will never execute.


This cascade of conditions was copied in full into one more fragment:

  • V695 Range intersections are possible within conditional expressions. Example: if (A < 5) { ... } else if (A < 2) { ... }. Check lines: 338, 340. modelmesh_debugpc.cpp 340

V695 Range intersections are possible within conditional expressions. Example: if (A < 5) { ... } else if (A < 2) { ... }. Check lines: 393, 399. xmlcpb_nodelivewriter.cpp 399

enum eNodeConstants
{
  ....
  CHILDBLOCKS_MAX_DIST_FOR_8BITS = BIT(7) - 1,    // 127
  CHILDBLOCKS_MAX_DIST_FOR_16BITS   = BIT(6) - 1, // 63
  ....
};

void CNodeLiveWriter::Compact()
{
  ....
  if (dist <= CHILDBLOCKS_MAX_DIST_FOR_8BITS) // dist <= 127
  {
    uint8 byteDist = dist;
    writeBuffer.AddData(&byteDist, sizeof(byteDist));
    isChildBlockSaved = true;
  }
  else if (dist <= CHILDBLOCKS_MAX_DIST_FOR_16BITS) // dist <= 63
  {
    uint8 byteHigh = CHILDBLOCKS_USING_MORE_THAN_8BITS | ....);
    uint8 byteLow = dist & 255;
    writeBuffer.AddData(&byteHigh, sizeof(byteHigh));
    writeBuffer.AddData(&byteLow, sizeof(byteLow));
    isChildBlockSaved = true;
  }
  ....
}

A similar mistake inside a condition was also found in the fragment above, except that this time the code that fails to get control is larger. The values of the constants CHILDBLOCKS_MAX_DIST_FOR_8BITS and CHILDBLOCKS_MAX_DIST_FOR_16BITS are such that the second condition will never be true.


V547 Expression 'pszScript[iSrcBufPos] != '=='' is always true. The value range of char type: [-128, 127]. luadbg.cpp 716

bool CLUADbg::LoadFile(const char* pszFile, bool bForceReload)
{
  FILE* hFile = NULL;
  char* pszScript = NULL, * pszFormattedScript = NULL;
  ....
  while (pszScript[iSrcBufPos] != ' ' &&
    ....
    pszScript[iSrcBufPos] != '=' &&
    pszScript[iSrcBufPos] != '==' &&  // <=
    pszScript[iSrcBufPos] != '*' &&
    pszScript[iSrcBufPos] != '+' &&
    pszScript[iSrcBufPos] != '/' &&
    pszScript[iSrcBufPos] != '~' &&
    pszScript[iSrcBufPos] != '"')
  {}
  ....
}

A large conditional expression contains a subexpression that is always true. The '==' literal will have type int and correspond to the value 15677. The pszScript array consists of elements of type char, and a value of type char can't be equal to 15677, so the pszScript[iSrcBufPos] != '==' expression is always true.


V734 An excessive expression. Examine the substrings "_ddn" and "_ddna". texture.cpp 4212

void CTexture::PrepareLowResSystemCopy(byte* pTexData, ....)
{
  ....
  // make sure we skip non diffuse textures
  if (strstr(GetName(), "_ddn")              // <=
      || strstr(GetName(), "_ddna")          // <=
      || strstr(GetName(), "_mask")
      || strstr(GetName(), "_spec.")
      || strstr(GetName(), "_gloss")
      || strstr(GetName(), "_displ")
      || strstr(GetName(), "characters")
      || strstr(GetName(), "$")
      )
    return;
  ....
}

The strstr() function looks for the first occurrence of the specified substring within another string and returns either a pointer to the first occurrence or an empty pointer. The string "_ddn" is the first to be searched, and "_ddna" is the second, which means that the condition will be true if the shorter string is found. This code might not work as expected; or perhaps this expression is redundant and could be simplified by removing the extra check.


V590 Consider inspecting this expression. The expression is excessive or contains a misprint. goalop_crysis2.cpp 3779

void COPCrysis2FlightFireWeapons::ParseParam(....)
{
  ....
  else if (!paused &&
          (m_State == eFP_PAUSED) &&        // <=
          (m_State != eFP_PAUSED_OVERRIDE)) // <=
  ....
}

The conditional expression in the ParseParam() function is written in such a way that its result does not depend on the (m_State != eFP_PAUSED_OVERRIDE) subexpression.


Here's a simpler example:

if ( err == code1 &amp;&amp; err != code2)
{
  ....
}

The result of the whole conditional expression does not depend on the result of the (err != code2) subexpression, which can be clearly seen from the truth table for this example (see Figure 4)


image10.png


Figure 4 - Truth table for a logical expression


Comparing unsigned values with zero

image11.png


When scanning projects, we often come across comparisons of unsigned values with zero, which produce either true or false every time. Such code does not always contain a critical bug; it is often a result of too much caution or changing a variable's type from signed to unsigned. Anyway, such comparisons need to be checked.


V547 Expression 'm_socket < 0' is always false. Unsigned type value is never < 0. servicenetwork.cpp 585

typedef SOCKET CRYSOCKET;
// Internal socket data
CRYSOCKET m_socket;

bool CServiceNetworkConnection::TryReconnect()
{
  ....
  // Create new socket if needed
  if (m_socket == 0)
  {
    m_socket = CrySock::socketinet();
    if (m_socket < 0)
    {
      ....
      return false;
    }
  }
  ....
}

I'd like to elaborate on the SOCKET type. It can be both signed and unsigned depending on the platforms, so it is strongly recommended that you use special macros and constants specified in the standard headers when working with this type.


In cross-platform projects, comparisons with 0 or -1 are common that result in misinterpretation of error codes. CryEngine V project is no exception, although some checks are done correctly, for example:

if (m_socket == CRY_INVALID_SOCKET)

Nevertheless, many parts of the code use different versions of these checks.


See the file CryEngine5_V547.txt for other 47 suspicious comparisons of unsigned variables with zero. The code authors need to check these warnings.


Dangerous pointers

image12.png


Diagnostic V595 detects pointers that are tested for null after they have been dereferenced. In practice, this diagnostic catches very tough bugs. On rare occasions, it issues false positives, which is explained by the fact that pointers are checked indirectly, i.e. through one or several other variables, but figuring such code out isn't an easy task for a human either, is it? Three code samples are given below that trigger this diagnostic and look especially surprising, as it's not clear why they work at all. For the other warnings of this type see the file CryEngine5_V595.txt.

<h4>Example 1</h4>

V595 The 'm_pPartManager' pointer was utilized before it was verified against nullptr. Check lines: 1441, 1442. 3denginerender.cpp 1441

void C3DEngine::RenderInternal(....)
{
  ....
  m_pPartManager->GetLightProfileCounts().ResetFrameTicks();
  if (passInfo.IsGeneralPass() && m_pPartManager)
    m_pPartManager->Update();
  ....
}

The m_pPartManager pointer is dereferenced and then checked.


<h4>Example 2</h4>

V595 The 'gEnv->p3DEngine' pointer was utilized before it was verified against nullptr. Check lines: 1477, 1480. gameserialize.cpp 1477

bool CGameSerialize::LoadLevel(....)
{
  ....
  // can quick-load
  if (!gEnv->p3DEngine->RestoreTerrainFromDisk())
    return false;

  if (gEnv->p3DEngine)
  {
    gEnv->p3DEngine->ResetPostEffects();
  }
  ....
}

The gEnv->p3DEngine pointer is dereferenced and then checked.


<h4>Example 3</h4>

V595 The 'pSpline' pointer was utilized before it was verified against nullptr. Check lines: 158, 161. facechannelkeycleanup.cpp 158

void FaceChannel::CleanupKeys(....)
{

  CFacialAnimChannelInterpolator backupSpline(*pSpline);

  // Create the key entries array.
  int numKeys = (pSpline ? pSpline->num_keys() : 0);
  ....
}

The pSpline pointer is dereferenced and then checked.


Miscellaneous

image13.png


V622 Consider inspecting the 'switch' statement. It's possible that the first 'case' operator is missing. mergedmeshrendernode.cpp 999

static inline void ExtractSphereSet(....)
{
  ....
  switch (statusPos.pGeom->GetType())
  {
    if (false)
    {
    case GEOM_CAPSULE:
      statusPos.pGeom->GetPrimitive(0, &cylinder);
    }
    if (false)
    {
    case GEOM_CYLINDER:
      statusPos.pGeom->GetPrimitive(0, &cylinder);
    }
    for (int i = 0; i < 2 && ....; ++i)
    {
      ....
    }
    break;
  ....
}

This fragment is probably the strangest of all found in CryEngine V. Whether or not the case label will be selected does not depend on the if statement, even in case of if (false). In the switch statement, an unconditional jump to the label occurs if the condition of the switch statement is met. Without the break statement, one could use such code to "bypass" irrelevant statements, but, again, maintaining such obscure code isn't easy. One more question is, why does the same code execute when jumping to the labels GEOM_CAPSULE and GEOM_CYLINDER?


V510 The 'LogError' function is not expected to receive class-type variable as second actual argument. behaviortreenodes_action.cpp 143

typedef CryStringT&lt;char&gt; string;
// The actual fragment name.
string m_fragName;
//! cast to C string.
const value_type* c_str() const { return m_str; }
const value_type* data() const  { return m_str; };
  
void LogError(const char* format, ...) const
{ .... }
  
void QueueAction(const UpdateContext& context)
{
  ....
  ErrorReporter(*this, context).LogError("....'%s'", m_fragName);
  ....
}

When it is impossible to specify the number and types of all acceptable parameters to a function, one puts ellipsis (...) at the end of the list of parameters in the function declaration, which means "and perhaps a few more". Only POD (Plain Old Data) types can be used as actual parameters to the ellipsis. If an object of a class is passed as an argument to a function's ellipsis, it almost always signals the presence of a bug. In the code above, it is the contents of the object that get to the stack, not the pointer to a string. Such code results in forming "gibberish" in the buffer or a crash. The code of CryEngine V uses a string class of its own, and it already has an appropriate method, c_str().


The fixed version:

LogError("....'%s'", m_fragName.c_str();

A few more suspicious fragments:


  • V510 The 'LogError' function is not expected to receive class-type variable as second actual argument. behaviortreenodes_core.cpp 1339
  • V510 The 'Format' function is not expected to receive class-type variable as second actual argument. behaviortreenodes_core.cpp 2648
  • V510 The 'CryWarning' function is not expected to receive class-type variable as sixth actual argument. crypak.cpp 3324
  • V510 The 'CryWarning' function is not expected to receive class-type variable as fifth actual argument. crypak.cpp 3333
  • V510 The 'CryWarning' function is not expected to receive class-type variable as fifth actual argument. shaderfxparsebin.cpp 4864
  • V510 The 'CryWarning' function is not expected to receive class-type variable as fifth actual argument. shaderfxparsebin.cpp 4931
  • V510 The 'Format' function is not expected to receive class-type variable as third actual argument. featuretester.cpp 1727

V529 Odd semicolon ';' after 'for' operator. boolean3d.cpp 1314

int CTriMesh::Slice(....)
{
  ....
  bop_meshupdate *pmd = new bop_meshupdate, *pmd0;
  pmd->pMesh[0]=pmd->pMesh[1] = this;  AddRef();AddRef();
  for(pmd0=m_pMeshUpdate; pmd0->next; pmd0=pmd0->next); // <=
    pmd0->next = pmd;
  ....
}

This code is very strange. The programmer put a semicolon after the for loop, while the code formatting suggests that it should have a body.

V535 The variable 'j' is being used for this loop and for the outer loop. Check lines: 3447, 3490. physicalworld.cpp 3490

void CPhysicalWorld::SimulateExplosion(....)
{
  ....
  for(j=0;j<pmd->nIslands;j++)                 // <= line 3447
  {
    ....
    for(j=0;j<pcontacts[ncont].nborderpt;j++)  // <= line 3490
    {
  ....
}

The project's code is full of other unsafe fragments; for example, there are cases of using one counter for both nested and outer loops. Large source files contain code with intricate formatting and fragments where the same variables are changed in different parts of the code - you just can't do without static analysis there!


A few more strange loops:

  • V535 The variable 'i' is being used for this loop and for the outer loop. Check lines: 1630, 1683. entity.cpp 1683
  • V535 The variable 'i1' is being used for this loop and for the outer loop. Check lines: 1521, 1576. softentity.cpp 1576
  • V535 The variable 'i' is being used for this loop and for the outer loop. Check lines: 2315, 2316. physicalentity.cpp 2316
  • V535 The variable 'i' is being used for this loop and for the outer loop. Check lines: 1288, 1303. shadercache.cpp 1303

V539 Consider inspecting iterators which are being passed as arguments to function 'erase'. frameprofilerender.cpp 1090


float CFrameProfileSystem::RenderPeaks()
{
  ....
  std::vector<SPeakRecord>& rPeaks = m_peaks;
  
  // Go through all peaks.
  for (int i = 0; i < (int)rPeaks.size(); i++)
  {
    ....
    if (age > fHotToColdTime)
    {
      rPeaks.erase(m_peaks.begin() + i); // <=
      i--;
    }
  ....
}

The analyzer suspected that the function handling a container would receive an iterator from another container. It's a wrong assumption, and there is no error here: the rPeaks variable is a reference to m_peaks. This code, however, may confuse not only the analyzer, but also other programmers who will maintain it. One shouldn't write code in a way like that.


V713 The pointer pCollision was utilized in the logical expression before it was verified against nullptr in the same logical expression. actiongame.cpp 4235


int CActionGame::OnCollisionImmediate(const EventPhys* pEvent)
{
  ....
  else if (pMat->GetBreakability() == 2 &&
   pCollision->idmat[0] != pCollision->idmat[1] &&
   (energy = pMat->GetBreakEnergy()) > 0 &&
   pCollision->mass[0] * 2 > energy &&
   ....
   pMat->GetHitpoints() <= FtoI(min(1E6f, hitenergy / energy)) &&
   pCollision) // <=
    return 0;
  ....
}

The if statement includes a rather lengthy conditional expression where the pCollision pointer is used multiple times. What is wrong about this code is that the pointer is tested for null at the very end, i.e. after multiple dereference operations.


V758 The 'commandList' reference becomes invalid when smart pointer returned by a function is destroyed. renderitemdrawer.cpp 274

typedef std::shared_ptr&lt;....&gt; CDeviceGraphicsCommandListPtr;

CDeviceGraphicsCommandListPtr
CDeviceObjectFactory::GetCoreGraphicsCommandList() const
{
  return m_pCoreCommandList;
}

void CRenderItemDrawer::DrawCompiledRenderItems(....) const
{
  ....
  {
    auto& RESTRICT_REFERENCE commandList = *CCryDeviceWrapper::
      GetObjectFactory().GetCoreGraphicsCommandList();

    passContext....->PrepareRenderPassForUse(commandList);
  }
  ....
}

The commandList variable receives a reference to the value stored in a smart pointer. When this pointer destroys the object, the reference will become invalid.


A few more issues of this type:

  • V758 The 'commandList' reference becomes invalid when smart pointer returned by a function is destroyed. renderitemdrawer.cpp 384
  • V758 The 'commandList' reference becomes invalid when smart pointer returned by a function is destroyed. renderitemdrawer.cpp 368
  • V758 The 'commandList' reference becomes invalid when smart pointer returned by a function is destroyed. renderitemdrawer.cpp 485
  • V758 The 'commandList' reference becomes invalid when smart pointer returned by a function is destroyed. renderitemdrawer.cpp 553

Conclusion


It costs almost nothing to fix bugs caught during the coding phase unlike those that get to the testers, while fixing bugs that have made it to the end users involves huge expenses. No matter what analyzer you use, the static analysis technology itself has long proved to be an extremely effective and efficient means to control the quality of program code and software products in general.


Our collaboration with Epic Games has shown very well how integration of our analyzer into Unreal Engine 4 has benefited the project. We helped the developers in every aspect of analyzer integration and even fixed the bugs found in the project so that the developer team could continue scanning new code regularly on their own. We've shown that similar collaboration can be achieved with Crytek.


Try PVS-Studio on your own C/C++/C# project!


Pupping - a method for serializing data

$
0
0

Introduction


Serialization is the process of taking structures and objects along with their states and converting them to data that is reproducable with any computer environment. There are many ways to do this - it can be a struggle to figure out something that consistantly works. Here I will talk about a pattern or method I like to call pupping, and it is very similar to the method boost uses for its serialization library.


Packing and Unpacking


Pup stands for pack-unpack, and so pupping is packing/unpacking. The ideas is this; rather than create serialize/de-serialize functions for each object type and/or each type of medium (file, network, GUI widgets), create pupper objects for each type of medium bundled with a set of read/write functions for each fundamental data type. A pupper object contains the data and functions necessary to handle reading/writing to/from the specific medium. For example, a binary file pupper might contain a file stream that each pup function would use to read/write from/to.


Explanation


This pattern is fairly similar boost serialization, though I was using it before hearing of boost. It is useful in any case to understand it and possibly use a custom implementation so that no boost dependency is needed. The "pupper" is somewhat equivalent to the boost archive, and pup functions are equivalent to boost serialize functions. The code presented here is more simple than boost, and does not overload operators as boost does. It is as non-invasive as possible, and not template heavy.

The idea is that any object, no matter how complex, can be serialized to a stream of bytes by recursively breaking down the object until reaching fundamental data types. Any fundamental data type can be directly represented by bytes. The process of saving/loading/transmitting the raw data is separable from serializing objects. It is only necessary, then, to write the code to serialize an object once and anything can be done with the raw data.

The pupper pattern differs from most serialization methods in a few ways:

1) Read and write operations are not separated except at the lowest level (in the pupper object)

2) Objects that need to be serialized do not need to inherit from any special classes

3) Can be implemented with very small overhead, using no external libraries, while remaining extendable and flexible

4) Writing class methods, virtual or otherwise, is largely not necessary

If polymorphic serialization is required, a virtual method is needed in base classes. CRTP can be used to aid in this process. This case is covered later.

Instead of creating a class method in each object to provide for serialization, a global function is created for each object. All global functions should have the same name and parameters, except the parameter for the object that should be serialized. Making any object “serializable” is then just a matter of writing a global function. These functions can be named whatever as long as they all have the same name, but I find “pup” fitting. Some examples of pup prototypes for stl containers are shown below.


template<class T, class T2>
pup(pupper * p, std::map<T,T2> & map, const var_info & info);

template<class T >
pup(pupper * p, std::vector<T> & vec, const var_info & info);

template<class T >
pup(pupper * p, std::set<T> & set, const var_info & info);

The pupper pointer and var_info reference parameters will be explained later. The important thing is that the serialization work is done in a global function, not a member function.

The pup pattern is easiest shown by example. In this article pupper objects for binary and text file saving/loading are coded, and a few example objects are saved/loaded using them. An example is given using the pupper pattern along with CRTP to serialize polymorphic objects. Also, a std::vector of polymorphic base objects is saved/loaded illustrating the flexibility this pattern allows when using other library defined types (std::vector).

So without further ado, take a look at the pupper header file.


#define PUP_OUT 1
#define PUP_IN 2

#include <string>
#include <fstream>
#include <inttypes.h>

struct var_info
{
	var_info(const std::string & name_):
    name(name_)
	{}

	virtual ~var_info() {}

	std::string name;
};

struct pupper
{
    pupper(int32_t io_):
    io(io_)
    {}

	virtual ~pupper() {}

	virtual void pup(char & val_, const var_info & info_) = 0;
	virtual void pup(wchar_t & val_, const var_info & info_) = 0;
	virtual void pup(int8_t & val_, const var_info & info_) = 0;
	virtual void pup(int16_t & val_, const var_info & info_) = 0;
	virtual void pup(int32_t & val_, const var_info & info_) = 0;
	virtual void pup(int64_t & val_, const var_info & info_) = 0;
	virtual void pup(uint8_t & val_, const var_info & info_) = 0;
	virtual void pup(uint16_t & val_, const var_info & info_) = 0;
	virtual void pup(uint32_t & val_, const var_info & info_) = 0;
	virtual void pup(uint64_t & val_, const var_info & info_) = 0;
	virtual void pup(float & val_, const var_info & info_) = 0;
	virtual void pup(double & val_, const var_info & info_) = 0;
	virtual void pup(long double & val_, const var_info & info_) = 0;
	virtual void pup(bool & val_, const var_info & info_) = 0;

	int32_t io;
};

void pup(pupper * p, char & val_, const var_info & info_);
void pup(pupper * p, wchar_t & val_, const var_info & info_);
void pup(pupper * p, int8_t & val_, const var_info & info_);
void pup(pupper * p, int16_t & val_, const var_info & info_);
void pup(pupper * p, int32_t & val_, const var_info & info_);
void pup(pupper * p, int64_t & val_, const var_info & info_);
void pup(pupper * p, uint8_t & val_, const var_info & info_);
void pup(pupper * p, uint16_t & val_, const var_info & info_);
void pup(pupper * p, uint32_t & val_, const var_info & info_);
void pup(pupper * p, uint64_t & val_, const var_info & info_);
void pup(pupper * p, float & val_, const var_info & info_);
void pup(pupper * p, double & val_, const var_info & info_);
void pup(pupper * p, long double & val_, const var_info & info_);
void pup(pupper * p, bool & val_, const var_info & info_);

A var_info struct is declared first which simply has a name field for now – this is where information about the pupped variable belongs. It is filled out during the pupping process, and so a constructor requiring field information is made so that it isn’t later forgotten.

The pupper base class defines the set of methods that any type of pupper must implement – a method to handle reading/writing each fundamental data type from/to the medium. A set of global functions named “pup” are declared and defined, establishing the fundamental usage of the pupping pattern. The idea is to be able to call pup(pupper, object, description) almost anywhere in code in order to serialize/de-serialize any object (that should be serializable).

Creating a new pupper object type includes implementing a pup method for each fundamental data type. These methods are then used by the pup global functions, which in turn are used by pup functions for more complicated types. No matter how many new pupper types are created, the pup functions to serialize each object need only be written once. This is exactly what makes this pattern useful.

To make all objects serializable to file in binary, create a binary file pupper. To make all objects serializable to file in text, create a text file pupper. To make all objects serializable to a Qt dialog, create a Qt dialog pupper.

Some types of pupper objects may require additional information about the variables. For example, there are multiple ways a double can be represented in a GUI – a vertical slider, horizontal slider, spin box, etc. The var_info struct allows new information about variables to be added. Any pupper object that does not need that information can just ignore it. With the Qt example, a flag could be added to the var_info struct and used by the Qt pupper object. The objects that need to be shown in a GUI would then need to set the flag, and all pupper objects that don’t have use for the flag ignore it.

By making the destructor of var_info virtual, the var_info struct can be extended. This is useful, again, if creating a library that others will be using. It allows the user to create their own pupper object types and add any necessary data to var_info without needing to edit the library source code.

There are a few reasons for using pup(pupper, object, description) instead of pupper->pup(object, description) or object->pup(pupper, description).

The reasons for not using pupper->pup(object, description) are:

1) The base pupper class would have to be extended for every new type of object. If creating a library with extendable classes, the user of the library would have to edit the base pupper class for every class they extended in which the library is still responsible for serializing

2) The pack/unpack code would be separated from the object making it prone to bugs when changes are made to the object

And the reasons for not using object->pup(pupper, description) are:

1) You cannot easily extend third party library objects (such as std::vector) to include a pup function – they would require a special function or wrapper class

2) Since many objects would not include a “pup” function, there would be inconsistencies with the pup usage. This is purely an aesthetics/convenience argument, and is of course an opinion. But I would argue that writing:


pup(pupper,obj1,desc1);
pup(pupper,obj2,desc2);
pup(pupper,obj3,desc3);
pup(pupper,obj4,desc4);
//etc...

is both easier to understand and remember than:


obj1->pup(pupper,desc1);
pup(pupper,obj2,desc2);
obj3->pup(pupper,desc3);
pup(pupper,obj4,desc4);
//etc...

If the same pup function format is used for everything, writing pup functions becomes trivial because they are just combinations of other pup functions of the same format.

Creating concrete pupper objects can be easy - binary and text file pupper objects are included as an example. The definition code for them is boring so it won’t be shown here - but the declarations are below.



//binary_file_pupper header
#include "pupper.h"

struct binary_file_pupper : public pupper
{
    binary_file_pupper(std::fstream & fstrm, int mode);
    std::fstream & fs;
    void pup(char & val_, const var_info & info_);
	void pup(wchar_t & val_, const var_info & info_);
	void pup(int8_t & val_, const var_info & info_);
	void pup(int16_t & val_, const var_info & info_);
	void pup(int32_t & val_, const var_info & info_);
    void pup(int64_t & val_, const var_info & info_);
	void pup(uint8_t & val_, const var_info & info_);
	void pup(uint16_t & val_, const var_info & info_);
	void pup(uint32_t & val_, const var_info & info_);
	void pup(uint64_t & val_, const var_info & info_);
	void pup(float & val_, const var_info & info_);
	void pup(double & val_, const var_info & info_);
	void pup(long double & val_, const var_info & info_);
	void pup(bool & val_, const var_info & info_);
};

template <class T>
void pup_bytes(binary_file_pupper * p, T & val_)
{
    if (p->io == PUP_IN)
        p->fs.read((char*)&val_, sizeof(T));
    else
        p->fs.write((char*)&val_, sizeof(T));
}

//text_file_pupper header
#include "pupper.h"

struct text_file_pupper : public pupper
{
    text_file_pupper(std::fstream & fstrm, int mode);
    std::fstream & fs;
    void pup(char & val_, const var_info & info_);
    void pup(wchar_t & val_, const var_info & info_);
	void pup(int8_t & val_, const var_info & info_);
	void pup(int16_t & val_, const var_info & info_);
	void pup(int32_t & val_, const var_info & info_);
	void pup(int64_t & val_, const var_info & info_);
	void pup(uint8_t & val_, const var_info & info_);
	void pup(uint16_t & val_, const var_info & info_);
	void pup(uint32_t & val_, const var_info & info_);
	void pup(uint64_t & val_, const var_info & info_);
	void pup(float & val_, const var_info & info_);
	void pup(double & val_, const var_info & info_);
	void pup(long double & val_, const var_info & info_);
	void pup(bool & val_, const var_info & info_);
};

template<class T>
void pup_text(text_file_pupper * p, T val, const var_info & info, std::string & line)
{
    std::string begtag, endtag;
    begtag = "<" + info.name + ">"; endtag = "</" + info.name + ">";

    if (p->io == PUP_OUT)
    {
        p->fs << begtag << val << endtag << "\n";
    }
    else
    {
        std::getline(p->fs, line);
        size_t beg = begtag.size(); size_t loc = line.find(endtag);
        line = line.substr(beg, loc - beg);
    }
}

The template functions are there as a convenience – all of the pupper methods use them. The pup_text template function fills in the string “line” with the variable being read if the pupper is set to read mode, but if it is set to write mode the variable is written to the file stream and the line is left empty. The pup_bytes function is self-explanatory (I know it’s not multi-platform safe).

Writing pup functions to serialize objects using the pupper objects requires no specific knowledge of the pupper object; it just needs to be passed along. Take a look at the header and definition file for an example object (obj_a).


#include "pupper.h"
#include "math_structs.h"

class obj_a
{
    public:
    
	friend void pup(pupper * p_, obj_a & oa, const var_info & info);

	void set_transform(const fmat4 & tform);
	void set_velocity(const fvec4 & vel);
	const fvec4 & get_velocity() const;
	const fmat4 & get_transform() const;
    
    private:

	fmat4 transform;
	fvec4 velocity;

};

void pup(pupper * p_, obj_a & oa, const var_info & info)
{
	pup(p_, oa.transform, var_info(info.name + ".transform"));
	pup(p_, oa.velocity, var_info(info.name + ".velocity"));
}

The pup function responsible for serializing obj_a calls the pup functions responsible for serializing fmat4’s and fvec4’s. Take a look at the code defining fmat4 and fvec4.


struct fvec4
{
    fvec4(float x_=0.0f, float y_=0.0f, float z_=0.0f, float w_=0.0f);

	union
	{
		struct
		{
			float x;
			float y;
			float z;
			float w;
		};
		
		struct
		{
			float r;
			float g;
			float b;
			float a;
		};

		float data[4];
	};

    fvec4 operator+(const fvec4 & rhs);
    fvec4 operator-(const fvec4 & rhs);
    fvec4 & operator+=(const fvec4 & rhs);
    fvec4 & operator-=(const fvec4 & rhs);
};

void pup(pupper * p_, fvec4 & vc, const var_info & info)
{
    pup(p_, vc.data[0], var_info(info.name + ".x"));
    pup(p_, vc.data[1], var_info(info.name + ".y"));
    pup(p_, vc.data[2], var_info(info.name + ".z"));
    pup(p_, vc.data[3], var_info(info.name + ".w"));
}

struct fmat4
{	
	fmat4(fvec4 row1_ = fvec4(1.0f,0.0f,0.0f,0.0f), 
	      fvec4 row2_ = fvec4(0.0f,1.0f,0.0f,0.0f),
	      fvec4 row3_ = fvec4(0.0f,0.0f,1.0f,0.0f),
          fvec4 row4_ = fvec4(0.0f,0.0f,0.0f,1.0f));
    
    union
	{
		struct
		{
			fvec4 rows[4];
		};
		float data[16];
	};
};

void pup(pupper * p_, fmat4 & tf, const var_info & info)
{
    pup(p_, tf.rows[0], var_info(info.name + ".row1"));
    pup(p_, tf.rows[1], var_info(info.name + ".row2"));
    pup(p_, tf.rows[2], var_info(info.name + ".row3"));
    pup(p_, tf.rows[3], var_info(info.name + ".row4"));
}

The pup function for fvec4 calls the pup function for floats four times, which was defined in pupper.h. The fmat4 pup function calls the fvec4 pup function for fvec4 four times. Notice that no matter what concrete pupper is used, none of these functions change.

It is easy to write pup functions for other library types also. As an example, take a look at the pup function for a std::vector.


template<class T>
void pup(pupper * p_, std::vector<T> & vec, const var_info & info)
{
    uint32_t size = static_cast<uint32_t>(vec.size());
    pup(p_, size, var_info(info.name + ".size"));
    vec.resize(size);
    for (uint32_t i = 0; i < size; ++i)
        pup(p_, vec[i], var_info(info.name + "[" + std::to_string(i) + "]"));
}

There are some disadvantages with this particular function – mainly it won’t work for types of T that don’t have a default constructor. There are ways to write this function so that it will work – like with parameter packs – but they aren’t needed here.

With a pup function to handle std::vector, it can be used to pup any vectors contained in an object. Take a look at the obj_a_container, which owns the obj_a’s it contains.


#include "pupper.h"
#include "derived_obj_a.h"
#include <vector>

struct obj_a_desc
{
	obj_a_desc();
    
	int8_t type;
	obj_a * ptr;
};

struct obj_a_container
{
	~obj_a_container();

	void release();

	std::vector<obj_a_desc> obj_a_vec;
};

void pup(pupper * p_, obj_a_desc & oa_d, const var_info & info)
{
	pup(p_, oa_d.type, var_info(info.name + ".type"));
	if (oa_d.ptr == nullptr)
	{
		// This is a bit of a cheat because I don't feel like writing factory code
		if (oa_d.type == 1)
            oa_d.ptr = new obj_a;
		else
            oa_d.ptr = new derived_obj_a;
	}
    pup(p_, *oa_d.ptr, info);
}

void pup(pupper * p_, obj_a_container & oa, const var_info & info)
{
    pup(p_, oa.obj_a_vec, var_info("obj_a_vec"));
}

By creating a pup function for an obj_a *, memory can be allocated if need be. The vector pup function will call the pup obj_a* function on every element – which allocates memory if the pointer is null.

But how does the pupper pattern handle polymorphic class types which should still be serialized by the library? For example, if it should be possible to derive from obj_a and still have obj_a_container be able to pup the derived object given a pointer to the base object.

Well, it may be apparent that something strange is going on in the pup pointer to obj_a function – there is this obj_a_desc struct wrapping each obj_a pointer along with a type field that is checked against 1. This is a makeshift way to allow the obj_a_container to allocate derived_obj_a’s (which will be defined shortly). Normally this would be done with some type of factory – but that’s not the focus of the article. Instead, the description struct is serialized so that the pup function knows which object to allocate. It’s serialized using a – you guessed it – pup function.

After allocation, the pup function for obj_a is called - there is no special pup function for derived_obj_a and no pointer casting is done. To accomplish this a bit of curiously recurring template pattern (CRTP) code is needed.

First, a virtual pack/unpack method needs to be added to obj_a. Instead of requiring every derived object to implement this method, a template class is created which inherits from obj_a and implements it. The derived classes then inherit from the template class, with their type as the template parameter. For clarity the virtual method will be called pack_unpack instead of pup. The new header file for obj_a is shown below.


#include "pupper.h"
#include "math_structs.h"

class obj_a
{
public:
	friend void pup(pupper * p_, obj_a & oa, const var_info & info);

	void set_transform(const fmat4 & tform);
	void set_velocity(const fvec4 & vel);
	const fvec4 & get_velocity() const;
	const fmat4 & get_transform() const;

protected:

	virtual void pack_unpack(pupper * p, const var_info & info) {}

private:

	fmat4 transform;
	fvec4 velocity;

};

template<class T>
class puppable_obj_a : public obj_a
{
public:

    puppable_obj_a(T & derived_):
    derived(derived_)
    {}

protected:
	void pack_unpack(pupper * p, const var_info & info)
	{
        pup(p, derived, info);
    }

private:

    T & derived;
};

void pup(pupper * p_, obj_a & oa, const var_info & info)
{
    oa.pack_unpack(p_,info);
	pup(p_, oa.transform, var_info(info.name + ".transform"));
	pup(p_, oa.velocity, var_info(info.name + ".velocity"));
}

The oa.pack_unpack(p, info) will call the derived pack_unpack function, which in turn will call the correct pup function for the derived type. This means that the pup function for derived_obj_a will be called first, followed by the pup functions to serialize transform (fmat4) and velocity (fvec4). The code for derived_obj_a is shown below.


class derived_obj_a : public puppable_obj_a<derived_obj_a>
{
public:
	derived_obj_a(float health_=100.0f, float max_health_=100.0f):
    puppable_obj_a<derived_obj_a>(*this),
    health(health_),
    max_health(max_health_)
	{}

	float health;
	float max_health;
};

void pup(pupper * p, derived_obj_a & oa, const var_info & info)
{
    pup(p, oa.health, var_info(info.name + ".health"));
    pup(p, oa.health, var_info(info.name + ".max_health"));
}

The derived class, as stated earlier, inherits from the template class puppable_obj_a with its own type as the template parameter, which in turn inherits from obj_a and overwrites the pack_unpack method. Doing this allows the correct pup function to be called, pupping the health and max_health fields of derived_obj_a.

From the outside looking in, only a pup call is needed to serialize obj_a – even if it is a polymorphic pointer which actually points to derived_obj_a storage. An example program is included illustrating this fact. The read and write to file functions show the pup pattern in action.


void read_data_from_file(obj_a_container * a_cont, const std::string & fname, int save_mode)
{
	std::fstream file;
    pupper * p = nullptr;
    std::ios_base::openmode flags = std::fstream::in;

	if (save_mode)
	{
        flags |= std::fstream::binary;
		p = new binary_file_pupper(file, PUP_IN);

	}
	else
	{
		p = new text_file_pupper(file, PUP_IN);
	}

    file.open(fname, flags);
	if (!file.is_open())
	{
		std::cout << "Error opening file " << fname << std::endl;
		delete p;
		return;
	}

	a_cont->release();
	pup(p, *a_cont, var_info(""));
	std::cout << "Finished reading data from file " << fname << std::endl;
}

void write_data_to_file(obj_a_container * a_cont, const std::string & fname, int save_mode)
{
	std::fstream file;
	pupper * p;
    std::ios_base::openmode flags = std::fstream::out;

	if (save_mode) // if save mode is one then write in binary mode
	{
        flags |= std::fstream::binary;
		p = new binary_file_pupper(file, PUP_OUT);

	}
	else
	{
		p = new text_file_pupper(file, PUP_OUT);
	}

	file.open(fname, flags);
	if (!file.is_open())
	{
		std::cout << "Error opening file " << fname << std::endl;
		delete p;
		return;
	}

	pup(p, *a_cont, var_info(""));
	std::cout << "Finished writing data to file" << fname << std::endl;
}

Saving all of the obj_a’s within the obj_a_container is as simple as creating a pupper object and calling pup passing in the pupper object, the object to be serialized, and a var_info describing the object. The pupper object can be as simple or as complicated as it needs to be. The binary pupper, for example, could make sure (and probably should) that endianness is consistent for multi-byte chunks. It could also hold a buffer and only write to file once the buffer was full. Whatever – do what you gotta do. There is definitely some flexibility though.


Conclusion


Serialization can be daunting, but if it is broken down in to small enough pieces, then its easy to handle. The main benefit of using the methods presented in this article is that serialization code only needs to be written once, and different puppers can be created as needed. Doing this using global functions is non-intrusive and easy to understand. Ousting templates (mostly) makes for easier error messages when a pup function is forgotton. Lastly, the code presented here has virtually no dependencies and can be extended with minimal or no changes to existing code.

I hope this article has provided something useful. I look forward to hearing your feedback and thoughts.

All source code is attached along with a CMakeLists.txt for easy compilation. Cmake should make the compilation process painless. Even without cmake – no external libraries are needed so just add the files to a project and it should be good to go.

Feel free to use, copy/paste, or redistribute any of the source code in any way desired.

Here is the git repo, the zip is also attached.

Discussing Errors in Unity3D's Open-Source Components

$
0
0

Unity3D is one of the most promising and rapidly developing game engines to date. Every now and then, the developers upload new libraries and components to the official repository, many of which weren't available in as open-source projects until recently. Unfortunately, the Unity3D developer team allowed the public to dissect only some of the components, libraries, and demos employed by the project, while keeping the bulk of its code closed. In this article, we will try to find bugs and typos in those components with the help of PVS-Studio static analyzer.

image1.png


Introduction

We decided to check all the components, libraries, and demos in C#, whose source code is available in the Unity3D developer team's official repository:

  1. UI System- system for GUI development.
  2. Networking- system for implementing multiplayer mode.
  3. MemoryProfiler - system for profiling resources in use.
  4. XcodeAPI- component for interacting with the Xcode IDE.
  5. PlayableGraphVisualizer - system for project execution visualization.
  6. UnityTestTools - Unity3D testing utilities (no Unit tests included).
  7. AssetBundleDemo - project with AssetBundleServer's source files and demos for AssetBundle system.
  8. AudioDemos - demo projects for the audio system.
  9. NativeAudioPlugins - audio plugins (we are only interested in the demos for these plugins).
  10. GraphicsDemos - demo projects for the graphics system.

I wish we could take a look at the source files of the engine's kernel itself, but, unfortunately, no one except the developers themselves have access to it currently. So, what we've got on our operating table today is just a small part of the engine's source files. We are most interested in the UI system designed for implementing a more flexible GUI than the older, clumsy one, and the networking library, which served us hand and foot before UNet's release.

We are also as much interested in MemoryProfiler, which is a powerful and flexible tool for resource and load profiling.


Errors and suspicious fragments found


All warnings issued by the analyzer are grouped into 3 levels:

  1. High - almost certainly an error.
  2. Medium - possible error or typo.
  3. Low - unlikely error or typo.

We will be discussing only the high and medium levels in this article.

The table below presents the list of projects we have checked and analysis statistics across all the projects. The columns "Project name" and "Number of LOC" are self-explanatory, but the "Issued Warnings" column needs some explanation. It contains information about all the warnings issued by the analyzer. Positive warnings are warnings that directly or indirectly point to real errors or typos in the code. False warnings, or false positives, are those that interpret correct code as faulty. As I already said, all warnings are grouped into 3 levels. We will be discussing only the high- and medium-level warnings, since the low level mostly deals with information messages or unlikely errors.

image2.png

</p>

For the 10 projects checked, the analyzer issued 16 high-level warnings, 75% of which correctly pointed to real defects in the code, and 18 medium-level warnings, 39% of which correctly pointed to real defects in the code. The code is definitely of high quality, as the average ratio of errors found to the number of LOC is one error per 2000 lines of code, which is a good result.

Now that we are done with the statistics, let's see what errors and typos we managed to find.



Incorrect regular expression

V3057 Invalid regular expression pattern in constructor. Inspect the first argument. AssetBundleDemo ExecuteInternalMono.cs 48

private static readonly Regex UnsafeCharsWindows = 
  new Regex("[^A-Za-z0-9\\_\\-\\.\\:\\,\\/\\@\\\\]"); // <=

When trying to instantiate the Regex class using this pattern, a System.ArgumentException exception will be thrown with the following message:

parsing \"[^A-Za-z0-9\\_\\-\\.\\:\\,\\/\\@\\]\" -
Unrecognized escape sequence '

This message indicates that the pattern used is incorrect and that the Regex class cannot be instantiated using it. The programmer must have made a mistake when designing the pattern.



Possible access to an object using a null reference


V3080 Possible null dereference. Consider inspecting 't.staticFieldBytes'. MemoryProfiller CrawledDataUnpacker.cs 20

.... = packedSnapshot.typeDescriptions.Where(t =&gt; 
  t.staticFieldBytes != null & t.staticFieldBytes.Length > 0 // <=
)....

An object is accessed after a null check. However, it is accessed regardless of the check result, which may cause throwing NullReferenceException. The programmer must have intended to use the conditional AND operator (&&) but made a typo and wrote the logical AND operator (&) instead.



Accessing an object before a null check


V3095 The 'uv2.gameObject' object was used before it was verified against null. Check lines: 1719, 1731. UnityEngine.Networking NetworkServer.cs 1719

if (uv2.gameObject.hideFlags == HideFlags.NotEditable || 
    uv2.gameObject.hideFlags == HideFlags.HideAndDontSave)
  continue;
....
if (uv2.gameObject == null)
  continue;

An object is first accessed and only then is it tested for null. If the reference to the object is found to be null, we are almost sure to get NullReferenceException before reaching the check.

Besides that error, the analyzer found 2 more similar ones:

  • V3095 The 'm_HorizontalScrollbarRect' object was used before it was verified against null. Check lines: 214, 220. UnityEngine.UI ScrollRect.cs 214
  • V3095 The 'm_VerticalScrollbarRect' object was used before it was verified against null. Check lines: 215, 221. UnityEngine.UI ScrollRect.cs 215


Two if statements with the same condition and the unconditional return statement in the then block

It's quite an interesting issue, which is a perfect illustration of how mighty copy-paste is; a classical example of a typo.

V3021 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains method return. This means that the second 'if' statement is senseless UnityEngine.UI StencilMaterial.cs 64

if (!baseMat.HasProperty("_StencilReadMask"))
{
  Debug.LogWarning(".... _StencilReadMask property", baseMat);
  return baseMat;
}
if (!baseMat.HasProperty("_StencilReadMask")) // <=
{
  Debug.LogWarning(".... _StencilWriteMask property", baseMat);
  return baseMat;
}

The programmer must have copy-pasted a code fragment but forgot to change the condition.

Based on this typo, I'd say that the second check was meant to look like this:

if (!baseMat.HasProperty("_StencilWriteMask"))


Instantiating an exception class without further using the instance

V3006 The object was created but it is not being used. The 'throw' keyword could be missing: throw new ApplicationException(FOO). AssetBundleDemo AssetBundleManager.cs 446

if (bundleBaseDownloadingURL.ToLower().StartsWith("odr://"))
{
#if ENABLE_IOS_ON_DEMAND_RESOURCES
  Log(LogType.Info, "Requesting bundle " + ....);
  m_InProgressOperations.Add(
    new AssetBundleDownloadFromODROperation(assetBundleName)
  );
#else
  new ApplicationException("Can't load bundle " + ....); // <=
#endif
}

Class ApplicationException is created but not used in any way. The programmer must have wanted an exception to be thrown but forgot to add the throw keyword when forming the exception.



Unused arguments in a string formatting method

As we all know, the number of {N} format items used for string formatting must correspond to the number of arguments passed to the method.

V3025 Incorrect format. A different number of format items is expected while calling 'WriteLine' function. Arguments not used: port. AssetBundleDemo AssetBundleServer.cs 59

Console.WriteLine("Starting up asset bundle server.", port); // &lt;=
Console.WriteLine("Port: {0}", port);
Console.WriteLine("Directory: {0}", basePath);

Judging by the logic of this code, it seems that the programmer forgot to remove the argument in the first line. This typo is not critical from the technical viewpoint and it won't cause any error, but it still carries no meaning.

A loop that may become infinite under certain conditions

V3032 Waiting on this expression is unreliable, as compiler may optimize some of the variables. Use volatile variable(s) or synchronization primitives to avoid this. AssetBundleDemo AssetBundleServer.cs 16

Process masterProcess = Process.GetProcessById((int)processID);
while (masterProcess == null || !masterProcess.HasExited) // <=
{
  Thread.Sleep(1000);
}

The programmer must have intended the loop to iterate until completion of an external process but didn't take into account the fact that the masterProcess variable might initially have the value null if the process was not found, which would cause an infinite loop. To make this algorithm work properly, you need to access the process using its identifier at each iteration:

while (true) {
  Process masterProcess = Process.GetProcessById((int)processID);
  if (masterProcess == null || masterProcess.HasExited) // <=
    break;
  Thread.Sleep(1000);
}


Unsafe event initialization

The analyzer detected a potentially unsafe call to an event handler, which may result in throwing NullReferenceException.

V3083 Unsafe invocation of event 'unload', NullReferenceException is possible. Consider assigning event to a local variable before invoking it. AssetBundleDemo AssetBundleManager.cs 47

internal void OnUnload()
{
  m_AssetBundle.Unload(false);
  if (unload != null)
    unload(); // <=
}

In this code, the unload field is tested for null and then this event is called. The null check allows you to avoid throwing an exception in case the event has no subscribers at moment when it is being called.

Imagine, however, that the event has one subscriber. At the point between the null check and the call to the event handler, the subscriber may unsubscribe from the event, for example, in another thread. To protect your code in this situation, you can fix it in the following way:

internal void OnUnload()
{
  m_AssetBundle.Unload(false);
  unload?.Invoke(); // <=
}

This solution will help you make sure that testing of the event for null and calling to its handler will be executed as one statement, making the event call safe.



Part of a logical expression always true or false


V3063 A part of conditional expression is always false: connId < 0. UnityEngine.Networking ConnectionArray.cs 59

public NetworkConnection Get(int connId)
{
  if (connId < 0)
  {
    return m_LocalConnections[Mathf.Abs(connId) - 1];
  }

  if (connId < 0 || connId > m_Connections.Count) // <=
  {
    ...
    return null;
  }

  return m_Connections[connId];
}

The connId < 0 expression will always evaluate to false the second time it is checked in the get function, since the function always terminates after the first check. Therefore, evaluating this expression for the second time does not make sense.

The analyzer found one more similar error.

public bool isServer
{
  get
  {
    if (!m_IsServer)
    {
        return false;
    }

    return NetworkServer.active && m_IsServer; // <=
  }
}

You surely know that this property can be easily simplified to the following:

public bool isServer
{
  get
  {
    return m_IsServer && NetworkServer.active;
  }
}

Besides these two examples, there are 6 more issues of that kind:

  • V3022 Expression 'm_Peers == null' is always false. UnityEngine.Networking NetworkMigrationManager.cs 710
  • V3022 Expression 'uv2.gameObject == null' is always false. UnityEngine.Networking NetworkServer.cs 1731
  • V3022 Expression 'newEnterTarget != null' is always true. UnityEngine.UI BaseInputModule.cs 147
  • V3022 Expression 'pointerEvent.pointerDrag != null' is always false. UnityEngine.UI TouchInputModule.cs 227
  • V3063 A part of conditional expression is always true: currentTest != null. UnityTestTools TestRunner.cs 237
  • V3063 A part of conditional expression is always false: connId < 0. UnityEngine.Networking ConnectionArray.cs 86


Conclusion


Just like any other project, this one contains a number of errors and typos. As you have probably noticed, PVS-Studio is especially good at catching typos.

You are also welcome to try our static analyzer with your own or someone else's project in C/C++/C#.

Thank you all for reading! May your code stay bugless!

Rapid Prototyping Tip: Defining data as code files

$
0
0



When you're trying to churn out a game fast, it might make more sense to define your items, texts or conversation in code files. Here's how to do it, an example from my current project.

On my first two games (Postmortem and Karaski) I set up convenient tools for managing my data. Here is an example of my dialogue trees:

karaski129-indie-game-dialogue-sneaking-

I used the free yEd graph editor and a bit of custom coding to import the XML data into my game (read about it here!). I also parsed simple text files for all the in-game text items like newspapers. The system worked great, but took a while to set up and debug.

As I started on my current experimental story/adventure game HEADLINER, I was embracing a more rapid-prototyping mindset. I did not want to come up with new fancy data structures and write parsers yet again in JavaScript. And it occurred to me - why not define my data directly in code?

Simple strings in newspapers


The "proper" way to read the newspaper data strings would be to create a neatly laid out JSON file, parse that, and create appropriate objects. But I skipped the middle-man, and instead defined them in a separate JavaScript file directly as objects and arrays. Here's what it looks like:

headliner-newspaper-files-800x495.jpg

It's barely a little bit of extra syntax fluff, and I had instantly-accessible data structure I could reference by simply including the JS file! I didn't need to do any "loading" or parsing either. I also dded a few scripting members (like the ReqApproved) that I'd just check each game day to see if given paper should show or not.

Conditional branching in conversations


Previously I used graphs for flexibility, and many RPG games used flat text files or the (godawfully difficult to follow) trees, like in Neverwinter Nights.

neverwinter-nights-dialogue-editor-800x6

Since I didn't plan on too much branching but wanted conditional responses, I'd need to set up some if/else conditions, a way to reference world-state variables, and a way to set them. Meaning: coding a parser and implementing some form of scripting.

Again, I realized - wait, why write code that interprets code? I can just write code directly. And so my conversations became yet another Javascript file. I wrote a few helper functions for setting what the NPC says or getting tokens like Player Name, and boom!

headliner-conversation-files-800x498.jpg

No need to parse code or tokens, since it's automatically executed. Yes, it does require more syntax fluff, but it's pretty manageable once you get the hang of the weird structure and helper functions (like SetThread() or AddResponse()).

Entities and NPCs


To create a world brimming with lively creatures and lucrative loot, most games have some sort of editor to visually place those in 3d or 2d space. The game then loads these "scenes" at startup and creates all the entities as defined. Parsing, parsing, parsing...

11_valve_hammer_editor1b-800x640.jpg

I didn't have that with Phaser and JavaScript (though I recommend checking out the Phaser Editor if you'd like an out of the box solution), and each game day would vary a lot depending on numerous conditions. Like before, I differed to the code itself:

headliner-npc-spawning-code-800x496.jpg

With a few helper functions (Spawn two NPCS with a dialogue, spawn a group of protesters, spawn a rioter who runs and throws a molotov etc.) and pre-defined constants for locations markers, I could quickly create my game world. Because it is code, I could customize each game day by checking for variables, event triggers or randomizing results. All my changes showed up instantly without any exporting/parsing from a 3rd party editor.

This solution is not for everyone


An important caveat needs to be made. This data-in-code approach is really meant for prototyping or small games where you know the amount of data will always be manageable and everyone working with it knows the basics of coding.

It can be a huge huge time saver when used properly, but a hindrance on bigger projects with more complex structures or delineated team roles. There is a reason why I set up a whole system to import XML data from a 3rd party graph editor with scripting support for conversations in my first two games. Over the two years of production, the ability to visually edit the dialogue was very important, and it allowed my non-tech savvy writers to work with it as well. Trying to define THIS purely in code would have been a disaster:

karas64-indie-game-dialogue-tree-800x372

So, as with any online game dev tutorial, exercise a bit of common sense. It's just a specific tool meant to be used in specific situations.

Curious about my games?


The project that inspired this blog is HEADLINER, story-driven game where you control public opinion and (potentially) start a riot. Check out the official page here. Better yet, why not follow me on Facebook or Twitter for updates and other game dev nuggets of wisdom?

headliner-32-riot-breaks-out.gif

headliner-40-newsticker.gif</p>

Anomalies in X-Ray Engine

$
0
0

The X-Ray Engine is a game engine, used in the S.T.A.L.K.E.R. game series. Its code was made public in September 16 2014, and since then, STALKER fans continue its development. A large project size, and a huge number of bugs in the games, gives us a wonderful chance to show what PVS-Studio is capable of.

 

image1.png


Introduction


X-Ray was created by a Ukrainian company, GSC GameWorld, for the game S.T.A.L.K.E.R.: Shadow of Chernobyl. This engine has a renderer supporting DirectX 8.1/9.0c/10/10.1/11, physical and sound engines, multiplayer, and an artificial intelligence system - A-Life. Later, the company was about to create a 2.0 version for their new game, but development was discontinued, and the source code was put out for public access.


This project is easily built with all of its dependencies in Visual Studio 2015. To do the analysis we used the engine source code 1.6v, from a repository on GitHub and PVS-Studio 6.05 static code analyze, which can be downloaded from this link.


Copy-paste


Let's start with errors related to copying code. The way they get to the code is usually the same: the code was copied, parts of variables were changed, and some remained forgotten. Such errors can quickly spread in the code base, and are very easy to overlook without a static code analyzer.


image2.png


MxMatrix& MxQuadric::homogeneous(MxMatrix& H) const
{
  ....
  unsigned int i, j;

  for(i=0; i<A.dim(); i++)  for(j=0; j<A.dim(); i++)
    H(i,j) = A(i,j);
  ....
}

PVS-Studio warning: V533 It is likely that the wrong variable is being incremented inside the 'for' operator. Consider reviewing 'i'. mxqmetric.cpp 76


The analyzer detected that in the nested for loop, the variable i gets incremented, but another variable - j gets checked, which leads to an infinite loop. Most likely, a programmer just forgot to change it.


void CBaseMonster::settings_read(CInifile const * ini,
                                 LPCSTR section,
                                 SMonsterSettings &data)
{
  ....
  if (ini->line_exist(ppi_section,"color_base"))
    sscanf(ini->r_string(ppi_section,"color_base"), "%f,%f,%f",
           &data.m_attack_effector.ppi.color_base.r,
           &data.m_attack_effector.ppi.color_base.g,
           &data.m_attack_effector.ppi.color_base.b);
  if (ini->line_exist(ppi_section,"color_base"))
    sscanf(ini->r_string(ppi_section,"color_gray"), "%f,%f,%f",
           &data.m_attack_effector.ppi.color_gray.r,
           &data.m_attack_effector.ppi.color_gray.g,
           &data.m_attack_effector.ppi.color_gray.b);
  if (ini->line_exist(ppi_section,"color_base"))
    sscanf(ini->r_string(ppi_section,"color_add"), "%f,%f,%f",
           &data.m_attack_effector.ppi.color_add.r,
           &data.m_attack_effector.ppi.color_add.g,
           &data.m_attack_effector.ppi.color_add.b);
  ....
}

PVS-Studio warnings:

  • V581 The conditional expressions of the 'if' operators situated alongside each other are identical. Check lines: 445, 447. base_monster_startup.cpp 447
  • V581 The conditional expressions of the 'if' operators situated alongside each other are identical. Check lines: 447, 449. base_monster_startup.cpp 449

In this fragment we see several conditional expressions in a row. Obviously, we need to replace the color_base with color_gray and color_add according to the code in the if branch.


/* process a single statement */
static void ProcessStatement(char *buff, int len)
{
  ....
  if (strncmp(buff,"\\pauthr\\",8) == 0)
  {
    ProcessPlayerAuth(buff, len);
  } else if (strncmp(buff,"\\getpidr\\",9) == 0)
  {
    ProcessGetPid(buff, len);
  } else if (strncmp(buff,"\\getpidr\\",9) == 0)
  {
    ProcessGetPid(buff, len);
  } else if (strncmp(buff,"\\getpdr\\",8) == 0)
  {
    ProcessGetData(buff, len);
  } else if (strncmp(buff,"\\setpdr\\",8) == 0)
  {
    ProcessSetData(buff, len);
  }
}

PVS-Studio warning: V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 1502, 1505. gstats.c 1502


As in the previous example, two similar conditions are used here (strncmp(buff,"\\getpidr\\",9) == 0). It's hard to say for sure whether this is a mistake, or simply unreachable code, but it's worth revising for sure. Perhaps we should have blocks with getpidr/setpidr by analogy with getpdr/setpdr.


class RGBAMipMappedCubeMap
{
  ....
  size_t height() const
  {
    return cubeFaces[0].height();
  }

  size_t width() const
  {
    return cubeFaces[0].height();
  }
  ....
};

PVS-Studio warning: V524 It is odd that the body of 'width' function is fully equivalent to the body of 'height' function. tpixel.h 1090


Methods height() and width() have the same body. Bearing in mind that we evaluate faces of a cube here, perhaps there is no error. But it's better to rewrite the method width() in the following way:


size_t width() const
{
  return cubeFaces[0].width();
}

Improper use of C++


C++ is a wonderful language that provides the programmer with many a possibility... to shoot yourself in the foot in a multitude of the cruelest ways. Undefined behavior, memory leaks, and of course, typos. And that's what will be discussed in this section.


image3.png


template <class T>
struct _matrix33
{
public:
  typedef _matrix33<T>Self;
  typedef Self& SelfRef;
  ....
  IC SelfRef sMTxV(Tvector& R, float s1, const Tvector& V1) const
  {
    R.x = s1*(m[0][0] * V1.x + m[1][0] * V1.y + m[2][0] * V1.z);
    R.y = s1*(m[0][1] * V1.x + m[1][1] * V1.y + m[2][1] * V1.z);
    R.z = s1*(m[0][2] * V1.x + m[1][2] * V1.y + m[2][2] * V1.z);
  }
  ....
}

PVS-Studio warning: V591 Non-void function should return a value. _matrix33.h 435


At the end of the method there is no return *this. According to the standard, it will lead to undefined behavior. As the return value is a reference, it will probably lead to a program crash, upon attempting to access the return value.


ETOOLS_API int __stdcall ogg_enc(....)
{
  ....
  FILE *in, *out    = NULL;
  ....
  input_format    *format;
  ....
  in = fopen(in_fn, "rb");

  if(in == NULL)  return 0;

  format = open_audio_file(in, &enc_opts);
  if(!format){
    fclose(in);
    return 0;
  };

  out = fopen(out_fn, "wb");
  if(out == NULL){
    fclose(out);
    return 0;
  }
  ....
}

PVS-Studio warning: V575 The null pointer is passed into 'fclose' function. Inspect the first argument. ogg_enc.cpp 47


Quite an interesting example. The analyzer detected that the argument in the fclose is nullptr, which makes the function call meaningless. Presumably, the stream in was to be closed.


void NVI_Image::ABGR8_To_ARGB8()
{
  // swaps RGB for all pixels
  assert(IsDataValid());
  assert(GetBytesPerPixel() == 4);
  UINT hxw = GetNumPixels();
  for (UINT i = 0; i < hxw; i++)
  {
    DWORD col;
    GetPixel_ARGB8(&col, i);
    DWORD a = (col >> 24) && 0x000000FF;
    DWORD b = (col >> 16) && 0x000000FF;
    DWORD g = (col >> 8)  && 0x000000FF;
    DWORD r = (col >> 0)  && 0x000000FF;
    col = (a << 24) | (r << 16) | (g << 8) | b;
    SetPixel_ARGB8(i, col);
  }
}

PVS-Studio warnings:


  • V560 A part of conditional expression is always true: 0x000000FF. nvi_image.cpp 170
  • V560 A part of conditional expression is always true: 0x000000FF. nvi_image.cpp 171
  • V560 A part of conditional expression is always true: 0x000000FF. nvi_image.cpp 172
  • V560 A part of conditional expression is always true: 0x000000FF. nvi_image.cpp 173

In this fragment, we see that logical and bitwise operations get confused. The result will not be what the programmer expected: col will be always 0x01010101 regardless of the input data.


Correct variant:


DWORD a = (col >> 24) & 0x000000FF;
DWORD b = (col >> 16) & 0x000000FF;
DWORD g = (col >> 8)  & 0x000000FF;
DWORD r = (col >> 0)  & 0x000000FF;


Another example of strange code:

VertexCache::VertexCache()
{
  VertexCache(16);
}

PVS-Studio warning: V603 The object was created but it is not being used. If you wish to call constructor, 'this->VertexCache::VertexCache(....)' should be used. vertexcache.cpp 6


Instead of calling a constructor from another, a new object of VertexCache gets created, and then destroyed, to initialize the instance. As a result, the members of the created object remain uninitialized.


BOOL CActor::net_Spawn(CSE_Abstract* DC)
{
  ....
  m_States.empty();
  ....
}

PVS-Studio warning: V530 The return value of function 'empty' is required to be utilized. actor_network.cpp 657


The analyzer warns that the value returned by the function is not used. It seems that the programmer confused the methods empty() and clear(): the empty() does not clear the array, but checks whether it is empty or not.


Such errors are quite common in various projects. The thing is that the name empty() is not very obvious: some view it as an action - deletion. To avoid such ambiguity, it's a good idea to add has, or is to the beginning of the method: it would be harder to confuse isEmpty() with clear().


A similar warning:


V530 The return value of function 'unique' is required to be utilized. uidragdroplistex.cpp 780


size_t xrDebug::BuildStackTrace(EXCEPTION_POINTERS* exPtrs,
                                char *buffer,
                                size_t capacity,
                                size_t lineCapacity)
{
  memset(buffer, capacity*lineCapacity, 0);
  ....
}

PVS-Studio warning: V575 The 'memset' function processes '0' elements. Inspect the third argument. xrdebug.cpp 104


During the memset call the arguments got mixed up, and as a result the buffer isn't set to zero, as it was originally intended. This error can live in a project for quite a long time, because it is very difficult to detect. In such cases a static analyzer is of great help.


The correct use of memset:


memset(buffer, 0, capacity*lineCapacity);

The following error is connected with incorrectly formed logical expression.


void configs_dumper::dumper_thread(void* my_ptr)
{
  ....
  DWORD wait_result = WaitForSingleObject(
             this_ptr->m_make_start_event, INFINITE);
  while ( wait_result != WAIT_ABANDONED) ||
         (wait_result != WAIT_FAILED))
  ....
}

PVS-Studio warning: V547 Expression is always true. Probably the '&&' operator should be used here. configs_dumper.cpp 262


The expression 'x != a || x != b' is always true. Most likely, && was meant to be here instead of || operator.


More details on the topic of errors in logical expressions can be found in the article "Logical Expressions in C/C++. Mistakes Made by Professionals".


void SBoneProtections::reload(const shared_str& bone_sect,
                              IKinematics* kinematics)
{
  ....
  CInifile::Sect &protections = pSettings->r_section(bone_sect);
  for (CInifile::SectCIt i=protections.Data.begin();
       protections.Data.end() != i; ++i)
  {
    string256 buffer;
    BoneProtection BP;
    ....
    BP.BonePassBullet = (BOOL) (
                atoi( _GetItem(i->second.c_str(), 2, buffer) )>0.5f);
    ....
  }
}

PVS-Studio warning: V674 The '0.5f' literal of the 'float' type is compared to a value of the 'int' type. boneprotections.cpp 54


The analyzer detected an integer comparison with a real constant. Perhaps, by analogy, the atof function, not atoi was supposed to be here, but in any case, this comparison should be rewritten so that it doesn't look suspicious. However, only the author of this code can say for sure if this code is erroneous or not.


class IGameObject :
  public virtual IFactoryObject,
  public virtual ISpatial,
  public virtual ISheduled,
  public virtual IRenderable,
  public virtual ICollidable
{
public:
  ....
  virtual u16 ID() const = 0;
  ....
}

BOOL CBulletManager::test_callback(
  const collide::ray_defs& rd,
  IGameObject* object,
  LPVOID params)
{
  bullet_test_callback_data* pData =
             (bullet_test_callback_data*)params;
  SBullet* bullet = pData->pBullet;

  if( (object->ID() == bullet->parent_id) &&
      (bullet->fly_dist<parent_ignore_distance) &&
      (!bullet->flags.ricochet_was)) return FALSE;

  BOOL bRes = TRUE;
  if (object){
    ....
  }

  return bRes;
}

PVS-Studio warning: V595 The 'object' pointer was utilized before it was verified against nullptr. Check lines: 42, 47. level_bullet_manager_firetrace.cpp 42


The verification of the object pointer against nullptr occurs after the object->ID() is dereferenced. In the case where object is nullptr, the program will crash.


#ifdef _EDITOR
BOOL WINAPI DllEntryPoint(....)
#else
BOOL WINAPI DllMain(....)
#endif
{
  switch (ul_reason_for_call)
  {
  ....
  case DLL_THREAD_ATTACH:
    if (!strstr(GetCommandLine(), "-editor"))
      CoInitializeEx(NULL, COINIT_MULTITHREADED);
    timeBeginPeriod(1);
    break;
  ....
  }
  return TRUE;
}

PVS-Studio warning: V718 The 'CoInitializeEx' function should not be called from 'DllMain' function. xrcore.cpp 205


In the DllMain, we cannot use a part of WinAPI function, including CoInitializeEx. You may read documentation on MSDN to be clear on this. There is probably no definite answer of how to rewrite this function, but we should understand that this situation is really dangerous, because it can cause thread deadlock, or a program crash.


Precedence errors


int sgetI1( unsigned char **bp )
{
  int i;

  if ( flen == FLEN_ERROR ) return 0;
  i = **bp;
  if ( i > 127 ) i -= 256;
  flen += 1;
  *bp++;
  return i;
}

PVS-Studio warning: V532 Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'. lwio.c 316

The error is related to increment usage. To make this expression more clear, let's rewrite it, including the brackets:


*(bp++);

So we'll have a shift not of the content by bp address, but the pointer itself, which is meaningless in this context. Further on in the code there are fragments of *bp += N type, made me think that this is an error.


Placing parentheses could help to avoid this error and make the evaluation more clear. Also a good practice is to use const for arguments that should not changed.


Similar warnings:


  • V532 Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'. lwio.c 354
  • V532 Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'. lwob.c 80

void CHitMemoryManager::load    (IReader &packet)
{
  ....
  if (!spawn_callback || !spawn_callback->m_object_callback)
    if(!g_dedicated_server)
      Level().client_spawn_manager().add(
          delayed_object.m_object_id,m_object->ID(),callback);
#ifdef DEBUG
  else {
    if (spawn_callback && spawn_callback->m_object_callback) {
      VERIFY(spawn_callback->m_object_callback == callback);
    }
  }
#endif // DEBUG
}

PVS-Studio warning: V563 It is possible that this 'else' branch must apply to the previous 'if' statement. hit_memory_manager.cpp 368


In this fragment the else branch is related to the second if because of its right-associativity, which doesn't coincide with the code formatting. Fortunately, this doesn't affect the work of the program in any way, but nevertheless, it can make the debugging and testing process much more complicated.


So the recommendation is simple - put curly brackets in more, or less, complex branches.

void HUD_SOUND_ITEM::PlaySound(HUD_SOUND_ITEM&     hud_snd,
                                const Fvector&     position,
                                const IGameObject* parent,
                                bool               b_hud_mode,
                                bool               looped,
                                u8                 index)
{
  ....
  hud_snd.m_activeSnd->snd.set_volume(
    hud_snd.m_activeSnd->volume * b_hud_mode?psHUDSoundVolume:1.0f);
}

PVS-Studio warning: V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '*' operator. hudsound.cpp 108


A ternary conditional operator has a lower precedence than the multiplication operator, that's why the order of operations will be as follows:


(hud_snd.m_activeSnd->volume * b_hud_mode)?psHUDSoundVolume:1.0f

Apparently, the correct code should be the following:


hud_snd.m_activeSnd->volume * (b_hud_mode?psHUDSoundVolume:1.0f)

Expressions containing a ternary operator, several if-else branches, or operations AND/OR, are all cases where it's better to put extra brackets.


Similar warnings:


  • V502 Perhaps the '?:' operator works in a different way than was expected. The '?:' operator has a lower priority than the '+' operator. uihudstateswnd.cpp 487
  • V502 Perhaps the '?:' operator works in a different way than was expected. The '?:' operator has a lower priority than the '+' operator. uicellcustomitems.cpp 106

Unnecessary comparisons


void CDestroyablePhysicsObject::OnChangeVisual()
{
  if (m_pPhysicsShell){
    if(m_pPhysicsShell)m_pPhysicsShell->Deactivate();
    ....
  }
  ....
}

PVS-Studio warning: V571 Recurring check. The 'if (m_pPhysicsShell)' condition was already verified in line 32. destroyablephysicsobject.cpp 33


In this case m_pPhysicsShell gets checked twice. Most likely, the second check is redundant.


void CSE_ALifeItemPDA::STATE_Read(NET_Packet &tNetPacket,
                                  u16 size)
{
  ....
  if (m_wVersion > 89)

  if ( (m_wVersion > 89)&&(m_wVersion < 98)  )
  {
    ....
  }else{
    ....
  }
}

PVS-Studio warning: V571 Recurring check. The 'm_wVersion > 89' condition was already verified in line 987. xrserver_objects_alife_items.cpp 989


This code is very strange. In this fragment we see that a programmer either forgot an expression after if (m_wVersion > 89), or a whole series of else-if. This method requires a more scrutinizing review.


void ELogCallback(void *context, LPCSTR txt)
{
  ....
  bool bDlg = ('#'==txt[0])||((0!=txt[1])&&('#'==txt[1]));
  if (bDlg){
    int mt = ('!'==txt[0])||((0!=txt[1])&&('!'==txt[1]))?1:0;
    ....
  }
}

PVS-Studio warnings:


  • V590 Consider inspecting the '(0 != txt[1]) && ('#' == txt[1])' expression. The expression is excessive or contains a misprint. elog.cpp 29
  • V590 Consider inspecting the '(0 != txt[1]) && ('!' == txt[1])' expression. The expression is excessive or contains a misprint. elog.cpp 31

The check (0 != txt[1]) is excessive in the expressions of initialization of the bDlg and mt variables. If we omit it, the expression will be much easier to read.


bool bDlg = ('#'==txt[0])||('#'==txt[1]);
int mt = ('!'==txt[0])||('!'==txt[1])?1:0;

Errors in data types


image4.png


float CRenderTarget::im_noise_time;

CRenderTarget::CRenderTarget()
{
  ....
  param_blur           = 0.f;
  param_gray           = 0.f;
  param_noise          = 0.f;
  param_duality_h      = 0.f;
  param_duality_v      = 0.f;
  param_noise_fps      = 25.f;
  param_noise_scale    = 1.f;

  im_noise_time        = 1/100;
  im_noise_shift_w     = 0;
  im_noise_shift_h     = 0;
  ....
}

PVS-Studio warning: V636 The '1 / 100' expression was implicitly cast from 'int' type to 'float' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. gl_rendertarget.cpp 245


The value of the expression 1/100 is 0, since it is an operation of integer division. To get the value 0.01f, we need to use a real literal and rewrite the expression: 1/100.0f. Although, there is still a chance that such behavior was meant to be here, and there is no error.


CSpaceRestriction::merge(....) const
{
  ....
  LPSTR S = xr_alloc<char>(acc_length);

  for ( ; I != E; ++I)
    temp = strconcat(sizeof(S),S,*temp,",",*(*I)->name());
  ....
}

PVS-Studio warning: V579 The strconcat function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the first argument. space_restriction.cpp 201


The function strconcat gets the buffer size as the first parameter. S buffer is declared as a LPSTR, i.e. as a pointer to a string. sizeof(S) will be equal to the pointer size in bites, namely sizeof(char *), not the number of symbols in the string. To evaluate the length we should use strlen(S).


class XRCDB_API MODEL
{
  ....
  u32 status; // 0=ready, 1=init, 2=building
  ....
}

void MODEL::build (Fvector* V, int Vcnt, TRI* T, int Tcnt,
                   build_callback* bc, void* bcp)
{
  ....
  BTHREAD_params P = { this, V, Vcnt, T, Tcnt, bc, bcp };
  thread_spawn(build_thread,"CDB-construction",0,&P);
  while (S_INIT == status) Sleep(5);
  ....
}

PVS-Studio warning: V712 Be advised that compiler may delete this cycle, or make it infinite. Use volatile variable(s) or synchronization primitives to avoid this. xrcdb.cpp 100


The compiler can remove the check S_INIT == status as a measure of optimization, because the status variable isn't modified in the loop. To avoid such behavior, we should use volatile variables, or types of data synchronization between the threads.


Similar warnings:


  • V712 Be advised that compiler may delete this cycle or make it infinite. Use volatile variable(s) or synchronization primitives to avoid this. levelcompilerloggerwindow.cpp 23
  • V712 Be advised that compiler may delete this cycle or make it infinite. Use volatile variable(s) or synchronization primitives to avoid this. levelcompilerloggerwindow.cpp 232

void CAI_Rat::UpdateCL()
{
  ....
  if (!Useful()) {
    inherited::UpdateCL        ();
    Exec_Look                  (Device.fTimeDelta);

    CMonsterSquad *squad = monster_squad().get_squad(this);

    if (squad && ((squad->GetLeader() != this &&
                  !squad->GetLeader()->g_Alive()) ||
                 squad->get_index(this) == u32(-1)))
      squad->SetLeader(this);

    ....
  }
  ....
}

PVS-Studio warning: V547 Expression 'squad->get_index(this) == u32(- 1)' is always false. The value range of unsigned char type: [0, 255]. ai_rat.cpp 480


To understand why this expression is always false, let's evaluate values of individual operands. u32(-1) is 0xFFFFFFFF or 4294967295. The type, returned by the method squad->get_index(....), is u8, thus its maximum value is 0xFF or 255, which is strictly less than u32(-1). Consequently, the result of such comparison will always be false. This code can be easily fixed, if we change the data type to u8:


squad-&gt;get_index(this) == u8(-1)

The same diagnostic is triggered for redundant comparisons of unsigned variables.


namespace ALife
{
  typedef u64 _TIME_ID;
}
ALife::_TIME_ID CScriptActionCondition::m_tLifeTime;

IC bool CScriptEntityAction::CheckIfTimeOver()
{
  return((m_tActionCondition.m_tLifeTime >= 0) &&
         ((m_tActionCondition.m_tStartTime +
           m_tActionCondition.m_tLifeTime) < Device.dwTimeGlobal));
}

PVS-Studio warning: V547 Expression 'm_tActionCondition.m_tLifeTime >= 0' is always true. Unsigned type value is always >= 0. script_entity_action_inline.h 115


The variable m_tLifeTime is unsigned, thus it is always greater than or equal to zero. It's up to the developer to say if it is an excessive check, or an error in the logic of the program.

The same warning:


V547 Expression 'm_tActionCondition.m_tLifeTime < 0' is always false. Unsigned type value is never < 0. script_entity_action_inline.h 143


ObjectFactory::ServerObjectBaseClass *
CObjectItemScript::server_object    (LPCSTR section) const
{
  ObjectFactory::ServerObjectBaseClass *object = nullptr;

  try {
    object = m_server_creator(section);
  }
  catch(std::exception e) {
    Msg("Exception [%s] raised while creating server object from "
        "section [%s]", e.what(),section);
    return        (0);
  }
  ....
}

PVS-Studio warning: V746 Type slicing. An exception should be caught by reference rather than by value. object_item_script.cpp 39


The function std::exception::what() is virtual, and can be overridden in inherited classes. In this example, the exception is caught by value, hence the class instance will be copied, and all information about the polymorphic type will be lost. Accessing what() is meaningless in this case. The exception should be caught by reference:


catch(const std::exception& e) {

Miscelleneous


void compute_cover_value (....)
{
  ....
  float    value    [8];
  ....
  if (value[0] < .999f) {
    value[0] = value[0];
  }
  ....
}

PVS-Studio warning: V570 The 'value[0]' variable is assigned to itself. compiler_cover.cpp 260


The variable value[0] is assigned to itself. It's unclear, why this should be. Perhaps a different value should be assigned to it.


void CActor::g_SetSprintAnimation(u32 mstate_rl,
                                  MotionID &head,
                                  MotionID &torso,
                                  MotionID &legs)
{
  SActorSprintState& sprint = m_anims->m_sprint;

  bool jump = (mstate_rl&mcFall)     ||
              (mstate_rl&mcLanding)  ||
              (mstate_rl&mcLanding)  ||
              (mstate_rl&mcLanding2) ||
              (mstate_rl&mcJump);
  ....
}

PVS-Studio warning: V501 There are identical sub-expressions '(mstate_rl & mcLanding)' to the left and to the right of the '||' operator. actoranimation.cpp 290


Most probably, we have an extra check mstate_rl & mcLanding, but quite often such warnings indicate an error in the logic and enum values that weren't considered.


Similar warnings:


  • V501 There are identical sub-expressions 'HudItemData()' to the left and to the right of the '&&' operator. huditem.cpp 338
  • V501 There are identical sub-expressions 'list_idx == e_outfit' to the left and to the right of the '||' operator. uimptradewnd_misc.cpp 392
  • V501 There are identical sub-expressions '(D3DFMT_UNKNOWN == fTarget)' to the left and to the right of the '||' operator. hw.cpp 312

RELATION_REGISTRY::RELATION_MAP_SPOTS::RELATION_MAP_SPOTS()
{
  ....
  spot_names[ALife::eRelationTypeWorstEnemy] = "enemy_location";
  spot_names[ALife::eRelationTypeWorstEnemy] = "enemy_location";
  ....
}

PVS-Studio warning: V519 The variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 57, 58. relation_registry.cpp 58


The analyzer detected that the same variable is assigned with two values in a row. In this case, it seems that it's just dead code, and it should be removed.


void safe_verify(....)
{
  ....
  printf("FATAL ERROR (%s): failed to verify data\n");
  ....
}

PVS-Studio warning: V576 Incorrect format. A different number of actual arguments is expected while calling 'printf' function. Expected: 2. Present: 1. entry_point.cpp 41


An insufficient number or arguments is passed to the printpf function: the format '%s' shows that the pointer to a string should be passed. Such a situation can lead to a memory access error, and to program termination.


Conclusion


image5.png


The analysis of X-Ray Engine detected a good number of both redundant and suspicious code, as well as erroneous and hazardous moments. It is worth noting that a static analyzer is of great help in detecting errors during the early stages of development, which significantly simplifies the life of a programmer and provides more time for creation of new versions of your applications.


By Pavel Belikov

20 Best Unity Tips and Tricks for Game Developers

$
0
0

Unity is a popular game development platform. It is impressive regarding functionality, and also caters to the different game development requirement. Game developers can use Unity to create any type of game imaginable, from world-class RPG games to the most popular augmented reality game, Pokémon Go. With widespread use around the world, many developers use Livecoding to showcase their Unity skills and build an audience even before the game is released! Furthermore, many beginners utilize Unity to learn game development or game programming in general.

This is Daniel from Preston, United Kingdom, is working on his first game, Colour Me Crazy. You can check his development here.

<iframe src="https://www.livecoding.tv/thisisdaniel/videos/Rr7LD/embed" width="560" height="315" frameborder="0" allowfullscreen="true" scrolling="no"></iframe>

The real impact of Unity is more diverse as it is a perfect tool for both indie game developers as well as big teams working on a project. The ecosystem also helps Unity to sustain and grow in the right direction. Due to its complexity--it handles design, scripting, debugging, and other aspects of game development--Unity can be tough to manage.

Attached Image: image001.jpg

And that’s why we will go through the 20 Best Unity Tips and Tricks for Game Developers.




20 Best Unity Tips and Tricks for Game Developers


Before we start, understand that Unity is updated frequently, so the best tips listed here can differ from version to version. It is always a good idea to introspect and modify the tips according to your project and the version of Unity you are using. Let’s get started with the tips.




Five Workflow Improvement Tips


Workflow improvement tips are clearly aimed to help you improve your game development process. They will ensure that your project moves faster and in the right direction.

Let’s list the five best workflow improvement tips for Unity game developers:

1. Source control your work for maximum effectiveness - Make proper use of source control to improve your workflow. This will ensure that you don’t lose any of your work and also enable you to go back and forth to check what’s changed. You can serialize assets, use branching strategy to maximize control over production, and use sub-modules to maximize effective control of your source code.

2. Ensure that you decide on the scale of assets you are going to use in your project. The decision depends on the type of project you are working on, and the resolution the game is aimed to run at.

3. Always automate your build process to save time. Automating the build process will also ensure you can work on different game versions simultaneously, and also help make small changes now and then without going through the whole build process after every change.

4. Properly document your work. There can be no big disaster when you find yourself stuck over a piece of code that you wrote earlier but forgot to do code documentation. Also, documentation can help other teammates to better understand your work and collaborate on the project. You can use Livecoding for video code documentation. Read this to learn more.

5. Test scenes can become a bulky part of the project and they are useless after the project is done. To make sure that your project files don’t become bulky, keep test scenes separate from your code and delete them when the project is complete.


Five Coding Improvement Tips


Now let’s move to the most important part of game development, i.e., coding! Let’s get started.

1. Use namespace to your advantage. Namespace enables you to handle your code better as it allows you to avoid any classes with 3rd-party libraries and other classes within your code.

2. Coroutines is a great tool for solving many game problems, but they are equally hard to understand and debug. If you are using Coroutines, make sure you know what you are doing. Understand how they work in sequence and parallel mode, etc. Read more about coroutines here.

3. Assertions can be your best friend when finding bugs in your code. You can use the Unity.Assertions.Assert class for using assertions.

4. Extension methods are great for improving your syntax readability and management.

5. Localization should be done in separate files. Only keep one language in one file.


Five Debugging Improvement Tips


Debugging can be a tough nut to crack. With proper debugging, you can make your game release-ready and ensure that final game quality is maintained. Let’s get started with some debugging tips for Unity.

1. Master the debugging tools available in Unity. The debugging tools in Unity provide a lot of functionality, including functions that can effectively help you debug your game. Utilize functions such as Debug.Break, Debug.Log, Debug.DrawRay, and Debug.DrawLine to your advantage. The first two functions are used for understanding game state, whereas the last two functions helps you to visually debug the game. You can also use the debug visual inspector for locating runtime private fields.

2. As Unity doesn’t provide any special IDE to work with, you can opt to use any IDE for your development work. It is alshttps://unity3d.com/learn/tutorials/topics/scripting/debugging-unity-games-visual-studioo a good idea to master the IDE debugging features. Check out Visual Studio’s debugging article to learn more.

3. Unity has released many test tools. You can check them out and enhance your debugging methods. You can also check the tutorial for Unity test tools here. In addition, you can use the available tools for running scratchpad tests. Scratchpad tests are more conventional, and don’t require you to run a scene.

4. Console logging can be very useful if used in conjunction with an extension. For example, you can use Console Pro Enhanced to make your console amazing!

5. You need to debug differently to debug visual animation. The Visual debugger can help you do that by generating graphs over time. For example, you can use Monitor Components to do so.


Five Performance Improvement Tips


Optimizing your game optimization is necessary to make your game successful. The game could be great, but is still plagued with performance issues. And games with performance issues are not received well by the end users. To make sure your Unity game is well-optimized, try out the following tips.

1. Before you start optimizing your game, you need to find out where the performance issues are coming from. For starters, it is a good idea to find out whether or not it is coming from the GPU or the CPU. Finding the culprit will help you approach your optimization better, because both GPU and CPU have different performance optimization strategy.

2. Performance optimization is important, but don’t write code that is complex to read and hard to maintain. The decision should be made according to what performance gains you are getting for the change. If it is not substantial, ignore it. If the gains are high, keep them and do proper code documentation for others to go through the code.

3. Try to share object material in a scene to improve performance per scene.

4. Check if the game work better by lowering the game resolution. If that’s the case, use better materials and algorithm to make it work on a higher resolution.

5. Use profiler to understand and track performance problems. You can get started here.


Conclusion


Game development is a complex trade, and requires mastery of different skills. The above tips will help you to make your game development more refined. Additionally, the above tips are not exhaustive at all. It is all about mastering your craft and learning on the go. If you are a Unity game developer, you can showcase your work and build audience at the same time by broadcasting your work on Livecoding.tv. The platform also offers unique value on feedback as other game developers chip in by sharing their thoughts to help improve the community.

Do you think the article lack some important points? If so, don’t forget to comment below and let us know.

Viewing all 17625 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>