• Advertisement
Sign in to follow this  
  • entries
    72
  • comments
    104
  • views
    45587

About this blog

Check out what's new with Citizen here. For great justice.

Entries in this blog

Dynamic properties revealed
Properties continued from here.
Alright, as I promised in the last entry here is the full source for the property container I'm using. The core functionality of the prop::container is to map strings to values of any type. This is done using the templated get/set functions.

The set() function either sets an existing property to a value or creates a new one. The property type is specified via the template parameter.

The get() function requires that the sought property of the requested type exists or it will throw an exception. The type must match the one used with set().

To avoid having to wrap each get() call in a try-catch there is a req() function that should be called at the beginning of each scope that uses the property. If the given property exists the req() call does nothing. If it doesn't, req() creates the property and sets it to some initial value. One can think of req() as the equivalent of a variable declaration.

Right now there are no functions to remove or query existence of properties but they should be simple to write.

There is finally a set_callback() function that takes a existing property name and a prop::weak_callback_ptr object. This sets a callback object that should be invoked every time the given property is written to. To use this you should inherit the prop::callback class and implement the operator(). Then you should create an instance of that object somewhere using prop::callback_ptr p(new derived_cb()); This will give you a reference counted pointer object that can safely be given to the property container by set_callback("prop_name", prop::weak_callback_ptr(p));

Since callback_ptr is reference counted it will not need to be manually deleted. The pointer we give to set_callback() will not become orphaned if our callback object is destroyed. Because we send in a weak pointer the callback object will not be pinned down by the container. If the callback object is destroyed the weak pointer will become invalidated and the container safely removes it.

Beyond the addition of the has() and rem() functions noted above there are a few things I'd like to add in the future. Most importantly a property should be able to have more than one callback associated so that systems can listen independently. Currently req() does not enforce that the required property has the right type which may be a design error. Also I'd like the prop::prop_value and prop::property classes to be private to prop::container to clean up the external interface.

The code is contained in one C++ header file and it needs several boost classes to build. If you don't have boost, get it here. RTTI needs to be enabled when compiling. This is a requirement of boost::any.

? Check out the full implementation below, after the usage example program.

Use it, abuse it, and if you find anything good or bad about it, tell me! [smile]

And, yeah, funny story. GD does not allow me to upload .HPP header files. I assume they have been shown to be potentially more harmful than their .H brethren. [looksaround] There is a zip-archive containing both files though.

Sample program [source]

#include
#include "prop_container.hpp"

int main(int argc, char* argv[])
{
prop::container props;

// Require the new property and output its value.
props.req<int>("my_int", 0);
std::cout << "get(\"my_int\") gives: " << props.get<int>("my_int") << std::endl;

// Define a local var to be the copy_callback destination.
int copied_result = 110;

// Create a copy_callback object and register it with the container.
prop::callback_ptr p(new prop::copy_callback<int>(copied_result));
props.set_callback("my_int", prop::weak_callback_ptr(p));

std::cout << "Initially copied_result contains: " << copied_result << std::endl;

// Now write to the property and output the results of the write.
props.set<int>("my_int", 120);

std::cout << "After writing copied_result contains: " << copied_result << std::endl;
std::cout << "get(\"my_int\") gives: " << props.get<int>("my_int") << std::endl;

// No cleanup necessary. All objects are local or ref-counted. Happy times!
return 0;
}

/*
Program output:

get("my_int") gives: 0
Initially copied_result contains: 110
After writing copied_result contains: 120
get("my_int") gives: 120
Press any key to continue . . .
*/







Property container code [source]
// Copyright 2009-2010 Staffan Einarsson.
// This file is part of the Citizen project.

#ifndef PROP_CONTAINER_H
#define PROP_CONTAINER_H

#include
#include
#include
#include

namespace prop {

// This is the value type wrapper class that is contained in the container.
// It has templated get/set functions that do type-safe conversions.
class prop_value {
public:

// Contructors and assignment operators.
prop_value() {}
prop_value(const prop_value &o) : val(o.val) {}
template prop_value(const T &o) : val(o) {}
prop_value &operator=(const prop_value &o) { val = o.val; return *this; }
template prop_value &operator=(const T &o) { val = o; return *this; }

// Get/set pair
template
T get( ) {
return boost::any_cast( val );
}

template
void set( T new_val ) {
val = new_val;
}

private:

// Property value
boost::any val;

};

// --------------------------------------------------------------

// This is the base class for all callbacks used in the container.
// Derive from this to make your own callback.
class callback {
public:
// When the callback is invoked on a property write, the old value and
// new value are sent to this function.
virtual void operator()(prop_value old_val, prop_value new_val) = 0;
};
// Smart pointer types used by the container.
typedef boost::shared_ptr callback_ptr;
typedef boost::weak_ptr weak_callback_ptr;

// Sample callback implementation that simply copies the new val to
// a specifed variable. The class gets a reference to the destination
// variable through its constructor.
template
class copy_callback : public callback {
public:
copy_callback(T &val) : val_ref(val) {}
void operator()(prop_value old_val, prop_value new_val) {
val_ref = new_val.get();
}
private:
T &val_ref;
};

// --------------------------------------------------------------

// This is the container item class, which has a value and an associated
// callback pointer. The operations include typed get/set value and set/unset
// callback.
class property {
public:

// Constructors and assignment operators.
property() {}
property(const property &o) : val(o.val), callback(o.callback) {}
template property(const T &o) : val(o) {}
property &operator=(const property &o) { val = o.val; callback = o.callback; return *this; }
template property &operator=(const T &o) { val = o; callback.clear(); return *this; }

// Get/set pair
template
T get() {
return val.get();
}

template
void set(T new_val) {
// Set new value. Send old and new val to callback.
prop_value old_val = val;
val = new_val;
// Check that callback ptr is still valid before calling.
if (!callback.expired()) (*callback.lock())(old_val, val);
}

// Set/unset callback pair
void set_callback(const weak_callback_ptr &new_callback) {
callback = new_callback;
}

void unset_callback() {
callback.reset();
}

private:

prop_value val;

// A pointer to the callback function object.
// Weak so it doesn't prvent the callback from deleting.
weak_callback_ptr callback;

};

// --------------------------------------------------------------

// This is the top level container class, the one to use in external code.
// It has operations to manipulate values and callbacks on properties identified
// by their names.
class container {
public:

// Only get the value if the property exists and is of the right type.
template
T get( const std::string &name ) {
property_map::iterator it = props.find(name);
if (it != props.end()) {
return it->second.get();
}
throw does_not_exist("Property \"" + name + "\" does not exist.");
}

// Set value of an existing property or create a new one.
template
void set(const std::string &name, T val) {
// Create a new value or overwrite the old one.
property_map::iterator it = props.find(name);
if (it == props.end()) {
props[name] = property(val);
} else {
it->second.set(val);
}
}

// Set new callback only if given property exists.
void set_callback(const std::string &name, const weak_callback_ptr &callback) {
property_map::iterator it = props.find(name);
if (it != props.end()) {
it->second.set_callback(callback);
return;
}
throw does_not_exist("Property \"" + name + "\" does not exist.");
}

// Unset callback only if given property exists.
void unset_callback(const std::string &name) {
// Unset callback only if property is found.
property_map::iterator it = props.find(name);
if (it != props.end()) {
it->second.unset_callback();
return;
}
throw does_not_exist("Property \"" + name + "\" does not exist.");
}

// Require that the given property exists.
// If not, create a new one with given type and value.
template
void req(const std::string &name, T initVal) {
// Same as set() but never overwrites.
property_map::iterator it = props.find(name);
if (it == props.end()) {
props[name] = property(initVal);
}
}

// Exception types used by the container.
typedef boost::bad_any_cast bad_cast;
typedef std::out_of_range does_not_exist;

private:

// Map containing the properties.
typedef std::map property_map;
property_map props;

};

}

#endif






More Box2D integration
Continued from here.
Been busting may ass off for two days integrating Box2D into the Citizen engine. For most parts it's been successful. Although Box2D is very simple to get working, there are hundreds of small connections that need to be welded together.

The biggest incision I've had to make so far is to create a logical coordinate system separate from the pixels-units I have been using. This is also what's taken most of the time.

Using logical units has been one of those things I knew from the start I should do but then never got around to implement. Now, a hundred little sins later, the pixel units were so deeply rooted into the system that in weeding them out I had to review most of the graphics code and then some. *groan*

At the time of typing all things render correctly but sizes are way off and need to be recalibrated in with new numbers. Box2D interactions work fine with all different object types. I mentioned before how entity hierarchies had to be scrapped in newer versions of the system. By using hinge joints I've re-enabled the option to attach one object to another, which currently is used for ship turrets, and now such attachments are physically accurate.

The next big issue is that the behaviors that control movement need to be adapted to the new physics. What was done before by controlling the velocity of actors directly now have to be done using forces, so some control theory might be appropriate to avoid oscillations.

I've also beaten out the last dents in the property container so the next entry will be going into all of its details, source included. I could do it now but I'm too tired to write proper documentation. Got to get up early tomorrow. See you back here soon.
You'll have to excuse any weird typing today. My head is completely worn down by moving all weekend. I've evacuated my apartment and will be staying with the family for a few weeks. Then it's off to Stavanger, Norway for a two-month internship position. Awesome! [grin]

Integrating the properties
Read about my property system here, and about the Body component here.
I've shifted my efforts towards integrating the new property system together with the new Body component. It may seem dangerous to do several integrations simultaneously, and it is, see below. However the property container is a necessary part of the system the way I see it evolve so putting it off until later will only complicate things.

I'm going to scrap the current way of supplying configuration data to the components by their constructor. Instead I want all such values to be properties that can be set before or after the component has been crated.

The key difference here is that properties belong to the entity and the components just use them. The component must declare in its constructor which properties it will use, and give a fallback value if no such property exists. This way no props will be missing and no config data will be overwritten.

This is the current prop_container interface.
  • T get("name")

  • void set("name", value)

  • require_prop("name", def_value)

  • void set_callback("name", callback_ptr)

