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

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.


Credits


I did not invent this serialization method - it was introduced to my by my simulations class professor Orion Lawlor at University of Alaska Fairbanks. He showed this to me after watching me give a presentation where I had painfully hand-created dialog fields to edit attributes of a particle system. There were a lot of fields, and after class he showed me some code examples he'd written using the method and suggested I use it instead of manually making such dialogs. I followed his advice and tweaked the method for my own use, and it has been great! I'm hoping to get a link for the original code samples he gave me soon.


Game Engine Containers - handle_map

$
0
0

Introduction

This article explores the creation of a data container for game programming. This container is meant to take the place of C++ standard library containers such as std::map and std::unordered_map, with an alternative that stores data contiguously in memory. Data should be contiguous to maximize cache locality when iterating over the data, resulting in faster performance vs. containers that individually allocate elements on the heap. The goal here is not to produce a drop-in replacement for the STL containers. That being said, whenever practical I tried to remain consistent with the conventions and semantics established in the STL. The std::vector is my poor-man's memory allocator of choice.


What sets this container apart from other associative containers like std::map and std::unordered_map, is that we do not specify the key when inserting data. This is why I'm referring to the key as a handle. We receive a handle back from the container upon inserting. Quite often when using associative containers, I find that the values of keys are not actually important. We just end up storing the keys and using them only for lookup purposes anyway. Often we use hash maps purely for the associative semantics of the container, and not because we actually care about the key values. We're still forced into supplying a key, which can lead to convenient but flawed choices like std::string. This is where handles can be a great alternative. We still have to store the handle somewhere, but the burden of type selection and supplying the value is relieved from the programmer. Handles also hold information internally that make this container smarter and safer than bog standard hash maps.


So now that I've given an overview of the container and why you might want to use it, let's outline the goals for the implementation.


Goals


  1. Store data in a contiguous block of memory
    This is key to our performance goals. We will separate our container into a dense set and a sparse set, where the data itself is kept in the dense set, tightly packed to ensure best cache locality during traversals. Even though this is an associative container, semantically it doesn't make sense to traverse in the order of the handles' values. That would also incur a performance penalty due to the double-indirection of handle lookups (more on that later). More likely, what you'll want to do is either look up a single entry by handle, or traverse all entries by their in-memory order just as you would an array/vector, completely bypassing the use of handles. This is not an ordered container, so traversal of the dense set is not guaranteed to visit elements in the same order each time.

  2. Create an associative mapping between a handle and the data
    The sparse set will play two important roles. First, it is the handle index and remains stable with insertions and deletions. This allows us to adjust the memory location of elements in the dense set to keep them tightly packed, without invalidating our lookup value. The second role of the sparse set is to internally store a freelist. Gaps in the sparse set resulting from deletions will be filled by subsequent insertions. The sparse set will always fill gaps before growing, and thus will itself try to remain dense.

  3. O(1) lookups
    Constant time lookups are achieved because handles contain information about their location in the sparse set. This makes lookups into the sparse set nothing more than an array lookup. The information contained in the sparse set knows the item's exact location in the dense set, so again just an array lookup. One consideration for lookups by handle vs. a direct pointer dereference is the cost of an extra indirection, so traversal "by handle" (over the sparse set) is not recommended. Therefore, iterators to the dense set are provided by the handle_map for traversals.

  4. O(1) insertions
    Constant time insertions are done by pushing the new item to the back of the dense set, and using the first available slot in the sparse set's freelist for the index.

  5. O(1) removals
    Constant time removal is made possible by using the "swap and pop" trick in the dense set. That is, we swap the item being removed with the last item in the array, and then pop_back to remove it. We also have to update the index of the last item referenced in the sparse set. We have to discover the location in the sparse set of the corresponding inner id for the (former) last item that we are swapping with. The index field must be changed to reference its new location in the dense set. One option would be to linearly search through the sparse set until we find the match, but to achieve O(1), we instead introduce a third storage set (in addition to the dense set and sparse set) which we'll term the meta set. The meta set is maintained in parallel with the dense set, and contains an index back to the sparse set, giving us a two-way link. We do not combine the meta information directly into the dense set because it's currently only used for deletion, and there is no sense in harming cache locality and alignment of the main object store for infrequently used data.


Implementation


Let's begin with an explanation of the handle structure itself. The handle is a 64-bit value, so there is no additional overhead compared to a pointer on 64-bit platforms.


/**
* @struct Id_T
* @var    free        0 if active, 1 if slot is part of freelist, only applicable to inner ids
* @var    typeId      relates to m_itemTypeId parameter of handle_map
* @var    generation  incrementing generation of data at the index, for tracking accesses to old data
* @var    index       When used as a handle (outer id, given to the client):
*                        free==0, index of id in the sparseIds array
*                    When used as an inner id (stored in sparseIds array):
*                        free==0, index of the item in the dense items array
*                        free==1, index of next free slot, forming an embedded linked list
* @var    value       unioned with the above four vars, used for direct comparison of ids
*/
struct Id_T {  
    union {
        /**
        * the order of this bitfield is important for sorting prioritized by free, then typeId,
        * then generation, then index
        */
        struct {
            uint32_t index;
            uint16_t generation;
            uint16_t typeId : 15;
            uint16_t free : 1;
        };
        uint64_t value;
    };
};
typedef std::vector<Id_t> IdSet_T;  
#define NullId_T    Id_T{}

It is worth mentioning the two ways that the handle structure is used, which I've termed "outer id" and "inner id." The outer id is an Id_T value that's given to the user of the container as a handle. The inner id is the related Id_T value that is stored in the sparse set. The two values differ in how the index property is used. The outer id stores an index into the sparse set, while the inner id stores an index into the dense set.


The typeId field is optional extra metadata, set in the container's constructor, that can introduce an element of type safety into your outer id handles. For example, say you have two handle_maps, one storing Apples and the other storing Oranges. The typeId would differentiate handles between the two, that would otherwise be equivalent. The programmer simply needs a mechanism to assign a unique 15-bit integer for each type contained within a handle_map. In many cases, you'll just bypass this and set zero. However, in cases where many types are tracked in an array of handle_maps, like components in an entity component system (ECS), or fields in a SoA inversion, the typeId can come in very handy. In addition to type safety, the typeId can be used as an additional O(1) lookup to first get you into the correct handle_map (within an array of handle_maps), before getting you to the final value. This is exactly how automatic storage for all component types is done in the griffin engine's ECS implementation.


The generation field is used as a safety mechanism to detect when a stale handle is trying to access data that has since been overwritten in the corresponding slot. Every time a slot in the sparse set is removed, the generation increments. Handle lookups assert that the outer id and inner id generations match. If debug asserts aren't safe enough for your purposes, an exception could be thrown and handled at runtime, but I view stale lookups as a bug and prefer to just crash hard and fix the issue.


The free field is a 1-bit flag to indicate if the sparse set entry represents a value, or if it is actually empty and part of the internal freelist. When free==1, the inner id's index field no longer stores a lookup index into the dense set, and instead stores the index of the next free slot in the internal freelist. This gives us an embedded singly linked list within the sparse set array, and as long as we store the front index of the freelist separately, it is an O(1) operation to find the next available slot and maintain the singly linked list.


The following free functions are used to directly compare Id_T types.


// struct Id_T comparison functions
inline bool operator==(const Id_T& a, const Id_T& b) { return (a.value == b.value); }  
inline bool operator!=(const Id_T& a, const Id_T& b) { return (a.value != b.value); }  
inline bool operator< (const Id_T& a, const Id_T& b) { return (a.value < b.value); }  
inline bool operator> (const Id_T& a, const Id_T& b) { return (a.value > b.value); }  

Let's jump into the handle_map itself. I've removed comments for brevity, so we can focus on the important parts. Links to the full implementation are provided at the bottom of the article.


template <typename T>  
class handle_map {  
public:  
    struct Meta_T {
        uint32_t    denseToSparse;  //!< index into m_sparseIds array stored in m_meta
    };

    typedef std::vector<T>      DenseSet_T;
    typedef std::vector<Meta_T> MetaSet_T;

    // Functions

    T&          at(Id_T handle);
    const T&    at(Id_T handle) const;
    T&          operator[](Id_T handle)         { return at(handle); }
    const T&    operator[](Id_T handle) const   { return at(handle); }

    template <typename... Params>
    Id_T emplace(Params... args) { return insert(T{ args... }); }

    template <typename... Params>
    IdSet_T emplaceItems(int n, Params... args);

    typename DenseSet_T::iterator       begin()         { return m_items.begin(); }
    typename DenseSet_T::const_iterator cbegin() const  { return m_items.cbegin(); }
    typename DenseSet_T::iterator       end()           { return m_items.end(); }
    typename DenseSet_T::const_iterator cend() const    { return m_items.cend(); }

    size_t erase(Id_T handle);
    size_t eraseItems(const IdSet_T& handles);

    Id_T insert(T&& i);
    Id_T insert(const T& i);

    void clear() _NOEXCEPT;
    void reset() _NOEXCEPT;

    bool isValid(Id_T handle) const;

    size_t size() const _NOEXCEPT { return m_items.size(); }
    size_t capacity() const _NOEXCEPT { return m_items.capacity(); }

    template <typename Compare>
    size_t  defragment(Compare comp, size_t maxSwaps = 0);

    DenseSet_T&         getItems()                  { return m_items; }
    const DenseSet_T&   getItems() const            { return m_items; }
    MetaSet_T&          getMeta()                   { return m_meta; }
    const MetaSet_T&    getMeta() const             { return m_meta; }
    IdSet_T&            getIds()                    { return m_sparseIds; }
    const IdSet_T&      getIds() const              { return m_sparseIds; }

    uint32_t            getFreeListFront() const    { return m_freeListFront; }
    uint32_t            getFreeListBack() const     { return m_freeListBack; }

    uint16_t            getItemTypeId() const       { return m_itemTypeId; }

    uint32_t            getInnerIndex(Id_T handle) const;

    explicit handle_map(uint16_t itemTypeId, size_t reserveCount) :
    	m_itemTypeId(itemTypeId)
    {
        m_sparseIds.reserve(reserveCount);
        m_items.reserve(reserveCount);
        m_meta.reserve(reserveCount);
    }

private:  
    bool freeListEmpty() const { return (m_freeListFront == 0xFFFFFFFF); }

    // Variables

    uint32_t    m_freeListFront = 0xFFFFFFFF; //!< start index in the embedded ComponentId freelist
    uint32_t    m_freeListBack  = 0xFFFFFFFF; //!< last index in the freelist

    uint16_t    m_itemTypeId;   //!< the Id_T::typeId to use for ids produced by this handle_map<T>

    uint8_t     m_fragmented = 0; //<! set to 1 if modified by insert or erase since last complete defragment

    IdSet_T     m_sparseIds;    //!< stores a set of Id_Ts, these are "inner" ids indexing into m_items
    DenseSet_T  m_items;        //!< stores items of type T
    MetaSet_T   m_meta;         //!< stores Meta_T type for each item
};

The insert and erase functions do most of the heavy lifting to keep things consistent.


template <typename T>  
Id_T handle_map<T>::insert(T&& i)  
{
    Id_T handle = { 0 };
    m_fragmented = 1;

    if (freeListEmpty()) {
        Id_T innerId = {
            (uint32_t)m_items.size(),
            1,
            m_itemTypeId,
            0
        };

        handle = innerId;
        handle.index = (uint32_t)m_sparseIds.size();

        m_sparseIds.push_back(innerId);
    }
    else {
        uint32_t outerIndex = m_freeListFront;
        Id_T &innerId = m_sparseIds.at(outerIndex);

        m_freeListFront = innerId.index; // the index of a free slot refers to the next free slot
        if (freeListEmpty()) {
            m_freeListBack = m_freeListFront;
        }

        // convert the index from freelist to inner index
        innerId.free = 0;
        innerId.index = (uint32_t)m_items.size();

        handle = innerId;
        handle.index = outerIndex;
    }

    m_items.push_back(std::forward<t>(i));
    m_meta.push_back({ handle.index });

    return handle;
}


template <typename T>  
size_t handle_map<T>::erase(Id_T handle)  
{
    if (!isValid(handle)) {
        return 0;
    }
    m_fragmented = 1;

    Id_T innerId = m_sparseIds[handle.index];
    uint32_t innerIndex = innerId.index;

    // push this slot to the back of the freelist
    innerId.free = 1;
    ++innerId.generation; // increment generation so remaining outer ids go stale
    innerId.index = 0xFFFFFFFF; // max numeric value represents the end of the freelist
    m_sparseIds[handle.index] = innerId; // write outer id changes back to the array

    if (freeListEmpty()) {
        // if the freelist was empty, it now starts (and ends) at this index
        m_freeListFront = handle.index;
        m_freeListBack = m_freeListFront;
    }
    else {
        m_sparseIds[m_freeListBack].index = handle.index; // previous back of the freelist points to new back
        m_freeListBack = handle.index; // new freelist back is stored
    }

    // remove the component by swapping with the last element, then pop_back
    if (m_items.size() > 1) {
        std::swap(m_items.at(innerIndex), m_items.back());
        std::swap(m_meta.at(innerIndex), m_meta.back());

        // fix the ComponentId index of the swapped component
        m_sparseIds[m_meta.at(innerIndex).denseToSparse].index = innerIndex;
    }

    m_items.pop_back();
    m_meta.pop_back();

    return 1;
}

In the spirit of the data-oriented programming mantra "where there is one, there are many" I have provided a couple of methods to emplace and erase items in batch. We also get to have some fun with C++11 variadic templates, which enable us to pass any constructor arguments needed for the items.


template <typename T>  
template <typename... Params>  
IdSet_T handle_map<T>::emplaceItems(int n, Params... args)  
{
    IdSet_T handles(n);
    assert(n > 0 && "emplaceItems called with n = 0");
    m_fragmented = 1;

    m_items.reserve(m_items.size() + n); // reserve the space we need (if not already there)
    m_meta.reserve(m_meta.size() + n);

    std::generate_n(handles.begin(), n, [&, this](){ return emplace(args...); });

    return handles; // efficient to return vector by value, copy elided with NVRO (or with C++11 move semantics)
}

template <typename T>  
size_t handle_map<T>::eraseItems(const IdSet_T& handles)  
{
    size_t count = 0;
    for (auto h : handles) {
        count += erase(h);
    }
    return count;
}

The at function shows our O(1) lookup by handle, plus typeId and generation asserts for added safety.


template <typename T>  
inline T& handle_map<T>::at(Id_T handle)  
{
    assert(handle.index < m_sparseIds.size() && "outer index out of range");

    Id_T innerId = m_sparseIds[handle.index];

    assert(handle.typeId == m_itemTypeId && "typeId mismatch");
    assert(handle.generation == innerId.generation && "at called with old generation");
    assert(innerId.index < m_items.size() && "inner index out of range");

    return m_items[innerId.index];
}

Example:


handle_map<int> testMap(0, 1);  
Id_T handle = testMap.insert(1);  
int i = testMap[handle];  

Use Cases


There are several areas in a game engine where handle_map can be employed. This container is not a silver bullet, but given the performance characteristics, I often prefer to use it where a plain array or vector are unsuitable.
Some examples include:


  • Entity component systems -The components of an ECS could be stored in a handle_map, and use handles externally to store references to the entity or its components.

  • Scene graphs and embedded linked lists -An embedded list or tree structure is straightforward to represent by storing handles instead of direct pointers. Combined with a defragment sort, the flat representation can also be optimized for cache locality of adjacent nodes.

  • Resource caches -It is common practice to reference resources by handle rather than directly by pointer or shared_pointer, but the handle_map isn't ideal for directly storing large blobs since entries are constantly being swapped around. Consider this pattern instead: cache the resource in a memory location that doesn't change, and store a shared_ptr to it in the handle_map cache. There is more to it, but I am planning a future article covering my resource cache implementation in full detail.


Performance


I did some profiling to compare handle_map's performance to a vector of heap allocated unique_ptr's, and unordered_map. This should give a rough idea of the performance advantages over those two alternatives.


Creating 100,000 items:


handle_map: create 100000 items, time = 4.749955 ms  

unique_ptr: create 100000 items, time = 111.625944 ms  

unordered_map: create 100000 items, time = 89.358049 ms  


Iterating over all items, summing each item into total:


handle_map: iteration over dense set, total = 100000, time = 0.263713 ms  

unique_ptr: iteration, total = 100000, time = 0.524748 ms  

unordered_map: iteration by iterator, total = 100000, time = 3.466641 ms  


Iterating by lookup:


handle_map: iteration by handle, total = 100000, time = 0.653704 ms  

unordered_map: iteration by lookup, total = 100000, time = 15.921830 ms  


Clearing all items:


handle_map: clear 100000 items, time = 0.377051 ms  

unique_ptr: clear 100000 items, time = 10161.480165 ms  

unordered_map: clear 100000 items, time = 7615.724425 ms  


