Elysian Shadows

Transforms, Subsystems, and Components

I have also implemented a good deal of additional functionality (runtime getting and setting of the terrain and tile/object attributes). These methods will probably only be utilized through scripts (C++ or Lua). I'm posting the class declaration, just so you can have a look at function prototypes that have been implemented.

class TerrainSystem: public System { private: 	unsigned char currentTile[256]; 	unsigned char currentObject[256]; 	//toggles 	bool renderTileLayer[2], renderObjectLayer[2]; 	bool animate, renderSolidity; 	bool isValidLayer(const unsigned short int layer) const; 	bool isValidTile(const unsigned char index) const; 	bool isValidObject(const unsigned char index) const; 	bool isInMapBounds(const unsigned short int x, const unsigned short int y) const; public: 	TerrainSystem(); 	void Update(); 	void Render(); 	//void DBGDump(); 	//-------LUA STUFF--------- 	void getMapSize(int &w, int &h) const; 	unsigned short int getTileAmount() const; 	unsigned short int getObjectAmount() const; 	//Toggles 	bool getAnimate() const; 	void setAnimate(const bool v); 	bool getRenderSolidity() const; 	void setRenderSolidity(const bool v); 	bool getRenderTileLayer(const unsigned short int layer) const; 	void setRenderTileLayer(const unsigned short int layer, const bool v); 	bool getRenderObjectLayer(const unsigned short int layer) const; 	void setRenderObjectLayer(const unsigned short int layer, const bool v); 	Tile *getTile(const unsigned char index) const; 	Object *getObject(const unsigned char index) const; 	Tile *getCurrentTile(const unsigned char index) const; 	Object *getCurrentObject(const unsigned char index) const; 	unsigned char getTileLayer(const unsigned short layer, const int x, const int y) const; 	void setTileLayer(const unsigned short layer, int x,  int y, const unsigned char index); 	unsigned char getObjectLayer(const unsigned short layer, const int x, const int y) const; 	void setObjectLayer(const unsigned short layer, const int x, const int y, const unsigned char index); }; 

[b]gyro::Transform Parenting[/b]

Transform parenting is a very important aspect of the transform matrix. It allows us to create an entity (such as a sprite, collidable region, etc) whose location is [i]relative[/i] to another entity. In our engine, each entity is basically a rectangle. When you attach something like a "sprite" its transform becomes parented by the entity's transform. This enables the sprite to move around based on where the entity is located. Collidable regions work similarly. Some entities might have collidable regions that aren't the same as their sprite or rect regions. These collidables can have their own local position that serves as an offset from the parent transform.

Mathematically, this is implemented by transforming a transform by its parent (3x3 Matrix multiplication in 2D). For the behavior to be as we would expect, the child's transform must be [i]relative[/i] to the parent's. This actually becomes quite a pain in the ass and is the reason I'm not yet done.

So what if we have a transform heirarchy? The child's parent has a parent? Well, we must traverse the hierarchy upwards and examine each parent to determine value's for the child that is relative to each. It's technically a O(n) traversal every time we want to screw with a child's transform. (Sounds smart, huh?) I have yet to implement that functionality. 

Here is what I have of the Transform2D class. It is a bunch of ugly ass linear-algebra math. It'll be much uglier when I move this to assembly on the Dreamcast or PSP. 

class Transform2D: private Matrix3x3 { private: 	bool updateTransform; 	Vector2 position; 	Vector2 size; 	Vector2 scale; 	float orientation; 	float z; 	Transform2D *parent;  public: 	inline Transform2D(): Matrix3x3(), parent(NULL), position(0, 0), size(1.0f, 1.0f), scale(1.0f, 1.0f), orientation(0.0f), z(50.0f), updateTransform(true) {} 	inline Vector2 getPosition() { return position; } 	inline void getPosition(float &x, float &y) const { x = position.x; y = position.y; } 	inline void setPosition(const Vector2 pos) { position = pos; updateTransform = true; } 	inline void setPosition(const float x, const float y) { position.x = x; position.y = y; updateTransform = true; } 	inline float getZ() const { return z; } 	inline void setZ(const float p_z) { z = p_z; updateTransform = true; } 	inline Vector2 getSize() const { return size; } 	inline void getSize(float &x, float &y) const { x = size.x; y = size.y; } 	inline void setSize(const Vector2 &p_size) { size = p_size; updateTransform = true; } 	inline void setSize(const float x, const float y) { size.x = x; size.y = y; updateTransform = true; } 	inline Vector2 getScale() const { return scale; } 	inline void getScale(float &x, float &y) const { x = scale.x; y = scale.y; } 	inline void setScale(const Vector2 &p_scale) { scale = p_scale; updateTransform = true; } 	inline void setScale(const float x, const float y) { scale.x = x; scale.y = y; updateTransform = true; } 	inline float getOrientation() const { return orientation; } 	inline void setOrientation(float angle) { orientation = angle; updateTransform = true; } 	inline Matrix3x3 getMatrix3x3() { UpdateMatrix3x3(); return *this; } 	inline void setMatrix3x3(const Matrix3x3 &tr) { *((Matrix3x3*)this) = tr; updateTransform = false; } 	inline void setParent(Transform2D &p) { parent = &p; } 	inline void Rotate(float angle) { orientation += angle; updateTransform = true; } 	inline void Move(const Vector2 &dist) { position += dist; updateTransform = true; } 	inline void Move(const float xdist, const float ydist) { position.x += xdist; position.y += ydist; updateTransform = true; } 	inline void Scale(const Vector2 &amount) { scale *= amount; updateTransform = true; } 	inline void Scale(const float xamount, const float yamount) { scale.x *= xamount; scale.y *= yamount; updateTransform = true; } 	inline Vector2 Transform(const Vector2 &vec) {  		UpdateMatrix3x3(); 		if(parent) parent->UpdateMatrix3x3(); 		return (!parent)? (*((Matrix3x3*)this) * vec) : ((*((Matrix3x3*)this) * (*((Matrix3x3*)parent)))) * vec;  		//return (*((Matrix3x3*)this)*vec); 	} 	inline void UpdateMatrix3x3() {  		if(!updateTransform) return; 		Vector2 relativeSize = size*scale; 		CalculateTransform(orientation, relativeSize, position); 		updateTransform = false; 	} }; 

[b]CameraSystem[/b]

The camera system is currently NOT done. The plan was to be able to set an entity's transform as the camera transform's parent. This would allow the camera to follow any entity without having to be constantly passed a position every frame. If a script needs to manipulate the camera directly, it can set the camera's parent to NULL, and directly manipulate the transform's position, rotation, and scale. This is useful for cutscenes especially. I need to finish parenting functionality completely before I can do this, though.

[b]New Scripting Hierarchy[/b]

First of all, note that the "script" inheritance hierarchy has changed a little bit. Components that are built-in to our engine are simply inheriting from component (sprite, collidable, warp, rigidbody, etc). 

The next level down brings us to a "behavior." This is what I referred to as a "script" before. A behavior has a group of trigger functions like OnHit(), OnTalk(), OnMove(), OnUpdate(), etc that are called when a specific event occurs.

Any compiled C++ behavior simply inherits from this level (with it's own editor and engine implementation). It simply overwrites each virtual trigger that it wishes to implement for a particular behavior.

On the other end of the spectrum, we have a LuaBehavior, or a "script." The LuaBehavior's implementations of these virtual functions simply invoke functions from the Lua Script. So you can almost think of the Lua scripts as virtually inheriting from LuaBehavior.

[b]Dynamic C++ Behavior Registration[/b]

This is going to be difficult to explain... it is really just a solution that I found to help further separate user-defined C++ behaviors from our actual "engine." It's a pretty, object-oriented solution. 

The "Entity" class will contain a static vector of "allocator" functions. An allocator function basically provides an integer for its corresponding component, receives a file-handle, and allocates/deserializes its respective component. 

This way each new C++ behavior will not have to be some hardcoded addition to the Entity::Load()'s component switch statement. Entity simply calls the allocator (in its internal static array) that corresponds to the component index. The entity allocator gets passed a file handle, and returns its respective component (which is then polymorphed back up to a behavior and added to the entity's list of behavior's).

Don't worry if I've confused you. Just know it's very pretty.

[b]Conclusion[/b]

Everything that I have done so far is in my repository. It all builds perfectly with a fresh version of Visual Studio 2008 (provided that you set the correct working directory). [b]All that it does is render a map[/b] through the TerrainSystem currently. You will notice plenty of "could not load" errors in your console when you run the engine. This is because we have yet to save/load or handle things like rigidbodies, warps, enemies, etc. and the level function is expecting them.

I will next be working on command-line arguments so that Marcel's level editor can easily invoke the engine with a current Level and Area. Hopefully this will be an easy-to-access File->Run Current that allows you to seamlessly test your levels as you develop them. Peace, gents.

Falco Girgis
Falco Girgis is the founder and lead software architect of the Elysian Shadows project. He was previously employed in the telecom industry before taking a chance on Kickstarter and quitting his job to live the dream. He is currently pursuing his masters in Computer Engineering with a focus on GPU architecture.