The exact operation of require_prop() is to add the property and set the default value only if it doesn't already exist. One can also specify a callback function object that is called every time a property is written to. Such an object must inherit the prop_callback interface and is held through a weak_ptr.

I'd love to post the whole prop_container class source if only I had it checked in, but it's on another computer right now and in a non-functional state. I'll try to remember this later. Some people have shown interest in the property implementation.

Converting test code to CppUnit format
There has been a big problem with my testing code for a while. Namely, the whole test application has not failed, but crashed hard without any helpful debug output.

Up until now I've been using FCTX, Fast C-Testing, xUnit, something. This is obviously a framework suited for C-programs and the main reason I picked it was it is contained in one single header and was darn easy to get running.

However, since FCTX is based on C macros it does not work well with stack trace analysis, so in the end I just ignored the whole thing as the game did run (the very opposite of good conduct).

Now, finally, I've taken on the problem by ditching FCTX and going over to CppUnit instead. It was a crap-load of work converting all the tests (WHY DON'T VISUAL STUDIO SUPPORT MULTILINE SEARCH AND REPLACE!?!). Just want you to know...

Anyway now I can trace the crash down to something in the new property container. Apparently the deepest informative level is the string key comparer in the map that holds the property objects. It's not solved yet but at least I'm getting closer.

CppUnit is nice too. Now I don't have to hack my way around testing the exception handling. And without the macro-orgy VS intellisense actually knows what's going on. The poor thing.
Here is another issue of Staaf's Irregular Weekend Updates. Digest with a glass of red and some soothing music.

After a discussion with my editor I agreed to try to cross-reference my posts if a subject carries over from one entry to another. So here goes nothing.

Vehicle and Obstacle components merged into one Body component
Previous discussion about Obstacle in this entry.

Last weekend I began to tear up the Vehicle and Obstacle components and to try to combine them into one Body component. You may remember than Vehicle controls autonomous movement of entities (mostly through steering behaviors) and Obstacle does simple physics.

My reason to merge them is that they were never independent anyway. Physical response affects motion too so they both relied on a single integrator. Placing the integrator in either one would cause a coupling while having two independent integrators would be very, very bad.

The key point of the Body component is that it is based on the Box2D physics library instead of my own simple one. Steering behaviors are now an optional part of the Body component and they simply exert extra forces sand torques on the body when active.

I've never worked with Box2D before but it was simple enough to get up and running. The hard part was and, still is, to adapt the system and design the new component, atoning for past mistakes.

For example the bodies need a way to track other bodies both passively (for cohesion/separation behavior) and actively (for targeting/seeking). The passive is done by simply giving the steering behavior update a collection of all bodies.

Previously the active tracking was done by storing the ID of the tracked entity and looking up its vehicle component each frame. The continual lookup was slow but I didn't want to hand out pointers between components because of the security risk. Another flaw was that the tracked entity was required to have a vehicle component even if it didn't do any auto-movement (like the mouse pointer).

The new system introduces a Target component that is lightweight and only exists to flag an entity as targetable. When a body component picks a target it gets coupled with this target component and can request its location each frame. The coupling is done using a set of smart pointers, see below, so it's safe if the target is destroyed.

So far so good. Right now I'm trying to figure out how to attach entities to each other in the new design. My previous concept of hierarchical structures of entities doesn't work here. I'm figuring if using joints between bodies could work but that would require a Body component on both attached entities. Hmm.

Extensive use of smart pointers
Previous discussion about smart pointers in this entry.

I've also gone further in my studies of smart pointers and how to apply them. I recently talked about using boost::ptr_map and other collections. Now with the added requirement that my components should be safely distributable within the system such a sealed collection was not enough.

Instead, by using a collection (map) of boost::shared_ptr, components flying around are reference counted to prevent premature deletion. The system distributes boost::weak_ptr objects instead of actual boost::shared_ptr objects so that if a component is distributed and then deleted the links to it will be safely inoperable.

You can read more about boost::shared_ptr and boost::weak_ptr in the documentation. Briefly, boost::weak_ptr is a sealed pointer that needs to be promoted to a boost::shared_ptr through a lock() call before it can be used. This way it doesn't pin down the resource until it really needs to.

Yeah, and please ignore this old lashing out on shared pointers from a year back! I overreacted to say the least. Of course you should use them only where they fit in.
My hardware decided to play nice with me today so I could work undisturbed. [smile]

Got some things done.

Wrapped up the optimization of text rendering
I continued to formalize the rendering of text. The initial DX rendering via DrawPrimitiveUP() has been changed to a DrawIndexedPrimitive() call. Since the index buffer is the same for every line of text this reduced the vertex writes from six to four per character.

The idea of the rendering is to fill a buffer with vertices computed from the font metrics and then draw the whole text in one draw call. Because the letters are spread out all over the font texture it is not possible to use anything but triangle lists.

Triangle lists requires six vertices per quad/character with two being duplicates. Using a constant index buffer with the size of six times the text length one can remove the two duplicate vertices that are updated for each new text to draw.

With the in-game debug overlay turned on the text drawing still ate ~45% of the CPU time, most of it spent in the DX runtime. I guess this is not unexpected because with the profiler tree being drawn each frame we are talking over 12000 characters.

In the end I decided to remove the profiler output from the debug overlay and added the option to dump it to a report file instead. It had outgrown its usefulness anyway as the tree didn't fit on screen on my lil' netbook.

Better looking text
So with the core drawing of text wrapped up and tested, I have begun adding more variants of fonts. Right now there is a small-sized monospace system font for debug info and a medium interface font for user interaction.

The screens show the new font in action. I used GIMP to add a border and some tones. Very easy once I had the core image from BMFont.

The gameplay screen only uses the new font in the kill-count panel (and it is too big to fit, I know.)



Some improvements to Shiny Profiler
For some time I have been tweaking Shiny here and there to suit my needs. Things like sorting of result on self time on output to more easily see where most time is spent.

My latest modification was to be able to compute a span of minimum and maximum time spent in a node along with the average (that is the default output).

The original routine uses a geometrical average to saturate the mean value towards the current over several iterations, based on a weight. My addition checks the current node timing if it is outside the span of min and max and expands the span if it is. If it is inside the span however it lets the min and max saturate towards the current, at a rate ten times slower than the saturation of the mean.

This way the min/max span slowly shrinks when too large and ends up as a good measurement of how the node time has varied over the past frames.

