• Advertisement
  • entries
    10
  • comments
    13
  • views
    7835

About this blog

My adventures trying to hack up something resembling a game using different hacky technologies

Entries in this blog

Voxelshop commented on my entry yesterday and said that he solved the normals problem I had with COLLADA. I just checked it out and it all works! So COLLADA is back on the menu and so is VoxelShop. I am definitively sticking to it now. Its user interface is way more comfortable then other voxel editors. Thanks Voxelshop!

Here is rendered .dae in game with normals on:

collada_Works.png

This is with my dodgy shader for the lights. Looks good. And don't forget to grab Java installer for latest version of VoxelShop.

While irrLicht got its own data serialization, I will use my own entity manager for that. It is super simple and very straightforward. For my game I want everything to be data driven, especially entity setup, loading and saving game. To store the data I will use plain xml and tinyXml to manipulate it.

The idea behind it is simple, you have a hierarchy of entities, each entity instantiates its children and their children instantiates its and so on. Same goes for save/load, entity loads/saves itself and then calls it children to do the same.

Before I go into details lets look at the data. First of all there are two type of files. First one is .entity.xml, which is a simple collection of entity descriptors. Data in these files is constant and cannot be changed by the game run-time (it just a rule, it actually can be with a simple fix, but I stick to the rule to keep everything clean). Then there is a save file, which is a hierarchy of entities from .entity.xm files and contains entities members that can be edited by game run-time.

Lets look at an example. Here is space_system.entity.xml:[code=xml:0] maxresdefault.jpg space.jpg
As you can see there is two entities with a single parameter "skybox". Skybox is constant and cannot be change during the run-time. Also there is "type" which is class ID, used for identification, and a "class" which is class name.


Here is a new game file, which is just like a safe file but all values at some default state:[code=xml:0] Main SA World SS1 SS1
Here you can actually the the hierarchy, Galaxy got System entities. This is a most basic example which I will use to get the blank system to load up for testing. All members can be changed, for example would change in save file if players moved to another system, or name can be change if player renames the system.