Considerations


  1. Not lightweight
    Under the hood, handle_map is implemented with three vectors plus (at least) 11 bytes of additional overhead.

  2. Extra indirection on lookups
    Lookups by handle require indexing into two arrays, which can add an extra cache miss over just dereferencing a pointer. This isn't something you should worry about too much, just make sure to iterate over the dense set using getItems() whenever possible. If handle_map is used to store components for an ECS, the "system" implementation should be able to bypass handles and operate directly on the dense set.

  3. What to do when an obsolete key is used for lookup
    My preference is to use this as a debug-time assert. The assumption is that I don't ever expect it to happen, and in a release build we'd have undefined behavior and possibly a crash. It would be trivial to instead throw an exception, and try to recover from the situation in much the same way you would recover from failing to lock a dangling weak_ptr.

  4. Thread safety
    This container is not thread-safe. Other than concurrent_queue, I'm not a big fan of implementing thread safety at the container level. I find that doing so often leads to two subtle problems. First, over-locking: where each function call is protected by a mutex, and calls to the container's functions become internally serialized. Adjacent function calls lose the ability to reduce contention by sharing a lock. Second, under-locking: where race conditions can still exist because locks are not made at a higher "transactional" level. I prefer instead to do multithreading with tasks.

  5. clear() vs. reset()
    There are two ways to purge all data from the container. The slower but safer method is to use clear(), which maintains the integrity of the sparse set, increments the generation of all entries, and can detect future lookups with a stale handle. The faster method is to use reset() which destroys the sparse set and empties the freelist.

  6. Fragmentation of entries
    Over time, items will be added out of order, and removed using the "swap and pop" method. This leads to a loss of adjacency and cache locality for items that were originally (or should be) added next to each other. I have provided a defragment function that allows the client to define a sort order for the dense set.

    If the implementation looks menacing, it's really not all that bad. You're basically looking at a standard insertion sort, with a small optimization for trivially_copyable types. In my profiling, I found the optimized version to be about 8-10% faster. Additionally, the m_fragmented flag is used to eliminate all work when no changes to the container have occurred since the last defragment completion (which should be the statistically common case).

    I chose insertion sort due to its properties of being stable and adaptive, with very little overhead. The assumption I'm making is that initially, the data set may be small but randomly ordered. Over time (with occasional defragmentation), the data set may grow but remain in mostly sorted order. The data set is also likely to have "few unique" items where they just need to be grouped next to each other. Using my new favorite web page, I found that insertion sort looked like a good fit.


template <typename T>  
template <typename Compare>  
size_t handle_map<T>::defragment(Compare comp, size_t maxSwaps)  
{
    if (m_fragmented == 0) { return 0; }
    size_t swaps = 0;

    int i = 1;
    for (; i < m_items.size() && (maxSwaps == 0 || swaps < maxSwaps); ++i) {
        T tmp = m_items[i];
        Meta_T tmpMeta = m_meta[i];

        int j = i - 1;
        int j1 = j + 1;

        // trivially copyable implementation
        if (std::is_trivially_copyable<t>::value) {
            while (j >= 0 && comp(m_items[j], tmp)) {
                m_sparseIds[m_meta[j].denseToSparse].index = j1;
                --j;
                --j1;
            }
            if (j1 != i) {
                memmove(&m_items[j1+1], &m_items[j1], sizeof(T) * (i - j1));
                memmove(&m_meta[j1+1], &m_meta[j1], sizeof(Meta_T) * (i - j1));
                ++swaps;

                m_items[j1] = tmp;
                m_meta[j1] = tmpMeta;
                m_sparseIds[m_meta[j1].denseToSparse].index = j1;
            }
        }
        // standard implementation
        else {
            while (j >= 0 && (maxSwaps == 0 || swaps < maxSwaps) &&
                   comp(m_items[j], tmp))
            {
                m_items[j1] = std::move(m_items[j]);
                m_meta[j1] = std::move(m_meta[j]);
                m_sparseIds[m_meta[j1].denseToSparse].index = j1;
                --j;
                --j1;
                ++swaps;
            }

            if (j1 != i) {
                m_items[j1] = tmp;
                m_meta[j1] = tmpMeta;
                m_sparseIds[m_meta[j1].denseToSparse].index = j1;
            }
        }
    }
    if (i == m_items.size()) {
        m_fragmented = 0;
    }

    return swaps;
}

Example:


const int N = 100000;  
struct Test { int val, sort; };  
handle_map<Test> testMap(0, N);

for (int i = 0; i < N; ++i) { testMap.emplace(1, i); }  
std::random_shuffle(testMap.begin(), testMap.end());

timer.start();  
size_t swaps = testMap.defragment([](const Test& a, const Test& b) { return a.sort > b.sort; });  
timer.stop();  

As you can see, a near worst case scenario sort (profiled above) can be quite heavy on performance.


handle_map: defragment swaps = 99987, total = 100000, time = 32538.735446 ms

For that reason, the operation is reentrant and can be approached iteratively. Passing maxSwaps will effectively set a ballpark time budget, and calling it periodically will cause the container to always approach the ideal order. Keep in mind, this does not change the fact that this is semantically still an unordered container.


Conclusion

The handle_map is an associative container implemented with the principles of data-oriented design in mind. The container is semantically similar to std::map or std::unordered_map, and exhibits constant time complexity for inserts, lookups, and removals. Handles are used as keys for lookups and contain "smart" internal safety features such as typeId and generation checks. A defragment sort operation can be defined by the user to incrementally approach the ideal in-memory order of the contiguous data set.

Future Work


  • Create a way to specify a custom allocator?
  • Turn Meta_T into template parameter to enable additional metadata?
  • Anything else?


Acknowledgments

For more reading on this concept, and alternative implementations, be sure to view the blog posts that inspired me to create this container for my game project.

Source


Github repo: https://github.com/y2kiah/griffin-containers
Online documentation available at: https://y2kiah.github.io/griffin-containers/


Article Update Log

7 Sept 2016: Initial release

Shadows and light on Kepler 22

$
0
0

Overview


As many of you know lighting a wide environment on a mobile game can be quite a challenge. Static appraches, like lightmaps, require far too much memory and dynamic tecniques too much cpu and gpu power.
I will try to describe how I approached the problem In my game kepler 22 (http://www.nuoxygen.com/public/kepler22-fps.html)

Kepler22 is an open world game with indoor / outdoor environment and poses some unique challenges that are seldom faced in mobile game development:

  • A wide environment (16 square km), impossible to light-map with a reasonable resolution.
  • Day/Night cycles requiring dynamic lighting/shadows.
  • Shadows varying from ultra-big objects, like buildings, to very small ones, like a gun barrel.
  • In-door omnidirectional lights. (You can see up to 7 in the same scene)
  • A first person shooter camera that can get very near to every object and potentially reveal the pixel grain of shadow maps.

Attached Image: labs_last.jpg


All of this is usually solved in any high end pc game with a cascade of high resolution shadow maps for the outdoor and shadow-cube-maps for the indoor lights, all seasoned by PCF or some other method to smooth the pixels.

Unfortunately, in this case, I had further constrains dictated by the platform.


The Constrains

  • avoid multiple render targets
  • saving shaders cycles
  • drawing all the solid geometry first and try to avoid blend modes (i.e. avoid multipass tecniques).
The last one constraint is required by the Power VR architecture and I want to explain it better. It relates to a tecnique that Power VR uses to save shader cycles. I can't ensure the description is 100% accurate but this is roughly how it works:
If you are not using a blend mode, Power VR GPUS write the frame buffer without running the fragment shader, they just mark a region of the screen with a triangle id. As the scene is complete (or when you first draw something using a blend mode), the GPU then runs the fragment shaders just once per pixel selecting the shader and its inputs based on the triangle id. This process serves the purpose of running the shader exactly once per pixel regardless the scene depth complexity (overdraw level). The final goal being to allow for fairly more complex shaders.

So the best way to use the Power VR GPUs (any good IOS game developer should know) is:
  • draw all the opaque stuff first. Here you can use even a quite complex shaders (read: you can use per pixel lighting)
  • then draw all the alpha tested stuff (part of the shader runs to determine the test result).
  • draw your alpha blended geometry last. As you start doing that, all the shaders are evaluated once per pixel and following that the gpu operates in a standard way, i.e. after this point then you start trading pixel shader clocks for the depth complexity. (this suggests to keep simple shaders on the particles and translucents !)
  • avoid as much as possible render target switches (expensive !!)

I decided to go with shadow volumes


because:
  • unlike light maps they can get the same quality on static and dynamic objects and pose no additional memory requirements (at least not in direct connection with the world size).
  • Unlike shadow maps they handle nicely near and far away objects at any possible size scale.
  • Omnidirectional and directional lights cost the same (shadow maps would require cube maps and multiple depth targets)
Again this wasn't enough so I made some trade-offs to unload the GPU:
  • Ignore light coming from other rooms: in this implementation light doesn't propagate through the door to the neighboring rooms.
  • Just one per-pixel shadow-casting light per room. (The strongest one). Other lights evaluated per vertex.
The trade-offs allowed me to draw all the lights in a single pass.

The traditional shadow volumes render loop:

  1. Draw scene ambient light (this also initializes the depth buffer)
  2. Enable alpha blend (additive mode)
  3. For every light in the scene
    • clear the stencil
    • compute and draw the shadow volumes of the light
    • render the lighting contribution from the light (using stencil test to leave shadowed parts dark)
  4. Render translucent surfaces enabling the appropriate alpha blending mode.
  5. Apply post processing stuff

Then became:

  1. draw all the opaque geometry. For every object use the appropriate lights set. In a single pass the shader takes care of ambient lighting, direct lighting and fog.
  2. draw all the shadow volumes. Every object casts ONE shadow from the light which is lighting it. Shadow volumes of walls are not drawn - they would need by far too much filling rate.
  3. Draw a translucent mask to darken the parts of the screen where the shadows are marked in the stencil.
  4. draw alpha tested/blended stuff sorted far to near using a cheap shader.
  5. post process.

This new method has many Advantages because requires a single render pass (much lower fill rate) and keeps the dreaded alpha blend off until the very last stages of the process.

Unfortunately, it also comes with a good share of drawbacks.


New Challenges


Preventing shadow volumes from a first light from leaking in the nearby rooms and interfering with the other lights' shadow volumes.

Attached Image: leak.jpg

The cause of this artifact is that we don't compute a stencil mask for every single light but we use a single stencil mask for marking all the shadows from all the lights at once. The problem could be theoretically solved clipping the shadow volumes in the room volume but this would be pretty expensive, especially for shader computed shadow volumes.
Adding the shadows of the walls would be even worst: all the world would be shadowed because, while the traditional algorithm lights an object with the sum of all the not-occluded lights, the simplified one shadows the pixel if at least one shadow (from any light) drops on the pixel.


Another challenge: Modulating the shadows so that they are more faint if the receiver is far from the light (it receives less direct and more ambient light). When the receiver is so distant from the light that the direct contribution into the shader is null and only the ambient light is left, the object should cast no shadow at all.

Attached Image: light_dist.jpg


In the presence of fog, modulating the shadows so that they are more faint if the receiver is far from the observer and when an object fades out in the fog its shadows fades out with him.

Attached Image: fog_small.jpg


Preventing the shadows from highlighting the objects' smoothed edges with self-shadowing.


Attached Image: smooth.jpg


Handle objects across the rooms (especially the doors themselves).


I figured out these solutions



The Light ID Map

  • Is a screen space map.
  • Tells which light is affecting the pixel.
  • Solves: shadow volumes interfering with other rooms' shadow volumes
  • Stored into 3 of the 8 bits of the stencil buffer.
  • Extremely cheap: Drawn simultaneously with solid geometry, don't requires extra vertex or pixel shader executions.


The shadow Intensity Map

  • Is a screen space map
  • Tells how much a pixel is affected by the shadow. (0 = fully affected, 255 = unaffected)
  • Solves: modulating the shadow, preventing the shadow from revealing the casters' edges.
  • Stored into the alpha channel of the color buffer.
  • Extremely cheap: Drawn simultaneously with solid geometry, don't requires extra vertex or pixel shader executions.

How the Light ID Map is implemented


Step1 – To be done During the first pass (solid geometry drawing) Before drawing an object.

The CPU must configure the light ID of the light which is lighting the object to be drawn. Remember: every object is lit only by a single per-pixel shadow-casting light so there is a 1 by 1 relation between objects and (per pixel) lights.

The following code forces the GPU to mark the upper stencil bits of the pixels of the object with the light-id.

  void GLESAdapter::WriteLightId(int light_id)
  {
      glEnable(GL_STENCIL_TEST);

      // always passes, refrence has 3 msbits bits set as the light id, 5 lsbits set at a midway value
      // (note you can't allow the counter to wrap else it would corrupt the ID.
      // so _stecil_offset must be half the counter range).
      glStencilFunc(GL_ALWAYS, (light_id << lightid_shift) + stencil_offset, 0xff);

      // affects the stencil if and only if the color buffer is affected (both stencil and zeta tests pass)
      glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);        
  }

If you reserve 3 bits for the light ID, lightid_shift must the 5 and stencil_offset must be 0x10 (middle of the range of the 5 bits shadow counter).
With 3 bits there are only 8 possible id values and one is reserved for 'not directly lit' so id values are assigned to lights as needed and on a per frame basis. This means that in a single scene you can't have more than 7 lights but you can have as many as you want in the level.

Step2 – To be done before an object shadow volume is drawn.

Again the CPU must tell the GPU the light id. This time this is done for the purpose of preventing the gpu from writing those fragments which don’t belong to the selected light.

  void GLESAdapter::FilterLightId(int light_id)
  {
      glEnable(GL_STENCIL_TEST);

      // lightid_mask keeps only the 3 msbits so the func just tests that the pixel actually match the
      // light_id. - if the id doesn't match the stencil test fails.
      glStencilFunc(GL_EQUAL, light_id << lightid_shift, lightid_mask);
  }

   // NOTE: stencil Op set by drawing functions (depends on z-pass/z-fail method)
   // IN ANY CASE: if the stencil func fails must not alter the stencil value.
    if (zfail_mode) {
        glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR, GL_KEEP);
        glStencilOpSeparate(GL_BACK,  GL_KEEP, GL_INCR, GL_KEEP);
    } else {
        glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR);
        glStencilOpSeparate(GL_BACK,  GL_KEEP, GL_KEEP, GL_DECR);
    }

This actually culls away the shadow volume on the objects whose ID doesn't match the light from which the shadow volume is cast.
Note that this is a quite intensive use of the stencil buffer. It is read, partially compared to the ref value, if all is right, based on z test and winding incremented or decremented and finally written back.


How the Shadow Intensity Map is implemented.


While drawing solid geometry the shader computes the alpha value taking into account:
  • The light distance. If the pixel is further from the direct light, the ambient contribution rises and the shadow faints.
  • The distance from the viewer. To attenuate the shadow effect with the fog.
  • The angle between the lit surface and the light direction. So that surfaces about 90 degrees from the light direction, which are supposed to not receive direct light, don’t receive shadows. This actually prevents the self-shadowing artefacts.

Once the color, alpha, stencil buffers have been appropriately written you just need to multiply the
buffer color with the alpha. (only in those places where the stencil buffer is different than the reference – i.e. only in shadows)

    glEnable(GL_STENCIL_TEST);

    // write the shadow where the stencil counter is different than the reference
    // (you would test different from 0 in a traditional environment)
    // note that you also must mask out the light_id so it doesnât interfer 
    glStencilFunc(GL_NOTEQUAL, stencil_ref, ~lightid_mask);

    // you dont want to alter the stencil now
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);        
  
    // ignore the source color, blend the destination color with the destination alpha
    glEnable(GL_BLEND);
    glBlendFunc(GL_ZERO, GL_DST_ALPHA);

    // draw full screen quad
    draw_quad(...)


Crossing the doors.


When crossing a door the light from the new room switches to pixel-level quality and the light from the previous room (which still lights the model) switches to per-vertex mode. The process is abrupt but hardly noticeable because all the objects that cross a door are soldiers (tipically engaged in combat, and running) or thrown objects and both move quite fast.

Beside direct lighting the game uses a per-room ambient light level which is computed by the cpu and propagates from room to room based on the state of the doors ([partially]open/close) and the volume of the rooms. This makes possible for a completely unlit room to be fully dark when the door is closed but be dimly lit if the door opens.

The ambient light of two neighboring rooms is interpolated for the purpose of lighting bodies that cross doors. This is done taking into account the bodies position with respect to the door.

Doors don’t’ receive direct light (there would be two when the door is open) but compute ambient light per vertex to have a different light level on the two sides.


Shadow volumes extraction


Here are some of the trickiest parts

- skinned characters.
I have a good vertex shader that I use in DX9 to automatically extract shadow volumes from skinned characters but unfortunately it requires 9 vertex attributes and really a lot of operations so I had to extract the shadow volumes with the cpu. This is one of the slowest operations in the render process because it is cpu intensive and uses dynamic geometry - which is slow.