The report now looks like this:
flat profile                                                  hits (min-max)                                 self time (min-max)                                total time (min-max)
0.0 ( 0.0- 0.0) 19 ms ( 18 ms- 19 ms) 1665% (1628%-1671%) 20 ms ( 20 ms- 20 ms) 1765% (1723%-1805%)
cn::Application::doFrame 1.0 ( 1.0- 1.0) 78 us ( 44 us- 465 us) 7% ( 4%- 41%) 1 ms ( 1 ms- 2 ms) 100% ( 95%- 137%)
cn::SceneManager::updateByFrame 1.0 ( 1.0- 1.0) 523 ns ( 0 ns- 2 us) 0% ( 0%- 0%) 194 us ( 183 us- 207 us) 17% ( 16%- 18%)
cn::SceneManager::doDrawCalls 1.0 ( 1.0- 1.0) 177 ns ( 38 ns- 469 ns) 0% ( 0%- 0%) 47 us ( 46 us- 52 us) 4% ( 4%- 5%)
cn::D3D9Device::addSprite 51.0 ( 50.7- 52.3) 19 us ( 16 us- 21 us) 2% ( 1%- 2%) 19 us ( 16 us- 21 us) 2% ( 1%- 2%)
cn::D3D9Device::addText 4.0 ( 4.0- 4.0) 2 us ( 2 us- 3 us) 0% ( 0%- 0%) 2 us ( 2 us- 3 us) 0% ( 0%- 0%)
cn::D3D9Device::commit 1.0 ( 1.0- 1.0) 68 us ( 64 us- 75 us) 6% ( 6%- 7%) 656 us ( 643 us- 674 us) 58% ( 57%- 60%)
cn::D3D9Device::drawSprite 51.0 ( 50.7- 52.3) 10 us ( 9 us- 12 us) 1% ( 1%- 1%) 103 us ( 98 us- 108 us) 9% ( 9%- 10%)
cn::D3D9Device::getResource 55.0 ( 54.7- 56.3) 16 us ( 14 us- 18 us) 1% ( 1%- 2%) 16 us ( 14 us- 18 us) 1% ( 1%- 2%)
cn::D3D9Device::drawSprite 51.0 ( 50.7- 52.3) 76 us ( 74 us- 80 us) 7% ( 7%- 7%) 78 us ( 75 us- 83 us) 7% ( 7%- 7%)
cn::D3D9SpriteResource::Load 4.0 ( 4.0- 4.0) 138 ns ( 38 ns- 523 ns) 0% ( 0%- 0%) 138 ns ( 38 ns- 523 ns) 0% ( 0%- 0%)
cn::D3D9Device::setBlendMode 124.8 ( 124.2- 126.9) 6 us ( 4 us- 8 us) 1% ( 0%- 1%) 6 us ( 4 us- 8 us) 1% ( 0%- 1%)
cn::D3D9Device::drawText 4.0 ( 4.0- 4.0) 309 us ( 299 us- 324 us) 27% ( 26%- 29%) 352 us ( 340 us- 369 us) 31% ( 30%- 33%)
cn::BitmapFont::prepareVertices 4.0 ( 4.0- 4.0) 42 us ( 40 us- 44 us) 4% ( 4%- 4%) 42 us ( 40 us- 44 us) 4% ( 4%- 4%)
cn::EntityData::set 0.4 ( 0.0- 1.5) 257 ns ( 0 ns- 994 ns) 0% ( 0%- 0%) 257 ns ( 0 ns- 994 ns) 0% ( 0%- 0%)
cn::EntityData::set 36.6 ( 36.4- 37.9) 20 us ( 19 us- 21 us) 2% ( 2%- 2%) 20 us ( 19 us- 21 us) 2% ( 2%- 2%)
cn::GameplayScene::updateByFrame 1.0 ( 1.0- 1.0) 30 us ( 22 us- 34 us) 3% ( 2%- 3%) 193 us ( 179 us- 217 us) 17% ( 16%- 19%)
cn::ComponentManager<class cn::Gun>::updateByFrame 1.0 ( 1.0- 1.0) 2 us ( 427 ns- 9 us) 0% ( 0%- 1%) 2 us ( 427 ns- 11 us) 0% ( 0%- 1%)
cn::ComponentManager<class cn::Obstacle>::updateByFra 1.0 ( 1.0- 1.0) 7 us ( 6 us- 10 us) 1% ( 0%- 1%) 59 us ( 55 us- 86 us) 5% ( 5%- 8%)
cn::ObstacleGroup::testCollisions 5.0 ( 5.0- 5.0) 5 us ( 4 us- 8 us) 0% ( 0%- 1%) 52 us ( 49 us- 79 us) 5% ( 4%- 7%)
cn::CircleCollider::apply 35.6 ( 35.0- 54.0) 34 us ( 33 us- 53 us) 3% ( 3%- 5%) 48 us ( 44 us- 76 us) 4% ( 4%- 7%)
cn::EntityData::get 107.5 ( 106.3- 145.0) 20 us ( 18 us- 29 us) 2% ( 2%- 3%) 20 us ( 18 us- 29 us) 2% ( 2%- 3%)
cn::ComponentManager<class cn::Vehicle>::updateByFram 1.0 ( 1.0- 1.0) 20 us ( 19 us- 21 us) 2% ( 2%- 2%) 99 us ( 98 us- 103 us) 9% ( 9%- 9%)
cn::Vehicle::updateByFrame 36.4 ( 36.2- 37.2) 39 us ( 36 us- 40 us) 3% ( 3%- 4%) 80 us ( 75 us- 83 us) 7% ( 7%- 7%)
cn::Seek::getVelocity 5.0 ( 5.0- 5.0) 946 ns ( 427 ns- 1 us) 0% ( 0%- 0%) 946 ns ( 427 ns- 1 us) 0% ( 0%- 0%)
cn::Align::getOmega 1.0 ( 1.0- 1.0) 373 ns ( 38 ns- 427 ns) 0% ( 0%- 0%) 373 ns ( 38 ns- 427 ns) 0% ( 0%- 0%)
cn::Facing::getOmega 13.0 ( 13.0- 13.0) 3 us ( 3 us- 4 us) 0% ( 0%- 0%) 3 us ( 3 us- 4 us) 0% ( 0%- 0%)
cn::Separation::getVelocity 4.0 ( 4.0- 4.0) 7 us ( 7 us- 7 us) 1% ( 1%- 1%) 7 us ( 7 us- 7 us) 1% ( 1%- 1%)
cn::Cohesion::getVelocity 4.0 ( 4.0- 4.0) 3 us ( 3 us- 3 us) 0% ( 0%- 0%) 3 us ( 3 us- 3 us) 0% ( 0%- 0%)
cn::ComponentManager<class cn::Emitter>::updateByFram 1.0 ( 1.0- 1.0) 3 us ( 2 us- 4 us) 0% ( 0%- 0%) 3 us ( 2 us- 4 us) 0% ( 0%- 0%)
cn::GameplayScene::doDrawCalls 1.0 ( 1.0- 1.0) 4 us ( 3 us- 5 us) 0% ( 0%- 0%) 47 us ( 45 us- 52 us) 4% ( 4%- 5%)
cn::D3D9Device::addLine 1.0 ( 1.0- 1.0) 8 us ( 8 us- 10 us) 1% ( 1%- 1%) 8 us ( 8 us- 10 us) 1% ( 1%- 1%)
cn::ComponentManager<class cn::Sprite>::doDrawCalls 1.0 ( 1.0- 1.0) 14 us ( 13 us- 16 us) 1% ( 1%- 1%) 30 us ( 26 us- 33 us) 3% ( 2%- 3%)
cn::ComponentManager<class cn::Emitter>::doDrawCalls 1.0 ( 1.0- 1.0) 197 ns ( 0 ns- 322 ns) 0% ( 0%- 0%) 751 ns ( 0 ns- 1 us) 0% ( 0%- 0%)
cn::ComponentManager<class cn::HudElement>::doDrawCal 1.0 ( 1.0- 1.0) 2 us ( 1 us- 2 us) 0% ( 0%- 0%) 3 us ( 2 us- 4 us) 0% ( 0%- 0%)
cn::D3D9Device::drawLine 73.8 ( 73.5- 75.0) 129 us ( 128 us- 132 us) 11% ( 11%- 12%) 133 us ( 131 us- 137 us) 12% ( 12%- 12%)
cn::SceneManager::doDebugDrawCalls 1.0 ( 1.0- 1.0) 41 us ( 39 us- 45 us) 4% ( 3%- 4%) 157 us ( 153 us- 163 us) 14% ( 14%- 14%)
cn::Vehicle::doDebugDrawCalls 36.4 ( 36.2- 37.0) 54 us ( 53 us- 58 us) 5% ( 5%- 5%) 115 us ( 112 us- 121 us) 10% ( 10%- 11%)
data_fetch 36.4 ( 36.2- 37.0) 6 us ( 6 us- 7 us) 1% ( 1%- 1%) 14 us ( 12 us- 16 us) 1% ( 1%- 1%)
cn::EntityData::get 36.4 ( 36.2- 37.0) 7 us ( 6 us- 8 us) 1% ( 1%- 1%) 7 us ( 6 us- 8 us) 1% ( 1%- 1%)
cn::D3D9Device::addLine 72.8 ( 72.5- 74.0) 47 us ( 45 us- 49 us) 4% ( 4%- 4%) 47 us ( 45 us- 49 us) 4% ( 4%- 4%)