Now to the actual code. There three high level classes in entity system. First one is EntityDB, it loads all entity.xml files and keep them as a library for instantiating of game run-time entities. At the start of the run time it get a directory with entity.xml files and goes through each and one of them. Late GetDesc() function can be used to get descriptor of an entity.namespace GAME{ class EntityDB : public StaticSingleton, public Factory { public: // Entity DB bool LoadResources(const char* dirName); bool AddResource(const char* fileName); void FreeResources(); inline bool IsResourceLoaded(const StringId rcName) const { return m_entityRc.find(rcName)!=m_entityRc.end();} inline const XmlNode* GetDesc(uint32 typeId) const { EntityDescs::const_iterator it=m_entityDesc.find(typeId); return it!=m_entityDesc.end()?&((*it).second):NULL;} uint EnumDescs(std::vector &descs) const; EntityPtr Create(uint32 typeId, uint32 id); protected: typedef Factory EntityFactory; typedef std::map EntityDescs; typedef std::map EntityResources; EntityDescs m_entityDesc; EntityResources m_entityRc; // Utils EntityPtr Create(const char* name, uint32 id); public: struct EntityRegistrar { EntityRegistrar(const char* name, EntityFactory::CreateFunction func) { EntityDB::Ref().Add(name, func);} }; };}
Next class is Entity. That is where all the magic happens. In its Init() function it reads constant data from entity.xml. In its Load() function it read from the save file. There also a PostLoad() function, mainly to set up references to other entities after everything was loaded. In Activate() and Deactivate() function the visual state usually created and set.namespace GAME{ class Entity; typedef SharedPtr EntityPtr; class Entity { public: Entity():m_id(0),m_typeId(0) {} virtual ~Entity() {} uint32 GetId() const { return m_id;} uint32 GetTypeId() const { return m_typeId;} StringId GetClassId() const { return m_classId;} virtual bool Init(const XmlNode* desc) { m_desc = desc; return true;} virtual void Done(); virtual void Update(float dt); virtual bool Save(XmlNode& data); virtual bool Load(const XmlNode& data, bool newGame = false); virtual bool PostLoad(bool newGame = false); virtual void Activate(ISceneNode* parent); virtual void Deactivate(); virtual bool IsSerializable() { return true; } virtual EntityPtr GetChildById(uint32 id, bool recursively = true) const; protected: typedef std::vector Entities; uint32 m_id; uint32 m_typeId; StringId m_classId; const XmlNode* m_desc; Entities m_children; friend class EntityDB; }; }
Last class is EntityManager, which is the root node of all entities. It can be used to find any entity in hierarchy, to active, deactivate all entities, or simply clean them all up. Also a note, EntityManager only hold entities that need to be save or loaded. Entities without constant state, like for example projectiles, will be managed by different EntityManager.


Here is an Load() function from Entity class. As you can see, if the it is a new game, each entity will get new id, which then will be saved as its attributed. I will paste in whole Entity.cpp. Each function is pretty much a loop which goes through its children and calls same function on it!namespace GAME{ static StringId tagEntities("entities"); static StringId tagEntity("entity"); static StringId tagClass("class"); static StringId tagType("type"); static StringId tagId("id"); void Entity::Done() { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Done(); } } void Entity::Update(float dt) { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Update(dt); } } bool Entity::Save(XmlNode& data) { data.Attr(tagType) << m_typeId; data.Attr(tagClass) << m_classId; data.Attr(tagId) << m_id; if(m_children.size() > 0) { XmlNode childrenData = data(tagEntities); for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { if((*it)->IsSerializable()) { XmlNode node = childrenData(tagEntity); (*it)->Save(node); } } } return true; } bool Entity::Load(const XmlNode& data, bool newGame) { // load children const XmlNode& childrenData = data(tagEntities); for (const XmlNode node = childrenData(tagEntity);node;node.Next()) { uint32 typeId = 0, id = 0; node.Attr(tagType) >> typeId; if(newGame) { id = EntityManager::Ref().GenerateId(); } else node.Attr(tagId) >> id; EntityPtr e = EntityDB::Ref().Create(typeId, id); if(e) { const XmlNode* desc = EntityDB::Ref().GetDesc(typeId); if(desc) { if(e->Load(node,newGame)) { m_children.push_back(e); } else SLOGS_ERR(entities,("Cannot Load entity with type <%d> and id <%d>", typeId, id)); } else SLOGS_ERR(entities,("Cannot find entity desc with type <%d> and id <%d>", typeId, id)); } else SLOGS_ERR(entities,("Cannot create entity with type <%d> and id <%d>", typeId, id)); } return true; } bool Entity::PostLoad(bool newGame) { bool res = true; for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { res = res && (*it)->PostLoad(newGame); } return res; } void Entity::Activate(ISceneNode* parent) { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Activate(parent); } } void Entity::Deactivate() { for(Entities::iterator it=m_children.begin();it!=m_children.end();++it) { (*it)->Deactivate(); } } EntityPtr Entity::GetChildById(uint32 id, bool recursively) const { for(Entities::const_iterator it=m_children.begin();it!=m_children.end();++it) if((*it)->GetId() == id) return *it; if(recursively) { for(Entities::const_iterator it=m_children.begin();it!=m_children.end();++it) { EntityPtr ch = (*it)->GetChildById(id, recursively); if(ch) return ch; } } return EntityPtr(); }}
Also in Entity you can see the way attributes are being read and written. A static member with attributed name is declared at the top of the class and then used to read or write into XmlNode, which got operators >> and << overloaded for most of the simple types. static StringId tagName("name"); static StringId tagActiveSys("active_system"); bool Galaxy::Save(XmlNode& data) { data(tagName) << m_name; data(tagActiveSys) << m_activeSys->GetId(); return Entity::Save(data); } bool Galaxy::Load(const XmlNode& data, bool newGame) { data(tagName) >> m_name; m_nodeSys = data(tagActiveSys); return Entity::Load(data, newGame); }
That is all for today. I hope all of that made at least a little bit of scene. But good news is that we are finally to the stage where different entities can be set up. I will start with a simple static entity which will load a mesh and place it somewhere in space. I will need to decide on scale of the voxel square as it will be a measurement of a single "pixel". Finally some exciting stuff!

A simple shader

After I expoted my obj object I still had one problem, irrLicht, for some reason, did not mix a directional and ambient light. Maybe because of the obj format, or maybe because I needed to give it a proper material? I am not sure, but I decided that by writing a simple shader I will easily fix that problem, and will have more control over the light. What I needed is a shader that can take a single directional light, apply it to the texture color and mix in ambient. I went with High-Level Shading Language (HLSL) as it is easy to understand and use, and it is kind of the present and the future.

I took an example shader from irrLicht, reworked it a little bit and made it do what I need. Here it is://-----------------------------------------------------------------------------// Global variables//-----------------------------------------------------------------------------float4x4 mWorldViewProj; // World * View * Projection transformationfloat4x4 mInvWorld; // Inverted world matrixfloat4x4 mTransWorld; // Transposed world matrixfloat3 mLightPos; // Light positionfloat4 mLightColor; // Light colorfloat4 mAmbientLight; // Light color// Vertex shader output structurestruct VS_OUTPUT{ float4 Position : POSITION; // vertex position float4 Diffuse : COLOR0; // vertex diffuse color float2 TexCoord : TEXCOORD0; // tex coords};VS_OUTPUT vertexMain(in float4 vPosition : POSITION, in float3 vNormal : NORMAL, float2 texCoord : TEXCOORD0 ){ VS_OUTPUT Output; // transform position to clip space Output.Position = mul(vPosition, mWorldViewProj); // transform normal float3 normal = mul(float4(vNormal,0.0), mInvWorld); // renormalize normal normal = normalize(normal); // position in world coodinates float3 worldpos = mul(mTransWorld, vPosition); // calculate light vector, vtxpos - lightpos float3 lightVector = worldpos - mLightPos; // normalize light vector lightVector = normalize(lightVector); // calculate light color float tmp = max(0, dot(-lightVector, normal)); float4 endColor = mLightColor * tmp; Output.Diffuse = endColor; Output.TexCoord = texCoord; return Output;}// Pixel shader output structurestruct PS_OUTPUT{ float4 RGBColor : COLOR0; // Pixel color};sampler2D myTexture; PS_OUTPUT pixelMain(float2 TexCoord : TEXCOORD0, float4 Position : POSITION, float4 Diffuse : COLOR0 ) { PS_OUTPUT Output; float4 col = tex2D( myTexture, TexCoord ); // sample color map // multiply with diffuse Output.RGBColor = col * (Diffuse * (float4(1.0, 1.0, 1.0, 0.0) - mAmbientLight) + mAmbientLight); //Output.RGBColor *= 7.0; return Output;}
The vertex pass (vertexMain) sets the positions, calculated the light color and sets it into defuse output. The pixel path (pixelMain) samples the texture for the pixel's color and mixes in diffuse and ambient lights/colors. Simple and easy.Output.RGBColor = col * (Diffuse * (float4(1.0, 1.0, 1.0, 0.0) - mAmbientLight) + mAmbientLight);
This line looks strange because I tried to make the shading break little it softer. I think it does it, I think I see a little bit of difference. But simple multiplication works fines too.


IrrLitch got a very simple way to handle shaders using video::IGPUProgrammingServices and addHighLevelShaderMaterialFromFiles function. To setup shader's global variables a callback class (video::IShaderConstantSetCallBack) is needed. Here is one I wrote, again taken from irrLitch shaders example: BasicShaderCallBack::BasicShaderCallBack(IrrlichtDevice* d, ILightSceneNode* l) { device = d; light = l; } void BasicShaderCallBack::OnSetConstants(video::IMaterialRendererServices* services, s32 userData) { video::IVideoDriver* driver = services->getVideoDriver(); // set inverted world matrix // if we are using highlevel shaders (the user can select this when // starting the program), we must set the constants by name. core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD); invWorld.makeInverse(); services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16); // set clip matrix core::matrix4 worldViewProj; worldViewProj = driver->getTransform(video::ETS_PROJECTION); worldViewProj *= driver->getTransform(video::ETS_VIEW); worldViewProj *= driver->getTransform(video::ETS_WORLD); services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16); // set camera position core::vector3df pos = light->getAbsolutePosition(); services->setVertexShaderConstant("mLightPos", reinterpret_cast(&pos), 3); // set light color video::SColorf col(1.0f, 1.0f, 1.0f, 0.0f); services->setVertexShaderConstant("mLightColor", reinterpret_cast(&col), 4); // ste ambient light video::SColorf colAmbient(0.1f, 0.1f, 0.1f, 1.0f); services->setPixelShaderConstant("mAmbientLight", reinterpret_cast(&colAmbient), 4); // set transposed world matrix core::matrix4 world = driver->getTransform(video::ETS_WORLD); world = world.getTransposed(); services->setVertexShaderConstant("mTransWorld", world.pointer(), 16); // set texture, for textures you can use both an int and a float setPixelShaderConstant interfaces (You need it only for an OpenGL driver). s32 TextureLayerID = 0; services->setPixelShaderConstant("myTexture", &TextureLayerID, 1); }
It takes a single light (ILightSceneNode) and device (IrrlichtDevice) to work out the color and positioning. It setups the direction light position, its color, ambient color, world matrices and actual texture. Colors are hardcoded for now, I will make it data driven later when I will setup proper lights.


Now in actual game code I do following: //directional light scene::ILightSceneNode* light2 = m_smgr->addLightSceneNode(0, core::vector3df(0, 0, 0), video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 100.0f); light2->setDebugDataVisible(scene::EDS_NORMALS); io::path hsFileName = "d3d9.hlsl"; // check for shader compatability if (!m_driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) && !m_driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)) { m_device->getLogger()->log("WARNING: Pixel shaders disabled "\ "because of missing driver/hardware support."); hsFileName = ""; } if (!m_driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) && !m_driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)) { m_device->getLogger()->log("WARNING: Vertex shaders disabled "\ "because of missing driver/hardware support."); hsFileName = ""; } //grab gpu video::IGPUProgrammingServices* gpu = m_driver->getGPUProgrammingServices(); s32 newMaterialType1 = 0; if (gpu) { BasicShaderCallBack* mc = new BasicShaderCallBack(m_device, light2); // create material from high level shaders (hlsl, glsl or cg) newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles( hsFileName, "vertexMain", video::EVST_VS_1_1, hsFileName, "pixelMain", video::EPST_PS_1_1, mc, video::EMT_SOLID, 0, video::EGSL_DEFAULT); } scene::IAnimatedMesh* mesh = m_smgr->getMesh("chr_old.obj"); IMeshSceneNode* node = m_smgr->addMeshSceneNode(mesh); if (node) { node->setMaterialTexture(0, m_driver->getTexture("chr_old.png")); node->setScale(vector3df(0.2f, 0.2f, 0.2f)); node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1); node->setDebugDataVisible(scene::EDS_NORMALS); }
Two important lines are: BasicShaderCallBack* mc = new BasicShaderCallBack(m_device, light2); // create material from high level shaders (hlsl, glsl or cg) newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles( hsFileName, "vertexMain", video::EVST_VS_1_1, hsFileName, "pixelMain", video::EPST_PS_1_1, mc, video::EMT_SOLID, 0, video::EGSL_DEFAULT);
Creating of actual material to use. And:node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
Set the material to a node. Easy and straight forward. Of course it is all good for simple testing, in a proper setup it will be a little bit more flexible and complicated. Shader will need to be able to take multiple lights and maybe do some effect like specular reflection. The output of all of that is:


shader.png

Looks nice and just the way I want it.

Sorry for slow updates, life been a little bit busy lately. Next I will try to work on a simple scene setup and maybe cover some of the Spark systems.

I want to use Voxel editors to create graphical assets for my game. It is easy to use them and create relatively good looking 3D objects. Plus with a little bit of work the game can look quite "stylized" and that it is what I am going for. I played around with number of Voxel editors and ended up with two contenders.

VoxelShop - free and has very user friendly interface. I just loved using it and could quickly mock up stuff I need.

MagicaVoxel - again free but not as easy to use. I found it quite hard to create something fast. Maybe I just need to get use to it or man up and read the manual.

It all was nice and dandy until I got to the exporting part. VoxelShop can export to a .dae format and that is what IrrLicht can load. At first I though that it was going to be all I need but then I tried to light it up. I exported a simple cube, loaded it into a scene and set up a moving light source. The light on a cube was all over the place and when I enabled debug render for normals I understood why.

coladatyak.png

As you can see above the normals are all over the place! At first I thought that because of compression for vertices, but even without it the normals apparead super strange. Actually not that strange, more like averaged. And that is not what I really need.

bla1.png

As I understood once again a lot of duplicate points were removed during the export and what we left with is a lot of missing normals. It seems like it is a command problem for .dae. I actually got the source code for VoxelShop to try and fix it myself, but then decided not to bother. It will take me ages to understand what is going on.

So I moved on to MagicaVoxel and it can export into .obj which got all of points, vertices and normals I will ever need! I will stick with it for now as I do not want to spend to much time on trying to export everything perfectly. I will try to learn MagicaVoxel and see where it takes me.

In the next post I will talk about a simple shader I made to light up my nice normals. I have not used shaders in ages, was nice to check them out once again.

P.S Anybody knows good, free or cheap Voxel editors? That can export in a lot of different game related formats?

Ok, in this post I will go quickly over gameApp.cpp and .h. This is the main game entry and that is where the main game loop sits.

Lets first look at the header file:[code=:0]namespace GAME {class GameApp;extern GameApp app;class GameApp : public irr::IEventReceiver{public:enum SceneState{CM_NORMAL = 0,CM_UNLOAD,CM_LOAD,CM_UNLOADANDLOAD,CM_NEWGAME};GameApp(): m_loadFile(""), m_sceneState(CM_NORMAL), m_newGameLoad(true){}bool Init();void Done();int Run();irr::IrrlichtDevice* GetDevice() { return m_device;}irr::video::IVideoDriver* GetDriver() { return m_driver;}irr::gui::IGUIEnvironment* GetUIEnv() { return m_ui_env;}irr::scene::ISceneManager* GetSceneManager() { return m_smgr;}CameraAnimator* GetCameraAnimator() { return m_cameraAnim;}Camera* GetCamera() { return m_cmr;}DebugText* GetDebugText() { return m_debugText;}void AddEventReceiver(irr::IEventReceiver* er);void RemoveEventReceiver(irr::IEventReceiver* er);void NewGame();void SaveGame(char* name);void LoadGame(char* name);void HandleCollisions();protected:typedef std::vector EventReceivers;irr::IrrlichtDevice* m_device;irr::video::IVideoDriver* m_driver;irr::gui::IGUIEnvironment* m_ui_env;irr::scene::ISceneManager* m_smgr;CameraAnimator* m_cameraAnim;Camera* m_cmr;EventReceivers m_eventReceivers;DebugText* m_debugText;DebugGrid* m_debugGrid;bool m_newGameLoad;bool OnEvent(const irr::SEvent& event);private:void DoLoadGame();SceneState m_sceneState;StringId m_loadFile;};} // namespace GAME
As you can see this class keeps all of the major components - IrrlichtDevice, IVideoDriver, IGUIEnvironment, ISceneManager. This is all needed to setup an empty irrLitch scene. gameApp creates them, sets them up and then allows other object to access them. Also I create camera and camera animator in there.

Main functions are of course Init(), Done() and Run(). There other functions but they are empty for now. GameApp also inherits irr::IEventReceiver and can handle input, but that too is for future use.

Lets take a look at two interesting functions. First Init():[code=:0] bool GameApp::Init() { //preferencies registration and installation Spark::Options::Register("Client preferences") ("video.mode", Spark::Options::Preference(&preferences.screenRect), "screen rect") ("video.windowed", Spark::Options::Preference(&preferences.windowed), "windowed or full screen") ("audio.soundVolume", Spark::Options::Preference(&preferences.soundVolume), "sound volume") ("audio.musicVolume", Spark::Options::Preference(&preferences.musicVolume), "music volume") ("audio.sound", Spark::Options::Preference(&preferences.sound), "sound") ("audio.music", Spark::Options::Preference(&preferences.music), "music") ("avatar.name", Spark::Options::Preference(&preferences.name), "avatar nickname") ("network.ServerIp", Spark::Options::Preference(&preferences.address), "master server address") ("debug.bullet", Spark::Options::Preference(&preferences.bullet), "show bullet debug") ; Options::SetRT("app.title", "Space Adventures"); Options::SetRT("file.mount", "path:data ra", -1); // parse options Spark::Options::Init(GetCommandLine(), "SA", "game.ini"); // initialize log system Spark::Log::Init(); // log banner SLOG_BANNER << "application: " << "Space Adventures"; char time_buffer[32]; struct _timeb t; _ftime(&t); tm details; details = *localtime(&t.time); sprintf(time_buffer, "%02d/%02d/%04d %02d:%02d:%02d.%03hu", details.tm_mon+1, details.tm_mday, details.tm_year+1900, details.tm_hour, details.tm_min, details.tm_sec, t.millitm); SLOG_BANNER << "date : " << time_buffer; OSVERSIONINFO ver; ver.dwOSVersionInfoSize = sizeof(ver); GetVersionEx(&ver); SLOG_BANNER << "host : Windows " << ver.dwMajorVersion << '.' << ver.dwMinorVersion << '.' << ver.dwBuildNumber; MEMORYSTATUS mem; GlobalMemoryStatus(&mem); SLOG_BANNER << "memory : " << mem.dwTotalPhys/1024 << "K total, " << mem.dwAvailPhys/1024 << "K free"; // Create Irrlicht devise Spark::VideoMode videoMode = Spark::Options::GetValue("video.mode"); irr::SIrrlichtCreationParameters cp; cp.DriverType = (irr::video::E_DRIVER_TYPE)Options::GetValue("video.driver", irr::video::EDT_DIRECT3D9); cp.Fullscreen = !Spark::Options::GetValue("video.windowed", true); cp.WindowSize = irr::core::dimension2d(videoMode.width, videoMode.height); cp.Bits = (irr::u8)videoMode.bpp; cp.EventReceiver = this; m_device = irr::createDeviceEx(cp); if(!m_device) { SLOGS_ERR(app, ("Cannot create Irrlicht device: driver type = %d", cp.DriverType)); return false; } m_driver = m_device->getVideoDriver(); if(!m_driver) { SLOGS_ERR(app, ("Invalid video driver %d", cp.DriverType)); return false; } m_device->setWindowCaption(L"Space Adventures"); m_device->setEventReceiver(this); m_ui_env = m_device->getGUIEnvironment(); //add all data folders m_smgr = m_device->getSceneManager(); m_smgr->getFileSystem()->addFolderFileArchive("data/"); m_smgr->getFileSystem()->addFolderFileArchive("data/meshes"); m_smgr->getFileSystem()->addFolderFileArchive("data/fonts"); m_smgr->getFileSystem()->addFolderFileArchive("data/pfx"); m_smgr->getFileSystem()->addFolderFileArchive("data/sprites"); //add a basic lightning to the scene m_smgr->addLightSceneNode(0, core::vector3df(200,200,200), video::SColorf(1.0f,1.0f,1.0f),2000); m_smgr->setAmbientLight(video::SColorf(0.1f,0.3f,0.3f)); m_cmr = new Camera(m_smgr->getRootSceneNode(), m_smgr, -1); m_smgr->setActiveCamera(m_cmr); m_cmr->setTarget(core::vector3df(0,0,0)); m_cmr->setPosition(core::vector3df(3,3,3)); m_cmr->setFarValue(42000.0f); m_cmr->setNearValue(0.5f); m_cmr->setFOV(core::PI*0.25f); m_cameraAnim = new CameraAnimator(m_device->getCursorControl(), 10.0f, 10.0f, 10.0f); m_cmr->addAnimator(m_cameraAnim); m_device->getCursorControl()->setVisible(false); m_debugGrid = new DebugGrid(0.0f, 50, 1.0f, SColor(255,128,128,128), m_smgr->getRootSceneNode(), m_smgr, -2); if(!EntityDB::Ref().LoadResources("entities")) { SLOGS_ERR(app, ("Cannot load entities DB")); return false; } // new game NewGame(); //--------DEBUG CONSOLE STUFF--------------- m_debugText = new DebugText(); //--------DEBUG CONSOLE STUFF--------------- return true; }
As you can see, beside creating every major component, it also reads game.ini and handles all of the options. Options are handled by Spark::Options, which is just a nifty little class. I think the whole function is quite self descriptive, just tons of inits. Also all the logs are handled by Spark too. It is quite flexible logging system, with many levels and targets for log.

Init() also adds some simple light to the scene:[code=:0]m_smgr->addLightSceneNode(0, core::vector3df(200,200,200), video::SColorf(1.0f,1.0f,1.0f),2000);m_smgr->setAmbientLight(video::SColorf(0.1f,0.3f,0.3f));
I think in future I will make it data driven for each sector, so light can actually come from a star or some other source.

Also you can see the data structure for the project:[code=:0]m_smgr->getFileSystem()->addFolderFileArchive("data/");m_smgr->getFileSystem()->addFolderFileArchive("data/meshes");m_smgr->getFileSystem()->addFolderFileArchive("data/fonts");m_smgr->getFileSystem()->addFolderFileArchive("data/pfx");m_smgr->getFileSystem()->addFolderFileArchive("data/sprites");
I think I might move materials out of "data/meshes", but for now, when I barely got any objects, that should suffice.[code=:0]m_debugGrid = new DebugGrid(0.0f, 50, 1.0f, SColor(255,128,128,128), m_smgr->getRootSceneNode(), m_smgr, -2);
DebugGrid is a debug class that draws a infinite grid over x,y plane. It is used to better see positioning of object in the space.

Now onto Run() function:[code=:0]int GameApp::Run(){scene::ICameraSceneNode* fixedCam = m_smgr->addCameraSceneNode(m_smgr->getRootSceneNode(), core::vector3df(0,50,0),core::vector3df(0,0,0));int lastFPS = -1;while(m_device->run() && m_driver){// renderif (m_device->isWindowActive()){Time::Update();switch(m_sceneState){case CM_NORMAL:EntityManager::Ref().Update(Time::GetElapsedTime());break;case CM_UNLOAD:EntityManager::Ref().Deactivate();m_sceneState = CM_NORMAL;break;case CM_UNLOADANDLOAD:EntityManager::Ref().Deactivate();EntityManager::Ref().Clear();DoLoadGame();m_sceneState = CM_NORMAL;break;}m_driver->beginScene(true, true, irr::video::SColor(0,100,100,200));////TEST MAP RENDER// set render target texture// make cube invisible and set fixed camera as active cameram_smgr->setActiveCamera(fixedCam);// set back old render target// The buffer might have been distorted, so clear itm_driver->setRenderTarget(0, true, true, 0);// make the cube visible and set the user controlled camera as active onem_smgr->setActiveCamera(m_cmr);////TEST MAP RENDERm_smgr->drawAll();if(m_sceneState == CM_NORMAL){HandleCollisions();}//draw a orientation triangle at 0,0 ,0 coordsm_driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);video::SMaterial m;m.Lighting = false;m_driver->setMaterial(m);m_driver->draw3DLine(core::vector3df(0,0,0), core::vector3df(5,0,0), video::SColor(255,255,0,0));m_driver->draw3DLine(core::vector3df(0,0,0), core::vector3df(0,5,0), video::SColor(255,0,255,0));m_driver->draw3DLine(core::vector3df(0,0,0), core::vector3df(0,0,5), video::SColor(255,0,0,255));m_driver->draw3DLine(core::vector3df(0,5,0), core::vector3df(5,0,0), video::SColor(255,255,255,255));m_driver->draw3DLine(core::vector3df(0,0,5), core::vector3df(0,5,0), video::SColor(255,255,255,255));m_driver->draw3DLine(core::vector3df(5,0,0), core::vector3df(0,0,5), video::SColor(255,255,255,255));m_ui_env->drawAll();m_driver->endScene();int fps = m_driver->getFPS();if (lastFPS != fps){core::stringw str = L"Space Adventures [";str += m_driver->getName();str += "] FPS:";str += fps;m_device->setWindowCaption(str.c_str());lastFPS = fps;m_debugText->SetText(str.c_str());}}}return 0;}
Inside of the While loop we got a small state machine that switches between scene states - normal, unload, and unloadandload (I think that should just be called reload).

Then there is this:[code=:0]m_driver->draw3DLine(core::vector3df(0,0,0), core::vector3df(5,0,0), video::SColor(255,255,0,0));m_driver->draw3DLine(core::vector3df(0,0,0), core::vector3df(0,5,0), video::SColor(255,0,255,0));m_driver->draw3DLine(core::vector3df(0,0,0), core::vector3df(0,0,5), video::SColor(255,0,0,255));m_driver->draw3DLine(core::vector3df(0,5,0), core::vector3df(5,0,0), video::SColor(255,255,255,255));m_driver->draw3DLine(core::vector3df(0,0,5), core::vector3df(0,5,0), video::SColor(255,255,255,255));m_driver->draw3DLine(core::vector3df(5,0,0), core::vector3df(0,0,5), video::SColor(255,255,255,255));
This is a cool trick to see the orientation of a scene. Again can be used for positioning and sorting out loading objects after exporting them from graphical editors or simply to see the origin of the scene. Each line is color coded to see where is x,y,z points to.

The last bit is a simple fps counter, which is very important when you hacking stuff up. Always make sure that you hit your FPS cap! If you do not, it is time to investigate and optimize![code=:0]if (lastFPS != fps){core::stringw str = L"Space Adventures [";str += m_driver->getName();str += "] FPS:";str += fps;m_device->setWindowCaption(str.c_str());lastFPS = fps;m_debugText->SetText(str.c_str());}
I think I covered everything worth covering. I am still working out how to do these posts in an interesting way. I will try to cover other interesting bits separately and in more depth. In the next post I will take a look at graphics editors I want to use for this project. Currently there are two but I will settle for one.

And here is a screenshot of an empty scene! That what those Init() and Run() do right now!

first_Scene.png

Just a quick test!

It seems like I cannot simply copy and paste images into post on here so I am testing out some random image hosting page. It is called postimage.org if anybody is interested. Hope it works.

project_Image.png

Above you should see the image of basic project setup I have done. It just all the basic libraries I will need and the actual game project. Hope it worked!

I finally got time to setup the project. I re-made the Spark libraries in Visual Studio 2015 Community and hooked up new version Irrlicht. It was an adventure in it self. Working out all project settings and trying to link it all up. But I think it is all good now. Next I will try to setup basic game loop and see if everything actually links and works. Then I will try to load up some basic assets, maybe meshes or something like that, simply to test everything out.

By the way I decided to use Visual Studio 2015 Community. It works fine and got all of the features I will ever need. I do have access to Visual Studio 2015 Enterprise, but for now I will stick to the free version, just to make this project as Indy as possible.

Also a funny story, I decide to switch my old project to new Irrlicht version. It was pretty easy, just point it to new includes and libraries. But after I did so my FPS feel from 60, which is where it was capped, to 10 max! Super strange! I think it might be something to do with Irrlicht and irrBulled, because irrBulled can slow down greatly if not used correctly. I did not investigate it and just switched it back to the old version of Irrlicht. I will worry about it if I get the same problem when I try to hook up Bullet. Onto next adventure!

So today I needed to decide what Visual Studio to use for my project. So far I used, at least at home, VS 2010. It works well, but it is quite old by now. At work I use 2015 and I do like it, although I used it for C# rather then C++. So I decide, what the heck, I will use Visual Studio 2015 Community! It is a free edition of Visual Studio and, after looking at its features list, I am sure it got everything I need for my project.

Only think that I forgot about that Visual Studio 2015 is using new standard of C++, I think its compiler like gazillion version newer then 2010 and it got a lot of new project management features. I setup my new solution, added Spark and Irrlicht project to its and... got tons of errors and warnings. I had to spend a couple of hours just going through them and sorting them out. There a lot of stuff that VS 2015 does not like anymore! And there a lot of warning it treats as error! I was kind of prepared for it, as converting projects to newer version of VS usually needed a lot of ironing.

After I fixed all errors and some warnings (who cares about warnings! right? right?!) I run my project and it simply did nothing. What I was excepting is a window opening with simple Irrlicht scene set up inside of it, but I got nothing! Nothing! The application was running but there was no window and it never reached main(). How? What? After a lot of debugging and trying to work out what is going on I found the problem. It was Static Singleton that is used a lot in the Spark. All the static staff was compiled and all Static Singleton instantiated and for some reason in some of them, inside of its own constructor, it was referencing itself. It worked fine in VS 2010, but in 2015 it kinds of stuck in loop. It was not stack overflow! I am not sure what it was! But moving all Ref() from constructors into its classes' Init() functions and everything worked fine. Problem solved!

Now I have a running project, it is blank but it is working. But before I move ahead with all fun stuff, I want to re-setup some of the Spark's libraries in VS 2015. Also I want to grab new Irrlich version and see if they got a project for VS 2015. Just to keep everything updated.

So here where I am today. Next update coming soon! Hopefully!

The Game - The Idea

So lets discuss "The Idea" for the game I want to make. It is not a simple one because in my dreams I image a perfect game, with great gameplay and amazing graphics. But lets start with a simple thing - name.

I named my game quite simple - "Space Adventure". Well, because that is what my game will be all about, adventuring in space. You get a space ship, you get a large galaxy, and off ya go, making money and fame. I think "Space Adventure" is a nice name. Not too short, not too long, just right. I might change it later, but for know my project will be known as "Space Adventure", or "Space Adventures", I am still thinking about that point.

Next lets talk about player's avatar, or character, or representation. It will be a simple ship. You can buy it, trade it, exchange it. You can equip weapons on it, engines, armor, shields and so on. But you are always inside of a ship.

The view of the game will be top down, or maybe slightly leaning behind the ship. This will be worked out during pre-alpha phase of the project or might be just a simple option in the game. I personally like top down, especially if the actual game is in 3D. It gives good lightning and depth and a lot of cool elements can be applied to it.

Now, the scope! There will be a large galaxy with a many systems. Each system will have planets and stations that player can dock to. So it will be a usual scope for any good space game. The planets and stations will have a market, some kind of a bar, maybe a bullet board, maybe some kind of NPC to talk to. It will be all driven by simple UI, because I cannot make 3D stuff. It will be ugly, but usable.

The game will be single player, at least for now. As I go I will think about adding fundamentals for multiplayer. It might be to hard or too time consuming. We will see. But in my sweet dreams my game is always a simple single player.Or maybe it will be a multiplayer like No Man's Sky! Where people cannot see each other! Ha Ha!

The NPC out side of the station will be other ships. I want make some kind of simple "galaxy" AI, where it will move random ships around, making everything looks busy and alive. I want to have some kind of dynamic economy, again, something simple but fun.

What else do I need to cover? Physics of the ship will be kind of realistic, where you speed up and then have to break or your ship will simply continue flying forever. Of course, once again, it all can change when I get to proper prototyping. Fun is always the top priority. Controls will be with mouse and keyboard. Guns will be fixed, so you can have forward, backwards, so side guns.

Ok, I think I got all basics covered. If I forgot something I will discuss it in other posts. And I will go more in depth about each feature of the game as I get to implement it.

The Beginning

Here we go again... I am trying to quickly make a game using some technologies that I am remotely families with. Actually this is less of "making" a game, but rather experimenting with a new stuff. I will start by remaking my old project, by striping it back and starting from scratch. In theory I want to make a space RPG, something like an action version of Space Rangers. Fly around a huge galaxy, fight, trade and do quests, that sort of thing. The main feature of the game, or what makes it special, is going to be a fast paced combat. Fight tons of enemies at the same time, shoot them up, blow them up, fry em with lasers, anything goes. I want a very satisfactory combat, I want it to be a glue for the rest of the game. Like the carpet from The Big Lebowski. But all of that fun part is looong way off.

First lets decide what engines/technologies we are going to use. Here is my list so far:


  • Spark - this is home made engine I used for my University project. It is 2D and got tons of components, but in my case I only will need two things from it. Core library for its file system, tinexml, logging and tons of other useful untils. Then there is Math library that got your usual math stuff in it, vectors, shemktors and all that jazz.
  • Irrlicht Engine - this is 3D engine and is just what doctor ordered it. It is well written, easy to understand, got tons of features, quite compact and molds well with Spark. Plus it is written in very familiar style to me and I just love it. I never had a problem debugging or modifying it and I spend quite some time with it before. So it is what will drive the heart of graphics for my game.
  • Bullet Physics - most people are probably familiar with this one. It been used in GTA (and in gazillion of other projects) and is totally open source. Bullet is very powerful and quite big. I have spent sometime with it, but before I used irrBullet to make it work. This time around I will try to hook it up by myself. If it becomes to unwieldy, I will go back to irrBullet and see if I can hack it up to my liking, because the last time I used it I had a lot of problem with making it to do what I needed. What ever happen, this will be quite interesting and painful.
  • Squirrel - this is a scripting language. It syntax is similar to c++, it suppose to be fast and simple to use. I never actually used the actual scripting, but I did init it in my old project. I am not even sure why I need a scripting language in my project, but I do would like to use one. It will be a good practice and can make my game moddable! Which is probably very important, especially for PC game.
  • cAudio - I think this is a simple audio engine, but I never used it. I might stick with it, or maybe grab something else later. If I ever get to the sound bits. We will see.

    So here it is. The list of all engines I want to use and play with. In the next post I will try to describe in some details the game I want to make, just to make everything little bit more clearer. So till the next time!

  • Advertisement