- capping the building holes.
To draw the shadows of the building is even worst. Not wanting to take into account the intricacies of the internal geometry, the building shadow volumes are built just from the external surfaces of the building.
This surface is extracted automatically by the software and then automatically capped to make it a closed mesh. (to avoid holes where windows and doors are positioned).
The cap is conditionally created and extruded based on the cap facing with respect to the light and the camera (quite complex here – So much I wouldn't be able to explain it better myself without reading the code).

Other shadow volumes are easier and are automatically extracted by the vertex shader using classic algorithms.


Other effects in Kepler22


Exposition control (faking the eye adaptation process).
The target Exposition level is set by the cpu based on the time of day and based on the portion of screen which is filled with indoor or outdoor scene. This is a forward computation (uses the open portal of the nearest door) which doesn't use any information from the actually rendered scene, saving expensive render buffer reads.
The target exposition is then low-passed to simulate a slow adaptation of the eye.

Visibility culling indoor is based on classic bsp/portals/sectors algorithms. bsp nodes belonging to a same room are coalesced into a sector and their portals ignored. The bsp is accordingly pruned.

Visibility culling outdoor is distance based (the objects are arranged into a regular grid so that only the ones near enough are tested for distance) and artifacts are hidden by the fog (which is applied in the same single pass with light ids, shadow intensity, color).

And ok, I could go on forever, because there are many more details, so let’s stop here.


Conclusion


Hope I have given a clear enough overview of the lighting system of Kepler22. In special way I hope you got enough detail of the Light ID maps and Shadow Intensity maps to be able, if you need, to use them in your projects.

Attached Image: final_entering.jpg


Article Update Log


22 September 2016: Initial release

GPS on the Microsoft Hololens

$
0
0

Introduction


The Microsoft Hololens technology provides a myriad of possibilities, from its speech recognition, to the gaze and gesture controls, all in an untethered environment. But there is one glaring omission, the lack of a GPS sensor onboard the device. This article provides a way to transmit a Bluetooth GPS signal from your smart phone (in this article, an Android OS) to the Hololens.

Explaining the Concept


This technique leverages Bluetooth LE advertiser/watcher technologies available on the Android and UWP platforms. Similar advertiser code is available on iOS, which has subsequently coined the use of the term "iBeacons" which speaks to all Bluetooth advertiser type devices. Unfortunately, this technology is not available on all devices and should be checked for during runtime.

The benefit of the advertiser pattern on the smart phone is that a socket server (RFCOMM) connection does not need to be maintained on either the Hololens or Android side, reducing code complexity and making your application more crash proof. Another major benefit of the Beacon approach is lower overall power consumption on both the advertising and watching devices.

I felt the easiest way to explain my technique was to divide the implementation into 4 sections, the first two are generic to all Bluetooth Beacon applications and the next two sections explain the GPS specific implementation.

Implementation


BLE Advertiser on Android Java


Starting off with the content serving device, or Advertiser, the code below explains the initial setup of the data advertiser. GPS specific details are explained in other section.

One thing to note the max size of an advertisement is 31 bytes. If the amount is exceeded the advertisement callback will fail with a result of 1 or ADVERTISE_FAILED_DATA_TOO_LARGE.

A check is highly recommended in both the application manifest and Java code to ensure the device has the permission and capability to produce Bluetooth LE advertisements. The user will be kicked out of the application to turn on Bluetooth, if Bluetooth is off during activation.

Add the following to your Android Manifest file:

<!-- Manifest Statements -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

Add the following to your Android MainActivity file:

public class MainActivity extends AppCompatActivity {

    BluetoothAdapter mBAdapter;
    BluetoothManager mBManager;
    BluetoothLeAdvertiser mBLEAdvertiser;
    
    static final int BEACON_ID = 1775;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBManager = (BluetoothManager)getSystemService(BLUETOOTH_SERVICE);
        mBAdapter = mBManager.getAdapter();
        mBLEAdvertiser = mBAdapter.getBluetoothLeAdvertiser();
    }
    
    @Override
    protected void onResume()
    {
        super.onResume();

        if(mBAdapter == null || !mBAdapter.isEnabled())
        {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivity(enableBtIntent);
            finish();
            return;
        }

        if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
        {
            Toast.makeText(this,"No LE support on this device", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        if(!mBAdapter.isMultipleAdvertisementSupported())
        {
            Toast.makeText(this,"No advertising support on this device", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        startAdvertising();
    }
    
    @Override
    protected void onPause()
    {
        super.onPause();
        stopAdvertising();
    }

    private void startAdvertising()
    {
        if(mBLEAdvertiser == null) return;

        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
                .setConnectable(false)
                .setTimeout(0)
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
                .build();

        AdvertiseData data = new AdvertiseData.Builder()
                .addManufacturerData(BEACON_ID,buildGPSPacket())
                .build();

        mBLEAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
    }

    private void stopAdvertising()
    {
        if(mBLEAdvertiser == null) return;
        mBLEAdvertiser.stopAdvertising(mAdvertiseCallback);
    }

    private void restartAdvertising()
    {
        stopAdvertising();
        startAdvertising();
    }

    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            super.onStartSuccess(settingsInEffect);
            String msg = "Service Running";
            mHandler.sendMessage(Message.obtain(null,0,msg));
        }

        @Override
        public void onStartFailure(int errorCode) 
        {
            if(errorCode != ADVERTISE_FAILED_ALREADY_STARTED)
            {
                String msg = "Service failed to start: " + errorCode;
                mHandler.sendMessage(Message.obtain(null,0,msg));
            }
            else
            {
                restartAdvertising();
            }
        }
    };

    private Handler mHandler = new Handler()
    {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            /*
            UI feedback to the user would go here.
            */
        }
    };
    
    private byte[] buildGPSPacket()
    {
       
        byte[] packet = new byte[24];
        
         /* GPS code packet generation goes here */         
         
        return packet;
    } 

BLE Watcher in UWP C#


The C# code for the Bluetooth advertisement is straight forward. The watcher listens for a manufacturer data id the same as in the android application (in this example, 1775).

As it the watcher runs in another thread, you will need an application or event dispatcher to roll it back into the UWP / Unity application.

You also need to add Bluetooth as a capability in the UWP Package Manifest.

General UWP (Windows 10) code:

 public sealed partial class MainPage : Page
 {
     BluetoothLEAdvertisementWatcher watcher;
     public static ushort BEACON_ID = 1775;
     
     public MainPage()
        {
            this.InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if(watcher != null)
                watcher.Stop();

            watcher = new BluetoothLEAdvertisementWatcher();
            var manufacturerData = new BluetoothLEManufacturerData
            {
                CompanyId = BEACON_ID
            };
            watcher.AdvertisementFilter.Advertisement.ManufacturerData.Add(manufacturerData);

            watcher.Received += Watcher_Received;
            watcher.Start();
        }

        private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            ushort identifier = args.Advertisement.ManufacturerData.First().CompanyId;
            byte[] data = args.Advertisement.ManufacturerData.First().Data.ToArray();
            
            var ignore = Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
            () =>
                {        
                    /* GPS Data Parsing / UI integration goes here */
                }
            );
        }
 }

Adapted to ingest into Unity, do not forget to surround with #if NETFX_CORE preprocessor statements so the runtime editor does not complain.

First the role of the event dispatcher gameobject that acts like the Dispatcher in UWP for Unity (Also attach to a Game Manager object in the scene):

using System;
using System.Collections.Generic;
using UnityEngine;

public class EventProcessor : MonoBehaviour
{
    public void QueueEvent(Action action)
    {
        lock (m_queueLock)
        {
            m_queuedEvents.Add(action);
        }
    }

    void Update()
    {
        MoveQueuedEventsToExecuting();

        while (m_executingEvents.Count > 0)
        {
            Action e = m_executingEvents[0];
            m_executingEvents.RemoveAt(0);
            e();
        }
    }

    private void MoveQueuedEventsToExecuting()
    {
        lock (m_queueLock)
        {
            while (m_queuedEvents.Count > 0)
            {
                Action e = m_queuedEvents[0];
                m_executingEvents.Add(e);
                m_queuedEvents.RemoveAt(0);
            }
        }
    }

    private System.Object m_queueLock = new System.Object();
    private List<Action> m_queuedEvents = new List<Action>();
    private List<Action> m_executingEvents = new List<Action>();
}

Now the Unity GPS Watcher class:

using UnityEngine;
#if NETFX_CORE
using Windows.Devices.Bluetooth.Advertisement;
using System.Runtime.InteropServices.WindowsRuntime;
#endif
    
public class GPS_Receiver : MonoBehaviour
{
    #if NETFX_CORE
        BluetoothLEAdvertisementWatcher watcher;
        public static ushort BEACON_ID = 1775;
    #endif
    private EventProcessor eventProcessor;
    
    void Awake()
    {
        eventProcessor = GameObject.FindObjectOfType<EventProcessor>();
        #if NETFX_CORE
         watcher = new BluetoothLEAdvertisementWatcher();
            var manufacturerData = new BluetoothLEManufacturerData
            {
                CompanyId = BEACON_ID
            };
            watcher.AdvertisementFilter.Advertisement.ManufacturerData.Add(manufacturerData);

            watcher.Received += Watcher_Received;
            watcher.Start();
        #endif
    }

#if NETFX_CORE
    private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
    {
        ushort identifier = args.Advertisement.ManufacturerData[0].CompanyId;
        byte[] data = args.Advertisement.ManufacturerData[0].Data.ToArray();
        
        eventProcessor.QueueEvent(() =>
        {        
            /* Unity UI ingestion here
        });
    }
#endif

}    
        

At this point you can use an Android device to pass any information that you would like to the UWP application, but for the sake of this article, GPS data code is provided below.

GPS Sensor on Android Java


The hard part of setting up a Bluetooth advertiser/watcher is over, all that is left is to read the GPS sensor on your smart phone and serialize the information for an advertisement to the Hololens.

Mentioned above, the advertisement will fail if the size of it is over 31 bytes. Luckily for me, I only required 2 doubles (8 bytes per double for the Latitude and Longitude) and 2 floats for my GPS data (4 bytes per float for the heading and speed data) for a total of 24 bytes.

You also need to explicitly ask for permission to use location services in Android 6.0+ from the user and it cannot just be in the application manifest.

Add this to your Android manifest:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Add this to your MainActivity file:

LocationManager mLocationManager;
static final int PERMISSION_RESULT_CODE = 1;
Location currentLocation;

@Override
    protected void onCreate(Bundle savedInstanceState) {

        /* 
        * code from above section is same here 
        */
    	
        int permissionCheck = ContextCompat.checkSelfPermission((Context)this, Manifest.permission.ACCESS_FINE_LOCATION);

        if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
            startGPS();
        }
        else
        {
            // No explanation needed, we can request the permission.
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSION_RESULT_CODE);
        }
    }

	public void startGPS()
    {
        //Execute location service call if user has explicitly granted ACCESS_FINE_LOCATION..
        mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
        LocationListener listener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                UpdatePosition(location);
            }

            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {

            }

            @Override
            public void onProviderEnabled(String provider) {

            }

            @Override
            public void onProviderDisabled(String provider) {

            }
        };
        try
        {
            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, listener);
            currentLocation = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
            Log.i("GPS_Receiver", "startGPS: GPS Started..");
        }
        catch(SecurityException e)
        {

        }
    }

	public void UpdatePosition(Location location)
    {
        currentLocation = location;
        restartAdvertising();
    }

    /* Updated method */
	private byte[] buildGPSPacket()
    {
        Location location = gps_receiver.getBestLocation();
        byte[] packet = new byte[24];
        if(location != null) {
            try {
                double latitude = location.getLatitude();
                byte[] buffer = ByteBuffer.allocate(8).putDouble(latitude).array();
                for (int i = 0, j =7; i < 8; i++, j--) packet[i] = buffer[j];

                double longitude = location.getLongitude();
                buffer = ByteBuffer.allocate(8).putDouble(longitude).array();
                for (int i = 8, j =7; i < 16; i++, j--) packet[i] = buffer[j];

                float bearing = 0;
                bearing = location.getBearing();
                buffer = ByteBuffer.allocate(4).putFloat(bearing).array();
                for (int i = 16, j =3; i < 20; i++, j--) packet[i] = buffer[j];

                float speed = 0;
                speed = location.getSpeed();
                buffer = ByteBuffer.allocate(4).putFloat(speed).array();
                for (int i = 20, j =3; i < 24; i++, j--) packet[i] = buffer[j];

            } catch (NumberFormatException e) {
                packet = new byte[24];
            }
        }
        return packet;
    }



GPS Decoder in UWP C#


The last section of the decoder is the easiest since all that is required is deserialization of the data contained in the advertisement manufacturer data. I created a specific data class to handle the Data.

using System;

public class GPS_DataPacket
{
    public double Latitude;
    public double Longitude;
    public float Heading;
    public float Speed;

    public static GPS_DataPacket ParseDataPacket(byte[] data)
    {
        GPS_DataPacket gps_Data = new GPS_DataPacket();

        gps_Data.Latitude = BitConverter.ToDouble(data, 0);
        gps_Data.Longitude = BitConverter.ToDouble(data, 8);
        gps_Data.Heading = BitConverter.ToSingle(data, 16);
        gps_Data.Speed = BitConverter.ToSingle(data, 20);
        return gps_Data;
    }

    public override string ToString()
    {
        string lat, lng;
        if (Latitude > 0)
        {
            lat = string.Format("{0:0.00} ÃÃÃÃÃÃÃðN", Latitude);
        }
        else
        {
            lat = string.Format("{0:0.00} ÃÃÃÃÃÃÃðS", -Latitude);
        }
        if (Longitude > 0)
        {
            lng = string.Format("{0:0.00} ÃÃÃÃÃÃÃðE", Longitude);
        }
        else
        {
            lng = string.Format("{0:0.00} ÃÃÃÃÃÃÃðW", -Longitude);
        }
        return string.Format("Latitude: {0}, Longitude: {1}, Heading: {2:0.00}ÃÃÃÃÃÃÃð, Speed: {3:0.00} knots", lat, lng, Heading, Speed);
    }
}

Interesting Points


This technique provides the raw data to your GPS Android device, you still should follow some of the positioning filtering / business logic covered in the Android developer's guidance at:

https://developer.android.com/guide/topics/location/strategies.html

after you have started receiving the data you want to receive. Heading and Speed data can be unreliable if the user in not moving.

Another gotcha is the explicit request permission calls that need to be made in the MainActivity file for GPS position access. I had added permission statements in the manifest but still had the application crash on security exceptions before adding this to the MainActivity file (new security feature of the Android 6.0+ I guess).

Conclusion


The Youtube video:



was used as a reference for the bluetooth advertiser, it also has information on other Android Bluetooth LE techniques.

I hope this artcle helps others with getting GPS position data into their Hololens (or any other UWP devices like tablets, XboxOne, etc.). I think this way of sending data is more robust (stable) than the GATT socket server alternatives to passing information across devices. I did not include the frontend code the GPS information in either the UWP or Android, because I felt it was out of scope. Sample projects including this are available on request.

Please feel free to comment or work over an iOS advertiser version that I can roll into the article later. Otherwise, thanks for the read and pass on if it is useful. And if I see this in the Unity store for anything other than free, I will be pretty upset.

Article Update Log


9 Sep 2016: Initial release

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.

Static, zero-overhead (probably) PIMPL in C++

$
0
0

PIMPL (Pointer to IMPLementation, or "opaque pointer") is an idiom used for when you need "super" encapsulation of members of a class - you don't have to declare privates, or suffer all of the #include bloat or forward declaration boilerplate entailed, in the class definition. It can also save you some recompilations, and it's useful for dynamic linkage as it doesn't impose a hidden ABI on the client, only the one that is also part of the API.

Typical exhibitionist class:

// Foo.hpp
#include <big_header.hpp>
    
class Foo {
public:
	Foo(int);

private:
	// how embarrassing!
	Dongle dongle;
};

// Foo.cpp
Foo(int bar) : dongle(bar) {}

Now, it's developed a bad case of PIMPLs and decides to cover up:
// Foo_PIMPL.hpp
class Foo {
public:
	// API stays the same...
	Foo(int);

	// with some unfortunate additions...
	~Foo();
	Foo(Foo const&);
	Foo &operator =(Foo const&);

private:
	// but privates are nicely tucked away!
	struct Impl;
	Impl *impl;
};

// Foo_PIMPL.cpp
#include <big_header.hpp>

struct Foo::Impl {
	Dongle dongle;
};

Foo(int bar) {
	impl = new Impl{Dongle{bar}}; // hmm...
}

~Foo() {
	delete impl; // hmm...
}

Foo(Foo const&other) {
	// oh no
}

Foo &operator =(Foo const&other) {
	// I hate everything
}

There are a couple big caveats of PIMPL, and that's of course that you need to do dynamic memory allocation and suffer a level of pointer indirection, plus write a whole bunch of boilerplate! In this article I will propose something similar to PIMPL that does not require this sacrifice, and has (probably) no run time overhead compared to using standard private members.

Pop that PIMPL!



So, what can we do about it?

Let's start by understanding why we need to put private fields in the header in the first place. In C++, every class can be a value type, i.e. allocated on the stack. In order to do this, we need to know its size, so that we can shift the stack pointer by the right amount. Every allocation, not just on the stack, also needs to be aware of possible alignment restrictions. Using an opaque pointer with dynamic allocation solves this problem, because the size and alignment needs of a pointer are well-defined, and only the implementation has to know about the size and alignment needs of the encapsulated fields.

It just so happens that C++ already has a very useful feature to help us out: std::aligned_storage in the <type_traits> STL header. It takes two template parameters - a size and an alignment - and hands you back an unspecified structure that satisfies those requirements. What does this mean for us? Instead of having to dynamically allocate memory for our privates, we can simply alias with a field of this structure, as long as the size and alignment are compatible!

Implementation



