Alrighty, bitch brigaders. Here we go.
[size=40]BOOM![/size]
[b]Intro[/b]:
Our absolute highest priority has been to propel the artistic/graphical department of Elysian Shadows forward, as this has long been the bottleneck preventing us from achieving our dreams. Our plan to do this has been two-fold. We clearly need to recruit more pixel artists (with the next video(s)), but in order to do this, we finally need a Toolkit build that will enable them to do their jobs.
I humbly come before you today with an answer to the latter issue and great news with regard to the former issue. The "Alpha" release of the "Alpha Pixel Artist" release is completed. I would like to encourage all of you to take it for a test drive and provide feedback, so we can get it 100% ready for pixel artists, level designers, and scripters who will be accelerating the development of ES in the coming months.
This is going to be a two-part post. The first part will be largely low-level and technical with regard to back-end Engine development. The second portion will be a detailed discussion about the features of the new Toolkit release. Since I know most of you are bitches who don't bother reading low-level code without pictures, feel free to skip over the engine segment and jump directly into operation of the new Toolkit release.
[size=20][b]====PUSSIES CAN SKIP OVER THIS PART!====[/b][/size]
[b]Engine work[/b]:
Before I get into the Toolkit build, I wanted to recap on the enormous amount of development that has been required to even get to that point. My last large update was the completion of LibGyro2.0... which is a driver-layer abstraction for the Engine. How the fuck did we go from there to the Toolkit? Well, have a seat as a I weave a tale, children.
With the rewrite of LibGyro came a drastic change in the API through which the Engine utilizes the library. This immediately meant that a large amount of refactoring would have to take place for the Engine to use the new API and take full advantage of the new features (like the hardware accelerated matrix stack). Because the Toolkit utilizes a fairly large chunk of the engine's runtime (AssetIO framework), this refactoring had to happen before I could even touch the Toolkit.
[b]Instance Transform[/b]:
The most immediate area requiring refactoring was our matrix and linear algebra math. LibGyro2.0 provides a much lower-level, much more powerful matrix stack closely mimicing OpenGL's (with quite a few powerful additions). Unfortunately, this "stack" is pretty low-level for entities and their Lua behaviors to have to manipulate. The most common matrix operation for an object is the well-known "instance matrix" which is a compound operation of scale, rotation, and translation. This is enough to describe any normal object's location in the world. The engine now contains a cute little inlined-to-fuck InstanceTransform structure that serves as a very lightweight wrapper around LibGyro's matrix stack.
/* Essentially a thin, inlined-to-fuck, OO C++ wrapper around LibGyro's InstanceMat() operation. */ /*class*/ struct InstanceTransform { //This is public for overhead reduction, but the accessors are safer. //Lua should treat this as a class GYVector2 _pos; float _posZ; GYVector3 _size; GYVector3 _scale; InstanceTransform* _parent; float _orientZ; public: InstanceTransform(void): _parent(NULL), _orientZ(0.0f) { //safety precaution! _pos.x = _pos.y = _posZ = 100.0f; _size.x = _size.y = _size.z = 100.0f; _scale.x = _scale.y = _scale.z = 1.0f; } void Apply(void) const { gyMatInstance(_pos.x, _pos.y, _posZ, _size.x*_scale.x, _size.y*_scale.y, _size.z*_scale.z, _orientZ); } void setPosition(const float x, const float y, const float z) { _pos.x = x; _pos.y = y; _posZ = z; } void setPosition(const float x, const float y) { _pos.x = x; _pos.y = y; } void setPosition(const GYVector3& pos) { _pos.x = pos.x; _pos.y = pos.y; _posZ = pos.z; } void setPosition(const GYVector2& pos) { _pos = pos; } void getPosition(float& x, float& y, float& z) const { x = _pos.x; y = _pos.y; z = _posZ; } void getPosition(GYVector3& pos) const { pos.x = _pos.x; pos.y = _pos.y; pos.z = _posZ; } void getPosition(GYVector2& pos) const { pos = _pos; } void setPosZ(const float z) { _posZ = z; } float getPosZ(void) const { return _posZ; } void setSize(const float x, const float y, const float z) { _size.x = x; _size.y = y; _size.z = z; } void setSize(const float x, const float y) { _size.x = x; _size.y = y; } void setSize(const GYVector3& size) { _size = size; } void getSize(float& x, float& y, float& z) const { x = _size.x; y = _size.y; z = _size.z; } void getSize(GYVector3& size) const { size = _size; } void getSize(GYVector2& size) const { size.x = _size.x; size.y = _size.y; } void setScale(const float x, const float y, const float z) { _scale.x = x; _scale.y = y; _scale.z = z; } void setScale(const float x, const float y) { _scale.x = x; _scale.y = y; } void setScale(const GYVector3& scale) { _scale = scale; } void getScale(float& x, float& y, float& z) { x = _scale.x; y = _scale.y; z = _scale.z; } void getScale(GYVector3& scale) const { scale = _scale; } void getScale(GYVector2& scale) const { scale.x = _scale.x; scale.y = _scale.y; } void setOrientZ(const float z) { _orientZ = z; } float getOrientZ(void) const { return _orientZ; } void Rotate(const float z) { _orientZ += z; } void Translate(const float x, const float y, const float z) { _pos.x += x; _pos.y += y; _posZ += z; } void Translate(const float x, const float y) { _pos.x += x; _pos.y += y; } void Translate(const GYVector3& dist) { _pos.x += dist.x; _pos.y += dist.y; _posZ += dist.z; } void Translate(const GYVector2& dist) { _pos.x += dist.x; _pos.y += dist.y; } void Scale(const float x, const float y, const float z) { _scale.x *= x; _scale.y *= y; _scale.z *= z; } void Scale(const float x, const float y) { _scale.x *= x; _scale.y *= y; } void Scale(const GYVector3& factor) { gyVec3CompProdAccum(&_scale, &factor); } //void VectorMult(); };
This class offers an extreme convenience to both the Toolkit and Lua. More complex operations that are not simple InstanceTransforms (such as camera transformations and terrain rendering) use the libGyro API directly.
[b]Terrain Rendering[/b]:
Entity rendering was pretty straightforward, and unless I wanted to sort entities by texture-type or alpha level (which would be a huge pain in the ass and wouldn't be worth it for just a few entities), there isn't a whole lot of potential optimization available.
Terrain rendering on the other hand is a gigantic amount of uniform objects from the same sheet. There is plenty of room for potential optimization here.
void TerrainSystem::Render(void) { static GYColor4 terrainColor = { 1.0f, 1.0f, 1.0f, 0.5f }; //I s'pose this could be configurable? unsigned startX, startY, endX, endY; cameraSystem->getOnscreenTileRegion(startX, startY, endX, endY); startX = 0; startY = 0; endX = 100; endY = 100; //if(startX > endX || startY > endY) return; When would this EVER be possible? _renderCount = 0; //It's faster to not switch textures. Render tiles and objects separately. //Although it requires double the transformation calculations. Opting to sacrifice CPU for GPU. =/ //If we could alter the Z coord in the current matrix, it'd take HALF the transforms! gyVidAlphaLevel(GY_ALPHA_NONE); gyVidTexBind(_curLevel->tilesheet); gyVidRectListBegin(&terrainColor); for(unsigned j = startY; j < endY; ++j) { for(unsigned i = startX; i < endX; ++i) { gyMatPush(); gyMatTranslateScale(i*32.0f, j*32.0f, ELYSIAN_ZCOORD_TILE1, 32.0f, 32.0f, 1.0f); //APPLY FLAGS gyVidRect(_curArea->tileLayer[0][j][i ]); //Adjust Z gyMatPop(); gyMatPush(); gyMatTranslateScale(i*32.0f, j*32.0f, ELYSIAN_ZCOORD_TILE2, 32.0f, 32.0f, 1.0f); //APPLY FLAGS gyVidRect(_curArea->tileLayer[1][j][i ]); gyMatPop(); _renderCount += 2; } } gyVidRectListEnd(); gyVidTexBind(_curLevel->objectsheet); gyVidRectListBegin(&terrainColor); for(unsigned j = startY; j < endY; ++j) { for(unsigned i = startX; i < endX; ++i) { gyMatPush(); gyMatTranslateScale(i*32.0f, j*32.0f, ELYSIAN_ZCOORD_OBJECT1, 32.0f, 32.0f, 1.0f); //APPLY FLAGS gyVidRect(_curArea->objectLayer[0][j][i ]); gyMatPop(); gyMatPush(); gyMatTranslateScale(i*32.0f, j*32.0f, ELYSIAN_ZCOORD_OBJECT2, 32.0f, 32.0f, 1.0f); //APPLY FLAGS gyVidRect(_curArea->objectLayer[1][j][i ]); gyMatPop(); _renderCount += 2; } } gyVidRectListEnd(); }
The new TerrainSystem::Render() function uses LibGyro's Rect API (hardware sprites). Tiles and objects are rendered separately to prevent frequent texture switching. I've also added the function gyMatTranslationScale() as a specialized compound matrix operation to the libGyro API, so that translation and scaling for the map (no rotation required) can be applied as a single matrix operation.
Each object/tile rendered still requires two transforms (one for each layer), but I'm fairly certain that I could make a special-purpose function to immediately modify the current matrix's Z value, so that this would only require a single operation.
Even without that, this method is waaaaaaaaaaaaay faster than how terrain rendering was handled.
[b]Support for Dynamic Resolution[/b]
One really important feature for the new Engine is to support a variety of different resolutions dynamically. Because we're targetting many different platforms with completely different resolutions and are now running the engine at varying resolutions on PCs (which can be dynamically resized), it's going to be very important to not develop the game with any hardcoded resolution parameters.
To solve this issue, the GUI system now has a public method that enables other subsystems to obtain the current projection (resolution) size for rendering.
void GuiSystem::getResolution(unsigned int &width, unsigned int &height) const { width = _projWidth; height = _projHeight; }
The GuiSystem obtains this information by polling the LibGyro driver every frame. This mechanism works much like the TimerSystem providing a deltaTime for framerate-independent logic. This provides a size for resolution-independent rendering.
It is my hope that we will be able to develop a UI system that completely abstracts this away from the designer/scripter in the future. Sizes and locations of the UI will be specified "relatively" and the engine will automatically handle size and placement.
Random high-res map rendering
Random low-res map rendering
[b]RenderOverlay[/b]
With the new stack-based rendering paradigm, we can no longer render UI elements in the same functions as camera-affected entities. This is due to the fact that the camera transform has to be removed from the bottom of the stack before rendering something without influence from the camera.
To solve this issue, any subsystem, lua script, or entity that needs to render a UI object will need to implement this rendering functionality within a RenderOverlay() function. The engine invokes Render() functions with an active camera transform. The engine later invokes RenderOverlay() functions after this transform has been popped from the stack. It's definitely more efficient to separate the two like this, and I think it's prettier anyway...
[b]CONSTANTS[/b]
After my grand return back to engine development, I came to realize that there were quite a few magic numbers, strings, and constants burried within the code. Lots of these values were also hard-coded within the Toolkit. I've taken the liberty of extracting large portions of this shit and moving it into the AssetIO where it can be shared with the Toolkit. Many of these may also be useful to a Lua developer, who can easily look at a header file to see/modify these magic values for a build:
elysian_gui_system.hpp
#define ELYSIAN_FONT_IMAGE_PATH_VERDANA"font/verdana.png" #define ELYSIAN_FONT_WIDTH_PATH_VERDANA"font/verdana_width.txt" #define ELYSIAN_FONT_IMAGE_PATH_TWCEN"font/twcen.png" #define ELYSIAN_FONT_WIDTH_PATH_TWCEN"font/twcen_width.txt"
elysian_party_system.hpp
#define ELYSIAN_PARTY_PATH"party/party.txt"
elysian_area.hpp
#define ELYSIAN_AREA_MAP_WIDTH 200 #define ELYSIAN_AREA_MAP_HEIGHT 200 #define ELYSIAN_AREA_NAME_SIZE 50 #define ELYSIAN_AREA_PATH_SIZE 200 #define ELYSIAN_AREA_MAP_PATH "/map.txt" #define ELYSIAN_AREA_ENTITY_PATH "/entity.txt"
elysian_level.hpp
#define ELYSIAN_LEVEL_NAME_SIZE 50 #define ELYSIAN_LEVEL_PATH_SIZE 200 #define ELYSIAN_LEVEL_TILESET_PATH "/tile/tileset.txt" #define ELYSIAN_LEVEL_OBJECTSET_PATH "/object/objectset.txt" #define ELYSIAN_LEVEL_TILESHEET_PATH "/tile/tilesheet.png" #define ELYSIAN_LEVEL_OBJECTSHEET_PATH "/object/objectsheet.png"
[b]ESTABLISHING A Z-LAYERING MECHANISM[/b]
Something that we've desperately needed to establish both within the Engine and Toolkit is a Z-layering mechanism. The z coordinate of an entity dictates its current layer. Within the engine we have two tile and object layers, a background layer, a foreground layer, etc. Not only do these have to stay consistent throughout the Engine, but they must be the same within the Toolkit, so we don't have any rendering/serialization discrepencies.
Because of this, I have established an elysian_zcoords.hpp header file. This is part of the AssetIO for the Toolkit and will also be available to Lua scripters at runtime.
#ifndef ELYSIAN_ZCOORDS_HPP #define ELYSIAN_ZCOORDS_HPP /* Z coordinates in the elysian engine are essentially layer number. They determine render order. */ //MAP LAYERS #define ELYSIAN_ZCOORD_BG 1.0f #define ELYSIAN_ZCOORD_TILE1 2.0f #define ELYSIAN_ZCOORD_OBJECT1 3.0f #define ELYSIAN_ZCOORD_TILE2 6.0f #define ELYSIAN_ZCOORD_OBJECT2 7.0f #define ELYSIAN_ZCOORD_FG 10.0f //OVERLAY LAYERS #define ELYSIAN_ZCOORD_ADVLOG 20.0f #endif
As a work in progress, it will definitely grow and change. That's why you use the constants, though, bitches. ;)
[b]Coding Practices[/b]
As I've been working at so many different layers of abstraction, and interfacing between LibGyro, the Engine, and the Toolkit, there are a few design decisions that I have decided to make with respect to the engine.
1) Every engine file is prefixed with elysian_. Because the Toolkit encapsulates engine objects, it is very common for both to have their own entity, component, level, area, etc. Even with the elysian:: namespace it became a nightmare to differentiate between the two. It has even caused linker errors on occassion. The prefix now matches the namespace (as with LibGyro). "component.h" is a Toolkit component and "elysian_component.hpp" is an engine component... The Toolkit pollutes the shit out of the global namespace with QT, so it doesn't need a prefix. ;)
2) All header files have changed from the ".h" to the ".hpp" extension. Writing the Engine and libGyro in parallel forces me to switch between C and C++ 10 times a day... so drawing this distinction helps keep me sane as to which language I'm in. ".hpp" is also the "proper" C++ extension anyway for us purist/elitist bitches. ;)
[b]LibGyro NULL implementation[/b]
This is kind of complex from a systems-level standpoint... The AssetIO is a portion of the Engine's runtime that is shared with the Toolkit for serializing/deserializing entities. Many of these entity "attributes" are LibGyro datatypes such as GYVector2, GYColor4, etc. Because of this, the Toolkit needs to have access to these datatypes even though it doesn't actually implement (or link to) the library.
With a few magic void typedefs, the Toolkit may now #include <gyro.h> in its entirety and access every structure and inline function utilized within libGyro WITHOUT linking to the library. Probably TMI, but I don't care ;)
[b]New Debug Mechanism[/b]
As this gigantic amount of refactoring was going on, I did a considerable amount of debugging. Our current logging mechanism captures quite a vast amount of information that makes debugging from the Engine, Toolkit, or Lua quite convenient. If something fucks up, you know essentially exactly what happened...
In an adderall-driven fit of rage, I discovered a really cool way to expand upon this idea. Half stack-trace, half debug-log, my new logging mechanism offers much more readability to the Engine and a straight-up badass surprise to the Toolkit.
The original framework (from the engine's perspective) was posted on our Facebook:
The engine is tasked with multiplexing debug input from LibGyro, Lua, AssetIO, and itself into a single log. There are varying levels of debug information that can be toggled on and off. For example, when you're debugging a Lua script, you probably don't give a shit to see that a texture has been allocated successfully. But you probably would care if it failed to allocate and screws your script up. Not only does the log allow you to pay attention to only specific events, but it also keeps a "stack" of events, so that a failure can be traced all the way down to its root.
To utilize this mechanism, each function requiring a debug event uses the "debug stack." I have written an example of how to do this for the Engine and Toolkit in a header comment within elysian_asset.hpp:
DEBUG LOGGING CONVENTION * invokee prints its own name, pushes, prints errors/events, pops * invoker does NOT print anything about invokee * ensure that functions with early-outs pop their frames, or you've reested the stack * DON'T use tabs, newlines, or special characters and shit. That's the point of implementation-specific formatting. EXAMPLE CODE: bool LoadShit() { bool success = true; Log(VERBOSE, "LoadShit()"); //Function name/action comes before push Push(); //Only report errors happening within THIS function, not children. if(SOMETHING_REESTED) { Log(CRITICAL, "Something has reested!"); Pop();//EVERY EARLY RETURN MUST POP, otherwise you reest the stack! return false;//reested so badly, we must exit early } success &= LoadOtherShit(); //Children functions push their own frames and announce their own errors success &= LoadOtherShit(); Pop(); return success; } EXAMPLE STACK: + Level::Load + Level::LoadTileset + FAILURE! + Level::LoadObjectset + Level::LoadAreas + Area::Load + Area::LoadMap + FAILURE! + Area::LoadEntities + Entity::Load + Component::Load + Component::Load + Entity::Load + FAILURE! */
Now we can view the debug.txt that the engine shits out as it logs events. It's no longer a reesting mess. As a matter of fact, it's downright pretty. You can easily locate errors and fix them:
-----Debug Initialized----- LuaManager::LuaManager() - Initializing main Lua state. Engine::LoadAssets() PartySystem::LoadPlayable(party/party.txt) _playableAmount - 3 Entity::Load(ifstream) _name = TestCharacter2 ! - Unknown component type 255! Entity::Reested(TestCharacter2) - Dereesting entity! Entity::Load(ifstream) _name = 255 ! - Unknown component type 255! Entity::Reested(255) - Dereesting entity! Entity::Load(ifstream) _name = 255 ! - Unknown component type 14221312! Entity::Reested(255) - Dereesting entity! GuiSystem::LoadAssets() Font::Load(font/twcen.png, font/twcen_width.txt) Font::Load(font/verdana.png, font/verdana_width.txt) Level::Load(level/Test_town1) Level::LoadTileset(level/Test_town1/tile/tileset.txt) Tile Amount - 48 0: Next - 0, NextFrameTime - 0.000000 1: Next - 0, NextFrameTime - 0.000000 2: Next - 0, NextFrameTime - 0.000000 3: Next - 0, NextFrameTime - 0.000000 4: Next - 0, NextFrameTime - 0.000000 5: Next - 0, NextFrameTime - 0.000000 6: Next - 0, NextFrameTime - 0.000000 7: Next - 0, NextFrameTime - 0.000000 8: Next - 0, NextFrameTime - 0.000000 9: Next - 0, NextFrameTime - 0.000000 10: Next - 0, NextFrameTime - 0.000000 11: Next - 0, NextFrameTime - 0.000000 12: Next - 0, NextFrameTime - 0.000000 13: Next - 0, NextFrameTime - 0.000000 14: Next - 0, NextFrameTime - 0.000000 15: Next - 0, NextFrameTime - 0.000000 16: Next - 0, NextFrameTime - 0.000000 17: Next - 0, NextFrameTime - 0.000000 18: Next - 0, NextFrameTime - 0.000000 19: Next - 0, NextFrameTime - 0.000000 20: Next - 0, NextFrameTime - 0.000000 21: Next - 0, NextFrameTime - 0.000000 22: Next - 0, NextFrameTime - 0.000000 23: Next - 0, NextFrameTime - 0.000000 24: Next - 0, NextFrameTime - 0.000000 25: Next - 0, NextFrameTime - 0.000000 26: Next - 0, NextFrameTime - 0.000000 27: Next - 0, NextFrameTime - 0.000000 28: Next - 0, NextFrameTime - 0.000000 29: Next - 0, NextFrameTime - 0.000000 30: Next - 0, NextFrameTime - 0.000000 31: Next - 0, NextFrameTime - 0.000000 32: Next - 0, NextFrameTime - 0.000000 33: Next - 0, NextFrameTime - 0.000000 34: Next - 0, NextFrameTime - 0.000000 35: Next - 0, NextFrameTime - 0.000000 36: Next - 0, NextFrameTime - 0.000000 37: Next - 0, NextFrameTime - 0.000000 38: Next - 0, NextFrameTime - 0.000000 39: Next - 0, NextFrameTime - 0.000000 40: Next - 0, NextFrameTime - 0.000000 41: Next - 0, NextFrameTime - 0.000000 42: Next - 0, NextFrameTime - 0.000000 43: Next - 0, NextFrameTime - 0.000000 44: Next - 0, NextFrameTime - 0.000000 45: Next - 0, NextFrameTime - 0.000000 46: Next - 0, NextFrameTime - 0.000000 47: Next - 0, NextFrameTime - 0.000000 Level::LoadObjectset(level/Test_town1/object/objectset.txt) Object Amount - 48 0: Next - 13, NextFrameTime - 0.000000 1: Next - 13, NextFrameTime - 0.000000 2: Next - 13, NextFrameTime - 0.000000 3: Next - 13, NextFrameTime - 0.000000 4: Next - 13, NextFrameTime - 0.000000 5: Next - 13, NextFrameTime - 0.000000 6: Next - 13, NextFrameTime - 0.000000 7: Next - 13, NextFrameTime - 0.000000 8: Next - 13, NextFrameTime - 0.000000 9: Next - 13, NextFrameTime - 0.000000 10: Next - 13, NextFrameTime - 0.000000 11: Next - 13, NextFrameTime - 0.000000 12: Next - 13, NextFrameTime - 0.000000 13: Next - 13, NextFrameTime - 0.000000 14: Next - 13, NextFrameTime - 0.000000 15: Next - 13, NextFrameTime - 0.000000 16: Next - 13, NextFrameTime - 0.000000 17: Next - 13, NextFrameTime - 0.000000 18: Next - 13, NextFrameTime - 0.000000 19: Next - 13, NextFrameTime - 0.000000 20: Next - 13, NextFrameTime - 0.000000 21: Next - 13, NextFrameTime - 0.000000 22: Next - 13, NextFrameTime - 0.000000 23: Next - 13, NextFrameTime - 0.000000 24: Next - 13, NextFrameTime - 0.000000 25: Next - 13, NextFrameTime - 0.000000 26: Next - 13, NextFrameTime - 0.000000 27: Next - 13, NextFrameTime - 0.000000 28: Next - 13, NextFrameTime - 0.000000 29: Next - 13, NextFrameTime - 0.000000 30: Next - 13, NextFrameTime - 0.000000 31: Next - 13, NextFrameTime - 0.000000 32: Next - 13, NextFrameTime - 0.000000 33: Next - 13, NextFrameTime - 0.000000 34: Next - 13, NextFrameTime - 0.000000 35: Next - 13, NextFrameTime - 0.000000 36: Next - 13, NextFrameTime - 0.000000 37: Next - 13, NextFrameTime - 0.000000 38: Next - 13, NextFrameTime - 0.000000 39: Next - 13, NextFrameTime - 0.000000 40: Next - 13, NextFrameTime - 0.000000 41: Next - 13, NextFrameTime - 0.000000 42: Next - 13, NextFrameTime - 0.000000 43: Next - 13, NextFrameTime - 0.000000 44: Next - 13, NextFrameTime - 0.000000 45: Next - 13, NextFrameTime - 0.000000 46: Next - 13, NextFrameTime - 0.000000 47: Next - 13, NextFrameTime - 0.000000 Level::LoadTilesheet(level/Test_town1/tile/tilesheet.png) Level::LoadObjectsheet(level/Test_town1/object/objectsheet.png) Area::Load(level/Test_town1/area/Uptown) Area::LoadMap(level/Test_town1/area/Uptown/map.txt) Name - Size - 100x100 Area::LoadEntities(level/Test_town1/area/Uptown/entity.txt) 6 entities Entity::Load(ifstream) _name = Reested Entity::Load(ifstream) _name = Reested Entity::Load(ifstream) _name = Reested Entity::Load(ifstream) _name = Reested Entity::Load(ifstream) _name = Reested Entity::Load(ifstream) _name = Reested ! - PartySystem::AddPlayableToParty(TestCharacter2): Not a loaded playable! ! - PartySystem::AddPlayableToParty(TestCharacter3): Not a loaded playable! ! - PartySystem::AddPlayableToParty(TestCharacter4): Not a loaded playable!
Cute, eh? Just wait until you see what using this in the AssetIO lets us do in the Toolkit...
[size=20][b]====PUSSIES CAN READ THIS PART!====[/b][/size]
[b]Alpha Toolkit Build[/b]:
Has arrived. Up until this point, the only real working features within the Toolkit have been entity manipulation. Without a way to modify terrain, it has been essentially useless. This next revision puts the emphasis back on terrain and world creation with Marcel's selection system. I believe this will give us a firm starting point for commencing with level design in Elysian Shadows. It also features BASIC entity manipulation, which should be enough to lay characters, objects, warps, and collidables with advanced features being provided through Lua.
There was also a pretty large surprise promised by Marcel that will not be in this update. I'm not sugarcoating it this time, because I've once again easily done 5 times my fair share for a build that was supposed to be Marcel's responsibility. If/when Marcel decides to get off his ass and finish/commit his features, we'll have another impressive update on our hands. I will at least vouch for the usefulness of the features he has been "implementing." It may be days, it may be months. Either way, the project cannot afford to wait for him to get his priorities straight. If I'm to once again be the sole Toolkit developer along with the Engine and LibGyro, so fucking be it.
[b]Terrain Modification[/b]:
This was part of Marcel's "update" several months back. I don't know if it ever got an official internal release, or if any of you have even screwed with it, but it's pretty damn solid at this point. Other than the fact that the selection window requires a right click (rather than left click) to select tiles, I have no complaints. Marcel even over-achieved and made it support lump selection and what I call "random-as-shit-staggered selection" by holding control and clicking on other tiles. You can also use the right click to select tiles that have already been layed on the map.
Group-selecting existing tiles
Staggered selection (hold CTRL)
Just make sure to pay attention to the tile selection widget. If what you're doing seems confusing or weird, the chances are that you're viewing and modifying different layers. This works as it does in Photoshop. It's often easiest to disable all layers other than the one you are currently editing.
Layer Widget
[b]Basic Entity Manipulation[/b]
Previously, basic entity attributes INCLUDED color and texture. Since the refactoring of the engine, I've decided to abstract the rendering mechanisms away from entities (so that we can later on use other primitives like 3D models (WAY LATER)). Anyway, with such an abstraction in place, I introduce the "Renderable" component, as a logical mechanism through which renderable functionality may be added to an Entity. It makes sense that some entities are not renderable (warps, simple colliders, etc).
Anyway, when we create a new entity, these are its basic attributes:
Name: string by which it can be referenced
Pos: position on the map (z corresponds to its layer)
Size: base size
Scale: multiplier applied to size when determining actual size
Rot: orientation about the Z-axis
Please note that even though an entity isn't "renderable," we still want to see it in the Toolkit (even if we can't in the engine). As such, they default to semi-transparent gray.
We can now opt to attach a "Renderable" component to make the bastard renderable.
With this, we can choose between a making the Entity a colored rectangle or a colored sprite.
The swatches allow you to easily pick a color. The color should then be immediately reflected in the scene.
Checking the textured box signifies that we've gone from a rectangle to a sprite. We can now specify a texture for the entity.
The sheet width and height are populated automatically (so you don't kill the engine). You then specify the dimensions of each sprite within the sheet and your starting frame. Then you're ready to apply the texture:
Since there is quite a bit happening behind the scenes here (files loading, textures being split up, shit rendering), there's a lot that can go wrong. I've tested the fuck out of this with Tyler, and it's pretty stable. But you guys still might want to have a good look at this.
Specifying an invalid texture results in a blood-red error quad:
Also, keep in mind that you can STILL recolor a textured entity. It doesn't show up in the Toolkit (I haven't figured out how to do it yet), but it WILL show up in the engine. The above example would cause our entity character to also be green. :D
[b]Invoke Engine[/b]:
It's still here, but the state of the current build of the engine that is in the repositories makes it borderline useless. I still have quite a bit of work to do on this front, but I skipped ahead and jumped to the Toolkit to get art kicked off and level design happening in parallel with my Engine work (priorities, right)? I hope to return to getting the engine back to fully running soon after this post. At very least, you can see that the resolution is no longer fixed. Oh, and that the engine's debug log is much sexier...
Invoking the current Engine build...
This has NOT been tested extensively. Don't be suprised if the Engine crashes. There's no need to report that as a bug until I have a chance to do further work on it. The Toolkit should gracefully report error events with regards to the Engine... crashing, unable to invoke, etc.
ALSO with Visual Studio 2010, there is no longer a redistributable manifest for release builds. You need to install it from here (or don't bother and wait until there is more functionality): [url]http://www.microsoft.com/download/en/details.aspx?id=5555[/url]
[b]Revamped Debug Log[/b]:
The debugging mechanism that I described and showed within the Engine has also trickled its bitchass into the AssetIO and Toolkit. Reimplementing the AssetIO's stack-based debug interface allows us to do some really cool shit with the Toolkit's debug log.
We are now able to provide a full stack-trace that not only highlights errors, but also tells you whose fault they are... ASSETIO errors come from the Engine and are probably my fault. TOOLKIT errors come from Toolkit code and are probably Marcel's fult. Successful events are collapsed by default. An error causes the event to fully unravel and the debug log to pop automagically, so you can see clearly what the problem was.
No error!
Shit has reested! (and it automagically popped open).
Also, for post-mortem debugging, the Toolkit also produces an Engine-style text dump of this log that is saved as "toolkit_debug.txt" just like the Engine's "debug.txt." Please supply this log along with any crash reports that you post here. :D
[b]Outro[/b]:
So here's our first build. This is not the completed alpha that we are giving the artists, but it is the first release on our road to that point. Please try it out and provide feedback. You'll find the Windows builds of the Toolkit and Engine are already in the Project repository.
The OSX and Linux builds will be coming shortly. I'm fairly sure that all of you are using Windows, so I didn't want to waste time putting those two in there before making this post. At least Windows and OSX have been tested extensively.
Provide any crash reports/feedback here in this topic. I'll add them to the grand list of things to be addressed. Many of the things you bitch about are probably also already on that list. We're aware. ;)
We hope to have this all stable and good to go in parallel with the release of Chapter 18 and the new website. Ideally Marcel would be the one handling your requests while I get the engine up... but we'll see if that actually happens.
Also, I've been extensively interviewing somebody for a pixel artist position. This guy is AMAZING at pixel, concept, and any other form of art I've thrown at him. He is also really good at Premiere and After Effects, and wants to help take our videos to the next level. He's an ex marine and is married to a hooker. As soon as he finishes his sheet for his resume, I'll be posting it for you to judge.
Also, I used your image uploader, Dandymcgee, and it really made constructing this rape post faaar more convenient. Thanks for that!
As usual, feel free to get on my level and [color=#0000FF]Faithfully pursue a Policy of Pristinity[/color] rather than a [color=#FF0000]Requiem of Reest[/color], gentlemen. BACK TO WORK!