call tree hits (min-max) self time (min-max) total time (min-max)
0.0 ( 0.0- 0.0) 19 ms ( 18 ms- 19 ms) 1665% (1628%-1671%) 20 ms ( 20 ms- 20 ms) 1765% (1723%-1805%)
cn::Application::doFrame 1.0 ( 1.0- 1.0) 78 us ( 44 us- 465 us) 7% ( 4%- 41%) 1 ms ( 1 ms- 2 ms) 100% ( 95%- 137%)
cn::SceneManager::updateByFrame 1.0 ( 1.0- 1.0) 523 ns ( 0 ns- 2 us) 0% ( 0%- 0%) 194 us ( 183 us- 207 us) 17% ( 16%- 18%)
cn::GameplayScene::updateByFrame 1.0 ( 1.0- 1.0) 30 us ( 22 us- 34 us) 3% ( 2%- 3%) 193 us ( 179 us- 217 us) 17% ( 16%- 19%)
cn::EntityData::set 0.2 ( 0.0- 1.1) 160 ns ( 0 ns- 969 ns) 0% ( 0%- 0%) 160 ns ( 0 ns- 969 ns) 0% ( 0%- 0%)
cn::EntityData::set 0.1 ( 0.0- 0.6) 64 ns ( 0 ns- 272 ns) 0% ( 0%- 0%) 64 ns ( 0 ns- 272 ns) 0% ( 0%- 0%)
cn::ComponentManager<class cn::Gun>::updateBy 1.0 ( 1.0- 1.0) 2 us ( 427 ns- 9 us) 0% ( 0%- 1%) 2 us ( 427 ns- 11 us) 0% ( 0%- 1%)
cn::EntityData::set 0.2 ( 0.0- 1.4) 97 ns ( 0 ns- 907 ns) 0% ( 0%- 0%) 97 ns ( 0 ns- 907 ns) 0% ( 0%- 0%)
cn::EntityData::set 0.1 ( 0.0- 0.7) 51 ns ( 0 ns- 454 ns) 0% ( 0%- 0%) 51 ns ( 0 ns- 454 ns) 0% ( 0%- 0%)
cn::ComponentManager<class cn::Obstacle>::upd 1.0 ( 1.0- 1.0) 7 us ( 6 us- 10 us) 1% ( 0%- 1%) 59 us ( 55 us- 86 us) 5% ( 5%- 8%)
cn::ObstacleGroup::testCollisions 5.0 ( 5.0- 5.0) 5 us ( 4 us- 8 us) 0% ( 0%- 1%) 52 us ( 49 us- 79 us) 5% ( 4%- 7%)
cn::CircleCollider::apply 35.6 ( 35.0- 54.0) 34 us ( 33 us- 53 us) 3% ( 3%- 5%) 48 us ( 44 us- 76 us) 4% ( 4%- 7%)
cn::EntityData::get 71.1 ( 70.1- 108.0) 14 us ( 12 us- 22 us) 1% ( 1%- 2%) 14 us ( 12 us- 22 us) 1% ( 1%- 2%)
cn::ComponentManager<class cn::Vehicle>::upda 1.0 ( 1.0- 1.0) 20 us ( 19 us- 21 us) 2% ( 2%- 2%) 99 us ( 98 us- 103 us) 9% ( 9%- 9%)
cn::Vehicle::updateByFrame 36.4 ( 36.2- 37.2) 39 us ( 36 us- 40 us) 3% ( 3%- 4%) 80 us ( 75 us- 83 us) 7% ( 7%- 7%)
cn::Seek::getVelocity 5.0 ( 5.0- 5.0) 946 ns ( 427 ns- 1 us) 0% ( 0%- 0%) 946 ns ( 427 ns- 1 us) 0% ( 0%- 0%)
cn::Align::getOmega 1.0 ( 1.0- 1.0) 373 ns ( 38 ns- 427 ns) 0% ( 0%- 0%) 373 ns ( 38 ns- 427 ns) 0% ( 0%- 0%)
cn::EntityData::set 36.4 ( 36.2- 37.2) 20 us ( 19 us- 21 us) 2% ( 2%- 2%) 20 us ( 19 us- 21 us) 2% ( 2%- 2%)
cn::EntityData::get 36.4 ( 36.2- 37.2) 7 us ( 6 us- 8 us) 1% ( 0%- 1%) 7 us ( 6 us- 8 us) 1% ( 0%- 1%)
cn::Facing::getOmega 13.0 ( 13.0- 13.0) 3 us ( 3 us- 4 us) 0% ( 0%- 0%) 3 us ( 3 us- 4 us) 0% ( 0%- 0%)
cn::Separation::getVelocity 4.0 ( 4.0- 4.0) 7 us ( 7 us- 7 us) 1% ( 1%- 1%) 7 us ( 7 us- 7 us) 1% ( 1%- 1%)
cn::Cohesion::getVelocity 4.0 ( 4.0- 4.0) 3 us ( 3 us- 3 us) 0% ( 0%- 0%) 3 us ( 3 us- 3 us) 0% ( 0%- 0%)
cn::ComponentManager<class cn::Emitter>::upda 1.0 ( 1.0- 1.0) 3 us ( 2 us- 4 us) 0% ( 0%- 0%) 3 us ( 2 us- 4 us) 0% ( 0%- 0%)
cn::SceneManager::doDrawCalls 1.0 ( 1.0- 1.0) 177 ns ( 38 ns- 469 ns) 0% ( 0%- 0%) 47 us ( 46 us- 52 us) 4% ( 4%- 5%)
cn::D3D9Device::addSprite 0.0 ( 0.0- 0.0) 0 ns ( 0 ns- 0 ns) 0% ( 0%- 0%) 0 ns ( 0 ns- 0 ns) 0% ( 0%- 0%)
cn::D3D9Device::addText 0.0 ( 0.0- 0.0) 0 ns ( 0 ns- 0 ns) 0% ( 0%- 0%) 0 ns ( 0 ns- 0 ns) 0% ( 0%- 0%)
cn::GameplayScene::doDrawCalls 1.0 ( 1.0- 1.0) 4 us ( 3 us- 5 us) 0% ( 0%- 0%) 47 us ( 45 us- 52 us) 4% ( 4%- 5%)
cn::D3D9Device::addLine 1.0 ( 1.0- 1.0) 8 us ( 8 us- 10 us) 1% ( 1%- 1%) 8 us ( 8 us- 10 us) 1% ( 1%- 1%)
cn::ComponentManager<class cn::Sprite>::doDra 1.0 ( 1.0- 1.0) 14 us ( 13 us- 16 us) 1% ( 1%- 1%) 30 us ( 26 us- 33 us) 3% ( 2%- 3%)
cn::D3D9Device::addSprite 44.4 ( 44.2- 45.0) 16 us ( 13 us- 17 us) 1% ( 1%- 1%) 16 us ( 13 us- 17 us) 1% ( 1%- 1%)
cn::ComponentManager<class cn::Emitter>::doDr 1.0 ( 1.0- 1.0) 197 ns ( 0 ns- 322 ns) 0% ( 0%- 0%) 751 ns ( 0 ns- 1 us) 0% ( 0%- 0%)
cn::D3D9Device::addSprite 0.6 ( 0.0- 1.4) 554 ns ( 0 ns- 1 us) 0% ( 0%- 0%) 554 ns ( 0 ns- 1 us) 0% ( 0%- 0%)
cn::ComponentManager<class cn::HudElement>::d 1.0 ( 1.0- 1.0) 2 us ( 1 us- 2 us) 0% ( 0%- 0%) 3 us ( 2 us- 4 us) 0% ( 0%- 0%)
cn::D3D9Device::addSprite 2.0 ( 2.0- 2.0) 1 us ( 771 ns- 2 us) 0% ( 0%- 0%) 1 us ( 771 ns- 2 us) 0% ( 0%- 0%)
cn::D3D9Device::addSprite 4.0 ( 4.0- 4.0) 2 us ( 1 us- 2 us) 0% ( 0%- 0%) 2 us ( 1 us- 2 us) 0% ( 0%- 0%)
cn::D3D9Device::addText 1.0 ( 1.0- 1.0) 335 ns ( 38 ns- 427 ns) 0% ( 0%- 0%) 335 ns ( 38 ns- 427 ns) 0% ( 0%- 0%)
cn::D3D9Device::commit 1.0 ( 1.0- 1.0) 68 us ( 64 us- 75 us) 6% ( 6%- 7%) 656 us ( 643 us- 674 us) 58% ( 57%- 60%)
cn::D3D9Device::drawSprite 51.0 ( 50.7- 52.3) 10 us ( 9 us- 12 us) 1% ( 1%- 1%) 103 us ( 98 us- 108 us) 9% ( 9%- 10%)
cn::D3D9Device::getResource 51.0 ( 50.7- 52.3) 15 us ( 12 us- 17 us) 1% ( 1%- 2%) 15 us ( 12 us- 17 us) 1% ( 1%- 2%)
cn::D3D9Device::drawSprite 51.0 ( 50.7- 52
Found a little time to code this weekend.

Worn down hard by hardware
I couldn't really code away to my hearts' desire because hardware failures kept interrupting my flow. My video card is cranky and when strained it likes to deadlock. Probably due to its cheap nature and the fact that there are no good drivers for Win7 yet. I foresee the purchase of a new one in the near future.

Properties using boost::any
I made a first attempt towards a limited property implementation. The idea is that every entity holds a dynamic set of properties which its components use to publish and share data.

My current system has a way of doing this through a string-to-float mapping that all components can access. However only being able to store floats has been cumbersome when trying to store 2D vectors and integers.

The new attempt uses a mapping from string to a boost::any object. The any can be viewed as a boostified void pointer, but without any unsafe casting.

Apparently boost::any it uses the typeid() operator but implements its own any_cast<>() operation instead of using dynamic_cast<>(). I have to admit I don't know the finer points to this or how it compares in speed, but the robustness is there all right.

Using this little magic bean I swiftly coded up a property container with a get<>() and set<>() templated interface. Now I can write get("position") or set("health", 10) and it simply works. Very nice! [grin]

Bitmap fonts using AngelCode BMFont
Another concern has been that the text output during debugging was eating up 80% of the CPU time. Not that debug output optimization is that important, really, but lately it's been very hard to profile other parts of the code as their time was overshadowed by the colossal times of the text.

Something had to be done. I went for AngelCode's Bitmap Font Generator and the great, short tutorial by Promit on parsing the FNT file.

With this at my disposal the rest was easy. A quick and dirty implementation lowered the debug output CPU time to 50% of total. This is still horrible but less so than before. More to come on this.
Citizen stuff
Smart pointer discussion continued in this entry.

Took a little time to replace the map collection of components in ComponentManagerBase with a boost::ptr_map.

Not much to say about it really since it worked right out of the box with some minor tweaking. I had to replace an array access operator call with a method call, but that's about it.

I have been working a lot in C# recently so I'm getting more and more used to the convenience of having garbage collection on objects. And boost can really make my C++ life easier here.

Right now I'm looking into different uses of boost::scoped_ptr/shared_ptr and if possible to use boost::unordered_map with a ptr_map_adapter.

I have also been investigating if a property pattern approach to storing data on entities is the next step for the engine. So far I've found several good examples out there, as well as this thorough article by Steve Yegge. He discusses the usage of the pattern in general and also implementation and performance considerations in a language-independent context. Very well worth read.

EDIT:
I should mention that the above article was suggested by Trefall in an earlier forum post. His engine is also one of those I've looked at. *Tippin' the hat* [smile]

Still typing

General stuff
Ah it's good to be back home in GameDev City. Been out in the wilds for too long and now my bones ache. Really, for a month or so it's been quite intense at school and all past time activities (like game dev, eating peas, etc.) have been on hold. Specifically I've been doing a lot of programming for assignments so whenever I got some time off, more coding was the last thing I wanted to do. It's been been good though. I'm closing in on my M.Sc. degree. Soon I will be free. ^_^

Have been playing games instead, catching up a bit on all the titles I've missed so far. Since new-year I've plowed through inFamous, Brutal Legend, God of War I and II, all awesome experiences. Well I didn't really get my head around the Brutal Legend gameplay but then again that is not what you look for in a Schafer game. inFamous was probably the best city-roaming action game I've played to date and the God of War titles are just awesome. They give a new angle to the concept of "spartan decoration" of a room; a lot more blood and dead bodies.

Citizen stuff
Now, yesterday I finally got back to coding on Citizen, mainly by doing some refactoring. I realized I could smooth out the interface of the EntityManager class by using some member function templates for the component factories. Up until now every component type have had it's own custom factory function in EntityManager. This was a thorn in my side; I've wanted to generalize this as the factory implementations look the same apart from the component type.

The problem was to make EntityManager aware of which component type it creates so that it can pass it on to the correct manager. The component managers were members of EntityManager; they could be distinguished at run-time, but to use a templated factory they needed to be distinguishable at compile-time. The solution I found was instead to let EntityManager privately inherit all the component managers. By making the component manager classes specialized implementations of a ComponentManager template the factory template in EntityManager could can cast the this pointer to a component manager pointer using the template parameter, thereby getting access to the correct component manager at compile-time.

Confusing, yes? Don't worry, I'll try to illustrate it with an example.

Previous version
// Assume there exists a number of component types:
// FirstComp, SecondComp, ThirdComp...

// The component manager base class keeps core functions like
// managing the list of components of a certain type.
class ComponentManagerBase {
public:
// This is the actual factory that EntityManager should relay to.
void createComponent( ... );
};

// The component manager classes all inherit the base manager.
// Then they each add their own specific functionality.
class FirstCompManager : public ComponentManagerBase { ... };
class SecondCompManager : public ComponentManagerBase { ... };
class ThirdCompManager : public ComponentManagerBase { ... };

class EntityManager {
private:
// Each manager is instanced to manage components of only that type.
FirstCompManager firstCompMan;
SecondCompManager secondCompMan;
ThirdCompManager thirdCompMan;

public:
// Each manager factory call must be implemented separately.
void createFirstComp( ... ) {
... // Do entity stuff
firstCompMan.createComponent( ... );
}
void createSecondComp( ... ) {
... // Do entity stuff
secondCompMan.createComponent( ... );
}
void createThirdComp( ... ) {
... // Do entity stuff
thirdCompMan.createComponent( ... );
}

};



Current version
// Assume there exists a number of component types:
// FirstComp, SecondComp, ThirdComp...

// A dummy template for the managers. All implementations are actually specialized.
template class ComponentManager;

// The component manager base class keeps core functions like
// managing the list of components of a certain type.
class ComponentManagerBase {
public:
// This is the actual factory that EntityManager should relay to.
void createComponent( ... );
};

// The component manager classes all inherit the base manager.
// Then they each add their own specific functionality.
template<> class ComponentManager : public ComponentManagerBase { ... };
template<> class ComponentManager : public ComponentManagerBase { ... };
template<> class ComponentManager : public ComponentManagerBase { ... };

class EntityManager :
// Note: private inheritance.
private ComponentManager,
private ComponentManager,
private ComponentManager
{
public:
// Template for component factories.
template
void createComponent( ... ) {
// Cast yourself into a component manager aspect based on type parameter
// and call the factory function on that aspect.
... // Do entity stuff
((ComponentManager*)this)->createComponent( ... );
}
};



The key thing to realize here is that in the current version EntityManager is a component manager of each type so it can be cast to that type to access the members that otherwise would hide each other. Since the inheritance is private, the component manager aspects are invisible to the outside observer, making it appear as a basic class as before.

Note that this code is simplified for clarity. For example, the real ComponentManagerBase is also templated on CompType because it keeps a collection of objects of that specific type.

Conceptually, in the previous version, we have a straight inheritance tree from the managers to the manager base and an instance of each derived manager type in EntityManager. In the current version on the other hand we have what seems like a diamond setup, but isn't, because each component manager class derives from a different specialization of ComponentManagerBase<> which is a unique type.

All in all this change shortens the code and allows for much easier addition of new component types in the future. I begin to find myself more comfortable using templates in various situations. Good times.

Dark days

There's been some setbacks with my development this week.

Off screen

First and most notably, the display for my stationary machine gave up its breath. It was very undramatic; I rebooted and then it was all black. Yesterday I confirmed that it wasn't due to a bad cable, the signal seems to be going through, and the OS recognizes it as a working display, but no pixels are lighting up. This is bad because it was a good LCD, no more than four years old. I had hoped it to survive twice as long, and there is no way I can afford a new one right now.

This impacts development as even though I do most of the coding on the netbook, I need the stationary for large rebuilds and for doing artwork. Anyone willing to donate EUR150 for a new one? No?

Script-design mess

The other thing is that I'm going to have to scrap three-four days of code-work. I've been trying to flex up the Vehicle code (I talked about this before) so that it would be easier to define motion behavior. I wasn't satisfied with the previous way; each Vehicle component has a list of Behavior objects, all implemented in C++ code.

I wanted more flexibility so I tried a different approach with each Vehicle having a reference to a stored Lua function that implemented its behavior. The benefit would be that each Vehicle could use either some common function, a custom combination of such functions or a custom implementation all together. The drawbacks, that I found out, was speed of course since this would cause a large number of calls to Lua each frame. The slowdown could probably be mitigated somewhat by optimizing and calling selectively, etc.

There was also this deal-breaking drawback that all behavior definitions had to reside in Lua. This would work fine with explicitly defined entities, like the player and enemies, since they are all defined in a Lua script at startup and can access anything else within the script machine. The problem is that implicitly defined entities, like bullets, are created within the engine and are not aware of Lua-defined behaviors.

Right now there are two options. Either I extend the experiment so that the Lua-defined behaviors are registered and made visible to the engine-defined entities and vehicles. That would probably be the first step towards a perfect hell of cross-calls between C++ and Lua. Or I admit this wasn't such a good idea from the start and try to improve the old implementation instead. Right now I'm bent on scrapping it and going with number two.

Yeah, it sucks, but I guess this sort of failures are good in the long run. I mean at least it has led to new design decisions.

Redist redone!

This has come up in a few places recently so I thought I'd dive into it myself. Up until now I've used the static runtime library for Visual C++ 2008 (/MT) with Citizen, for my own convenience and laziness. Less file dependencies mean less hassle in deployment.

Seriously though, I'm generally against using dynamic linking because:
  • The program depends on external files that could be missing, which is not a big problem but it incurs a reliability risk that increases with the number of dependencies.

  • If you release an update for the DLL you need to make sure that the program loads the new version and not the old. If the new version of the DLL exposes a different interface than the old then the program must deal with this.


However, if the library is managed by someone else, that has taken on the responsibility for these reliability problems, then there are benefits. For example security updates may be available for common libraries. For this reason Citizen now uses dynamic linking with the VC++ runtime (/MD), the D3D9 runtime and OpenAL, while smaller libs that are not updated regularly like ALUT, Shiny, Lua, Ogg/Vorbis, TinyXML and FCTX are linked statically. Specifically ALUT had to be modified in the common header for static linking. There may be many opposing opinions about this but I see no reason to go crazy with DLLs. My philosophy is to keep the finally deployed files as few as possible, for reliability.

Anyway one thing that has popped up is how to do silent installs of the various redists. I currently let Citizen run directly from its unzipped directory. To install the dependencies I use a simple batch file. Maybe I'll go for a real installer later but now this is sufficient. The commands for silent installs for DirectX and OpenAL are easy as can be:
echo Installing DirectX 9.0c ...
"dep/dxwebsetup.exe" /Q
echo Installing OpenAL ...
"dep/oalinst.exe" /s

The command for silent VC++ redist install was a bit tricky but according to this (outdated and unofficial) source you can use the following command for the 2005 version:
echo Installing Visual C++ Runtime ...
"dep/vcredist_x86.exe" /q:a /c:"msiexec /i vcredist.msi /qn /l*v %temp%\vcredist_x86.log"

I tried using it with the 2008 SP1 version and it seemed to work alright. Apparently they didn't change the interface much. Still I will keep an eye on it if it acts out. This goes out to the people I commented to recently about using silent installs. [smile]

Untitled

I've taken the first steps towards better enemy movement in the game. Yesterday this meant rewriting the coordinate transform code of entities, and analyzing the vehicle component for how it should be designed in the future. I don't want to rush this but instead take the time to think through what types of movement it should support and by what parameters they should be controlled. Currently Vehicle is a mix of perhaps five different design ideas and works poorly as a result.

Reimplementing the transform code and defining a SpatialNode class, that is instanced by each Entity, gave me some good insight I think. Before, I was trying to decide whether to define velocity by a vector or by a speed and angle pair. The vector form was better for the transform hierarchy while the Vehicle code operates on speed and angle separately, so either way I had to do some conversion. Now that I got to think about it properly, to define a nested coordinate space in 2D you need both the vector velocity and the angle of the axes. I was good for a facepalm right then and there.

Anyway resolving this has laid the groundwork for the next generation of movement control. Now I have to think carefully of what kinds of enemy movement I want to support. The enemies uses a seeking behavior right now as well as cohesion and separation to keep them as a loose group. I need some kind of fly-by behavior to create faster fighter-like enemies. Maybe this can be constructed from some combination of seek and separation. Things like missiles could be created with pure seek and mines with pure cohesion (the difference being that cohesion attracts to all nearby targets). Actually, it would seem that if I just cleaned up the code I could create a lot with what I already have.

The model I use for the transform can be depicted as below, where system A is inert and system B has position r, velocity v, angle ? and angular velocity ?.


Made in Paint with a touchpad. Sorry about that.

Any object relative to B can be expressed relative to A as
rA = r + rB
vA = v + rot( vB, ? ) + ? rot( rB, ? + ?/2 )
?A = ? + ?B
?A = ? + ?B
where the A and B indexed quantities are those of the object relative to either system (rot means rotate by given angle). This can be recursed if system A is a rotating system relative to some other inert system, creating a hierarchy. (You may be more familiar with the 3D form where cross-products are used.)

I had a fleeting idea of using n time derivatives of r and ? to construct advanced motion. By theorem, any analytical function can be expressed as an infinite series of derivatives at some point, basically a Taylor expansion. If for instance some motion controller set n time-derivatives of r and ? instead of the current 2 (position and velocity), this system wouldn't have to be updated as often. Instead it would just integrate the n time-derivatives each time step.

Yeah this is probably moot in this kind of simple simulation where linear motion is sufficiently accurate. Maybe valuable in a more complex physical situation.

Anyone still reading? No?

... I like suiting up in rubber and getting a good spanking.

Oh you're still here. Damn!

VC++ issues

I'm sad to report that my previous statement that the transition from XP to Win7 was completely without issues was slightly inaccurate. Nothing big really but my automated testing of Citizen in Visual C++ 2008 is causing some problems. More to the point, the testing works fine but the output to the VS output window during build is truncated for some inconspicuous reason.

I posted a thread about it in the General Programming forum.

The weird thing is that if I run the testing app from outside VS its output is fine. If I run it as a tool from inside VS it is truncated. If I check the project out on another computer at home with XP (I have 100 computers and my dad is really strong) I get the correct output from VS. Seems it only appears in VS on Win7.

The whole ordeal has got me looking like this.

Don't ask me about the goggles.


You see my predicament, yes?

Some backstory

For some time now I've worked on a draft of a backstory for the game. I thought I'd post some of the material I have so far, since some have asked what the heck the deal is with the name of the game. I hope this clarifies some of what I have in mind. Feel free to bash it.
Quote:
The Voidfarer Chronicles: Citizen - Setting draft
Eight centuries after the first public use of inertia inhibition technology, the human population is governed by economic system-wide constructs known as trade alliances. Those that are not citizens of any of the established alliances do not have many of the benefits or rights that high civilization has to offer. Education, employment, day-to-day security, etc. are granted to those that are citizens, and denied to those that are not. If not born into citizenship a person has to be deemed useful to the particular society in order to be accepted. Many are not.

Another way to enjoy basic rights is to be on the crew of a free ship. The alliances are always in need of more shippers to run port-to-port with goods and people throughout system space. While their in-house trade fleets do the bulk of the work there is always the odd job offered to the able free shipper. Many successful free crew members have gone on to full employment on a fleet crew this way. As the fleet crew members enjoy full citizenship benefits this is a very attractive prospect and the competition is heavy.

Beyond the port-to-port trade within the local system there is sometimes necessary to transport goods to far destinations such as neighboring systems. Doing this as a shipper means stepping out of society due to the immense times in transit and because of this the alliances are reluctant to send their own fleet crews on such fares. Having fleet assets gone for decades incurs risk they are not willing to take. Instead they endorse free shippers to take these jobs by paying them handsomely and by treating them as upstanding members of society while they are in system. These free shippers, known as voidfarers, live very long lives due to time dilation while in near-light transit. They are treated with both awe and suspicion by system residents and tend to keep to themselves as their ties to society are lost over time.

A drifter is a member of a community outside any alliance. These people usually live in structural habitats in high orbit over a sun or a planet. The term drifter comes from the fact that they have no means for long distance travel beyond their community. They live off gathering scrap and repairing and refueling passing travelers. Alliance authority tend to accept the drifter communities as they provide important minor services to their trade fleets and security personnel. Some of the largest drifter communities are called drifter rings or drifter clouds since they mimic actual planet rings or dust clouds, becoming part of the very landscape. It is common however since one location only can support so many inhabitants that one community splits into two and one part migrates to a new location. The majority of human population throughout known space live in drifter communities.

Quote:
Some more on voidfarers and the Citizen
The voidfarers is a designation for people who fare the void using inertia inhibition drives and speeds close to that of light. Early versions of this was restricted to security force use and managed to accelerate a vessel to but a fraction of light speed. Now publicly available, though very expensive, the inertia inhibitors are able to attain speeds very close to that of light but it has been concluded time and time again that this limit can never be exceeded. Traveling at such speeds is still not without problems. The time dilation, and the great distances makes a voyage of ten light-years take less than a week for the ones traveling, while ten years will have passed at the origin and destination points. For this reason, few people are willing to travel in this way and those who do have usually no attachments outside of their crews. They are the voidfarers, forming a community of their own.

There is a legend of a voidfarer called the Citizen, who is said to be one of the very first ones. While faring has been done for close to 800 years it is said that he is still out there, traveling, his life prolonged by extensive time dilation. Many versions of this legend exists, detailing various deeds and adventures, and there are many names by which he is known. Some crews and communities regard him as something of a religious figure while most treat it as a mere tale.

Going Win7

I have had some really mean weeks in school and as always when studies are concerned, you never ever really get done. There is always something more you can do. I've been working through the weekends and today I just felt I had to take one day completely off.

So I had a really long sleep in, then a breakfast that lasted most of the morning, watching some SG1 (season 4 somewhere). Really needed this.

As of September last year I do most of my development on an Asus Eee 1005HA. I initially bought it to have a portable laptop (as in actually light enough to be carried around) to use in school for light stuff like writing reports and reading course material, etc. Well it turned out that I could fit my whole development environment on it, which was a lot better than I had hoped for. Suddenly I found myself able to use all my idle time during the day for short bursts of Citizen. Either these machines are ridiculously powerful or my needs aren't that big.

Anyway, up until today I have been using the WinXP install that came with it. Now, since I could get a free Win7Pro license off the MSDNAA deal my university has, I wanted to try to go full circle on the little guy. In all my computer admining life reinstalling an OS have never gone as smooth. Right now most of my necessary apps and tools are in place. Really, my biggest issue would be to get used to the slightly different interface, having been an XP user for eight years or something.

Running Citizen on this setup worked out of the box too, to my delight. It correctly reports the new OS in the log. This, kids, is why you should do you homework, read the docs thoroughly. I noticed some new black flicker at some points when playing. Maybe some driver thing, I'll let it be for now. Also the text rendering is very slow, which is not surprising considering all the new beauty-magic Aero would add to DrawText calls. Won't be a problem since I aim to switch to bitmapped text ingame, and tune down quality on the debug output.

As a final note, one can contemplate how strongly the new Microsoft OS is catching on when Chrome's US spell checker recognizes the above word Win7Pro, but not WinXP. [smile]

Post P2 thoughts

Right now I'm finding myself alone and abandoned at lunch at uni, so I thought I'd make some more comments on the release of P2. The reason I had to rush it was that I wanted to include a fresh official release in an application for a job this summer. So the the development had to be wrapped up early and a lot of planned things had to be cut out.

Looking back and comparing this prototype with the first one, one may think there is not that much difference at the surface, beyond the enemy movement and collisions. This is sad considering all the hours of hard work I've put into it over the scope of ten months. The truth is that the original system present in P1 was stretched to the point of snapping before that release, and the first and most important task post-P1 was to gradually redesign it to expand further. This redesigned system is now up and running, purring like a kitten at the core of P2. The sad part, if any, is that while P1 was burning its system through the floor, P2 is hardly using any of its potential, like modular Lua scripts chaining together with entities and components to form complex ingame events and objects. It's like a V8 fit into a scooter. I had to cut development before I could use it properly.

So, here are a number of things that I had planned but had to cut out of P2:
  • A new 'Interceptor' enemy type that resembles the ones in P1, i.e. a fast chaser that follows and guns at the player and then falls back to a safe distance for another run.

  • A new 'Latcher' enemy type. Small pesky things that attack in swarms and latch on to the player to drain his shield. Once they latch on thy don't let go easily. I've toyed with a few ideas for weapons/abilities that would damage/detach them or that flying at high speed would do the same.

  • Yes that is right, Shields for all able ships, which drain energy to regenerate. Energy should also be used for weapons, thereby placing a bound on how much the player can fire.

  • Repair powerup. As long as the game is in this "survival" mode I think regular powerups would be nice. A repair powerup would be as simple as restoring hull integrity when picked up.

  • Shield nova powerup. When the player picks this one up a nova is released that overloads and dissipates the shield of any enemy on screen. Then a fraction of the shiled points drained is returned to the player to regenerate his shield.

  • Physical obstacles and enemy ship derelicts. Leaving debris on the field of battle will eventually create a sort of terrain to make the fight more interesting.

  • A half-dead effect to indicate when damaged ships reach half their health. I don't want to have health bars on enemies ingame but I miss the indication on which ones have been damaged. If they begin to emit smoke and flame at a given point it would be easier to read.


Well as of now this is a snapshot of what to expect from P3 I guess. These things would make the game more interesting, by a lot. The coming months I'll be working a lot more on gameplay and presentation that on engine and system. If P2 was a celebration to architecture and stability, then P3 will be a celebration to what this is all about at the end of the day: fun.

Regards.
- Staffan


The Voidfarer Chronicles: Citizen - Prototype 2 [Windows download]
Yep. This is it.
Ten months after the last prototype, P2 has finally arrived. The gameplay video above has been updated. If you haven't watched the work in progress since, here are a few things that have happened.
  • New enemy behavior where they seek you out, band together and try to beat you in numbers. You must separate them if you want to survive. Enemies are tougher and, most importantly, can't be flown through!

  • New background art, ship trail and particle effects.

  • An entity/component system now pumps behind the scenes enabling scalability beyond anything the past system could manage. This isn't much of an end user feature, I know, but a promise of future expansion and maintainability.


I ended up setting the deadline for P2 to February 1 and have been coding like crazy the past week to get it done in time, and a lot of things were cut out as well. Hopefully I can get back to talk about them before too long. Stay tuned.

In the meanwhile, accept the challenge and beat back the approaching enemy fleet. I hope you'll enjoy the download and the new video. Drop me a line if you like/dislike what you see. Thanks for viewing.

Hello

Haven't posted in forever and haven't got time to post properly today either. Work is speeding along on Citizen however and I'm closing in on a second prototype P2. I uploaded the current WC to the zip file location if you're curious. New updated pretty particle effects inside!

The goal for P2 is still somewhat fuzzy but I want the current game object entity/component definitions to be moved from C++ to Lua, which requires a reasonably complete Lua interface. I'd say I'm 70% there now from my work during the holidays.

I also intend to add more gameplay content such as environment obstacles, several types of enemy ships and player weapons. I estimate 80% completion here so far, engine-wise, and then it's the actual content creation on top of that.

The last week I've updated the support for particle effects so that I could add back the nice little effects that were in P1, like gun flames, sparks and nice explosions. They are all there now, to stay this time.

Stay tuned for more stuff.

On difficulty

No Citizen talk today. I know it's been a while and I'll have some stuff to report soon.

I was reading gdunbar's journal entry on difficulty levels in games. He comments both Jeff Vogel's idea that games should always be simpler than you think, and Rampant Coyote's general beheading of difficulty sliders in games.

Reading through these posts got me thinking. I agree with Vogel that senselessly hard games offend me while trivially simple games at least give me the satisfaction of winning. I probably won't replay them for the challenge but I got to experience the full content. I can also see the point Coyote makes about difficulty adjustments are arbitrary and doesn't give you anything more than either making it harder to win or harder to lose.

In many games a central part of the concept is that the player has some advantage over the enemies. Be it some flashy set of supernatural powers or the ability to leap out past corners bullet-time-style guns ablaze, there's always some reason why the player can take on hundreds of enemies in a fair fight. I'm thinking that instead of arbitrarily making the bad guys harder to kill, a harder difficulty level could be achieved by permanently taking away some of this player edge. This has the benefit of being a) not arbitrary since the player knows exactly what he is missing and why this is making the game harder, and b) well integrated into the design since you only remove things, not changing any values.