To that end, let's design a straightforward structure to handle all of this somewhat automagically. I initially modeled it to be used as a base class, but couldn't get the inheritance of the opaque Impl type to play well. So I'll stick to a compositional approach; the code ended up being cleaner anyways.

First of all, we'll template it over the opaque type, a size and an alignment. The size and alignment will be forwarded directly to an aligned_storage.
#include <type_traits>

template<class Impl, size_t Size, size_t Align>
struct Pimpl {
	typename std::aligned_storage<Size, Align>::type mem;
};

For convenience, we'll override the dereference operators to make it look almost like we're directly using the Impl structure.
Impl &operator *() {
	return reinterpret_cast<Impl &>(mem);
}

Impl *operator ->() {
	return reinterpret_cast<Impl *>(&mem);
}

// be sure to add const versions as well!

The last piece of the puzzle is to ensure that the user of the class actually provides a valid size and alignment, which ends up being quite trivial:
Pimpl() {
	static_assert(sizeof(Impl) <= Size, "Impl too big!");
	static_assert(Align % alignof(Impl) == 0, "Impl misaligned!");
}

You could also add a variadic template constructor that forwards its parameters to the Impl constructor and constructs it in-place, but I'll leave that as an exercise to the reader.

To end off, let's convert our Foo example to our new and improved PIMPL!
// Foo_NewPIMPL.hpp
class Foo {
public:
	// API stays the same...
	Foo(int);
    
	// no boilerplate!

private:
	struct Impl;
	// let's assume a Dongle will always be smaller than 16 bytes and require 4-byte alignment
	Pimpl<Impl, 16, 4> impl;
};

// Foo_NewPIMPL.cpp
#include <big_header.hpp>

struct Foo::Impl {
	Dongle dongle;
};

Foo(int bar) {
	impl->dongle = Dongle{bar};
}

Conclusion



There's not much to say about it, really. Aside from the reinterpret_casts, there's no reason there could be any difference at run time, and even then the only potential difference would be in the compiler's ability to optimize.

As always, I appreciate comments and feedback!

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.

9 Tips on Localizing Audio

$
0
0

If you have ever dealt with audio recording, whether character voice acting for a game or a voice-over for a video, then you probably noticed that it is not cheap. It is important to do everything right the first time in order to reduce additional costs. The same thing applies to the localization of audio: every error is multiplied by the number of languages. In this article, we will share some tips on how to interact with recording studios and localization services, how to optimize and accelerate the process, and how to reduce the risks as well as the costs of audio localization. It does not matter whether you are ordering these services from Alconost or from another company — knowledge of all the following pitfalls will stand you in good stead.



Captain C-3PO by Jeff Nickel

1. Project Formatting Matters


The proper formatting of the script ensures a smooth and problem-free recording process. One best practice is to format the script in the form of a table, where each row corresponds to a single audio file.



We at Alconost are sometimes faced with situations where a customer only wants us to record narration in a foreign language, and they leave it to themselves to synchronize the audio narration with their video. In this case, the customer must work with audio recorded in a language they do not know, and we for our part must do everything possible to simplify this task for the customer:



When transcribing we ask the editor (a native speaker of the source language) to mark the timing (indicating what phrase should be said at what second) and to break the text into segments (for example, each scene occupies a separate cell in the table). The transcription itself is a process of "lifting" the text from the original recording, that is, of converting the spoken text into a written one;

When translating we ask the translator (a native speaker of the target language) to preserve the original layout (so that it is clear how each part of the original corresponds to a respective part of the translation);

When recording we ask the narrator to break the recording down into scenes (e.g., the recordings for each new scene are stored in a separate file).

2. Create a List of Characters and Describe their Personalities


If you need several voices, create a list of characters and briefly describe them, including their name, age, gender, and other characteristics. This will help the audio recording studio to provide an adequate selection of narrators for the roles, to ensure that there are enough voice actors given the different languages, and to avoid mistakes such as recording a female voice instead of a male one or vice versa. Incidentally, the gender of the character does not have to match the gender of the narrator: professional voice actors with a wide creative range can successfully voice tomboy girls and respected ladies, for example.

Describe the character's personality in order to allow the voice actor to present the character in the best possible way. For example:

Character A: male, 40 years old, good-natured, simple-minded (farmer), "one of the guys." He is friendly towards his own (though he doesn't horse around), and he is curt to outsiders (though he stops short of being outright rude). He has a powerful voice, though it is not harsh. Normal rate of speech.

Or:

Raccoon character: high-pitched, child-like voice, but laughs and makes snarky asides in a husky voice. His rate of speech is very fast, but it must always be intelligible. He must have five kinds of laughter (good-humored, malevolent, hysterical, demonic, and loud laughter), and three kinds of exclamations of fright.

It is ideal when the customer's assignment includes a screenshot or video clip depicting the character.


Melancolia by Kristina Alexanderson

If the voice actors could have the opportunity to listen to the recordings of their characters in any other languages, this would be plus. Any clarifications are welcome: "not as fast as in the reference clip," "just like in the example."

What if the Narrators Ignore My Descriptions of the Characters?

If the voice actors are unclear about a certain point, they will either ask you questions (this is the best option, but the need to exchange questions and answers can make it harder to keep to deadlines) or make a decision at their own discretion (what they think is right). In the second case, if it turns out that you do not like the version the narrator decided on, though the formal criteria have been satisfied, your complaint will be about something that was not stipulated in advance. In this case you may have to pay for another recording, although voice actors will often try to accommodate the customer's request.

3. Limit the Number of Voices


Often people want to spend less money on the localization of an audio recording than on the original audio. Since voice actors usually have high minimum rates, so limiting the number of voices is a good way to reduce costs. For example, just two professional voice actors, one man and one woman, may be all that are needed to provide the voice-overs for a video featuring 12 different interviewees.

4. Avoid Audio Files that Feature Multiple Characters


Ideally, the audio recording process should be simple and fast. The assignment should be provided with sufficient detail. The voice actors record their parts, and the recordings are checked by linguists who are native speakers and cleaned of any extraneous sounds and noises (such as the turning of pages). The recordings are split into files, and the files are named according to the assigned specifications.

When you have a single audio file that contains several voice actors, everything becomes much more complicated. Since voice actors always record their files in isolation from each other, the audio files are divided and recombined during the final stages of the process. This increases the risk that something will go wrong. After all, the engineers who edit the files are hardly familiar with all the foreign languages in which the recordings are made.

5. Prepare a Pronunciation Guide


If you read the text aloud, you will soon realize that some words are pronounced differently. This is especially true of abbreviations. Foreign languages make the task even more difficult.

Some rules of pronunciation are generally accepted, but others must be determined by the company itself. Your studio should study the script and make a list of all the words whose pronunciation may present discrepancies. Translators and editors should clarify any difficult points before recording.

If a recording should have a time limit, then Captain Obvious suggests that the voice actor should be warned about this in advance.

6. Leave Space in Your Video


If you are familiar with localization, then you probably know that English is one of the most compact languages. If we were to translate the same text into Russian, it would increase in length by 10%, and if we were to translate it into French or Spanish, then it would increase by 20%.

If you cannot change the length of your video, then it will be hard to speed up the localized audio to match the video without accelerating the rate of speech or reducing the length of the original text. Both of these options can make it more difficult for your audience to comprehend your video. Moreover, the text or action in a particular frame may not match the sense of the localized audio. This is particularly problematic for videos that would cost lots of money to lengthen or edit.

Therefore, the best option would be to leave a little extra space in your original video: add pauses of a few seconds where this is possible. This will simplify and speed up the localization process. We at Alconost always try to adapt the word length to the necessary length of the phrases when translating or editing a text for our customers. This is how we are able to avoid unnecessary pauses or undue haste in our voice-overs.

7. Provide Plenty of Source Materials


You will most likely be asked for them. In any case remember: you must provide samples of the original audio if you want the voice-over to be done in the style and tone of the original.

When it comes to video, make sure that you have the source video: this way you can slightly slow down or speed up the scenes when localizing so that the animation matches the rate of speech of the voice artist in the new language. It is desirable in this case that the narrator's voice be separated from the music and sound effects in the sound track so that you can simply adjust the placement of the sound effects as you move the animations.

And one more thing. When it comes to video, often the on-screen captions must be localized in addition to the text read by the voice artist. If you want to translate them as well, you will make this task easier for the service provider if you submit the video source files as well.

8. Ensure that the Script and the Video Match Each Other Exactly


This is particularly important when it comes to videos. The voice-over usually corresponds to the written script, but often changes are made to the video clip at the last minute. This is how inconsistencies crop up between the audio and video tracks. Carefully check the final video.

9. Select Professionals Who Speak Foreign Languages and Have Experience in Audio Localization


Often texts that are to be recorded are translated by the author's friends, the author him- or herself or by translators who are not native speakers of the target language. All of this provides reason to consider that you should allow a native speaker to proofread your text before recording the voice-over. Indeed, some voice actors may even refuse to provide a voice-over if they see that a text in their own language is not grammatical. Voice artists may offer their own proofreading services, and they may be better than nothing. However, ideally it is worth it to recruit your own editor (a native speaker of the target language) to proofread a text that has been translated by a non-native speaker. If the quality of the translation is poor, or if the sense of the original has been changed or lost in the translation, then it may be better to order editing instead of proofreading services (when the text is edited, it is compared to the original).

Working with foreign languages adds complexity to the entire audio recording process. Localization of audio requires the utmost care when recruiting voice artists and ensuring that they are able to work well with each other, drafting scripts, determining recording techniques, and linguistic testing of the final product. For example, if you entrust an Asian language localization project to a studio that mainly translates into English, you risk discovering in the final product that a Korean voice artist was used instead of a Chinese one, phrases were cut off in mid-sentence, or other inconsistencies occurred due to lack of knowledge of the target languages.

When you select a service provider to localize audio, make sure that they employ voice artists and editors who are professional native speakers of their working languages, have years of successful experience working in this market, and have received feedback from satisfied customers. The team here at Alconost is ready to help you with your localization projects to ensure that they are localized to high quality standards, even if the project should involve less common languages.

Automata, Virtual Machines and Compilers - Part 2

$
0
0

Welcome to the second article on the topic of Automata, Virtual Machines and Compilers. This time around I'm going to show and explain how to extend the original source code in the first article I wrote on this by adding variables into it, and of course operators bound to them.

Please read the first article if you have not already, otherwise this one may not make much sense for you. As for the required knowledge, understanding the previous article and topics in it is most likely enough.


Definitions


Before I jump into code examples and pseudo-code, I should introduce what is added and how it improves the language defined in previous article. While the previous article defined simple calculator, by adding variables the user is allowed to also use memory in a persistent way during the program execution, not only as temporary space for intermediate results during the computation.


Grammar


Each time somebody wants to do an extension to existing language, a grammar has to be extended. In this case the grammar used in previous article is the starting line. The grammar defined just a simple calculator:


<integer> ::= [0..9]+
<add_operation> ::= + | -
<mul_operation> ::= * | /
<factor> ::= (<expression>) | <integer>
<term> ::= <factor> [<mul_operation> <factor>]*
<expression> ::= <term> [<add_operation> <term>]*

To extend previous grammar with variables, some more expressions need to be added:

  • Identifier - each language allows variables to be declared or used using variable name. It is up to the grammar to solve this problem, therefore an identifier expression needs to be added. I did so using rule:
    <ident> ::= [A..z _][A..z 0..1 _]*
    Such rule allows for variables beginning with alphabetic character (both lower or upper case) or underscore, followed by any alphanumeric character or underscore.
  • Assignment operator - there has to be a way to assign result of an expression into a variable. Therefore and assignment operator is introduced. I used simple '=' symbol, although you are free to use others (another common is ':=')
  • Type - a variable has to be declared. I decided to go C-like way of variable declaration, which means a type of variable has to be put first. Even though the language here does not support different types as of now, I decided to go with standard word 'int' for this one.
  • Statement exit (Punctuation mark) - adding variables without ability to use multiple statements in the language would be boring. For separating the statements a statement exit rule (also denoted as punctuation mark) was introduced - semicolon ';'

These are the bottom level rules, e.g. the rules that are directly resolved to sequence of characters. By adding those we also have to update the rest (e.g. the rules that are resolved to other rules) and add some new rules. Going from top level:


  • Statement - the top level one, not anything exciting about this as the rule is straight forward:
    <stmt> ::= <expr><punct>
    E.g. and expression followed by semicolon.
  • Expression - in exchange from the previous grammar the addition rule was renamed, the expression in new grammar resolves to either declaration or assignment rule. E.g.:
    <expr> ::= <decl> | <assign>
  • Declaration - a declaration rule always declares one variable. There can be only one declaration in the statement (compared to C where declaration can be sequenced using sequence operator), always beginning with type rule, followed by identifier rule. And also it has to be possible to initialize the declared variable:
    <decl> ::= <type><ident> [<assign_op> <assign>]*
  • Assignment - most likely the most complex rule so far. The assignment itself might not sound complex, yet there are two problems around it (both are also related to previous rule). Starting with example for the first problem:
    x = 3;y = x = 7;y = x + 7;y = x + 7 = 3;
    Let us go one by one. In the first case, it is straight-forward - the value 3 is stored in x. In the second case, the value 7 is stored in both: x and y. In the third case, logically one would state that the value of x is added to 7 and stored in y. Wrong! How does the compiler know whether it is going to write value into x or read from it? And what should even happen in the last case?To overcome this, a l-value principle has to be introduced. The value on the right side (when on the right we read) is written into the left side. The only exception is rule number 2, where it is possible to conside both: x and y, to be l-values. The fourth example is therefore invalid. Such rule is going to be:
    <assign> ::= <ident> [<assign_op> <ident>]* [<assign_op> <sub>]^ | <sub>
    So that Assignment rule is either a Sub rule (which is our former expression - e.g. arithmetic expression), or it contains identifiers in form of l-value (linked with assignment operator) with or without one assignment of arithmetic expression in the end. The '^' symbol stands for 'zero-or-one'-times.
  • Sub rule - is a former arithmetic rule, other than name itself, nothing has changed for it.
  • Factor - while Term rule stayed, the Factor rule is extended. Inside parentheses and Assignment is allowed (e.g. there can be l-value again in parenthesis with expression on right side of assignment operator), otherwise a Factor is and integer or an identifier

This concludes all the changes to the grammar. I recommend picking a paper (or writing short script) and attemping to generate some programs using this grammar. Actually the grammar itself does not generate programs unless you add one more rule that allows generating zero or more statements, e.g.:

<program> ::= [<stmt>]*
This way, the grammar is able to generate various applications. For the convenience I'm also attaching full grammar here:
<program> ::= [<stmt>]*
<stmt> ::= <expr><punct>
<expr> ::= <decl> | <assign>
<decl> ::= <type><ident> [<assign_op> <assign>]^
<assign> ::= <ident> [<assign_op> <ident>]* [<assign_op> <sub>]^ | <sub>
<sub> ::= <term> [<add_op> <term>]*
<term> ::= <factor> [<mul_op> <factor>]*
<factor> ::= (<assign>) | <ident> | <integer>
<punct> ::= ;
<type> ::= 'int'
<ident> ::= [A..z _][A..z 0..1 _]*
<assign_op> ::= =
<add_op> ::= + | -
<mul_op> ::= * | /
<integer> ::= [0..9]+

Assignment - Second problem


Did I mention Assignment (and also Declaration) have two problems? By introducing the l-value, it is possible to determine whether that variable is going to be read, or written into. But what about the second one?


The second problem is compiler-related, as before actually setting the value into variable the right part of the rule has to be processed (most of the operators can be solved left-to-right, while assignment is to be solved right-to-left). It is not a big deal, as the actual code for instatiation can be printed into assembly after the compiler processed the rest of the rule.


Compilation


Variable Handling


Before going forward describing some of the changes I did (you can see all the changes in the source code accompanying the article), a discussion about where to store variables is necessary. The obvious answer here should be "in memory", which is partially correct, followed by a question: Where in a memory do we store variables?


The program itself splits memory to two blocks once executed - the block where instructions are stored, where the variables can't be stored, and the block where stack (temporary) variables are stored. Of course, all permanent variables should be stored where temporary variables are.


Temporary variables on the stack are always pushed during the arithmetic computation, but once that is finished all of them are poped up. Therefore all the variables can be stored there beginning with offset equal to 0. Following pseudo-code shows handling of identifier:


Ident(boolean declare, boolean lvalue)
{
    // If the identifier is declared (which means it is preceeded by 'type' token)
    if (declare is true)
    {
        // Store variable in a dictionary (key is variable name, value is offset from base stack address)
        // GetIdent() returns us name of written variable there
        // stackOffset is value initialized to 0 in the beginning
        variablesDictionary.insert(key = GetIdent(), value = stackOffset));
        
        // All we need to do is push value in first register, as first register always store value after
        // computation
        printLine("push.i32 r0");
        
        // And offset stack offset (so we can store more variables)
        stackOffset += 4;
    }
    // When we are not doing the declaration
    else
    {
        // If the variable is l-value, the value is stored in it (and not read), although we still need
        // to keep its value in first register (in case there is statment like 'x = y = 1;')
        if (lvalue is true)
        {
            string ident = GetIdent();
            
            // If the variable name is not inside variable dictionary, there is an undeclared identifier
            // error.
            if (variablesDictionary.contains(ident) is false)
            {
                Expected("Undeclared Identiefier");
            }
            
            // Print the assembly command, note that '[' and ']' are used to determine address. The 
            // register SP is used as reference and and offset is added.
            // Due to push/pop instructions, the actual offset at the moment can be different, determining
            // correct offset and updating that value is left to disassembly stage
            printLine("mov.mem.reg.i32 [sp+" + variablesDictionary.getValueAtKey(ident) + "] r0 ");
        }
        else
        {
            // Actually almost the same as previous function, but different assembly command (not to store,
            // but to read value from memory into register)
            string ident = GetIdent();
            if (variablesDictionary.contains(ident) is false)
            {
                Expected("Undeclared Identiefier");
            }
            printLine("mov.reg.mem.i32 r0 [sp+" + variablesDictionary.getValueAtKey(ident) + "]");
        }
    }
}

