Unuk

Procedural Generation.

Procedural generation is the act of producing content algorithmically. In games, content may often be generated procedurally to give the game more variation or depth. Such algorithms have also been used in motion picture films for procedurally modeling buildings, or to give more variation to special affects.

While there were no references or third party resources used in this procedural generation algorithm, there has been some research carried out on many ways that I could approach this task. One such method was Perlin Noise, developed by Ken Perlin in late 1990's. This algorithm used a seeded random number generator that takes an integer and a parameter, then returns a random number based on the given parameter. Perlin Noise is generally used to generate textures on the fly and is really useful in situations where memory may be short. Noise is mainly implemented as a function of either (x, y) (x, y, z) or (x, y, z, w) but can also be defined in a number of other dimensions.

I decided to implement procedural generation in my own way, as algorithms like Perlin Noise would have required some hacking just to have it accomplish the task I wanted. Like all procedural generators we needed a way to generate random numbers while also trying to keep it looking natural and meeting a given parameter by the user.

We deploy a very simple level generator within Unuk by placing object entities and NPC's at random locations on the map, while avoiding collision with other entities.

In /Unuk/src/LevelGen/LevelGen.*

GenerateEntities(const std::string&, int) takes in the name of the file minus the file extension, and the frequency in which entities will be drawn. The higher the frequency, the less densely populated that particular object will become, giving the user the ability to make the level less cramped with the same type of objects.

To calculate the frequency that an entity will appear we use a variable named nextEntityGen. This is used to indicate how many tiles we will place the next entity, after the tile is placed we then re-calculate again according to frequency to determine where the next entity shall be positioned. nextEntityGen is a value in the scope of

[1 … frequency-1] We have an offset of one as rand() returns a number between zero to frequency non-inclusive. Int nextEntityGen = 1 + (rand() % frequency);

We then iterate through a tile array to find where we should place the next entity before we calculate where we will place the next object.

Data Serialization.

Data Serialization is of utmost importance in most software projects, data structures often need to be stored in a file format that can be moved between other machines, streamed over a network or be easily configurable.

It was clear to me what format I would use and how I would use it to accomplish certain tasks within Unuk. XML was used for serialization and deserialization for Unuk.

XML started development in the 1990s with a desire to create a human readable, text based format. My choice to use XML came mainly down to prior experience in using the markup language with C++ and while it does not store the data in a binary format, encrypt or obfuscate the data, it offers a lot of freedom and flexibility in structuring data.

The main problem I had expected with using XML is the tedious amount of work that has to go in to writing an XML file to load in maps. Generally I would have not chosen such a format for creating maps, however, having decided to generate the maps procedurally, it was not so bad, as I would never have to write out an XML sheet. I would have procedural generation and data serialization work hand in hand to do all that for me.

Data Serialization for Unuk can be found in:

/Unuk/src/libUnuk/System/Serialiser.* and /Unuk/src/libUnuk/Serialisable.h

Our first two methods, StepIn(const string&) and StepOut(void) steps in and out of elements in an XML document. When we step in we are effectively creating a new child element, we push the current element onto the list of parent elements, then set the current element to the child node. When stepping out we pop the last element off the list reducing the size of the list thus moving back one element.

The next method we have is FirstElement(const string& name). This gives us a way to jump to the first element within a given block. Before we do anything we declare a variable of type TiXmlElement and store the memory address of our current element into it for safe keeping. If we then find that the current element is equal to NULL we go to the first element in that particular document, otherwise we go to the first element with the current elements block. We then check to see if we have a current element again, If we don't, we restore the old element we assigned earlier and return false. If we have a value other than NULL in oldElement we push it onto the list of parent elements.

To find the next element along we have a method conveniently declared NextElement(const string& name). It searches for the next element of a given 'name' provided 'name' is not the same as the element. So first of all, we check to see if the names match by ensuring 'name' is not equal to the current elements value. If the are not, then we return pass name into our FirstElement() method and return the result.

We then go on to store the current element into a temporary pointer named oldElement and search for the next sibling of _currentElement. If none are found, we simply restore the old element into _currentElement and return false, as there are no other elements.

Rollback is quite a straight forward method. We have to first of all make sure there is an element to go back to, so we make sure the size of _parentElements is greater than zero. If it is, then we can set the current element to the last element on the parent list. And then to clear that off the list of parents we pop it off of the stack.

Next up, is Load. This method simply takes in an XML filename, and calls TinyXml's Loadfile method on our document. The save method is pretty much the same, that all we have to do is pass in the filename. However, before calling TinyXml's SaveFile method we have to call LinkEndChild and pass in our _rootElement adding a new node related to this. The node is passed by pointer so will be owned by TinyXml.

RegisterMember(const string& name, int type, void* data) will allow us to serialize the data we intend to store in our XML file. We create a SerialiserMember object named member. This takes in the elements name as a string, then we insert the type of data that we would like to store, the types are stored conveniently in an enumerator. Lastly we pass in a pointer to the data we are storing. Next we push the data onto the member list.

Once we have our data, we can write it to the XML file. The method WriteMembers(void) will allow us to do this. First off we declare a stringstream named sstream. A stringstream allows us to manipulate strings. Then we need to iterate through the list of members stored in the _members list. Each member will be checked to see what data type it is. After every check we will call stringstreams clear() method on sstream to remove any error flags that may have accured. Once we have found what type of data we have, we pass it into the stream casting it's corresponding type. Finally we create a new TiXmlText pointer and convert the data into text.

All we have to do then is create a new element passing in the name, and assign the text to that element. After we have looped through all our data we will need to clear the list of members.

ReadMembers is the final method we need to complete data serialization. It is very much like WriteMembers, however once we know what data type we have, we store the information in '_data' using TiXmlElement 's GetText() method.

2012-03-07T23:41:32