This has been done a lot before. For example the Final Fantasy series uses it from time to time to make it more difficult, taking away player core abilities. I think that if you know exactly what you have lost then you'll end up really missing it.

In conclusion, by removing player ability and edge to form higher difficulty levels you get an obvious, acceptable increase in difficulty that also brings something new to the gameplay experience. Maybe the player must tackle the same challenge differently if he doesn't have all abilities. Additionally the ground state difficulty (or easy) will give you all the cool content you want if you only aim to play through the game once.

Untitled

One of the things I picked up while on the team was doing unit tests on written code. Mainly because the person I worked in pair with is sort of a TDD evangelist. He recommended me reading the book Test Driven Development By Example by Kent Beck, a good book which I've got half way through. I've felt deep in my stomach for some time that sooner or later I had to start testing the Citizen code-base but you know how it is.

Attacking from this new angle I've tried to test drive the design of new components that I've added to Citizen. This has been very informative and have proven why it is best to start test driving from the start. One of the inherent problems of testing code that has been written earlier is that the code is not likely that easy to test. Even though I consider myself to be a fan of clear structure and responsibility between modules I find myself in a mess of cross-dependencies and couplings when I begin test driving new classes that depend on old code. It is worth pointing out that with TDD the code automatically gets test-friendly which solves a serious problem in software design.