As mentioned in example code, the actual offset from stack pointer at the moment can differ (due to using variables in middle of computation), although when going through assembly it is possible to count the offset and update it (these values are going to differ only during the single statement computation - therefore this approach can be also used when introducing more complex control flow and function calling). This task is left to be performed by disassembler.


Multiple statements and statements


Implementing multiple statements in a row, and statements themselves is straight forward. For the sake of completness the pseudo-code for those is added:


// Statement itself compiles single expression and always matches punctuation symbol (semicolon)
// in the end.
Statement()
{
    Expression();
    Match(PUNCTUATION_SYMBOL);
}

// Base compilation function, as long as there are statements in source code, it compiles those.
Program()
{
    while (Look())
    {
        Statement();
    }
}

Right-to-Left


One of the mentioned problems was execution order. While in most cases (addition-subtraction and multiplication-division) the used way to perform the computation is left-to-right, in case of assignment it is right-to-left.


While this problem seem hard to solve, let me demonstrate an example. Assuming we have a code:


x = 4 + 7;

Using the left-to-right compilation, the produced assembly would be:


mov.mem.reg.i32 [sp+0] r0 
mov.reg.i32 r0 4
push.i32 r0
mov.reg.i32 r0 7
pop.i32 r1
add.i32 r0 r1

Where the first line is generated by assignment (assuming variable x is at base stack address + 0 byte offset), the rest is generated by the right side of statement. If we want to switch the order - what about putting the left side into buffer and printing it after the right side has been resolved and printed? That works, of course using one buffer is not enough, but LIFO stack of such buffers is required (due to multiple assignments in single statement).


The actual resolution in the code might be confusing if put here in form of pseudo code, the implementation can be seen in reference implementation done in C++. In the following code example the impact of such change is shown:


// Statement:
x = (y = 4) + 7;

// Variable locations:
// x -> [sp+0]
// y -> [sp+4]

// Which equals to:
'<ident="x"> <assign> <paren_l> <ident="y"> <assign> <integer="4"> <paren_r> <add> <integer="7"> <punct>'

// First the expression in parentheses is solved:
'<ident="y"> <assign> <integer="4">'
buffer   -> 'mov.mem.reg.i32 [sp+4] r0'
assembly -> 'mov.reg.i32 r0 4'
    
// Printing the buffer after what is in assembly yields:
mov.reg.i32 r0 4
mov.mem.reg.i32 [sp+4] r0 

// The expression outside is solved next to:
buffer   -> 'mov.mem.reg.i32 [sp+0] r0'  // Assignment into buffer (as it is l-value)
assembly -> 'push.i32 r0'                // Left operand of addition is pushed (result of parenthesis expression)
assembly -> 'mov.reg.i32 r0 7'           // Move second argument to register 0
assembly -> 'pop.i32 r1'                 // Pop from stack to register 1
assembly -> 'add.i32 r0 r1'              // Add those together
    
// Left side is in buffer, Right side in assembly, printing the buffer after it yields (with previous code):
'mov.reg.i32 r0 4'
'mov.mem.reg.i32 [sp+4] r0'
'push.i32 r0'
'mov.reg.i32 r0 7'
'pop.i32 r1'
'add.i32 r0 r1'
'mov.mem.reg.i32 [sp+0] r0'

Which is correct assembly for given example. Obviously after executing this code, we end up with value of 4 in variable y, and value of 11 in variable x - which is correct status after processing given statement.


Side note: As mentioned in one of the comments, generating an abstract syntax tree (which would be tree of structures), and then allowing to process left-then-right or right-then-left subtree is actually equivalent to this, what is actually done is traversing such abstract syntax tree (represented by the containers in the application), and switching execution order using stack on given sub-tree.


Disassembly


While disassembler in previous article was doing just replacement of instructions with numerical value, and possibly storing arguments as another values - variables complicate it a little bit. First of all it is required to add functions to parse register and offset put in into it - although that is straight forward. What is more interesting is calculating the actual offset. Imagine the following example:


int x = 3;
int y = 5;
x = 4 + y * 3;

The assembly generated by it is:


// Variable locations:
// x -> [sp+0]
// y -> [sp+4]

'mov.reg.i32 r0 3'
'push.i32 r0 '
'mov.reg.i32 r0 5'
'push.i32 r0 '
'mov.reg.i32 r0 4'
'push.i32 r0'
'mov.reg.mem.i32 r0 [sp+4]'
'push.i32 r0'
'mov.reg.i32 r0 3'
'pop.i32 r1'
'mul.i32 r0 r1'
'pop.i32 r1'
'add.i32 r0 r1'
'mov.mem.reg.i32 [sp+0] r0 '

The line where we are reading from variable y is stating to read from [sp+4], basically stack base position incremented by 4 bytes. The problem is, that stack base position is not known during the runtime - only the stack pointer. Stack pointer is offset each push and pop (either increased or decreased, as the operations are only on 32-bit integers, it is increased/decreased always by 4 bytes), therefore a variable counting this offset has to be introduced, allowing to compensate for this.


Note that this is a way how local variables can be stored. On x86 architecture a base pointer also exists (and in future articles on this topic, I would like to introduce procedures and their calling - which would lead to introducing base pointer register), such pointer points to beginning of current stack frame.


Virtual machine


A virtual machine extension reflects modifications done on disassebler. As these two new instructions are introduced:


'mov.mem.reg.i32'
'mov.reg.mem.i32'

Their respective handling is introduced. Their arguments are:


mov.mem.reg.i32

Moves value stored in register into memory at specified address.

  • Integer representing register (pointer to memory where 0x0 is start of memory for application) (32-bit integer type)
  • Integer Offset from that register (32-bit integer type)
  • Integer representing register (32-bit integer type)

mov.reg.mem.i32
  • Integer representing register (32-bit integer type)
  • Integer representing register (pointer to memory where 0x0 is start of memory for application) (32-bit integer type)
  • Integer Offset from that register (32-bit integer type)

For better understanding of virtual machine executing the code, I recommend downloading attached source and looking at Main.cpp file.


Conclusion


While the language supports variable declaration at this point, there is still one more thing missing for making the language full featured - a way to control the flow of the application. Loops and branches - also known as control structures. Adding those would make the language full featured, which means capable of creating literally any application.


As this sounds good, making the language usable needs more steps to take - while the features my vary on personal preference, in general I assume the list is going to look like this (also can be taken as approximate schema for following articles):


  • Control structures (branching, loops)
  • Procedures and their calling
  • Floating point types
  • pointer types
  • Global variables
  • Communicating with game engine
  • Linking
  • Custom data types (structures)

Also, if you made it here, thank you for reading and don't forget to check out accompanying source and executable (x64, Windows binary + Visual Studio 2015 project).


24 Sep 2016: Ready for Peer Review
24 Sep 2016: Initial release

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!

GameDev Protips: 5 Basic Yet Highly Effective Marketing Strategies

$
0
0



Indie developers are mostly brilliant when it comes to constructing their game but when release time comes many are often left with the question: “How do I get people to notice my game?” There really is no secret. The ones that find the most success are usually the ones with the best presentation and marketing alongside a solid core gameplay experience. Here are a few tips on how to get your game out there to the masses the right way.



Get on social media


The number one thing any and all studios should have is a social media presence. Set up a Facebook, Twitter, Blog, Website, YouTube channel, etc… anything and everything that can be used as a tool to broadcast your ideas, get on it! You do not know how many times indie game developers come asking for our marketing services without even a website for their studio or game. It really is super simple to set up, and the time invested in interacting with the community goes a real long way in getting word out about your game. “But I am not very social!” is an excuse that comes to mind when I state this but you need to make the effort to reach out to your community or else who will know about your product? Word of mouth only goes so far, and even then you have to establish the word to begin with.



Have a good design sense


Once you have set up your social media accounts for your studio you should keep it consistent as to what is posted and what imagery you use. You have to brand yourself with an appealing logo that is simple enough to be easily identified yet unique enough to be instantly recognizable. Use a color scheme between all platforms and keep the layouts fairly consistent. I think a lot of people underestimate the importance of a color scheme. Using a complementary blend that is appealing to the eye really gives off a look of professionalism and goes a long way to establishing your presence.



Post, post, post!



Anything and everything about your game, post! People enjoy quality content and the more you post, the more people will be hungry for more content. Post things that tell the players what your game is about, post early concept art (people LOVE concept art!), take some time and create a few short videos showing off gameplay or your story or even behind the scenes interviews with your developers. Post everything you can because the more content you post, the more buzz will be generated by people over your social media platforms in anticipation of your release.



Emphasize your website


So you have your Facebook, Twitter and YouTube accounts up and running, now move on to making your website. This part is very essential in linking all of your social media outlets together on one, readily available space. There is nothing more frustrating to a journalist than stumbling upon the Facebook or Twitter account of a studio that is creating an interesting game only to not have any more information on how to contact them. Your game’s website is the number one place any journalist will go to first when considering your title, so make sure you have at least something they can get at that has all your contact and press information easily accessible. Make sure at the very least you have a contact email address, links to your social media pages, and a press kit that includes all the information a journalist can use without having to ask you questions about your project. Be sure it has screenshots, logos, banners, bio about your studio and bio about your game.



Reach out to the press


This part can be exhaustive, but it is probably the most important job that needs to be done. Create a list of press contacts (websites, bloggers, youtubers, etc…) and send out press releases about your game. When you are closer to release, have some review keys ready to send out to reviewers or even a beta build that can be played by journalists. Nothing is worse for a journalist than when they receive a press release about a game that has no playable build handy. They simply cannot review something that they have not had a hands on experience with.



In the end there is no quick, guaranteed way for getting your game out there to the masses. You have to be willing to put in some extra work in establishing an online presence and interacting with your potential player base. You may be an incredible developer that has created something that is potentially ground-breaking but if no one knows about it then what have you accomplished? Having a marketing plan is vital to any games success and it’s not even that far-fetched to even say in some cases marketing is the key factor to making a game successful. Take some time and research the marketing campaigns of some older games that did find success. See what they did on social media and look at their online presence in different communities. The far majority of the time you will find that successful games become successful not only because of the content of the game, but because of the developers willingness to interact with people and the studios ability to reach a vast audience.



Important Takeaways


  1. Get on as many social media platforms as you and your team can handle
  2. Be consistent in your branding (don’t be afraid to hire some help for logo design)
  3. Once you’re on social media, don’t be afraid to keep posting — as long as what you’re posting is valuable of course. Think… awesome screenshots, epic gifs, and exclusive sneak peeks at the development process.
  4. Don’t neglect your website — again, don’t be afraid to hire some help. A sloppy website is a horrible website.
  5. Reach out to some press. As of this writing, it’s pretty difficult to get coverage if your game doesn’t stand out as something truly unique though. Make sure your game is worth marketing in the first place.

If you’ve found this article helpful, don’t forget to share it with your friends on social media!

Top 5 Most Expensive Game Localization Mistakes

$
0
0


This article was originally posted on Level Up Translation's blog on August 19th 2016.


Gaming is one of the few truly global industries, filled with passionate fans (like us) who really care about their favorite titles. Which means simply doing localization isn’t enough. You have to nail it.


Poor localization can make your games more expensive to produce, hurt sales and create all kinds of bad press. Yet we see publishers large and small making the same mistakes time and again.


Here are five of the most expensive game localization mistakes you want to avoid.


#1: Embedding text into the game’s core files

One of the most costly mistakes we often see is text being hard-coded into the core files. This will include things like the title of your game, menu text, and any dialogue printed on-screen during gameplay.


It can be tempting for developers to embed this text directly into the game’s code – especially if you’re launching in one language first. This is a bad idea, though – even if you don’t have any plans to expand into other languages at this stage.


Instead, you’ll want to store all text as variables in a separate resource file.
This way nobody needs to plough through source code to add translated text into the game. You can simply add the new variable and place the translation inside its own dedicated file. This not only makes future localizations easier for your team but also for the translators you call in.


#2: Cutting corners on translation

This isn’t only a problem for game localization but any project that needs accurate translation.


The fact is, cutting corners on translation only creates more work further down the line – and that means spending more money, too.


Forget machine translation and don’t even think about free tools like Google Translate. Not only are they world away from producing the accuracy you need, they’re a security threat for any sensitive content.


Such translation tools are vulnerable to hackers via your Internet connection – especially via WiFi. These risks are widely publicized but they may not be something you associate with online translation. More worryingly, anything you type in is handed over to the translation provider (eg: Google). It becomes their data and they can do anything they want with it.


Speak to your translation agency about non-disclosure agreements (NDAs). These contracts make sure everything in your game stays completely confidential – so you can relax while translators are working on the localization of your game.


Money-wise, the reality is, if you cut corners on translation, you’ll only end up paying more for it later. We get far too many partners who need to start all over again because they tried to take the faster/cheaper option on translation.


7c9354_7ad3d43d479d44238ff9b02a33d20323~


Take shortcuts and you’ll end up with the kind of mistakes we still mock the arcade classics for – or worse.


#3: Using translators who don’t know your game (or video games at all)

With context being so important in game localization, the more a translator knows about your video game, the better.


We still see too many publishers handing over spreadsheets full of text to translators who basically know nothing about the game they’re working on, or worse, nothing about gaming at all.


Instead, you should seek out experienced video game translators and give them the chance to play your title whenever this is possible. The more they know about the gaming experience you’re trying to build, the better equipped they’ll be to recreate that in another language.


If you can’t give translators access to play your game, then at least get someone in with a track record of translating games in your genre. Be sure to hand over as much information about your title as possible: glossaries, visuals, style guides and any translations you may have from previous releases.


And if your game is about to hit platforms like Playstation 4, Xbox One or Nintendo's consoles, make sure way ahead of submitting your build that it meets Sony, Microsoft and big "N"'s strict naming conventions in all languages, or risk getting it rejected. Here as well, it takes an experienced localization service provider to go smoothly through that phase.


#4: Ignoring cultural factors

Accurate translation isn’t the only goal of localization. You also need to be sure your titles are culturally sensitive to each market – or risk alienating one of your target audiences. Much of this comes down to the actual content of your game: the story, characters and events that take place.


Just ask Nintendo how important it is to get video game localization right. It spent the first quarter of 2016 embroiled in a localization row that escalated into debates over sexism, homophobia, child pornography, slut-shaming, prostitution and a hate campaign against one of its most infamous employees. It was pretty intense.


It all came down to a localization vs censorship debate that peaked with Fire Emblem Fates. The version for US and EU markets was heavily altered from the Japanese original and fans were less than pleased.


Shortly after, there were protests in Hong Kong after the firm decided to alter the name of beloved Pokémon Pikachu. It sparked a centuries-old sentiment of China encroaching on Hong Kong culture – all of which was reignited by the slight change of a name to one character.


7c9354_3448dcdef69b4ac7b5c39d705c57bafd~


Source: Quartz

#5: Thinking of localization as the last step of game development

Perhaps the most expensive mistake you can make with game localization is letting it sit at the bottom of your to-do list. It’s easy to think of this as the last stage of production – but that’s a costly assumption.


A classic example is the humble game description; vital to selling your game but something often overlooked. These few words are your only pitch to convince new gamers – especially if you’re not creating a famous brand title (eg: Star Wars, Final Fantasy, etc.)


All you have to do is visit Google Play to see how little effort many publishers put into these descriptions:


7c9354_0488655e8b934de0bd31618dca70b729~


A lot of words in there but not much that makes any sense.


7c9354_cef20c634f504429bd4dc4f514b2ff24~


And this publisher didn’t even bother trying...


It rarely ends with game descriptions either. Publishers who are happy to settle for this kind of first impression tend to take similar shortcuts in other areas of development.


7c9354_0c79f694d7c14205a81b728bad0377e6~


7c9354_3b31e0ca14f34c1d93684c526635d7ed~


The amazing thing about video game localization gone wrong is that most mistakes could have been avoided with the right planning.


Just go back to our first point about hard-coding text into a game and you can see how poor localization planning makes production costs skyrocket. Or think how many problems Nintendo could have avoided this year by localizing its content during the writing process, rather than leaving it until post-production.


The fact is localization should be included at the very beginning of game development – and you need to allocate enough time and budget for the entire process. This way you can avoid extra workload and costly mistakes further down the line.


So there you have it – the five most expensive game localization mistakes.
They’re all completely avoidable with the right planning in place from day one and the game localization company with the right experience and methodology.


If you like what you just read, there's more for you!
Just follow us for more game localization tips and insights:


