Final Report (Innovative Resources)
From EQUIS Lab Wiki
Contents |
Objectives and Motivation
Objective
It is our goal to provide a rich new selection of resources for the player to harvest. Providing the player with a wider selection of resources will be beneficial to both the longevity and the complexity of the gameplay. From the player's point of view, new choices will be introduced regarding the order in which resources can be harvested. Presently, collecting different resources does not yield any benefits to the player. However, this work will indirectly support future expansions to the game. Eventually, special buildings or abilities might be available to the player in exchange for particular harvested goods. More elusive resources can present challenges to the player, encouraging them to continue playing. A solid system of resources will be the foundation of a solid in-game economy which will result in a new dimension of gameplay (the managing of resources). The architectural goals in this project will be to create a framework within the software which can manage multiple resources. In the original code, there was no structure for resources beyond the hardcoded single resource (the tree). In order to create a new resource a recompilation would have been required along with the duplication of all of the code related to the resource, as well as the AI rules which villager harvesting behaviours. Thus, our complementary goal is to provide an interface for game developers to create and add new resources. Adding a new resource should not require a recompilation, only a few minor adjustments and additional definitions to a handful of configuration files. In order to accommodate this, configuration files will have to be read at runtime and then interpreted into the game world.
Motivation
Our motivation is to make the game more realistic through the addition of resources as well as increasing the villager population. In doing this we hope to make the game environment a more attractive area to explore and for players to appreciate. As more resources are added, the game will increase in depth and fun by giving the player more decisions.
User Interface
The user interface to the game will remain fairly similar to the original. However it will become much more dynamic. Currently, the user interface has two main elements, the HUD and the trigger notification. The head’s up display (HUD) which is always visible to the player reflects the status of the villagers, the amount of resources collected, as well as the time spent playing the game. The trigger notifications are displayed whenever a player approaches a resource node (the ogre head) to determine if the player would like to assign a villager to begin harvesting.
HUD
The only additions to the game's primary HUD will be the additional resources listed in the village status box (in the top-left corner). Originally, the positions of each label in the display were hardcoded down to the pixel in a configuration file. Our changes to the user interface have made the HUD far more dynamic. The dynamic information update area has been modified to display the varying amounts of each variety of resource. The dimensions and contents of this box will change based on the data found in the resource configuration scripts. Previously a recompilation was required to make any adjustments to the data displayed, and modifications to a separate overlays script were needed to resize the box to contain all of the new elements.
Notification
The other change to the overlay will be with respect to the harvest trigger notifications ("Press B to Chop Down Trees"). The original notification was tucked away in the skybox texture definitions. It is also stored as a large bitmap (not created with fonts at run-time like other HUD elements). This has been replaced with a dynamic notification which reflects the type of resource. This approach has also saved memory, as a large bitmap will not need to be loaded in order to notify the player. This also means that creating a new bitmap will not be required when the game designers are interested in adding new resources.
Art Requirements
The dynamic resource system now allows for arbitrary OGRE-compatible models to be attached to any particular resource. In our case, we used the stock “cube.mesh†model to represent the stone resource. We did not end up using any other new art. It would be nice to eventually replace the OGRE head resource node with something more iconic to the resource. Animated resource nodes might be easier to spot in a field of static resources. The game would be more attractive if the resource nodes reflected the type of resource they were representing. Future work could involve adding a resource node model attribute to the resource definition script so that every resource could have a different resource node model to reflect the resource type. They will no longer be the same for all resources. Now that villagers have been given several new abilities (quitting their jobs and relaxing) it is becoming more important to find appropriate ways of conveying this information visually through new models and animations.
Project Milestones and Achievements
When we began this project, we set the goal of creating a new resource (the rock) through the duplication of the existing tree code. As we went through the initial codebase with this in mind, we took note of which elements of the engine would require generalization in order to allow for a modular resource system. The next step required in this milestone was to generalize the interface to allow for adding new resources without compilation. Among the difficulties we anticipated were dealing the AI rules which currently are based on boolean variables tied directly to functions in the engine. There are approximately 200 lines of C++ code in the functions referred to by the AI rules, this encouraged us to generalize the AI rules so that they could be applied to any resource. Our aforementioned project milestone “Rockâ€, which entailed us to add a new resource to the game which would behave similarly to the existing tree resource has been completed. Resources are now loaded at runtime by the LIAV software and then incorporated into the dynamic resource architecture. During the initial phase of map generation, the new resources will be distributed throughout the in-game world according to the specifications in both the resource area bitmap (rsrcmap.png) and resource script file (liavresources.resources). We have also added new features to the villagers in order to make the game more fun and interesting. Villagers are now given a certain amount of energy (can be thought of as stamina) which decreases as they complete work. Once their energy has been used up they will quit their jobs and relax, restoring their energy. This is a very simple implementation which can be extended to create even more interesting behaviour, such as different methods of relaxing (sitting under a tree, going to a pub, etc.) which allows for more complex gameplay elements to be added at a later time.
Technology
The work we did can be separated into the following steps:
- Updating the game resource data
- Updating the map to have new resource areas
- Updating the XML script for our new resources UI
- Updating the overlay to reflect all of the resources collected
- Modifying the harvest query overlay to reflect the type of resource AI
- Generalizing the AI rules
- Adding additional AI rules for new villager behaviours
- Job Quitting
- Relaxing
- Gameplay
- Adding new features to the villagers (laziness)
- Energy (working capacity)
- Multiple villagers can harvest different resource at the same time
Tools
Automatic Map Generation Script
We used and modified the mappainter.py script to allow for the new resource. It accepts an XML file and a PNG image as input and processes them. The output is a collection of resource area maps, a heightmap, and a world texture map. Originally the mappainter.py had a method (shebang) which would generate the output. It originally contained a list which had been hardcoded into its source containing the list of potential resource areas (hill, tree, mountain, river, lake, and rock). This list did not allow for a generalized resource system. A slight change to the XML definition and the script has allowed for a general approach to adding additional resources and terrain areas. The location of different terrain types (and therefore resources) is still defined by the input bitmap ‘rsrcmap.png’. The resource script does not provide any kind of procedural terrain generation, it only extends the resource map by modifying a heightmap and texture map. Depending on the future design of LIAV (different world every game or a map hand-made by designers) this might also become a problem with generalizing resources, as all the resources will have to be kept in mind during the procedural generation. Their placements (which might be important to the gameplay) will also have to be defined in the resource script. As procedural terrain generation was not within the scope of our project, we have left it using the static map. Originally in mappainter.py:
# list of possible tag values in xml file taglist = [ "tree", "hill", "mtn", "river", "lake", "plain", "rock" ]
Reads from the following (rsrcmap.xml):
<rock> <alpha color="8000ff" bleed="30" /> <hf scale="0.03" bottom="0.01" top="0.20" xo="2" yo="2" /> <tex> <perlin scale="0.02" xo="10" yo="10"> <color value="888888" /> <color value="bbbbbb" /> </perlin> </tex> </rock> <hill> <alpha color="07ab0b" bleed="30" /> <hf scale="0.03" bottom="0.01" top="0.20" xo="2" yo="2" /> <tex> <perlin scale="0.02" xo="10" yo="10"> <color value="(95,150,16)" /> <color value="(82,111,58)" /> </perlin> </tex> </hill>
We have modified the Python script so that it will read all of the elements in the file, not only the ones contained in the hardcoded list. This allows the terrain generation script to remain the same while only the XML resource is being modified.
Resource Definition Script
<resources> <resource name="tree" harvesttime="2" carrycapacity="15" buildingname="mypinetree" good="Wood"/>
<resource name="rock" harvesttime="2" carrycapacity="10" buildingname="rock" good="Stone"/> </resources> At the core of our aforementioned new dynamic resource script is a simple XML file. We are parsing this XML file with the TinyXML functions already included in the CAXConcept. The root node is 'resources' and all of its children are 'resource' elements. Each is required to have attributes regarding its name (used for generating the alphamap), the time it takes to harvest in seconds, the carrying capacity of the element for a villager, the name of the building which is representing the resource, and the name of the good it produces, used for outputting onto the overlays. Here is an example of our debugging resources. Harvest times have been reduced so we could monitor the behaviour in a timely fashion.
How Generalized Resources Work
When a resource node is approached and the proximity trigger is activated, the avatar determines the type of resource it is representing by identifying the type of the closest resource node. Currently, proximity triggers do not contain any information beyond their position on the map, so they can not directly tell the villager which resource they will collect. Once the player accepts the query of harvesting, a villager (if one is available) is given a current ‘resource interest’ and they will begin collecting the resources closest to the activated resource node. This resource interest is used by the AI routines to determine where to drop off their resource after it has been collected. In order allow multiple villagers to harvest the same kind of resource at the same time without causing any errors, resources (which are represented internally as buildings) have been given a new attribute ‘isUsed’ which is set when a villager is assigned a resource to harvest. Resources which are already flagged as being used are ignored in the resource selection process. Before we implemented this we would have very strange crashes which would occur when two villager were harvesting the same tree at the same time, once the tree was harvested by one of the villagers it would be erased and the other villager would throw a null pointer exception.
Classes Modified
CAXWorldResourceManager
In the CAXWorldResourceManager class we have made extensive changes through the generalization of the existing tree code. The CAXWorldResourceManager is responsible for initializing the game's resources. Since we are now reading the list of resources from an XML file, we determine the filename of the alphamaps and then load them. Each resource is then distributed in a density based on the alphamap’s intensity. This causes the resources to thin out towards the outskirts of the region, providing a more natural appearance. We added the following functions to the map: The following are the methods we added to this class to allow for general resources: void distributeResources( ResourceType typeOfResource, string alphamap) This function takes a resource type (an unsigned integer referring to a particular resource type) and distributes resources based on a particular alphamap. This function is called for each resource found in the resource script.
void distributeResourceNodes( ResourceType typeOfResource, string alphamap);
Similar to the distributeResources function, this function will distribute resource nodes on a certain patch of terrain defined by the alphamap. Resource nodes are distributed far less densely than the resources.
CAXBuilding* getClosestResource(ResourceType resourceType,const Vector3& pos);
This function will return the closest resource to any particular point on the map. This function is used when a villager determining which resource to collect next, it is the closest resource to the resource node.
CAXBuilding* getClosestResourceNode(ResourceType resourceType, const Vector3& pos);
We use this function to help assign a resource type to a villager. By finding the closest resource node to the avatar (when the harvest resource query is accepted), we assign a villager to harvest the closest resources to this node.
CAXBuilding* getResourceForResourceNode( ResourceType resourceType, CAXBuilding& rsrcnode );
Provides the resource to be collected based on a resource node. This takes into account which resources are already being harvested as well as proximity to the resource node.
void removeResource(ResourceType resourceType, CAXBuilding* resource);
Once a resource has been harvested, we remove it from the map using this function.
unsigned numCollectedResources( ResourceType resourceType);
Provides access to the amount of resources collected through the resource manager.
void addCollectedResource ( ResourceType resourceType);
Once a resource has been collected, this increments the amount of the good stored in the resource manager.
CAXGameState
When the proximity trigger of a resource node is activated by an approaching avatar, we leave CAXGameState and enter CAXAction state. This is the original behaviour of the code. We have modified the code in ActionState to check which resource node is the closest to the avatar to determine what action to take. Our next goal will be to allow the ActionState to determine what action to take based on the contents of the resource script files. This will require internal changes to ActionState and ProximityTrigger classes, but we do not suspect any architectural reorganizations will be needed. We modified the method which is called when we enter the CAXActionState (that is, when a trigger has been activated) to grab the closest resource node and use that for future villager designations.
Bug fixed
At one point the 'int CAXActionInGameState::frameStarted( float elapsed )' function of the GameState would occasionally crash. This was caused by a possible situation where it would finish but not return a value. This was correcting by tracing through an elaborate if statement and finding the offending path.
Modifying the AI Routines
With the original code, we were faced with the problem of having specific AI rules for particular resources. This would have lead to much code duplication, because every AI rule was tied to a static function within the actual game code. Not only would we have to create additional AI rules for each new resource, hundreds of lines of code would be repeated every time we did so. This was not desirable so we generalized the AI rules to apply to harvesting in general, and left it up to the villager to determine how this affected them based on their current resource interest. An observation we made early in the development process was that once a villager starts working there would be no mechanism to stop them from consuming all resources available until the program would crash when a new one tries to be assigned. This problem would not present itself in early development where gameplay rarely goes on longer than several minutes, but when games are played which are longer than an hour this becomes a real possibility. In order to circumvent this problem we have introduced a new quality to villagers: laziness. These lazy villagers are initially given a limited working capacity which is decremented as they complete work (such as collecting resources) once this energy has been depleted, they quit their job (quit_job rule) and relax (do_relax rule). This is another example of a feature which can be extended in many interesting ways to improve gameplay. For example, relaxing is instantaneous at the moment, refreshing their working capacity entirely. As the game becomes more complex, relaxation can come from difference sources. Villagers might go for walks, take naps, go to the pub, based on individual preferences. This leads to more gameplay challenges for the player, who wants to create a village which is productive by taking care of their tired villagers.
Here are the new, generalized, AI functions:
job_harvest: Indicating harvesting of a simple resource go_to_resource: Builds a path to a tree the villager should cut down at_resource: Checks whether the villager is at a resource or not harvest_timeout: Checks whether the villager is finished collecting the resource remove_resource: Destroys the resource that the villager was harvesting drop_off_resource: Builds a path to a building where the villager should bring resources at_resource_drop_off: Checks whether the villager is at an appropriate resource drop off or not going_to_resource_drop: If the villager is en route to a resource drop off drop_resource: Drops the goods (adding to the user's resource stockpile) harvesting: Checks whether or not the player is currently harvesting something
The Evolution of the AI Rules
This is an example of the initial style of rules, the resource is referred to directly by the rule names leading to much required duplication of code each time a new resource is added. The following represents what would need to be replicated and modified.
IF job_chop_rock AND at_rock AND NOT chop_rock AND NOT going_to_stone_drop THEN start_timer AND chop_rock AND NOT move
IF job_chop_rock AND chop_rock AND chop_rock_timeout THEN remove_rock AND NOT chop_rock AND drop_off_stone AND going_to_stone_drop AND move
IF job_chop_rock AND going_to_stone_drop AND at_stone_drop_off THEN drop_stone AND NOT going_to_stone_drop AND go_to_rock AND move
IF job_chop_rock AND NOT chop_rock AND NOT going_to_stone_drop AND NOT move THEN go_to_rock AND move
Generalized rules:
IF job_harvest AND at_resource AND NOT harvesting AND NOT going_to_resource_drop THEN start_timer AND harvesting AND NOT move IF job_harvest AND harvesting AND harvest_timeout THEN remove_resource AND NOT harvesting AND drop_off_resource AND going_to_resource_drop AND move IF job_harvest AND NOT harvesting AND NOT going_to_resource_drop AND NOT move THEN go_to_resource AND move
Additional generalized rules with villager laziness integration:
IF job_harvest AND going_to_resource_drop AND at_resource_drop_off AND NOT is_tired THEN drop_resource AND NOT going_to_resource_drop AND go_to_resource AND move IF job_harvest AND going_to_resource_drop AND at_resource_drop_off AND is_tired THEN drop_resource AND NOT going_to_resource_drop AND quit_job AND do_relax AND NOT move
One of the issues with the AI for which we didn’t have enough time to implement was a to allow AI rules to become parameterized so that different resource types could be handled in different ways. The existing method of interaction between the AI rules interacted with the LIAV code would have needed to be completely rewritten. The system currently in place provided a std::map<string><BooleanFunction> which mapped AI rules to functions directly. Passing parameters would have first required the XML rule notation to be extended to allow for parameters on each rule which would then have to be parsed into the code and sent to the static function calls. While we didn’t implement this, we considered the approach of using parameters by modifying the existing map structure to refer parameter and rule pairs to something along the lines of: std::map<pair<string,string>,pair<BooleanFunction,string>. The second element of the pair would be responsible for the parameter of each function. Another thing required for this implementation would be the removal of AI symbols in favour of analogous functions which convey the same information.
The Changes in the Game Architecture
The following given UML diagram reflects our changes in the game architecture. We did not add any classes but, added some new methods and modified the existing methods.
The new functions we added were primarily in the CAXWorldResourceManager, CAXVillager, and CAXBuilding. The CAXBuilding class was given a new attribute ‘isUsed’ which prevents multiple villagers from harvesting the same resource, as well as the appropriate accessors and mutators for this value. The CAXVillager was also given a new attribute ‘energy’ which is accessed through new accessors and mutators. The villager was also given a general ‘isHarvesting’ function for the generalized AI rules to refer to. We have also added many functions to the CAXWorldResource manager which have been discussed earlier in section 5.3.1.
Interaction with other groups
Our project had a lot of overlap with The Chosen One’s extended building system, though we did not use any of their new features. The Chosen Ones used our new resource types to determine if new buildings could be constructed, and also added new buildings which would only behave as a resource drop off for certain resources. In doing so they revealed an existing weakness in the resource collection, which occurs when a resource is harvested when there are no existing buildings on the map. The villager can not determine which building to which they should return their resources, causing a null pointer exception. This problem has not been addressed, but there are several approaches we could take such as causing resource nodes to be invisible until an appropriate drop-off exists, to providing gentle feedback to the user who tries to harvest but can not.
Future Extensions
While we developed the generalized resources we had several ideas for future extensions to the game which we could not implement at this time. We offer these ideas to future LIAV developers.
Invisible resources
As discussed in class, not all resources have to be protruding from the terrain. Resources underground (rocks, gems, or potatoes) can be invisible in the game world, but still collectable by the villagers. This could allow the introduction of more varieties of resources, and even an agricultural aspects to the game. Replenishment of resources We found a straightforward approach to the replenishment of resources. When a resource is destroyed it can be saved in a list of discarded resources, they can then be returned after a specific amount of time or amount of resources collected. Other approaches exist, but even with lazy villagers, resources can be completely consumed given enough time.
Procedural Terrain Generation
Procedurally generated terrain based on resource and terrain type definitions
Parameterize AI rules
As the AI rules become more complex, some kind of parameterized AI rules should be implemented to allow for more different types of resources and villager behaviours in general. Perhaps a new paradigm might even be used (such as imbedded scripting)
Lessons Learned
Overall, we consider our experience working on LIAV to have been very successful. We found that using pair’s programming techniques and never developing individually lead to very few easily avoidable bugs. That is not to say there were not difficulties during the developmental process. Our initial difficulties came from adding new resources. There was very little structure in place for having multiple resources despite the fact that it was always intended for LIAV to have many different kinds of resources. Adding general resources would have been nearly trivial at design time, however delaying the addition of multiple resources made the process very complicated and time consuming. It reinforces the importance of developing a coherent game design ahead of time which outlines all of the required features, which is then used to develop the initial game architecture. Another difficulty was with the AI rules, the rules within were so specific that a lot of our time was spent simply updating the rules to be general as opposed to adding qualities to the resource system, such as the replenishment of resources. A very important lesson learned was dealing with source concurrency packages such as SVN. As we worked on our project, we removed a lot of the original resource-dependent functions which were not generalized and added a suite of our own. Before we committed our initial set of changes to the repository, we updated, which restored all of the deleted functions to our code before we committed. This left a lot of unused code in the source. By the time we realized the functions were not deleted, the Chosen Ones had used some of the deprecated functions (such as getNumCollectedWood instead of the generalized getNumCollectedResources) which led to commitment conflicts. We intend to correct these conflicts and eliminate the non-generalized function calls once and for all shortly.