Now, it's time for me to get off the high horses and admit that the main reason I'm into TDD is that it allows me to look at my code with new eyes which is interesting. Whether I'll make a habit out of it or not remains to be seen.

I also had to restructure my project in order to easily test the code. Simply adding the same source files to the test app would cause a double compile so I needed to make the original Citizen app into a library that the test app could call into. So right now I use three projects: one static lib for the code base, one test app that runs the suites and one launcher app that simply pushes the run-button in the library. I also noticed that pre-compiled headers in VC++ don't play nice over project borders so I have to use one PCH per project even though they contain the same code. Oh well, you can't win all the time.

Love

Hey you all

Things have been turbulent around Citizen during the last couple of months. I was briefly working with a small team here who is developing new server software to handle even more massive amounts of players than we know today. They needed a demo game and didn't want to spend their time making one. The idea was that I would work one-on-one with them to use Citizen as a client for their MMO server. Now Citizen was never meant to be an online game, even less an MMO, but the way it is designed I thought it might be possible to extend it this way.

First, I was just thrilled that someone professional took an interest in my game and that I would be able to work on my game under a professional roof. Also I was curious how the game engine would handle this new direction of development. I also felt that things had been leading up to this because just a few weeks earlier I put the finishing touches on the entity system that made this possible.

Any way, I worked with them intensively for some time, learning a lot about networking and the problems with it. The technology these guys are working on is awesome. However, in the end, I missed working on the original Citizen plan too much so I decided to leave the team and use what time I have to work on the game.