Facebook
Twitter
LinkedIn
Got a game that needs to be localized?
Tell us about it! We've got plenty of XP to help you level up!
Level Up Translation - Expert Video Game Localization Services - www.leveluptranslation.com

Procedural Modeling of Buildings with Shape Grammars

$
0
0


Introduction


Improvements in computer hardware have made the reproduction of larger and more detailed urban environments possible and the users are demanding the full use of such capabilities. However, these environments are complex spaces, offering a wide variety of elements and modeling them, using traditional tools, may take several man years since every urban element must be modelled in detail. To cope with such request for greater detail, there has been a shift of focus to procedural modeling systems which, from a small set of rules and coupled with a controlled level of randomness, are capable of generating geometry and textures in an semi-automatic way, relieving the user from the burden of manually producing the models that represent the urban environment.


Take for example the recent title No Man’s Sky that gives us an entirely procedural galaxy to explore. Creating all that content using traditional techniques would probably require thousands and thousands of hours to accomplish, since each and every element would have to be manually modelled. Also, budgets would be through the roof.


That is why we’re seeing procedural content more often. Procedural modeling is a group of techniques that allow more content to be created with less effort, by encoding the content generation into rules or procedures. These rules often exploit some form of repetition or pattern and can be parameterised by random or user-defined values allowing the generation of a large variety of models.


The patterns in these environments often stem from urban planning rules and guidelines with which cities are built. City layouts, for instance, are conditioned by roads and it is common to see grid or radial patterns. Buildings also possess repeating elements in their structure, such as windows, doors or ornaments laid out according to some pattern. Without these patterns, cities would have a chaotic aspect.


As for parameters, one can, for instance, state the number of floors a building has or, even, based on the date of construction, create buildings with different architectural styles. Coupled with randomness it is possible to generate vast amounts of models and, thus, create entire urban environments with ease. This is the true power of procedural modeling.


In this article I chose to discuss shape grammars for two reasons:

  1. They are a familiar and easy concept to understand even for newcomers to the procedural modeling field.
  2. They are extremely easy to implement. The geometric operations are, by far, the most difficult part but they are present in almost every procedural modeling technique.

Although you can find some theory behind shape grammars in this article, I recommend reading the papers from Parish and Muller [1], Wonka et al. [2] and Muller et al. [3] for a deep understanding of this methodology. For an overview of the literature on urban procedural modeling I recommend the survey by Vanegas et al. [4].


Shape Grammars


One of the most typical ways of encoding procedural rules is to use a formal grammar, where symbols execute operations on the geometry, starting with an initial shape. The grammar derivation process, iteratively refines the geometry, adding more detail to the result by replacing one shape by a new set of shapes. This is similar to what happens in grammars in linguistics. So for example the following grammar:


Sentence -> Noun_Phrase Verb_Phrase
Noun_Phrase -> Pronoun | Noun
Verb_Phrase -> Verb Noun_Phrase
Pronoun -> "I" | "You"
Verb -> "like" | "hate"
Noun -> "cats" | "dogs"

can be used to create the sentences "I like cats", "I like dogs", “You like cats” and so on, simply by collecting all terminal symbols.


In the case of procedural modelling of buildings we also have this type of grammatical rules. A rule has a labelled shape on the left hand side (which is often called the symbol) and commands on the right hand side which create new labelled shapes with the purpose of being further processed. Commands usually have parameters controlling how they affect the geometry.


A shape is considered terminal if there is no rule in the grammar that has its symbol in the right hand side and is non-terminal otherwise. Besides the geometry, a shape also has (typically) material information and an oriented bounding volume (called a scope) defining its position, a frame of coordinates and a size along each axis.


The most common commands in procedural modelling of buildings are presented next. Note that you will find these in almost any tool out there.


  • Extrusion: The extrusion command simply extrudes a shape along its normal by a given amount. This is useful for creating the building volume given its floor plan and almost all examples I’ve seen start with an extrusion.
  • Split: This command divides a shape into multiple shapes along one of the scope’s axis by a given amount. Usually, procedural modelling tools allow you to define the amount as an absolute value or as a value relative to the scope’s size and let you chain several subdivisions in the same operation.
  • Repeat Split: As the name implies, this command allows you to repeatedly apply a split to a shape. This is useful to scale the split operation to shapes of unknown size (e.g., floors in a building).
  • Face Split: This one allows you to access different faces of a shape. With this command you can, for example, create different geometry for the different façades of a building. Typically this command allows you to select the resulting shape based on some condition such as the front, right or left faces.
  • Insert: The insert command lets you load a pre modelled asset from file and put it in your procedural model so that you can end up with more complex buildings.

So now you know about rules, shapes and commands. How does it all come together to create buildings? There are (at least) two things you need:

  1. An initial shape (the axiom) with a symbol
  2. A shape grammar with all the rules

The axiom is, typically the building lot and it is often provided by some procedural method that is responsible for creating the city layout along with roads and building lots. However, you can also create it manually or import it from somewhere else such as a Geographic Information System. After you have the axiom your hard work is on the creation of the shape grammar.


When you have both, it’s time to have generate the building geometry. Shape grammars work by replacing a shape with a new set of shapes created by applying a set of commands. So, from any given configurations of shapes, the system finds a non-terminal shape and the rule with such symbol on its left hand side. It then executes the set of commands on the right hand side creating new shapes from the original one which are added to configuration of shapes. This is repeated until there are only terminal shapes. To this point, all there is left for the system to do is collect all terminal shapes and either render them or export them to a file.


So now we’re up to an example. Consider an axiom with the “Lot” symbol and the following grammar. Below you can see an image with the several steps in the derivation.


Lot -> extrude(10) Mass
Mass -> FaceSplit{
    sides: Facade
}
Facade -> Split("y"){
    3: FirstFloor,
    ~1: TopFloors
}
TopFloors -> Repeat("y"){
    1 : Floor
}
Floor -> Repeat("x"){
    1 : Window
}
Window -> insert("window.obj")

The first rule is applied to the axiom shape (top loft in the image) since it has the "Lot" symbol on the left hand side and simply extrudes the axiom by 10 units along its normal, resulting in the top middle image. The symbol “Mass” is associated to the resulting shape. Then, the Mass rule uses a face split to create a new shape for each of its faces and assigns the "Facade" symbol to the side faces.


The Facade rule splits each façade along its y axis (top right image), creating a ground floor 3 units tall and leaving the rest to be repeatedly divided into floors (the TopFloors rule). The ~1 indicates a relative to the scope size which, in this case, takes up all of the remaining façade space. The TopFloors rule uses the repeat split command to divide the shape vertically into floors 1 unit high (bottom left) and, similarly, the Floor rule divides each floor into a tile 1 unit wide (bottom middle image). Finally, the Window rule simply inserts a pre-modelled window asset into each tile as can be seen in the bottom right image.


Attached Image: GrammarDerivation.png

Implementing shape grammars


Of course, I can’t detail every aspect or write every line of code required to build a shape grammar system. Although simple to understand they are still complex systems mainly because of the geometric operations. That said, I can give you some pointers in how to implement them, specially in overall architecture.


The main components you need to implement a shape grammar are the following:

  • Geometry and Material representation: This is how you represent the geometry and materials in your system and you can practically choose any representation you see fit. However, take into account that every type of geometry representation has its advantages and drawbacks. For example, and vertex indexed has a low memory profile and is usually very fast but doesn’t allow you to know which faces are adjacent to a vertex. On the other hand, a half edge mesh allows you to do this, but is way more complex and limits you to two-manifold meshes. Although in practical scenarios I haven’t had this issue with buildings but it might not be suitable for other things.
  • Shape Tree: Possibly the best way to organise the shapes generated by your system is in a tree of shapes. You create this tree during the execution of a command by setting the created shapes as children of the previous shape. Leaf shapes are the next ones to be processed by the system. Moreover, if you later implement conditional execution in your shape grammar, you can do things such as selecting a shape based on its index number which is useful to create patters such as different even or odd floors.
  • Geometry Operations: Here you implement all the operations that manipulate geometry or materials. You will typically need, among others, extrusions, clipping geometries to planes, scales, translations and rotations. Make sure that the operations execute as fast as possible as this can become a bottleneck. I believe it is best to separate geometry operations from the implementation of the shape grammar commands in order to improve reusability (e.g., both split and repeat split use the clipping operation).
  • Shape Grammar: This is where you implement rules in your system. A rule has a symbol, a list of parametrisable commands that generate new shapes with different symbols. In this sub-system you should also implement the shape grammar execution algorithm. To do this, you can simply store all the rules indexed by their symbol (e.g., in a map). Then, you find a non terminal shape that hasn’t been derived (a non-terminal leaf in the shape tree) and execute the rule with the same symbol, adding the resulting shapes as children of the previous shape. You do this until there are only terminal shapes.
  • Shape Grammar Parser: You need to define a syntax for your shape grammar and create a parser for such syntax which creates the rules and respective commands with their arguments.
  • Geometry exporter or renderer: Finally, in order to be useful, you have to either export the created geometry to file or render it. To do this, you simply collect all leaf shapes in the shape tree, apply some post processing if you wish, and write it to disk.

Conclusions


As you can see, procedural modeling is an extremely useful way to generate content for video games. It cuts down both on time and budget and allows you to create a wide amount of models with the same set of rules.


Shape grammars were one of the first methods proposed to generate buildings and they are currently used in both commercial applications and in the research field. Currently there are extensions that add layers [5], free-form deformation [6], non-cubic convex scopes [7], visual languages [8,9] or advanced programming concepts [10,11] among many others. Nonetheless, they still have drawbacks. The most obvious one is that the resulting buildings can be rather repetitive if care is not taken.


Another method, which is (probably) more user user friendly, is to use node based approaches where nodes define operations and edges define a flow of geometries and data between nodes. The field is relatively new and more approaches will definetly appear which will make procedural modeling even more powerful.


References


[1] Parish, Yoav I H, and Pascal Müller. 2001. “Procedural Modeling of Cities.” In Proceedings of the 28th Annual Conference on Computer Graphics and Interactive Techniques - SIGGRAPH ’01, 301–8. SIGGRAPH ’01.


[2] Wonka, Peter, Michael Wimmer, François Sillion, and William Ribarsky. 2003. “Instant Architecture.” ACM Trans. on Graphics, SIGGRAPH ’03, 22 (3).


[3] Müller, Pascal, Peter Wonka, Simon Haegler, Andreas Ulmer, and Luc Van Gool. 2006. “Procedural Modeling of Buildings.” ACM Transactions on Graphics 25 (3).


[4] Vanegas, Carlos A, Daniel G Aliaga, Peter Wonka, Pascal Müller, Paul Waddell, and Benjamin Watson. 2010. “Modelling the Appearance and Behaviour of Urban Spaces.” Computer Graphics Forum 29 (1).


[5] Jesus, Diego, António Coelho, and António Augusto Sousa. 2016. “Layered Shape Grammars for Procedural Modelling of Buildings.” The Visual Computer 32 (6): 933–43.


[6] Zmugg, Rene, Wolfgang Thaller, Ulrich Krispel, Johannes Edelsbrunner, Sven Havemann, and Dieter W Fellner. 2013. “Deformation-Aware Split Grammars for Architectural Models.” In 2013 International Conference on Cyberworlds, 4–11. IEEE.


[7] Thaller, Wolfgang, Ulrich Krispel, René Zmugg, Sven Havemann, and Dieter W. Fellner. 2013. “Shape Grammars on Convex Polyhedra.” Computers and Graphics 37 (6). Elsevier: 707–17.


[8] Patow, Gustavo. 2012. “User-Friendly Graph Editing for Procedural Modeling of Buildings.” IEEE Computer Graphics and Applications 32 (2): 66–75.


[9] Silva, PB, Pascal Müller, Rafael Bidarra, and A Coelho. 2013. “Node-Based Shape Grammar Representation and Editing.” In Proceedings of PCG 2013 - Workshop on Procedural Content Generation for Games, Co-Located with the Eigth International Conference on the Foundations of Digital Games.


[10] Krecklau, Lars, Darko Pavic, and Leif Kobbelt. 2010. “Generalized Use of Non-Terminal Symbols for Procedural Modeling.” Computer Graphics Forum 29 (8): 2291–2303.


[11] Schwarz, Michael, and Pascal Müller. 2015. “Advanced Procedural Modeling of Architecture.” ACM Transactions on Graphics 34 (4): 107:1-107:12.

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>

Advertising Your Game – Playing fairly when it comes to sponsored reviews

$
0
0


In July this year Warner Brothers were wrapped on the knuckles by the American advertising watchdog, the Federal Trade Commission (the FTC). The FTC found that Warner Brothers had failed to adequately disclose to YouTube users that it paid influencers on the social media site "hundreds to tens of thousands of dollars" in return for those influencers to create sponsored videos that promote the game only in a positive way. The game in question was Warner Brother’s 2014 multi-platform release “Middle-earth: Shadow of Mordor”.


Whilst the game was relatively well acclaimed across mainstream news outlets, it received a lot of unwanted attention for its ‘shady’ advertising practices. In the settlement handed down by the FTC, although the games publisherwere not required to pay fines, it was asked to “provide each Influencer with a statement of his or her responsibility" to disclose endorsements clearly when engaged by Warner Brothers in the future.


A UK view – does the same apply?

In the UK, sales promotions and advertising are regulated by two bodies, the Advertising Standards Authority (ASA) and the Committee of Advertising Practice (CAP). The ASA is the independent regulator for advertising and CAP is responsible for writing and maintaining the UK Advertising Codes and providing advice on those rules.


Whilst not related to the gaming industry, the ASA has previously ruled against advertisers running promotional videos masked as influencer opinion, most notably in 2014 when it banned a series of videos called “Oreo Lick Race” which were actually adverts for Oreos biscuits.


Whilst there were statements from popular UK YouTubers including Phil Lester and Dan Howell (who have over 3.7 million subscribers on their YouTube channel) such as “Thanks to Oreo for making this video possible” and links to the Oreo promotion in the description, the ASA held that “…because the statements did not fully establish the commercial intent of the videos, and because no disclosures were made before consumer engagement with the material, the ads were not obviously identifiable as marketing communications.”


Surely this was also contrary to YouTube’s terms of service?

Whilst YouTube permits product placement in its videos, it always caveats this with statements such as “[you] will also have to comply with any applicable laws and regulations”. So in short, yes it was contrary to their terms of service.


However, there are also specific rules about placing product placement videos onto YouTube’s platform. Product placement can be notified to YouTube by “checking the appropriate box in your video's Advanced Settings tab.”This allows YouTube to ensure that they do not fall foul of their own commercial arrangements (for example YouTube playing adverts forcompetitor products on your video, which may breach their contracts/terms with their other advertisers). Failing to do this could lead to your video being removed without notice, which may not be ideal if you have already received a large amount of engagement from followers and subscribers to your channel.


If you are looking to add your promotional video onto another video platform (or a social media platform that allows video such as Facebook), you should also check those specific terms of use in respect of promotional videos as, although they are likely to be similar, will vary from site to site.


Respecting the rules – follow the guidance

If you are a developer or publisher looking to publish games and you want respected YouTubers to play and promote your game to potential customers, you should always follow both local advertising guidance and the content host’s terms of service. CAP has specific guidance on Advertisement Features (such as the Shadow of Mordor video) and is defined as an “announcement or promotion, the content of which is controlled by the marketer, not the publisher, that is disseminated in exchange for a payment or other reciprocal arrangement”.


Where this is the case, you will need to ensure the following:


In respect of identifying the video as an advertisement feature:

  • Marketers and publishers must make clear that advertorials are marketing communications, for example by heading them "advertisement feature" (as typically advertising features still follow the style of the influencer’s own videos)
  • The term ‘advertorial’ should be avoided because it does not make sufficiently clear to the uninformed reader whether the feature is an advertisement or an editorial.

In respect of the content of an advertisement feature:

  • “Marketing communications must not materially mislead or be likely to do so.”
  • “Subjective claims must not mislead the consumer; marketing communications must not imply that expressions of opinion are objective claims.”This would typically be by only highlighting the positives of a game and not the bugs, glitches and overall deficiencies.

Conclusions

As with any dealings with individuals, businesses including games publishers should be absolutely clear when they are paying an influencer on social media to promote a video game in the style of a normal video by that influencer such as a play through or play review. If in doubt, honesty is the best policy when it comes to advertising!



Article originally published: https://www.wrighthassall.co.uk/knowledge/legal-articles/2016/09/21/advertising-your-game-playing-fairly-when-it-comes/


Intermediate Advanced Mercurial DVCS (Hg) tips

$
0
0

Why use Mercurial? Why use command line tools?


Mercurial (Hg) is a powerful and sleek distributed version control system. It's quick and gives you the ability to do anything you'd ever want with source control. It can do anything you'd want from a DVCS, and easy and quick. It is an extendible platform because it provides a base command set while more advanced commands have to be explicitly enabled. It is made to be user-friendly and makes sure you know what you're doing. Some user-made extensions can even be downloaded online and can become a part of your standard workflow. With that said, which extensions to choose and where to use them becomes a user's preference. Over the years, it seems Hg has integrated a large part of best extensions into the core distribution and all you have to do is enable them. The installation of other extensions is really simple; you just place a Python file somewhere and point to it in a config file.

There are 2 main GUI clients for it on Windows, SourceTree, and TortoiseHg. First has been my personal favorite for a number of years but recently I have fallen out of love with it – the GUI changes made by Atlassian I personally don't find appealing and worse, some features had to be removed because of it (at the time of writing, they seem to still be catching up). TortoiseHg even seems more powerful, and I've found some features missing in SourceTree (like the ability to compact unpublished history between selected changesets), but the style of its GUI is not really appealing to me.

More importantly, to be able to actually use these options users have to first understand what these commands do and have them memorized to the point where it's instinctive to use them in everyday situations. This comes back to command line because these commands are best learned and explored where they have originally been conceived – in the command line. Additional options cannot be provided in the GUI, and while GUI seems quicker at first, the command line alternative, styled properly, can be just as appealing.


Proper installation:


This whole article is based on Windows usage. You have a couple of options:

1. Download only the executable from the Mercurial website: this is fine and will give you the latest version. However, there is no GUI with it.

2. Download SourceTree and use Hg from the terminal that comes with it. It does not add the path to hg executable to the Path system variable, so you have to do it manually. Also, you will not get the most recent version, depending on which version of ST you have installed. The best version of ST for windows, in my opinion, is 1.7.0.3, but it comes with an outdated version of hg. You can tell it to use the ‘System Mercurial’, whatever other installation you have, but then some things won’t work (like hgflow) without manual fiddling.

3. Download TortoiseHg. It is useful with a Visual Studio extension that I will explain below. I really haven’t used it much because of the archaic interface, but it has its following.

For beginners, I would actually recommend both (2) and (3), ST for everyday use and THg for integration with Visual Studio. To check which hg you have installed, type in: `where hg` and you will get a path to hg executable. If you don’t get anything you need to add it to your Path.

If you use Visual Studio in your development, you may want to have an extension for it which helps a bit with Hg repositories. There are a couple of options, but the best one is VisualHg2, as here: https://bitbucket.org/lmn/visualhg2. This version actually looks good. I would recommend opening a small window with status and docking it in a corner: it shows you which files are changed and provides some common options in the context menu. However, it relies on you having installed THg previously, thus the recommendation and all the operations chosen from VS menu will be diverted to it.

Besides graphical tools, for learning, it is best to use the command line. I am also not a fan of the default Windows cmd console, so I strongly recommend installing a terminal emulator such as ConEmu. That is one of the more useful free tools you can install. Highlights include: “knowing” if you want to select a line or a square space, automatic copy, allowing paste with Ctrl+V, multiple tabs split to sides and bottom, user themes. Just install it and select the default theme, with Consolas 11pt font, and it will look and work great. You also want it integrated into the Right-Click context menu, which it isn’t by default. You do that by going into Settings > Integration and clicking Register on the top option.

Here is how I use these tools. I open a VS project and Right-Click on the Solution “Open Folder in File Explorer”. This opens the folder where the project is. There I Right-Click and chose “ConEmu here” and I can command line from there really quickly. The same works from within Unity, with the "Show in Explorer" option. All the other external tools are optional, but I still use SourceTree sometimes.


Where to learn it:

It is best to learn from daily usage of these tools once you get the basic concepts, which you test on a test project. For basic concepts, there are 3 authoritative resources: http://hginit.com/ for complete beginners, this book: http://hgbook.red-bean.com/ which will tell you almost everything you need to know for the start, and you also need to understand branching. Once you have that, you’re not missing out, even if the book is pretty old. After the basic concepts, it’s pretty much StackOverflow and Googling when you need something, you know the drill.


On to the actual content, what do I do next:

You set up all options in mercurial.ini, which is found in your %UserProfile% folder. Once you make changes, you don’t have to restart anything or load anything, they will be used on the next command run. The file is separated in sections, where each section is determined by a heading like this [extensions].

So, let’s walk through all the extensions I use:


extdiff=

This is critical. It allows you to specify which GUI tool to use for merging conflicting code files. I would personally recommend CodeCompare, look around the site for tutorials about setting it up.


shelve=

Shelving takes files that status reports as not clean, saves the modifications to a bundle (a shelved change), and reverts the files so that their state in the working directory becomes clean.

To restore these changes to the working directory, use hg unshelve; this will work even if you switch to a different commit.

Let’s say you are working on a specific feature, but you don’t know exactly how to go about it. You try one solution, but you bump into a wall. Now the other solution seems much more appealing. Do you remove all the changes in your code, risking to have to redo them again? Do you commit and this becomes a permanent part of repo history, potentially confusing your teammates? No, you do a shelve operation. This feature for some reason wouldn’t work in SourceTree (at least for me at the time of writing!) and it is so nice that it is worth exploring the command line for.

color=

This colorizes the output of most commonly used commands, really handy with diff and status. Use this one, it makes things really readable.


purge=

This cleans up all the files in the repo that Hg doesn’t know about (haven’t been added to tracking yet). This tends to be really useful in everyday work.


churn=

This command will display a histogram representing the number of changed lines or revisions, grouped according to the given template. Statistics are based on the number of changed lines, or alternatively the number of matching revisions if the --changesets option is specified.

This is one is fancy but optional.


hgext.mq =

Mercurial Queues are critical when working with patches. They are explained in detail in the book I recommended above, and are a bit advanced but useful when you need them. You can’t do this with the GUI.


rebase =

This is a history editing operation, which means it’s advanced (and evil). You can certainly create a mess with it, but you can, for example, join a couple latest commits before pushing them, which is a common use case. It also serves as a base for some other commands such as hgflow and histedit, which automate a number of steps for specific use cases, so I would recommend them instead for these.


hgflow=C:\Program Files (x86)\Atlassian\SourceTree\extras\hgext\hgflow\hgflow.py