It's really an emotional decision. Citizen is my baby whom I want to spend time with. And I'm willing to sacrifice to do that.

I aint dead

So as predicted I haven't had time to write any more long in-depth posts about Citizen (I had a good thing going there for a while.) I still have made some progress though; mostly tweaks and adjustments to existing solutions. Most notably I've moved most gameplay settings to several XML files through TinyXML.

I've brought the working copy download up to date, so I'f you feel like it you can give it a spin. Among the news are better instructions on how to play and how to modify the datafiles to bend the game experience.
The semester is upon us. At the moment of writing I am a few hours away from the first set of lectures. For me this means that I'll have little time to spend on Citizen from now on. Because of this I spent most of yesterday trying to wrap up the work I'd done these last couple of weeks, tying up loose ends and leaving the project in a state where it is easy to start it up again.

So what's the situation?

System-wise I wrote a stub for a system to handle successive events during gameplay, basically a queue of elements containing a starting script, an ending script and an end condition. Script is a loose term here which could mean either an external script function or an internal function. Right now it only supports the latter since I have no scripting system yet.

The point here is to have a structured, data-driven way to configure chains of events during gameplay such as "Spawn 10 enemies of type A; When player defeats all, spawn 15 enemies of type B; when player defeats 5 of these, spawn boss enemy; etc."

I wrapped up my work on the vehicle components and made some tweaks to the enemy behaviors. Now, with a combination of seek, cohesion and separation, they move as a loose group trying to find a position half a screen away from the player, where they bomb away. Gameplay-wise this is ok; I can see enemies behaving this way, so I'll treat them as done. The next step here would be to add different kinds of enemies.

So, that's that. You can download a working copy of the program here, if you want to toy around with its current state. If I'm not going to update in a while I might as well show it. This does not constitute a second prototype because it still is flawed compared to P1 (there's no hud elements, the background is flat, and the program fails if the player dies.) Most of the changes are internal.

Thanks for interest shown. I'll be back soon.

Edit: Fixed above working copy so that the game does not fail if player dies.

Flow, cont.

Another long post. There is a code treat at the bottom if you stay with me.

There are still a few days left before the semester starts and right now I'm trying to make the most of them. As I said in the last post I wanted to put the entity system aside for a while and work on A) gameplay mechanics and B) presentation. However as I played the game I realized that the collisions were in a much worse state than I had thought, so it had to be resolved. Then there was the sound component too. Short things first.

Sound component:

There isn't that much to say here really. I wrapped up the previous AudioSource object in a new component. In order to make a gun component able to play a sound when it shoots I had to add an event to trigger when shooting, then attach the play command to that event. I already had some events in the system, like an onKill event for entities and an onDone event for components that have limited operation time. It's a primitive system, simply a function pointer, but it works for now.

I had to do some modifications to the audio API layer to streamline the workings of the sound component. It feels great to be able to go back to old code and apply new ideas to it. It shows to me that my skills are improving. While there I noted down some things in the TODO list on how I'd like the API layer to work (but when will I find the time).

Improved collision response:
Note: Obstacle component has been scrapped, see this entry.

The most annoying thing about the collisions was that obstacles (the colliding objects) tended to get stuck together sometimes. This was a result of the system not doing any inter-frame checks, like sweeping or sub-sampling, and that it uses an impulse response that mirrors the velocity along the surface axis. Even worse it did not project the obstacles out of intersection, causing the them to remain so unless their velocities were great enough to move them apart in a single frame.

At this time the only thing I did was adding a projection step to guard them from sticking together. I could have done more but it solved the problem at hand and I didn't want to overwork it. The system uses circle-circle tests now and, eventually, I'll change it to use general polygons instead.

I still get some strange jumps at times, [deja vu]but unless it acts up more than usual it can wait. It works most of the time. [rolleyes][/deja vu]

Broad phase culling:

The fact that collisions slowed things down wasn't much of a surprise since I only did narrow testing on all participating obstacles. Given n obstacles the number of narrow tests amounted to T = n(n-1)/2, testing each pair once and not testing against self.

My first attempt was a naive implementation of a quad-tree. Had I done my homework I would have anticipated this to be a bad fit since the obstacles tend to be clustered together and not evenly distributed in space. Many cells were unpopulated while some became crowded so the cut in tests did not outweigh the overhead for using the tree. Lesson learned.

In my second attempt I used the fact that groups of obstacles tend to gang up. Each obstacle gets a group id, currently an enum but I'll likely use strings eventually. The obstacles is kept in separate collections, one for each group.
The manager keeps a mapping for which groups will be tested against each other. F.ex. an enemy will collide with other enemies, the player and with player fire but not with enemy fire. The culling is done as follows:
  1. Test on manager level.
    1. Compute an AABB for each group.
    2. For each active group, get a list of other group ids it is allowed to collide with.
    3. Check each group against each other. If two groups can collide, enter group level test.
  2. Test on group level.
    1. Make sure the two groups' AABBs intersect.
    2. For each obstacle in one of the groups, make sure that its AABB intersects the other group's AABB. If so, do narrow test on it against each obstacle in other group.
There are several levels where culling can occur. In 1.3 illegal tests are skipped, in 2.1 two groups' worth of testing can be avoided if the groups are not intersecting, saving nm tests if the groups have n and m obstacles respectively. Finally, in 2.2 n tests can be avoided if an obstacle does not intersect the other group at all.

The translation of which groups are allowed to collide with each other is implemented as a std::multimap in the manager. It maps one id to a number of other allowed ids.

The manager also keeps a std::map, that maps group ids to group collection objects. When a new obstacle is added to the manager it is passed on and added to a group object based on its group id. This is the container being iterated over when group objects are tested against each other.

There is still some room for improvement here. In 2.2 we have two options, either test the members of group A against group B or test the members of group B against group A. The order would matter since the members of the groups can be distributed differently. One could argue that selecting the group with fewer members and testing them against the group with more could eliminate many tests in one go if the selected member is out of the group bounds. One could also argue that the members of the group with biggest bounding area should be tested against the other group, since chances are better that the members are not inside the smaller area. This would simply have to be tested and evaluated.

Yeah whatever, what about the results?

In my current setup I have one player, 10 enemies that fire periodically from two guns each. With the player firing away as well I end up with about 70 obstacles on average. With the broad phase culling the system does somewhere between 100-300 narrow tests. Without the broad phase we have by the formula 2415 narrow tests. Good show!

The relevant code:

I've tried to edit out irrelevant code to make it less confusing.
ObstacleManager.hpp
#ifndef CN_OBSTACLEMANAGER_H
#define CN_OBSTACLEMANAGER_H

#include "ComponentManagerBase.hpp"
#include "Obstacle.hpp"

namespace cn {

class EntityManager;

// --------------------------------------------------------------

class ObstacleGroup {
public:

void updateBounds( );
void testCollisions( ObstacleGroup &otherGroup, const Collider &collider );
void addObstacle( Obstacle *o );
void removeObstacle( Obstacle *o );

private:

typedef std::set ObstacleSet;
ObstacleSet obstacles;
rect boundingBox;

};

// --------------------------------------------------------------

class ObstacleManager : public ComponentManagerBase {
public:

ObstacleManager( EntityManager *entityManager );

void updateByFrame( );

void addObstacleToGroup( uint entityId );
void removeObstacleFromGroup( uint entityId );

private:

CircleCollider defaultCollider;

typedef std::multimap GroupIdMap;
typedef std::pair GroupIdMapIteratorPair;
typedef std::pair GroupIdPair;
GroupIdMap collisionTargets;

typedef std::map GroupIdGroupMap;
GroupIdGroupMap groups;

};

}

#endif









The updateByFrame() function is the motor here and the entering pint to the whole process. If you'd like to trace the code, that's where you start. The add/removeFromGroup() functions are called externally at add and remove times. The CircleCollider class inherits Collider and works like a functor performing the narrow test. There are several extra typedefs in the manager that are used in updateByFrame() to make it cleaner.

ObstacleManager.cpp
#include "Prerequisites.hpp"
#include "ObstacleManager.hpp"
#include "Obstacle.hpp"
#include "EntityManager.hpp"

namespace cn {

// --------------------------------------------------------------
// ObstacleManager class
// --------------------------------------------------------------

ObstacleManager::ObstacleManager( EntityManager *entityManager )
: ComponentManagerBase( entityManager ), defaultCollider( entityManager )
{
// Here the allowed collision targets for each id are defined.
// Allies maps to Allies, Enemies and EnemyFire, etc.
collisionTargets.insert( GroupIdPair( Allies, Allies ));
collisionTargets.insert( GroupIdPair( Allies, Enemies ));
collisionTargets.insert( GroupIdPair( Allies, EnemyFire ));
collisionTargets.insert( GroupIdPair( Enemies, Enemies ));
collisionTargets.insert( GroupIdPair( Enemies, Allies ));
collisionTargets.insert( GroupIdPair( Enemies, AlliedFire ));
collisionTargets.insert( GroupIdPair( AlliedFire, Enemies ));
collisionTargets.insert( GroupIdPair( EnemyFire, Allies ));
}

// --------------------------------------------------------------

void ObstacleManager::updateByFrame( ) {
// Make the bounds up to date.
// NOTE: However intermediate changes during collisions will not be caught.
GroupIdGroupMap::iterator it;
for ( it = groups.begin( ); it != groups.end( ); it++ ) {
it->second.updateBounds( );
}

// A-loop: Iterate over all present groups.
for ( it = groups.begin( ); it != groups.end( ); it++ ) {
// Find which groups the current group responds to.
GroupIdMapIteratorPair targetIds = collisionTargets.equal_range( it->first );
// Proceed if the range is not empty.
if ( targetIds.first != targetIds.second ) {
// B-loop: Iterate over all groups at same position and after.
GroupIdGroupMap::iterator jt;
for ( jt = it; jt != groups.end( ); jt++ ) {
// Proceed if A-element id responds to B-element id.
GroupIdMap::iterator kt;
for ( kt = targetIds.first; kt != targetIds.second; kt++ ) if ( kt->second == jt->first ) break;
if ( kt != targetIds.second ) {
// Now we know the two groups respond to each other.
// TODO: Make a decision on the test order, f.ex. by greatest area or least count.
it->second.testCollisions( jt->second, defaultCollider );
}
}
}
}
}

// --------------------------------------------------------------

void ObstacleManager::addObstacleToGroup( uint entityId ) {
groups[it->second->getGroupId( )].addObstacle( it->second );
}

// --------------------------------------------------------------

void ObstacleManager::removeObstacleFromGroup( uint entityId ) {
groups[it->second->getGroupId( )].removeObstacle( it->second );
}

// --------------------------------------------------------------
// ObstacleGroup class
// --------------------------------------------------------------

void ObstacleGroup::updateBounds( ) {
boundingBox = rect( 0, 0, 0, 0 );
ObstacleSet::iterator it;
for ( it = obstacles.begin( ); it != obstacles.end( ); it++ ) {
(*it)->updateBoundingBox( );
fit( boundingBox, (*it)->getBoundingBox( ));
}
}

// --------------------------------------------------------------

void ObstacleGroup::testCollisions( ObstacleGroup &otherGroup, const Collider &collider ) {
PROFILE_FUNC( );
// Check if groups intersect at all.
if ( intersect( boundingBox, otherGroup.boundingBox )) {
// Iterate over all local obstacles.
ObstacleSet::iterator it;
for ( it = obstacles.begin( ); it != obstacles.end( ); it++ ) {
// Check if the current local obstacle intersects the other group.
if ( intersect( (*it)->getBoundingBox( ), otherGroup.boundingBox )) {
// Check against all in other group.
ObstacleSet::iterator jt;
for ( jt = otherGroup.obstacles.begin( ); jt != otherGroup.obstacles.end( ); jt++ ) {
// Don't check against self.
if ( (*it)->getId( ) != (*jt)->getId( )) {
collider.apply( *it, *jt );
}
}
}
}
}
}

// --------------------------------------------------------------

void ObstacleGroup::addObstacle( Obstacle *o ) {
obstacles.insert( o );
}

// --------------------------------------------------------------

void ObstacleGroup::removeObstacle( Obstacle *o ) {
obstacles.erase( o );
}

}









Note that there are two classes here ObstacleManager and ObstacleGroup. The updateByFrame() function is really STL-messy, I know, so do ask.

Obstacle.hpp
#ifndef CN_OBSTACLE_H
#define CN_OBSTACLE_H

#include "ComponentManagerBase.hpp"
#include "ComponentBase.hpp"

namespace cn {

class EntityData;
class Obstacle;

// This is the group id definition.
enum ObstacleGroupId { Allies, Enemies, Neutrals, AlliedFire, EnemyFire, NeutralFire };

// --------------------------------------------------------------

class Obstacle : public ComponentBase {
public:

Obstacle( uint entityId, EntityData *entityData, const ComponentParameters ¶ms );

uint getId( ) { return id; }
const rect &getBoundingBox( ) { return boundingBox; }
void updateBoundingBox( );
ObstacleGroupId getGroupId( ) { return params.group; }

private:

// This object contains the group id for the obstacle.
ComponentParameters params;

rect boundingBox;

};

}

#endif









Here rect is simply a class describing a rectangle parallel to the coordinate axes.

Obstacle.cpp
#include "Prerequisites.hpp"
#include "Obstacle.hpp"
#include "EntityData.hpp"

namespace cn {

// --------------------------------------------------------------

void Obstacle::updateBoundingBox( ) {
// I didn't include the two functions below but they should be obvious.
vec2 pos = getPos( );
real radius = getRadius( );
boundingBox.left = pos.x - radius;
boundingBox.right = pos.x + radius;
boundingBox.bottom = pos.y - radius;
boundingBox.top = pos.y + radius;
}

}









Currently collisions are based on circles but if that changes, so will this function, obviously.

That's it for today folks.

Flow

I've had a real productivity streak this past week and have been able to push forward amazingly with the project. Before I began the system rewrite back in May I disabled a lot of functionality and now most of that is reimplemented using the new system. I'm very close to being perfectly happy with the entity system and yesterday it occurred to me that I should back away from it now before I start wasting time on minor implementation details. Given it's functional and stable I need to keep pushing on ahead if I'm ever going to make a game out of this.

The major improvements:

The Vehicle component, which controls the steering behavior movement of actors, has had its update code more or less rewritten. For its first incarnation I pulled most of the code directly from the Reynolds article, as I have mentioned in previous posts. That is, it used a very simple physics simulation where the different behaviors produced a propelling force. On the other hand I had grown to like the movement scheme I used in prototype 1, which is based on exponential interpolation (see below), so now the system uses this model instead.

As it works now, each behavior produces a velocity vector, all of which are summed to a desired velocity which the system tries to match up with. In this process speed and direction are treated separately to allow for more freedom. Speed is interpolated towards the desired speed using the weight 1-e-C(t1-t0) where C is a constant. This results in an interpolation that approaches but never reaches the endpoint value, giving a smooth damping effect. Also, since the system uses fixed-size timesteps, the weight becomes a constant expression. The direction is angularly interpolated towards the target in the same way although the shortest path around the circle must be determined first.

This was the model I used in P1 and, although not realistic to the point, it is smooth and pleasant to the eye, it's easy to get a feel for when playing, and it's very easy to maintain. The inputs are two interpolation weight and a maximum speed, and from that on everything else is naturally bounded. Perfect for autonomous control. The weights are divided by mass to simulate varying inertia.

The final model has one more regulation though. The motion may seem reckless if the agent is adapting to a large desired velocity opposite to its current direction. Because the forward acceleration begins before the agent has turned, its trajectory is something of a wide circle, which may cause it to slam into things if space is narrow sideways. To remedy this the desired speed is scaled by the saturated dot product of the current and the desired directions. For 180-90 degree diffs the speed will go to zero, and for 90-0 degree diffs the speed will gradually increase, and it works beautifully in practice. [cool]

I will keep the "reckless mode" movement around as an option for missiles and other full-speed-ahead things that seriously look cooler without the safer driving classes.

Here's the final expressions. angleLerp does linear interpolation on vector angles, after deciding the shortest path around.

// Update speed ( reckless mode )
speed += ( desiredSpeed - speed ) * accelWeight / mass;

// ... or

// Update speed ( delicate mode )
speed += ( desiredSpeed * clamp( dot( direction, desiredDirection ), 0, 1 ) - speed ) * accelWeight / mass;

// Update direction.
direction = angleLerp( direction, desiredDirection, rotSpeedWeight / mass );


Beyond that I've made an emitter component to handle particle effects, removed all old generation game objects and made sure the new ones match them, given components the ability to remove and create new entities by event functions and worked through a ton of bugs and shortcomings.

What's the next step?

There are still a number of problems with the entity-component system but as I said I'm not sure I should bother with them now. The vehicle component works now but it is not cleanly written and it's grown very bloated. In fact I consider splitting it up into three components, one for the steering, one for the facing control (since objects don't necessarily face the way they travel; take a gun turret for example) and one for simple motion with constant velocity. But I need more field experience with the system before I decide how to proceed with this.

The collision detection is both slow and error prone, but unless it acts up more than usual it can wait. It works most of the time. [rolleyes]

One thing that can't wait is the Audio component which, again, has been put off until the last moment. However this should be a simple matter to port from the old system.

The game has been overdue for a visual makeover for a long time and today I began looking into what style it should aim for. The reality is that I'm not a trained artist and I have no plans to enlist any others, so the style I pick must be simple enough for me to pull off myself. I toyed a bit with some palettes in Inkscape and made these background asteroids to try the colors (hey at least they're better than the old ones [grin]). I'll likely go with some flat style like this and spend more time picking good colors to set the mood.

Sign in to follow this  
  • Advertisement