HgFlow is an extension that implements gitflow ( http://nvie.com/posts/a-successful-git-branching-model/ ) Let me first say that for GameDev this branching strategy is a bit of an overkill. In fact, there are multitudes of articles online about alternatives to gitflow. What I think works best for GameDev is a variation of GitHub Flow (http://scottchacon.com/2011/08/31/github-flow.html ), as explained in my article here.
If you do decide to use it, it works out of the box in SourceTree, and if you want to use ST with latest Hg installation from the command line, you supply the path to the latest hgflow.py file in your config file. You can see my path in the example code above.

Side note: if you want to use the latest Hg with SourceTree 1.7.0.3 you’ll have to get the latest hgflow.py to the path listed above and it will work with the GUI.


histedit=

What histedit does is:

- 'pick' to [re]order a changeset

- 'drop' to omit changeset

- 'mess' to reword the changeset commit message

- 'fold' to combine it with the preceding changeset

- 'roll' like fold, but discarding this commit's description

- 'edit' to edit this changeset

It is powerful, but you have to know working with editing history is no picnic. There are cases where you chose to do some of these things, and this is where knowledge of tools like this comes in handy. It is easy to do what you want to do: a default text editor will pop up and you will get a choice of what to do through it.


Aliases:


This is my favorite part, aliases make things so easy and quick, and allow for nice customization. Aliases are defined in their own header [alias], they start with the name of the alias followed by ‘=’ and then the parameters that you’d normally supply to hg. If you start an alias with ‘!’ then that alias is instead a System command, and what follows you’d normally type in the shell you’re executing the hg command from. This means the aliases you use may not be cross platform. You don’t even have to type the whole command/alias every time, just the distinguishable part.


clean = !hg revert --all --no-backup && hg purge

Note: It's signs for (and) and (and) above. The editor here seems to have converted it...
Hg already has an alias clean that is a synonym for purge, but I decided to steal it. This creates no problems and ‘just works’. This alias has 2 parts: first, it reverts all files to the state of the last commit; second, it erases all the files found not known to Hg. The commands are separated by ‘&&’ which means you’d normally do something like this in 2 steps. Also, note the no-backup option. This will really clean up the working directory, so you’ve been warned.


pushbranch = push --rev .

In Hg, as opposed to Git, you push all branches when you do a push. This may not be what you want (though it normally is). With this alias you just push the branch you’re currently on.


slog = log --template "\x1B[4m{pad('({rev})',6,' ',True)} \x1B[5m{node|short} \x1B[8;9m{word(0, '{date|isodate}')}
{word(1, '{date|isodate}')} \x1B[6m=> {author|user} {ifcontains(rev, revset('.'), '\x1B[8;9m@', ':')}
\x1B[7m{desc|firstline} \x1B[8;9m[{branch}]{if(tags, ' [{tags}]')}{if(bookmarks, ' [{bookmarks}]')}\n"

Note: the whole command is a one-liner.
slog is my personal beast! It’s a shorthand of short log. The original log command produces quite line-heavy output, so I prefer output like this:

slog.png

Fancy, hmmm?

You would have to take a look at the templating mechanism to understand what this actually does, but the more confusing part are terminal codes starting with \x1B. This presumes you’ve enabled the color extension as explained before. After the ‘[‘ sign the code will determine the color. You can read more about terminal codes here: https://en.wikipedia.org/wiki/ANSI_escape_code

This is probably my #1 favorite hg command now, and you limit the output like this hg slog –l 5. I have limited the output already with the -l 15 you can see above, but if you specify it in your command it will take precedence.


glog = log -l 15 -G --template "\x1B[4m({rev}) \x1B[5m{node|short} \x1B[8;9m{word(0, '{date|isodate}')}
{word(1, '{date|isodate}')} \x1B[6m({author|user}) {ifcontains(rev, revset('.'), '\x1B[8;9m@', ':')}
\x1B[7m{desc|firstline} \x1B[8;9m[{branch}]{if(tags, ' [{tags}]')}{if(bookmarks, ' [{bookmarks}]')}\n"

Note: the whole command is a one-liner.
glog – the graphical log is even better:

glog_short.png

You can also see ‘closed branches’ displayed differently on the tree. This arguably replaces any need for a visual tool in most cases (depending on the complexity of your workflow). Another critical alias.


blame = annotate --user -number

A small optional alias that shows who actually changed which line of the supplied file. SourceTree has this option, but I find this really quick, if not necessarily frequently used.


noorig = !del /S/P *.orig

When you have merge conflicts you may leave .orig files laying around after you do the merge. This a shortcut on system command del, and it will prompt you for each individual file and search subdirectories. You would call it like `hg noorig`.


here = log -r .

Just a quick alias to show you the current commit you’re working off of. You could also change it for `here= slog -r .` if you liked that compact output from before, but I don’t see much point in this case.


plog = log --style=compact --patch -r .

As in patch-log. It shows the ‘patch view’ of the last commit relative to the commit before it. The path view is very useful, it shows differences very colorfully.


qlist = !echo \033[34m Series: | cmdcolor && hg qseries && echo \033[95m Applied: | cmdcolor && hg qapplied

Note: It's signs for (and) and (and) above. The editor here seems to have converted it...

hg_qlist.jpg

To get this to work you need to download the small utility from here (https://github.com/jeremejevs/cmdcolor) and place it in your Path. What it does is it allows for the same coloration we mentioned in the templating alias, but the cmd does not actually support that kind of coloration when output is printed to it. It does work, but the range of options is somewhat limited. Here is a list of colors with codes:

colors.png

Beyond that, it simply joins the outputs of 2 commands used together commonly when working with patches.


upsafe = update -check

When changing the current working directory, unless the option –check is supplied it will try to carry over the unclean content into the new commit. This seems somewhat out of line with Hg’s “safety first” policy because in Gamedev environments it can create a bit of havoc. It’s better to just use `hg ups` to be safe and let Hg warn you if there are any outstanding changes.


fa = forget "set:added()"

As in forget-all. If you added a range of files by mistake, this is the quickest way to “un-add” them all.


stnm = status -X "**.meta"

Unity’s .meta files are a bit annoying when you have to look at the file system. They also create some visual clutter when reviewing changes either in SourceTree or from the command line. This is a quick shorthand to exclude the .meta files from the output of the status command.


gst = !hg status | PathToTree /s /c | cmdcolor

Now for the real kicker, graphical status. For this, you would need my little utility downloaded from here: https://bitbucket.org/sirgru/pathtotree/overview and also the cmdcolor program mentioned before (optional).

path2tree.png

Instead of showing the linear list of files in the output of the status and building the tree in your head, why not let the computer display the tree? This is especially useful to us in GameDev because we tend to have somewhat deep hierarchies of folders. You can read the details about the utility on Bitbucket, so I won’t repeat it here.


gstnm = !hg status -X "**.meta" | PathToTree /s /c | cmdcolor

This combines the outputs of the last 2 aliases: it omits the .meta files and builds the tree.


close = commit -m "Closed branch" --close-branch

When you do feature branches, at some point you'll - of course - want to merge them back into the original. If you don't close feature branches first, they will show in the output of hg heads and hg branches, This may not be what you want, so you have to manually close the branch, preferably before merging it back, by creating another commit with --close-branch option. This option is a shorthand for such boilerplate commit.


Bonus tip:


When you rename a file tracked by Hg, it needs to know it’s the same file and not deletion + addition if you want to keep its history. This is determined by ‘similarity factor’: if it’s just the rename of the file it is 100% similar. However, when working with Unity it’s never just a simple rename: if you rename the source file you have to rename the class too because it would not work otherwise. SourceTree seems to be stuck at 100% similarity and I haven’t seen the way to change that. You need to do something like `hg addrem` and the system should automatically use some similarity lower than 100%. To achieve that, you put in this alias:

[defaults]
addremove = --similarity 90
Note: its '[defaults]'.


Bonus tip 2: Setting up pre-hook to not allow commits when there are unknown files:</h2>

By default, Hg will not warn you about the files created, but not yet added. This can result in a situation where you have created a commit, but that commit does not include newly created files. Everything seems fine, but the push is incomplete.... and you transfer to a new machine and you're missing files, or your colleagues are confused. This happened to me a couple of times, so I looked for ways to avoid it in the future. Admittedly, it's not hard to check for status every time before a commit, but you can still forget it sometimes. I usually dealt with this with a com --amend option after realizing I forgot to hg add, but there is a saying "if something bothers you more than 3 times within a week, make sure to fix it." So, here's how.

First, you will need my command line utility. It's a trivial thing that only returns a status code based on whether there was some standard input piped in. AnyStdIn.zip This will only work on 64-bit OS-es (Windows). Make sure it's on your Path system variable.

Second, you need to modify your .hg/hgrc file inside the repository that you want this hook to be relevant to. I prefer to have this for all repos, so I put it inside mercurial.ini file. What this does is creates a "hook" that will fire before the commit event, it will list all "untracked files", and if there are any they will be piped into AnyStdIn.exe, which will return status code 1 and abort the commit.

[hooks]
precommit.No_Untracked_Files = hg st -u | AnyStdIn
Note: it's '[hooks]'

And... that's it. If there are untracked files, the message will be something like:

abort: precommit.No_Untracked_Files hook exited with status 1

If for any reason you'd want to avoid this check you'd have to temporarily comment this hook's line.


Conclusion:


I hope you have enjoyed this article and tried these commands. Reading about these things is really easy, but in heat of development, problems of this nature are really not welcome. That’s why it’s important to be properly prepared and practiced when it comes to these things, and have a standardized, reliable, understood workflow in your GameDev shop.

If you're finding this article helpful, stop by here where I have more simple gamedev tips and tutorials, and consider our asset Dialogical on the Unity Asset store for your game dialogues.</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.

How to implement save/load functionality for games with levels

$
0
0

Introduction


Some games don't have levels. Open world games, flappy bird, ..and can't think of another example right now, but some games have levels. In this article, I will talk specifically about games where the character can move between levels, interact with objects around him and save the progress. Later he can load saved game and continue where he left off.

stock-vector-video-platform-game-interfa

Requirements


First, let's write down the requirements that define our game:
- the game has multiple levels, levels have static (inane) and moving (interactive) parts.
- the player starts a new game at level 1 but can load the game at any subsequent level
- the player moves between levels mostly forward, but he can go back to previous levels and he can jump between levels with some kind of teleport device. For example, on 5th level there is portal that takes him back to 2nd level, exactly the part of 2nd level that couldn't been accessed before.
- a level has multiple entry and exit points. This means if you go from level 3 to level 4 you appear by the river at the cave entrance. But if you go from level 8 to level 4 you appear in front of traders house. So level 4 has two entry points.
- the player can save position anywhere at any moment and load that saved position from anywhere in the game. if player exits game, unsaved progress is lost.

This is how we define level:
- it is the environment around the character with interactive pieces.
- the player can change the state of interactive pieces, closed doors are left opened, aroused levers are pulled down, healthy living monsters are now not so well, coins are gone...
- the player can not change static environment: background, props, ambient music, things that stay the same no matter what you do on that level (some games allow player to destroy background environment, leaving it completely changed - it's ok it just have to be taken into account as an interactive piece as well).

In this article, I will discuss a simplified version of such game where level only has 10 colored balls and static background. Each level has a different background, but it can not be changed by the player so no need to save it. The player can move the balls around and paint them different colors so we want to be able to save these changes. We are using this simple scenario just to demonstrate the save/load ecosystem without adding the complexity of level itself.

unique-design.gif


How do we code it?


I'm going to use some pseudocode in this article that sometimes resembles JavaScript and json, but it's not the code you can copy-paste and run. First, we define Level class/object with these fields:

LevelObj
{
    //simple number
    id

    //image path
    background

    //position of balls and their color
    array levelBalls[{position,color}]

    //position of exits and where they lead to
    array levelExits[{position,idOfNextLevel,idOfNextLevelEntrance}]

    //position of player and facing upon level entrance
    array levelEntrances[{id,position,rotation}]
}

Let's say our game has 20 levels; we are going to create 20 objects that describe initial state of our levels (where the balls are, what is their color, where are level exits and entrances). In real life, we would store these in 20 different files, but for our very simple example we will use just one array with 20 elements:

gameLevelsArray = [

//level 1
{
    id:1,
    levelBalls:[
        {
            "position": {"x":2, "y":3},
            "color": "red"
        },
        {
            "position": {"x":4, "y":6},
            "color": "red"
        },
        {
            "position": {"x":9, "y":9},
            "color": "green"
        },
        ...
    ],
    levelExits:[
        {
            "position": {"x":3, "y":3},
            "nextLevelID": 2,
            "nextLevelEntrance": 1
        },
        ...
    ],
    levelEntrances:[
        {
            "entranceID": 1,
            "position": {"x":1, "y":2},
            "rotation": 90
        },
        ...
    ]
},

//level 2
{
    id:2,
    levelBalls:[...],
    levelExits:[...],
    levelEntrances:[...]
},

...
];


Globals:
- currentLevelPointer //initially null
- gameState //initially GAME_STATE_MAINMENU;
- arrayOfVisitedLevels //initially empty, but we push levels to this array as player visits them

The player starts the game at Main Menu, and there he can choose "New Game" or "Load Game." We splash the loading screen and in the background load assets for either first level, or some saved level and position.

Let me explain what arrayOfVisitedLevels is about. When the player starts the game for the first time, he appears on the first level. He can then move to other levels: second, third, fourth, all without saving the game. And if he decides to go back a level, we want him to see all the changes he made on those previous levels, although he didn't hit the Save button yet. So this is what arrayOfVisitedLevels does, it holds all visited levels (and their changes) in RAM, and when the player hits the Save button, we take all these levels and store them to permanent memory and empty the array. So when the player moves from level 4 to level 5 we have to ask these questions:

- Is level 5 in arrayOfVisitedLevels? If yes it means player was just there
- If not, is this level saved on disk? If yes we want to load it.
- If not, the player never went to this level before, so we load its initial state from our gameLevelsArray.

lvl2screen2.png

Below is what level loading could look like. This function is called when the player is just starting a game, or when changing levels while playing the game.

//this function takes parameters
//id - what level we are going to
//entrance - what entrance we shall appear at
function loadLevel(id, entrance)
{
    gameState = GAME_STATE_LOADING;

    //if game hasn't just started, we need to cleanup previous level data
    if(currentLevelPointer != null)
    {
        //save current level - here we just push pointer to our level object to an array
        arrayOfVisitedLevels.push(currentLevelPointer);

        //clear current level - we want to render new one
        //for example this could delete objects from the 3d scene
        clearLevel(currentLevelPointer); //this function will have access to array of balls on that level and erase them, also the background
    }

    //if we are entering level that we already visited
    if(levelAlreadyVisited(id) //check arrayOfVisitedLevels to see if id is in there
    {
        //we get the level from array of visited levels
        //big idea here is that all the changes player made to this level are still in this object in memory
        nextLevel = getLevelFromArrayByID(arrayOfVisitedLevels,id);
    }
    else if(levelIsSaved(id) //check to see if level is saved
    {
        //we get the level from permanent storage
        nextLevel = getLevelFromArrayByID(savedLevels,id);
    }
    else
    {
        //get level from array of game levels - these are default unchanged levels
        //in real life we would load level from file here
        nextLevel = getLevelFromArrayByID(gameLevelsArray,id);
    }

    //now that we got the level we need, lets draw it
    loadLevelAssets(nextLevel);
    showLevel(nextLevel);

    //place player at given entrance
    player.position = entrance.position;
    player.facing = entrance.rotation;

    //remove the loading screen and start the game loop
    gameState = GAME_STATE_PLAY;
}



Game Save


In this example, we don't address how the player moves around and changes the ball's color and position, but he does and he is satisfied with what he's done and now he wants to save it. Let's consider different saving scenarios:
- The player starts a new game, moves through three levels and then press save.
- He then fiddles some more on the third level and goes back to the second level, and presses save there again.
- After that, he goes back to the third level, then fourth and fifth and finally saves again before exiting the game.

We want to have 5 levels saved so that when the player loads the game he can go back and see those levels exactly as he left them.

While the player was playing we decided to hold visited levels in a dynamic variable, in memory. When he presses save, it would be nice to store those visited levels in permanent storage and release dynamic objects from RAM. So first save is pretty straight forward - he hits the save - we save three levels, and release first and second level from memory (the third one is still being used). When the player wants to move to the second level again, we have to check first if we have that level in RAM, if not we have to check if that level was visited before, and if it is - we load it from saved file. So now player wants to hit save button second time. He is at the second level but has third and second changed a little, so we have to save that too. If he saves over the same game, we can overwrite those levels in saved file. If he saves new game slot, we have to keep previously saved data in case he wants to load that first saved game later, so we create new save file, but what do we put in second save file - just second and third level or all levels from the start? By the time he hits save button third time, we understood we need to go back one step and discuss save position some more.

Save slots


Some games have checkpoints for saving progress. On the level, there is some prop that player needs to approach to save the progress. If he dies, the game will automatically return him to last saved position. This is equivalent to one saving slot that is overwritten each time. Some games have multiple save slots, which allow you to name your saved game and then later overwrite it, or create a new one. When you think about it, saving each time to new game slot means last saved game should have all the data from previous saved games. We could make last saved game save only what is changed between a previously saved game and now. The differential approach means smaller save files, but we must traverse through all previously saved game files when we are looking for some older level. Alternatively, last saved game could have all the levels accessed (changed) from the game start.

Aladdin-Level3-AgrabahRooftops.png

Now the fun starts. Imagine the player has 20 saved games, and more than half of the game finished. And then he decides to click 'New Game.' He (or his brother) wants to start the game from the beginning, and after 5 levels hits the save. Now whether you have differential or all-in-one approach, this saved game must be somehow separated from all others. And not just the "New Game," even if player load some older saved game and starts from there - he will take a new path from that point and branch into a parallel adventure. When he keeps saving games on this new path, the differential approach must be able to trace back previous saved games not just by date, but some smarter linked list mechanism.

What I like to do here is create stories. Each time player starts a New Game he starts a new story. Each time he branches out from older saved game he creates another story. A story is a collection (linked list) of saved games that traces back to the beginning. Even if you make a game with one save slot (checkpoint use case) - you can use one story (in case you want to change it later). One save slot version has only one story. It can have numerous save nodes, but they are in a straight line. Load always loads the last one. Start from beginning starts a new story, and the previous one is deleted.

In this post, I will only show the scenario with one story. You can then do multiple stories as your own exercise, haha.

Here is example save function:

saveLevel()
{
    //here are some hints not related to our simple ball game:
    //save player stats in that moment: xp, hp, strength...
    //save player inventory, and also what he has equipped
    //these are level independent data, but needs to be saved each time
    //save player position and rotation

    //save what is in array of visited levels
    for (var i=0; i<arrayOfVisitedLevels.length; i++)
    {
        var levelId = arrayOfVisitedLevels[i].id;
        var levelToSave = getLevelFromArrayByID(arrayOfVisitedLevels,levelId);

        if(levelIsSaved(levelId)) //if this level is already saved, in one story we overwrite it
        {
            overwriteSavedLevel(savedLevels, levelId, levelToSave); //copy balls data to saved level object
        }
        else
        {
            addSavedLevel(savedLevels, levelId, levelToSave); //push new level object in savedLevels
        }
    }

    //now save current level, again repeating check if level is saved
    if(levelIsSaved(currentLevelPointer.id)) //if this level is already saved, in one story we overwrite it
    {
        //copy balls data to saved level object
        overwriteSavedLevel(savedLevels, currentLevelPointer.id, currentLevelPointer);
    }
    else
    {
        //push new level object in savedLevels
        addSavedLevel(savedLevels, currentLevelPointer.id, currentLevelPointer);
    }

    //now persist our saved data to file or database:
    storeSavedDataToPermanentMemory(savedLevels);

}

So I'm using savedLevels here as some kind of preloaded array of saved games, and I edit values in this array first before storing it to persistent memory. Even if your levels are small like this, you don't need this preloaded array of saved games, but work directly with data from file/database. I just thought this would make save logic easier to understand.

At this point, bugs like "Go to next level, save, load, pick an item, go to the previous level, save, load - drop an item on the ground, save, load - ITEM IS GONE!" start appearing. It is getting exponentially harder to reproduce bugs so you had better create good test scenarios that cover as many use cases you can think of, and get the QA guy repeat them until his eyes bleed.

But then you might want to complicate things some more with a little save/load optimization.


Optimization


You all played a game where loading screen was taking forever. Sometimes it even kills the joy out of playing. Sure you could always blame the computer being old, but sometimes the developers can do a little extra to make things snappy.

Imagine saving a game on level X, after the save you don't move to another level, you don't play for long and change many things, you just move one ball a little bit, change your mind, and hit load again. What you want to see is that ball moved back to saved position and nothing else. It's just a tiny little change, how long should it take? Well, if we look at our functions above, we are calling clearLevel(currentLevelPointer), then loading level from savedLevels and calling loadLevelAssets(nextLevel), followed by showLevel(nextLevel). So basically clear everything and load and draw from the scratch. It's a safe path, but it's not a superb solution. We can do many things to avoid this overhead, and I will show you one thing that I like to do.

screen02.png

I like to make an additional check if level to be loaded is same as current level.
If it is, I don't want to erase everything and load everything from scratch - it's already there on the screen. I just want to rearrange dynamic objects to their saved state, and the user will get the position he saved.

In our little example, I get the ball's position and color from saved data and move them back to saved state. In a more complicated level, I would also load player health, experience, monsters, container content, and everything else that can be changed, but it is still on the light level of changing properties and position and not doing the heavy loading of models and pictures again. This is why I don't release the monster from memory right after killing it, and I just make it invisible. Some games could not afford such luxury and they would have to do some reloading, but still not all. All static content is there, loaded, visible on the screen, whether it's a background image or 3d models of mountains and trees.

But as the game complexity grows, this little optimization will make you pull your hair out. Those will be the parts of your code that you don't remember how they work anymore, and when the cobwebs and dust cover those functions spooky variables will stare at you from the dark asking for sacrifices.


Time to start


After all said and done, I will add what needs to be done when New Game is started. This can happen during gameplay, so we might need to clear some stuff from the screen. Again, if the player hits New Game on the first level, we might optimize to avoid level reloading. This is what our new game function would look like.

function NewGame()
{
    gameState = GAME_STATE_LOADING;

    //get saved levels into practical array variable
    savedLevels = restoreSavedDataFromPermanentMemory();

    //erase this array
    arrayOfVisitedLevels = [];

    //check if this is a fresh start, or player is already progressing through the game
    if(currentLevelPointer != null)
    {
        //clear up, make a fresh start
        clearLevel(currentLevelPointer);
    }
    else if (currentLevelPointer == 1)
    {
        //optionally do optimized reload here
        reloadNewGameOnFirstLevel(); //just move the balls and player back to starting position.

        //remove the loading screen and start the game loop
        gameState = GAME_STATE_PLAY;
        return;
    }

    //get level from array of game levels - these are default unchanged levels
    firstLevel = getLevelFromArrayByID(gameLevelsArray,id);

    //now that we got the level we need, lets draw it
    loadLevelAssets(firstLevel);
    showLevel(firstLevel);

    //place player at given entrance
    player.position = firstLevel.levelEntrances[0].position;
    player.facing = firstLevel.levelEntrances[0].rotation;

    //remove the loading screen and start the game loop
    gameState = GAME_STATE_PLAY;
}

Remember, when the player hits New Game during gameplay, it doesn't mean he wants to lose his saved game. He still might want to hit Load Game and continue where he left off, BUT *mark this important* if he starts New Game and then hits Save Game - all his previous progress will be lost and you might want to warn him about it.

Extensibility


Once your save/load functionality is implemented and working flawlessly, you'll want to add new dynamic stuff to your levels. You or your boss will have this great idea that colored balls should be accompanied with colored squares. So how do we add the squares now?

There are two types of extensions, one that is affecting all levels, and another affecting only one specific level. If you want to add squares to all levels, you have to

1. Extend the level model/class:

LevelObj
{
    id //simple number
    background //image path
    array levelBalls[{position,color}] //position of balls and their color
    array levelSquares[{position,color}] //position of squares and their color
    array levelExits[{position,idOfNextLevel,idOfNextLevelEntrance}] //position of exit and where it leads to
    array levelEntrances[{id,position,rotation}] //position of player and facing upon level entrance
}

2. Next, you add square data to gameLevelsArray (see above, not going to copy here again with some square example data).

3. Function clearLevel will be changed to erase squares.

4. Functions loadLevelAssets and showLevel are extended to include squares.

5. Functions overwriteSavedLevel, storeSavedDataToPermanentMemory and restoreSavedDataFromPermanentMemory need some edits as well.

As you can see it's not small change, but it's manageable. It's not impossible to add savable data, but you have to remember all the places where you manipulate save and load data and add it there. For example, I forgot to add squares to one function now. It's the one I told you it would come back to haunt you: it's optimized loadGameOnSavedLevel. In this function, we are not clearing all level assets, but just moving back dynamic objects in saved position, so we need to add squares there as well.


Quirks


The second type of extension is that one specific thing that you want to appear on level 17 and you don't need it anywhere else, I call it quirks. You want a flower on level 17 and when the player eats a flower, he gets the extra life. It's totally out of game mechanics, specific thing that you want to be saved just as well. These things can bring something interesting to the game, but often you don't think of them at the very beginning. So you add generic quirks array to each level. And you can use it later if you need it.

1. Extend each level with quirks array. It can be empty array in all levels at first.

LevelObj
{
    id //simple number
    background //image path
    array quirks[] //what good is empty array for? TODO: remove this
    array levelBalls[{position,color}] //position of balls and their color
    array levelSquares[{position,color}] //position of squares and their color
    array levelExits[{position,idOfNextLevel,idOfNextLevelEntrance}] //position of exit and where it leads to
    array levelEntrances[{id,position,rotation}] //position of player and facing upon level entrance
}

2. Add levelOnLoad function for each level that is called when that level is loaded, and pass saved data to it. It can be empty function for all levels at first (if you use some sort of scripting in your games, then this can be script that is executed when level is loaded, it's convenient as you don't have to edit source code later)

3. Have quirks saved and loaded if the array is not empty. If your database doesn't like expanding arrays have it have fixed array of 10 integers - all zeros.

Now imagine you want to add a flower on level 17 at a later stage. When the level is loaded, you want to see the flower, but if the player eats it and saves the game you want to save the fact that flower is gone. This is what you do:

1. In gameLevelsArray add 1 to quirk array :
quirks:[1],
2. In levelOnLoad (script) function draw flower only if quirk is 1
3. When player eats the flower, give him extra life but also set currentLevelPointer.quirks[0] to 0

Maybe this is stupid cause you are changing the code to add this flower eating functionality so you can also edit save and load functions to include this new feature, but I like to avoid changing save/load functions cause an error in there can affect all other parts of the game. And sure it will look confusing to another coder what this level[17].quirks[0] a thing is. But you don't care anymore at this point.

Skyrim.jpg

Conclusion


This functionality can be quite complicated, but good planning and thinking ahead can make it easier. So hopefully this article shows you something you didn't think of and helps you with the planning.

All of these concepts are used in 'real' code in two of my games that you can find open sourced on github:
javascript game: https://github.com/bpetar/web-dungeon
c++ game: https://github.com/bpetar/leonline

In the end, I must tell you that no one told me how to make save/load functionality and I never read any existing article or book on this subject and I made this up all by my self, so maybe there is a better way to do this, and I advise you to browse some more.


Article Update Log


21 Sep 2016: Initial release

Composing Adaptive Music (Non-linear)

$
0
0

AKAudio is an online Sound Design Studio. We compose music from youtube jingles, cinematic shorts and adaptive video game music.

Attached Image: survival-shooter-2.jpg
Attached Image: Screen Shot 2017-01-26 at 11.04.16 AM.png

In this video tutorial we would like to show you some of the techniques that we use when composing non-linear music for our clients.

1. Themes
2. Instrumentation
3. Transitions
4. Sound Design



When composing music, you must remember to stick to the theme of the game. In this case we wanted to portray a magical atmosphere with a few aspects of horror since the game is about a baby who dreams of fighting off toy enemies.

Instrumentation inlcudes woodwinds that are set to the tempo of the enemies when they walk.
You can hear that we implemented a Celesta to help keep the music "magical"
You'll notice the instrumentation changed while we kept the same musical motif implemented by adding brass.
The transitions are always short and include a tempo change.
The end transition also changes to a slower tempo.
We incorporated a randomized Volume and Modulation wheel to help to keep the sounds effects fresh and new.

You can contact us with any questions and job inquiries at audio@akaudio.com or visit our website at AKAudio.com

https://soundcloud.com/akaudioak/survival-shooter

Thanks for watching!

Viewing all 17825 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>