Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    64
  • comments
    28
  • views
    51278

Entries in this blog

 

Space-Shooter project, the day after...

When I woke up today, still exhausted, I shot a youtube video of the project, and we went down to the visualization lab at the uni, where we will defend our project on the exam tomorrow. It's a huge screen run by an nVidia workstation.




That's approximately four meters wide, which is pretty epic to experience when you've worked so hard and so long on something! It ran smooth and with no problems, so I'm really excited for the exam tomorrow now!

And though it might be a bit early to look back at how I've tackled this project, I still want to give that a try.

First thing first though, the project's source code: http://code.google.com/p/ste6245physicsgame/

I started the project on January 16th. We had just come off a rather crazy start of the year, where I crunched in a big project on fluid mechanics visualization in a week with barely any sleep, so this project started a bit slow as I was recharging batteries. First off, the assignment required that we'd use Qt. I don't find it very suiting for game programming, but since it was a requirement, I had to run with it. I tried to use as little of the framework as possible, and thankfully the new 4.7.1 version supports creating an OpenGL3 context! This all took a while to figure out, and the guys at freenode (irc) #Qt and #OpenGL were awesome helping me out! On january 21st, I had the base application up and running with opengl rendering and updates flowing through my base game engine.

The work on this game engine started about two years prior to starting my master's here at the university, but it's first now during these school projects, where we focus on one project for about a month and a half before we go to the next one, that I've been able to rapidly improve and refactor the engine for each new project. Always grinding in features at the end of each project, finding weaknesses in my API, thinking out possible solutions that I could use on the next project, etc. It's been a great learning experience, and the game engine has really grown into something that's pretty usable for smaller game projects.

I then focused on getting AssImp in for loading meshes, and with it I found that I might as well incorporate DevIL as well for texture loading. Now things started to pick up. It was a lot of excitement getting these libraries working so fast (big thumbs up to AssImp for being such a simple API to work with!), and I quickly got to add in lighting and working on shaders.

I never spent a great deal of time before making sure all my matrix and lighting calculations were handled correctly. But this time around i figured I might as well do it properly, because I knew I had some bugs in there. So, I fixed my normal matrix, turns out the you really have to only transpose over the 3x3 version of the modelview matrix for this to work! I then also did quite a few experiments in shader with using object space light shading and tangent space light shading, and ended up with tangent space, since all the normal maps loaded from textures were stored in this space...

At the very end of january, I got this idea though, that I should really try to optimize my rendering. That is, I really wanted to improve on my OpenGL 3.3 spec correctness, remove redundant state changes, and overall lessen the number of calls to opengl. The idea that came to me was a material-driven rendering pipeline. If I sorted the entities by material, I would be able to bind shaders and textures for entire groups of entities, as well as bind uniform values like projection matrix, lighting, coloring,etc. Each entity type would then have a manager. It was in the manager that the VAO, VBO, IBO and all that mambo jambo was defined and loaded, while each entity instance of a type would only bind uniforms specific to itself, like the modelview matrix. The entity type manager idea was ideally set up for handling instanced rendering, but I never got around to it... A material was defined in XML, and here's an example material:






0.0,0.0,0.0
1.0,1.0,1.0
1.0
8.0


Mesh/FEROX_DI.tga
Mesh/FEROX_AO.tga
Mesh/FEROX_SP.tga
Mesh/FEROX_BU.tga
Mesh/FEROX_IL.tga
Skybox/Nebulea.png


mesh_default
ferox







Entity types in the engine was also defined in XML, and it was here that entities chose their material. A typical entity xml file would look something like this:



Ferox
Mesh
Ferox

InputThruster



FileName
Ferox/Ferox.3DS


Position
0,0,-10



MaxThrust

40.0



ThrustAccel

3.0







This was very cool to work with during development. A lot of the time I was simply working over a compiled release-mode executable and tweaking values in xml files, shaders and Lua scripts. This was very efficient for progress, and also a great way to force the C++ engine to stay general and clean, while specialized code was in the resource files.

However, I did find a great weakness in my Material-driven rendering pipeline design that I hadn't thought about. Transparent entities! I was rendering the scene by iterating over materials, and for each material, I rendered the entity types hooked up to it. How would I then sort back to front all transparent entities? Of course, I wouldn't be able to properly solve this without rewriting my pipeline, but at this point there was simply not enough time left... I had to let it go and be ok with the fact that I could only sort entities per material. I did sort the materials too based on the "weight" applied by the entities it owned with respect to distance from camera, but this was an ugly hack at best and only improved the blending mildly. Quite a few blending bugs is still present in the application today.

In the end, I don't really regret my decision to try this design. Being a student and having a ton of projects like these is a great opportunity to experiment with ideas, and I learn a lot from each and every experiment and idea that I try to implement, fail or success. I will definitely never make this mistake again in an API

A slightly cool thing I implemented, was the crosshair mechanic. I was trying to adapt the approach Starfox has, where the spaceship always follow the crosshair. I decided to render it as a sprite in worldspace instead of drawing it as a GUI element. This had some problems, especially the issue of ensuring that the crosshair never left the bounds of the screenspace. My solution was to convert the position to screenspace coordinates, then clamp it within the screen bounds, then convert it back to world space, and this worked very well



CL_Vec3f IGuiMgr::clampToScreenSpace(const CL_Vec3f &pos)
{
CL_Mat4f modelMat = CL_Mat4f::identity();
modelMat[12] = pos.x;
modelMat[13] = pos.y;
modelMat[14] = pos.z;

float w = (float)getWidth();
float h = (float)getHeight();

CL_Vec3f screenPos = coreMgr->getCam()->worldToScreen(pos, w, h); //-1 to 1

if(screenPos.x > w-2.0f)
screenPos.x = w-2.0f;
else if(screenPos.x screenPos.x = 2.0f;

if(screenPos.y > h-2.0f)
screenPos.y = h-2.0f;
else if(screenPos.y screenPos.y = 2.0f;

CL_Vec3f worldPos = coreMgr->getCam()->screenToWorld(screenPos, w, h);

return worldPos;
}




CL_Vec3f Cam::worldToScreen(const CL_Vec3f &worldPos, const float &w, const float &h)
{
//Transformation vectors
float fTempo[8];

//Modelview transform
fTempo[0] = viewMatrix[0] * worldPos.x + viewMatrix[4] * worldPos.y + viewMatrix[8] * worldPos.z + viewMatrix[12]; //w is always 1
fTempo[1] = viewMatrix[1] * worldPos.x + viewMatrix[5] * worldPos.y + viewMatrix[9] * worldPos.z + viewMatrix[13];
fTempo[2] = viewMatrix[2] * worldPos.x + viewMatrix[6] * worldPos.y + viewMatrix[10] * worldPos.z + viewMatrix[14];
fTempo[3] = viewMatrix[3] * worldPos.x + viewMatrix[7] * worldPos.y + viewMatrix[11] * worldPos.z + viewMatrix[15];

//Projection transform, the final row of projection matrix is always [0 0 -1 0]
//so we optimize for that.
fTempo[4] = projMatrix[0] * fTempo[0] + projMatrix[4] * fTempo[1] + projMatrix[8] * fTempo[2] + projMatrix[12] * fTempo[3];
fTempo[5] = projMatrix[1] * fTempo[0] + projMatrix[5] * fTempo[1] + projMatrix[9] * fTempo[2] + projMatrix[13] * fTempo[3];
fTempo[6] = projMatrix[2] * fTempo[0] + projMatrix[6] * fTempo[1] + projMatrix[10] * fTempo[2] + projMatrix[14] * fTempo[3];
fTempo[7] = -fTempo[2];

//The result normalizes between -1 and 1
if(fTempo[7] == 0.0f) //The w value
return CL_Vec2f(0.0f, 0.0f);

fTempo[7] = 1.0/fTempo[7];

//Perspective division
fTempo[4] *= fTempo[7];
fTempo[5] *= fTempo[7];
fTempo[6] *= fTempo[7];

//Window coordinates
//Map x, y to range 0-1
CL_Vec3f screenPos;
screenPos.x = (fTempo[4] * 0.5f + 0.5f) * w + 0.0f;
screenPos.y = (fTempo[5] * 0.5f + 0.5f) * h + 0.0f;

//This is only correct when glDepthRange(0.0, 1.0)
screenPos.z = (1.0f + fTempo[6]) * 0.5f; //Between 0 and 1
return screenPos;
}





CL_Vec3f Cam::screenToWorld(const CL_Vec3f &screenPos, const float &w, const float &h)
{
CL_Vec4f in, out;

//Calculation for inverting a matrix, compute projection x modelview
CL_Mat4f mvpMat = viewMatrix * projMatrix;
CL_Mat4f inv_mvpMat = CL_Mat4f::inverse(mvpMat);
if(inv_mvpMat == CL_Mat4f::null())
return CL_Vec3f(0.0f, 0.0f, 0.0f);

//Transformation of normalized coordinates between -1 and 1
in.x = 2.0f * (screenPos.x - 0.0f) / w - 1.0f;
in.y = 2.0f * (screenPos.y - 0.0f) / h - 1.0f;
in.z = 2.0f * screenPos.z - 1.0f;
in.w = 1.0f;

//Objects coordinates
out = inv_mvpMat * in;

if(out.w == 0.0f)
return CL_Vec3f(0.0f, 0.0f, 0.0f);

out.w = 1.0f / out.w;

CL_Vec3f worldPos;
worldPos.x = out.x * out.w;
worldPos.y = out.y * out.w;
worldPos.z = out.z * out.w;
return worldPos;
}


The component system was a great asset to rapid development of game mechanics. All components were scripted, and could listen to events, or even bind itself to receiving updates every frame. At one point I did the optimization that a component had to register which events it was interested in, and would only receive events that it had registered itself to. The update function was slightly harder to limit however... The only optimization I could think of would be to move some of those components over to C++, but the game was performing well after the event optimization, so there was no real need for it.

I spent a day trying to implement atmospheric scattering, but I couldn't do it. There's something magical about this shader that I haven't quite been able to weave yet. Guess I'm no arch wizard yet like Sean O'Neil

At this point there were like four days till deadline. The only physics I had was a hacked together Lua scripted component I called RigidBody... I moved the logic into a C++ physics system, and also implemented angular physics. For a spherical shape, angular physics is really easy to implement... I never found the time though to try my hand at approximating the inertia tensor for a complex mesh, but I think that would've looked really cool with collision response when shooting asteroids!

I also added a pretty bad particle system. It was a simple point cloud that was sent to shaders along with an attribute buffer or random directions. I would overload these random directions in the geometry shader via a uniform force direction vector. It worked "ok" for displaying dust clouds when shooting asteroids, but it was far from good enough to be used for things like engine flames, etc... Thus I didn't find time for that last bit...

At one point towards the very end, my program suddenly started crashing in the Gui code. After some investigation, I found that I had gone beyond the number of supported shader programs! Thankfully I could solve this by implementing a shader manager that made sure that no duplications of shaders were made. This was in the last couple days before deadline though, and I had to spend a day on it all! This is definitely something I should have foreseen and implemented (for good api) long ago!

On the last day I really had to work on my collision detection. The assignment clearly specified that we should have a very solid collision detection for spheres, to the point where we would have to look at all collisions occurring during a timestep, then sort the order of those collisions, calculate the response of the first collision pair and calculate physics up to the time of impact... we'd then have to run the collision detection again, to make sure that this new physical state wouldn't cause more collisions... if it did, we had to look for duplicates in the list of collisions already stored for the timestep and only keep those that occurred first... Potentially this could really damage the framerate, since very complex branches of collision could potentially occur during a timestep... As it turned out, with my implementation I didn't really have to worry about this at all... asteroids didn't spawn dense enough to cause mass-chain-reactions of collisions.

Finally, I finished off the project by converting Bill Lowe's html /css gui design to libRocket's rml and rcss format. It didn't go as smoothly as I had hoped, but I got it up and working after a couple of tries. I then quickly, via script, bound some gameplay logic to these HUD elements. When you thrust the spaceship up to speed, the speed percentage hud element would respond with numbers complementing this. The gun got attached to the energy level, where each shot would drain a given amount of energy from the ship, and when energy got below the gun energy drain level, you couldn't shoot. For each frame, the energy level would slowly recharge. It was pretty cool to see that my scripting system was flexible enough to tie all this logic together with the HUD elements without touching a single line of C++ code!

In the end I must say that it felt really good submitting the project three hours before the deadline. I could probably have added some more minor features and polish in that time, but I was simply too exhausted and tired at that point... and I really didn't want to introduce any misbehaving logic that close to the finish line!

Now I'm very excited for the exam tomorrow and hope everything will go well!

[media] [/media]

Trefall

Trefall

 

Space-Shooter project, ONE day to go!

It's Saturday evening... deadline is in 36 hours! It will be a crazy run to the finish line if I want to finish in style!

So, I figured out what the bug was that I experienced yesterday. Apparently, my GPU (2 x 8800 GTX M) doesn't support more than 64 shader programs, because when I tried to do glUseProgram(67), it failed with invalid value!

This forced me to spend time now writing a proper shader manager, which I should have done ages ago anyway Basically it book-keeps shaders that's been loaded, preventing duplication of vertex, fragment and geometry shaders, plus shader programs. This solved the problem!

Bill's html file didn't convert directly to libRocket's rml format, so I still have a little bit of work to do in that department, but I'll have to set up the priority list carefully for tomorrow... First, I'll riding the bus for 6.5 hours to my university (I study remote), then I'll have to spend a couple of hours focusing on the collision detection, this is a requirement if I want to land the best grade, so it's just stupid that I haven't focused on it earlier! I then have to also write a short report and land in some polish here and there... How well I'll finish will largely depend on how fast I can implement the collision detection!

Wish me luck!


Trefall

Trefall

 

Space-Shooter project, two days to go

A little late with this entry, but here we go! So, yesterday I stumbled into some problems that slowed me down...

First, realistic mass calculation for rigid bodies proved to be very hard to control. I ended up with some asteroids that would go into a spin frenzy and fly away faaaaast! I would then have some other, bigger asteroids that wouldn't feel the impact of gun shots at all!

Perhaps choosing that asteroids should be made of pure iron ore wasn't such a good idea but it was simply too difficult to control this beast. I ended up starting back at scratch, where everything had a mass of 1.0f. I then started tweaking a uniform asteroid value, until I landed on something that was ok to work with. 100.0f felt ok. I then added randomness to an asteroid's mass based on the random scale it gets when spawned, but with clamping control of the upper and lower limits. I got my control back, and the angular and linear physics reactions to collisions now feels good.

Still an issue is the fact that I use an AABB vs AABB for collision detection, then I calculate the collision response based on a spheric shape (direction of impulse spawned by collision + offset position of collision)... With asteroid, that's neither "boxy" nor "spheric", this ends up looking a bit strange. I'd have to have some kind of convex collision shape to make this work realistically, but there's just no time for implementing such detection algorithms now!

After I got physics to a stable place yesterday, I was going to quickly implement some UI elements into my libRocket-driven gui system that Bill had made. Well, I got the strangest crash, glGetUniformLocation returning -1 on me for no apparent reason. After some testing, I found that glGetError was returning 1281 on me... after some detective work I pinpointed the location to the previous UI element's render function, that tried to glUseProgram(67)... this was weird, because I could bind both uniforms and attributes to that shader program, and it had linked ok and everything...

well, I didn't get any wise last night and went to sleep. Whether I managed to solve it, and todays efforts will come later this evening... but needless to say, I didn't get anywhere close to where I wanted to be yesterday... with simulator-level collision detection remaining on my TODO list, time is running out FAST!

Trefall

Trefall

 

Space-Shooter project, three days to go

Today has been a slow day. I had got to the point where a good night sleep was more productive than hitting an early start for yet another day. The day didn't go quite as planned though, with some Real-Life emergencies occurring, but I did get some minor work in.

I went over my physics calculations once more today and added in a density measure for each rigid body. I also added in a spheric volume calculation based on the radius found for each rigid body, plus I used this info to calculate each rigid body's mass. These calculations are in meters and kilograms, and I'm working through my code, making everything consistent.



volume = (4.0f/3.0f) * M_PI * (radius*radius*radius);
mass = density * volume;



I then went and fixed my angular physics some. I had done it a bit wrong when I was adding in the angular velocity to the new orientation. So, in the end, here's how my physics calculation works (unless I find that I'm doing more things wrong, though I think this should be it):



void PhysicsMgr::addTorque(IEntity *body, const CL_Vec3f &force, const CL_Vec3f &position)
{
for(unsigned int i = 0; i {
if(body->getId() == bodies->entity->getId())
{
const CL_Vec3f &body_pos = body->GetProperty("Position").Get();
CL_Vec3f torque = CL_Vec3f::cross(position - body_pos, force);

bodies->torques.push_back(torque);
return;
}
}
}


void PhysicsMgr::update(float dt)
{
for(unsigned int i = 0; i {
CL_Vec3f P = bodies->position.Get();
CL_Vec3f V = bodies->velocity.Get();
CL_Vec3f L = bodies->angular_momentum.Get();
const CL_Mat3f &R = bodies->rotation_matrix.Get();
const CL_Mat3f &I_inverse = bodies->inertia_tensor;
const float &M = bodies->mass.Get();

CL_Vec3f F = CL_Vec3f(0.0f, 0.0f, 0.0f);
CL_Vec3f A;

CL_Vec3f T = CL_Vec3f(0.0f, 0.0f, 0.0f);
CL_Vec3f W; //Angular velocity

bool updateLinearVel = false;
if(bodies->impulses.size() > 0)
{
F = sumForces(bodies->impulses);
bodies->impulses.clear();
updateLinearVel = true;
}
else
{
F = sumForces(bodies->forces);
updateLinearVel = true;
}

bool updateTorque = false;
if(bodies->torque_impulses.size() > 0)
{
T = sumForces(bodies->torque_impulses);
bodies->torque_impulses.clear();
updateTorque = true;
}
else
{
T = sumForces(bodies->torques);
updateTorque = true;
}


if(updateLinearVel)
{
A = F / M;
V += A * dt;
}
P += V * dt;

if(updateTorque)
L += T * dt;

CL_Mat3f iI = R * I_inverse * CL_Mat3f::transpose(R);
W += iI * L;

bodies->pitch = bodies->pitch.Get() + W.x * dt;
bodies->yaw = bodies->yaw.Get() + W.y * dt;
bodies->roll = bodies->roll.Get() + W.z * dt;

bodies->velocity = V;
bodies->position = P;
bodies->angular_momentum = L;
}
}


void Mesh::updateMatrix(float dt)
{
CL_Mat4f mat;
CL_Quaternionf q;
qPitch = CL_Quaternionf::axis_angle(CL_Angle(pitch.Get(), cl_radians), CL_Vec3f(1.0f, 0.0f, 0.0f));
qHeading = CL_Quaternionf::axis_angle(CL_Angle(yaw.Get(), cl_radians), CL_Vec3f(0.0f, 1.0f, 0.0f));
qRoll = CL_Quaternionf::axis_angle(CL_Angle(roll.Get(), cl_radians), CL_Vec3f(0.0f, 0.0f, 1.0f));

q = qPitch * qHeading * qRoll;
rot = q.to_matrix();

mat = qPitch.to_matrix();
CL_Vec3f fwdDir;
fwdDir.y = mat[9];

q = qHeading * qPitch * qRoll;
mat = q.to_matrix();
fwdDir.x = mat[8];
fwdDir.z = mat[10];
leftDirection = CL_Vec3f::cross(fwdDir, upDirection);
forwardDirection = fwdDir;
}



Right now, I think the mass and velocity at which my spacecraft shots fires at, is too much for the system... either that, or I have to increase the mass of my asteroids, because when I hit them, and add a force impulse and torque impulse, they start spinning like crazy

Obviously there's some overhead in how I add forces to bodies, in that the entities themselves doesn't have the functionality for physics internally. Instead, at a collision for example, the two entities involved in the collision will be sent to the physics system through the AddImpulse and AddTorqueImpulse functions, with the force and center-of-mass offset position of the collision. So I have to look up the rigid body in the physics system before I can add the impact to the body's list of forces. This is still fast enough for the scope of my project for the exam though!

Tomorrow I plan on getting up real early and have at it. I'm first going to stabilize my angular physics, so that hitting them won't send them in a mad spinning frenzy by adding correct masses for the bodies. Then, I'll have to look at my collision detection algorithm. We're expected to add simulator level collision detection between spheres. This means that if I detect a collision, I'll have to run the simulation again, and check that this change of events didn't impact any other objects (thus causing more collisions). I'll also have to make sure no collisions happen within the time-frame of delta-time. Simply checking for a collision at dt isn't enough... this doesn't sound like it will hold up in a game... I'm afraid that in a dense asteroid field, this will simply slow down the performance too much, so I'll make sure to add the ability to turn it off completely, and I also only plan on adding this level of collision detection / simulation to the closest body (determined by LOD threshold).

Once this is in, it's all pollish left. Add some UI elements, get in some simple gameplay mechanics, etc I really hope that I can finish off the necessities tomorrow, so that on saturday and sunday I can focus on making it somewhat a game and not just a tech demo of shooting at asteroids without purpose!

So, though I don't have any updates to show for today... in respect to what will follow on this project after the exam, here's some simple sketches made by the artist on this project, Bill Lowe!




Trefall

Trefall

 

Space-Shooter project, four days to go

Today I actually managed to force myself into starting on physics right away! I ported my quick n' dirty RigidBody component, which was my entirely scripted physics logic, to C++. I also integrated angular physics.

So today I feel like I've really been a good student for a change... Angular physics turned out to be rather simple to implement, well, for solid sphere anyway The inertia tensor for a solid sphere i symmetrical and follows the simple formula

I = (2/5) * M * R^2



or in proper C++ code:


float inertia = (2.0f/5.0f) * body->mass *body-> radius * body-> radius;
body->inertia_tensor = CL_Mat3f::identity();
body->inertia_tensor[0] *= inertia;
body->inertia_tensor[4] *= inertia;
body->inertia_tensor[8] *= inertia;
body->inertia_tensor.inverse();



I inverse at the end there, since it's only the inverse we use during calculations.

But, of course, the response from asteroids when I shoot them, when the mass distribution acts as though it's a solid sphere, isn't realistic. It's a lot better than nothing for a class that focus on physics and collision detection/response in game/simulator development, but it's not sufficient to achieve anywhere near believable angular behavior when I shoot at my asteroids. I guess I'll have to go ahead and calculate a tetrahedron-based inertia tensor from the mesh.

I also fixed some long-time bugs I had laying around from some old experiments in my shaders. They now calculate stuff in tangent-space, like they should due to tangent-space bumpmap textures, and I fixed my specular calculation.

Tomorrow I'll have to work on collision detection with "simulator" precision, plus look at doing engine flame particle effect.


Trefall

Trefall

 

Space-Shooter project, five days to go

I wonder how this got into my head... but I woke up this morning thinking it was a great idea to attempt Sean O'Neil's Atmospheric Scattering implementation for my planet, when there was six days till deadline! As if my bloom filter implementation yesterday wasn't bad enough... will I ever learn???

So I started the day by adding a new C++ mesh entity type I called Atmosphere. The shader needs so many variables this was the easiest way to do it. So I set up the variables, exposed to both property layer (lua scripts, xml) and the uniforms sent to shader. I added a component that allowed me to alter the variables during runtime.

Well, as I seem to remember, the atmospheric scattering algorithm is really fragile where you really have to hit just the right variable combination in order to achieve the color scattering result you want... My planet is also moving with the player, so O'Neil's shader that expects the planet to be in origo had to go through some minor modifications...

In the end, I didn't manage to get it working today... After quite a few hours of variable tweaking and shader experiments with the famous shader variable to color debugging, trying to achieve my goal, I gave up on getting it to work... so I started to focus more on using some of the variables to achieve "some" form of scattering

Well, for a long day of work, the end result is laughable... and I really felt like I wasted an entire day on this. Tomorrow I will most definitely get started on more physics/collision related stuff, as that's very important on the exam.

One can't always come out on top...









Trefall

Trefall

 

Space-Shooter project, six days to go

Ok, so... starting a full post-process system implementation when there's only seven days till deadline might not have been the smartest move I've made... Well, at least I knew that when I started, guess I don't take no for an answer! If nothing else, I resisted the urge of creating a new PostProcess XML format that would make the definition of post processes more data-driven...

though, I did integrate it fully with my Entity and Material XML format That was kind of cool, because it allowed me to integrate it "rather" fast without too many hacks... well, without enormous amounts of hacks at least... so now my Materials support definition of "generated" textures and one can define them as RTT materials. Further, one can set which order the material should get in the MaterialManager's list of RTT materials... because, of course, when you're rendering a RTT texture attached to a fullscreen quad, that has to go through my Material system...

Though it did take a while longer to implement than I thought, it wasn't too bad to wrap my head around... though I do see that I have some refactoring to do on my design once the exam is over

So, behold! I expected my scene to turn up with a really nice bloom post-process filter...



I really, really love it when OpenGL throws these things at me... the rendering "kind of works"... but not really So, I fired up gDEBugger, my friend in need when I bump into rendering issues (and it sports a free license at the moment!!!). It told me I was making some redundant and deprecated calls in my FBO code for the post processing, but fixing those up didn't really change anything... so gDEBugger didn't really offer me much support...

so, I found that maybe it was a good idea to check the status of the FBOs after I created them! Finally some progress! The status check was throwing GL_FRAMEBUFFER_UNSUPPORTED at me. A quick google of the web lead me to OpenGL.org's Common Mistakes wiki page, which states the following under it's Render To Texture section:
[quote]
[font="sans-serif"][size="2"]Warning: NVIDIA's OpenGL driver has a known issue with using incomplete textures. If the texture is not texture complete, the FBO itself will be considered GL_FRAMEBUFFER_UNSUPPORTED, or will have GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. This is a driver bug, as the OpenGL specification does not allow implementations to return either of these values simply because a texture is not yet complete. Until this is resolved in NVIDIA's drivers, it is advised to make sure that all textures have mipmap levels, and that all glTexParameteri values are properly set up for the format of the texture. For example, integral textures are not complete if the mag and min filters have any LINEAR fields.[/font]
[/quote]


Fair enough, so I fixed my issues. I was indeed using LINEAR filtering for my FBO textures, that, when I think about it, doesn't make much sense when you're rendering to a screen sized texture that will only get rendered on a screen sized quad later I also added mipmapping (though not sure if that was really required)...

and, magic! It works! "Just" one of seven days wasted on a Bloom post-process filter!!! That's pretty amazing waste of time here at the very end... oh well, at least I can show off some sparkling sun reflection on that spaceship metal hull now! That ought to count for something!

Trefall

Trefall

 

Space-Shooter project, seven days to go

I started today by adding event subscription for components, so now all my scripted components subscribe to the events they need, and thus components (both C++ and Lua scripted) will only get event calls when an event they have subscribed to occurs. This had a very good impact on performance!

I then started on some design thinking. I know I should have focused on optimizing collisions and adding more advanced collision detection routines, but I got inspired, so what can I do




So, the idea is that the scene must have a huge planet in the distance. You're flying through it's asteroid belt. The tunnel walls should hold textures that creates an illusion of a vast asteroid belt, and then I'll spawn asteroid sprites ahead of the camera that fly by (but can't be collided with) + the fully fledged 3D mesh asteroids that will spawn in your path, that you can shoot and collide with and have to navigate around.

Then, I started implementing a planet. That went fast. I just had to create a new Material XML for it, a new Entity XML, and a specialized fragment shader, and I could add it in the scene initialization script. To create an illusion of scale, but without killing my depth buffer, I added a component to it that maintains the Z distance to my spaceship, thus no matter how fast my spaceship moves, I'll never get closer to the planet... I really felt this illusion worked.

Next, I kind of felt that I needed to add a sun to the scene... it's a cool thing to have after all So I hacked a sprite from the net, and in it's shader (geometry and fragment) I added a distortion animation to it's texcoord lookup, making the sun appear as though it's wrought in fire. The sinus behavior is still a bit obvious, but it's better than just a static sprite image... but then I though... man, this would look so much more awesome if I had bloom post process filtering!

... so I started on a Post Process system... and I'm still working on it Man, I suck at staying true to my priority list!


Trefall

Trefall

 

Space-Shooter project, eight days to go

After ending my coding late yesterday, I overslept this morning and didn't get up until eleven. That really hurt, because I had to work at the grocery store from one pm, and then it's a saturday evening, so I want to spend that socially with my wife. As such, I only got in an hour or so of work today!

I figured there was no point on starting a quad-tree implementation, so I went through the code and did some smaller optimizations. One trick I stumbled upon by accident recently was doing erasing from std vectors like this:

//O(1) removal

list = list.back();
list.pop_back();



I also have some Lua components now that subscribe to OnUpdate(), thus each of those components, for each instance of them, will require a transition between C++ and Lua. I suspect there's more overhead to this than my game really should like... so it might soon be time to move some of those components over to C++ code. Also, currently, components don't "subscribe" to events. If a scripted component has an OnEvent function declared, it will receive all events sent to it's entity via C++... Most components only care about a single specific event, maybe two... so needless to say, there's a lot of overhead here as well! I should really exchange this for a subscription-based event handling, so that the component's OnEvent function is only called when an event occurs that the component is going to use.

Tomorrow should be more productive

Trefall

Trefall

 

Space-Shooter game project, four day extension!

So, gladly, the exam got pushed back four days! Initially they cut an entire week off the project, so this was very good news I received today!

So, Project assignment is due on monday morning 8:30 GMT+1 on the 28th of february, with the exam following the immediate day, on 1st of march.

Today I worked in a somewhat working transparency order for my non-opaque entities, I fixed my shooter entity, so that it would shoot correctly down the spaceship's forward direction vector, and I got well on my way with the particle system. I ended up with particle emitters that spawns where a collision occurs between two entities. It's up to the entities to respond to the collision via components (a Collision event is sent to both entities). It takes the collision impact direction into account, to generate realistic looking dust-clouds depending on where on an asteroid you shoot for instance... though this may look a bit weird when asteroids start to collide. I'll fix those things as I bump into them. Also went over some smaller issues as well that's not really worth mentioning.

Major problem of today was really the transparency sorting. The problem is, that I have ordered my rendering based on materials. That is, I define materials in XML (holds texture, shader, etc definitions). An entity registers itself with a material type (in the entity XML definition)... thus, my entire scene is rendered from a list of materials. This is good at first look, because I can now bind the shader and textures only once per material type, as well as all shared uniforms (like lighting, and specific material properties). I then sort each entity type under those materials in a manager. The manager holds the render data of the entity type, while the entity instance holds it's per-instance uniform information (at current time I'm not using UBO or TBO with glDrawElementsInstanced). Using the manager, I can bind the VAO only once for each entity type, and then in a for loop, I bind uniforms per entity instance and draw based on glDrawElements.

The obvious problem occurs when I need to start sorting entities based on distance to camera (back to front), like you want to do with transparent entities to get proper alpha blending. I ended up with a hack. I will simply not have the time to rewrite my rendering pipeline to solve this issue properly. So, I ended up with the solution, that every entity type manager will calculate the average distance to camera for it's entities, and send that to it's material. The material then calculates the average weight it has received from it's entity types, and cast it to unsigned int. I then sort all transparent materials (which is in a separate list from opaque materials) based on this weight. Greater weight equals further from camera. This works fairly well for the average particle emitter, since it's particles will stay more or less around the same position... but with shooting, where one sprite can be close to the camera, and another can be far, far away (just before being culled/garbaged for reuse), the issue can quickly become quite apparent... especially when you have a sprite in between called the Crosshair, which the bullets are supposed to travel through! I think this issue will be ok enough if I just draw the crosshair using some simple mesh instead of a sprite... The internal sorting of entities within an entity type manager, and the sorting of vertex positions per particle emitter for instance, is easier to handle

Experiments and "good ideas" don't always end up like you expect In fact, they hardly ever do.... at the first (and second, and third, etc...) try anyway

Tomorrow I'll be working more on collisions. I need to get a spatial sorting of the collision bodies in place, because right now the brute-force collision detection is sucking CPU and FPS! Then we'll see... it stands between adding collision testing with discreet steps (ensuring a simulator level collision accuracy for the bounding objects used for testing collision (AABB or Sphere is what I'm using so far)), and working on some particle effects, like the spaceship engine flames, another spaceship gun type, plasma-bomb collision impact particle emitter, etc... guess the first one is more important right now, so I should probably stick to that... though doings things in the "correct" order isn't always the most fun!


Trefall

Trefall

 

Space-Shooter game project, one week left

So, at time of writing, there's one week left until my exam on this project. Assignment hand-in at midnight, Wednesday 23rd, with the opportunity for detail fixing during Thursday 24th, then exam on the 25th.

I've got a ton of features left to grind in this week, and I'll blog my progress as I go.

The project is a space shooter. We're supposed to do a project that focus on physics and collision detection/response from ground-up using OpenGL, Qt and my school's real-time API GMlib. I've worked these requirements into my own engine, which holds my component-based entity system using properties, my Lua binding solution using LuaPlus, XML loading, libRocket for gui, ClanLib for general awesomeness and convenience, DevIL for texture loading, AssImp for asset/mesh loading and glew for OpenGL extensions.

In collaboration with my friend, Bill Lowe, who's an artist, and with the license of using Andre Roux's Ferox spaceship for Inifinity - Quest for Earth (non-commercial school project license), I've set out on making a Starfox-esque on-rails space-flight shooter. Working your way through an asteroid field you'll have to battle enemies and debries to survive.

I've got the basic stuff in place already, currently working on depth-sorted transparent objects, shooting and optimizing my crude collision detection... I'll give a status update each day from now on, and will detail out what I've been working on, problems I face, solutions, refactoring decisions, etc, etc, etc.

So, wish me luck!



Trefall

Trefall

 

New year, right back at it!

So I had a great, relaxing vacation with family, but when I got back to school, I had one week to start and finish a project that had been going for two to three months. Why hadn't I started earlier? Well, we had two other projects running at the same time that was completed before the x-mass vacation, and I really needed some time off from coding, to get that itching back into my fingers! I got that itching back alright, but one week on a huge project isn't a lot of time!

Needless to say, I just spent a week coding without much sleep. Tuesday to Sunday I probably got a good four to six hours each night, so nothing to really complain about there. With a couple good espresso shots now and then, I kept that pace going. Sunday I took the bus up to my university and got a good six hours of sleep there. Then I went into true crunch-mode up till the deadline, sleeping no more than three hours each night until deadline on Tuesday night.

An exhausting experience coming straight from the calm and relax of vacation to that kick-start! You can check out the results of my efforts on YouTube:

[media] [/media]

[media][/media]

I wrote a C++/OpenGL3.3 application with XML and Lua script exposure. I implemented the Gui via libRocket for the first time, which really rocked! And I did my first experimentation with Geometry Shaders, where I drew the point-cloud as vectors using the velocity information for each particle. The particle simulation was done in Flow3d. Of course, I had a lot of my engine laying around from other previous projects, so throwing the engine together was a fairly straight forward task. It's kind of cool to see how fast I can whip together a new project now that I've got this much code laying around from previous ones.

Next is to start on my thesis, plus I really want to do some of my exams over again in projects where I felt I was close to the top grade, but just didn't reach it. Of course, that's also an excuse to make games!

I've also been starting up this 2D game with my good friend Sphair, who's one of the key devs on ClanLib. It'll be this 2D multiplayer airplane shooter. Quite excited about it too! I'll keep this blog updated with images and progress!

Trefall

Trefall

 

Scripted components

Over the summer, I integrated Lua scripting with my engine, and in that process also my component system. I'll try to write up how I did it in this journal entry, focusing on scripted components.

Overview
For the C++/Lua bridge, I used LuaPlus, as promoted by Mr. Mike in Game Coding Complete 3rd Ed. It gives the programmer a C++ layer on top of the Lua C API, which helps a lot when exposing functionality to Lua!

At the heart I have the ScriptManager. It's main functionality is to initialize the Lua API on the C++ side. We do this by creating a LuaStateOwner object in the ScriptManager header. From here we can start adding values into the globals table of the LuaState, and calling OpenLibs() on the LuaStateOwner instance is a good idea, to get all the common Lua functionality working in scripts! The ScriptManager will initialize all script wrappers of other manager classes in the engine that we want to expose to script. It also holds a doFile and doString function, that allows us to load a script file, and to execute a string of Lua script directly in C++.

Introducing my component system
My component system basically consist of an Entity that owns a collection of components and properties. Components can add properties to the entity, and keep references to these properties. Components can register listeners with properties, so that if some other component cause a property to change, we can "react" to that change.

Example of C++ component

Health::Health(Engine::Core::IManager *coreMgr, Engine::Object::IObject *go, const CL_String &name)
: Engine::Entity::Component(static_cast(go), static_cast(go), name), coreMgr(coreMgr), go(go)
{
alive_property = go->AddPropertybool>("Alive", true);
health_property = go->AddPropertyfloat>("Health", 100.0f);
maxhealth_property = go->AddPropertyfloat>("MaxHealth", 100.0f);

slotHealthChanged = health_property.ValueChanged().connect(this, &Health::OnHealthChanged);
slotAliveChanged = alive_property.ValueChanged().connect(this, &Health::OnAliveChanged);
}

void Health::ExecuteEvent(const Engine::Com::IEvent &event, Engine::Player::IPlayer *player)
{
if(event.getType() == CL_String("TakeDamage"))
{
if(event.getArgType() == Engine::Com::TYPE_FLOAT)
{
const Engine::Com::Eventfloat, Engine::Object::IObject*> *e;
e = static_castconst Engine::Com::Eventfloat, Engine::Object::IObject*>*>(&event);

float dmg = e->getArg();

CL_String msg = cl_format("%1 took %2 damage!", go->getName(), dmg);
coreMgr->getLogMgr()->log("Health::ExecuteEvent", msg, Engine::Log::L_DEBUG);

msg = cl_format("%1 HP: %2", go->getName(), health_property.Get() - dmg);
coreMgr->getLogMgr()->log("Health::ExecuteEvent", msg, Engine::Log::L_DEBUG);

health_property -= dmg;
}
}
else if(event.getType() == "Die")
{
if(event.getArgType() == Engine::Com::TYPE_OBJECT)
{
const Engine::Com::Event *e;
e = static_castconst Engine::Com::Event*>(&event);

Engine::Object::IObject *arg = e->getArg();
if(arg != go)
return;

CL_String msg = cl_format("%1 died!", go->getName());
coreMgr->getLogMgr()->log("Health::ExecuteEvent", msg, Engine::Log::L_DEBUG);
}
}
}

void Health::OnHealthChanged(const float &oldValue, const float &newValue)
{
if(newValue 0.0f)
alive_property = false;
}

void Health::OnAliveChanged(const bool &oldValue, const bool &newValue)
{
//If no longer alive, kill it properly to make sure it stays dead!
if(!newValue)
{
go->executeEvent(Engine::Com::Event("Die", go, go), NULL);
go->kill();
}
}






Thoughts
Although this component system deals with decoupling component to component dependencies through the use of shared properties, the syntax involved in using it relies quite heavily on templates. The result is that we end up with a lot of text to code something as simple as a Health component. In addition, if I wanted to change something about the logic within the Health component, I'd have exit the game, recompile my code and restart it! The larger the project, the longer this process takes! In addition, you'd have to register every new component you make with the ComponentFactory! This is a core class in the engine, causing a rather deep recompilation of the game! This is where Lua scripted components come to the rescue!

Example of Lua Component

if(HealthComponent == nil) then
HealthComponent = {}
end

function HealthComponent:OnInit(go)
go:AddProperty("Alive", true)
go:AddProperty("Health", 100.0)
go:AddProperty("MaxHealth", 100.0)

go:AddHealthListener("HealthComponent:OnHealthChanged")
go:AddAliveListener("HealthComponent:OnAliveChanged")
end

function HealthComponent:OnEvent(go, event, playerId)
if(event.id == "TakeDamage") then
local dmg = event.arg
go:SetHealth(go:GetHealth() - dmg)
elseif(event.id == "Die") then
Log("HealthComponent:OnEvent", go.name .. " died!", LOG_INFO)
end
end

function HealthComponent:OnHealthChanged(node, oldValue, newValue)
if(newValue 0.0) then
node:SetAlive(false)
end
end

function HealthComponent:OnAliveChanged(node, oldValue, newValue)
if(newValue == false) then
node:SendEvent("Die")
node:Kill()
end
end

RegisterComponent("HealthComponent")






Thoughts
There's no doubt that the code here is a lot tighter and faster to write! Of course, there's always improvements one could wish for! But in the end this is a good blend of robustness and productivity! You can now register the component within the same script file as you declare the component, properties automatically detects the template type, and once a property is added, you may use its getter and setter with it's name, like GetHP() and SetHP(hp). The components are also stateless in script, so you can reload them with new functionality at any moment, and the game will adapt at runtime without deprecating any data!

Implementation
Following, I'll briefly describe the implementation of my solution.

Entity
On the C++ side we have the Entity. An Entity inherits from ComponentContainer. A ComponentContainer is like it says, a container of components. So, in order to allow us to make new components in Lua scripts, we need to write a C++ component that can handle the bridging for us. This way, the C++ engine and entity won't care that the component list is compiled of a variation of both C++ written components, and scripted components. This is definitely something we want, because although it can be very convenient to write components in script at runtime, now and then you have to move some of them into C++ in order to optimize!

Property
On the C++ side, we have a template-based implementation of properties. Properties hold reference counted data, and in addition, has a signal that will be invoked every time the data in the property change state. Anyone with access to the property can add a listener to this signal in the form of a function pointer. Since our components in Lua script will be stateless, it will be through properties that we are able to access state, the state of the entity that the component belong to. The script-side property should be a lot more streamlined and intuitive however, and simpler to use, than the robust C++ version.
- Automatic type definition when adding a property
- Getters and Setters use name of the property (GetHP() and SetHP(hp) for instance).
- Same for listeners (AddHPListener(listener)

Specific class implementations
Thus, we end up with the following class implementations:

LuaComponent implementation
LuaComponent.h

#pragma once

#include
#include
#include

namespace Engine
{
namespace Core { class CoreManager; }
namespace Player { class IPlayer; }
namespace Script { class WrapComponent; }

namespace Component
{
class LuaComponent : public Engine::Entity::Component
{
public:
virtual ~LuaComponent() {}

virtual void Update(double dt);
virtual void ExecuteCommand(const CL_String &command, Engine::Player::IPlayer *player);
virtual void ExecuteEvent(const Engine::Events::IEvent &event, Engine::Player::IPlayer *player);

static CL_String GetType() { return "Lua"; }
static Engine::Entity::Component* Create(Engine::Core::CoreManager *coreMgr, Engine::Entity::IEntity *entity, const T_String &name) { return new LuaComponent(coreMgr, entity, name); }

void initLuaExposure(Script::WrapComponent *wComp);

protected:
LuaComponent(Engine::Core::CoreManager *coreMgr, Engine::Entity::IEntity *entity, const T_String &name);

Engine::Core::CoreManager *coreMgr;
Script::WrapComponent *wComp;

bool hasInit, hasUpdate, hasCommand, hasEvent;
};
}}







LuaComponent.cpp

#include "LuaComponent.h"
#include "WrapComponent.h"
#include "WrapEntityManager.h"
#include "WrapIEvent.h"
#include "ScriptManager.h"
#include
#include
#include
#include
#include
#include
#include

#include

using namespace Engine;
using namespace Component;
using namespace LuaPlus;

LuaComponent::LuaComponent(Engine::Core::CoreManager *coreMgr, Engine::Entity::IEntity *entity, const CL_String &name)
: Engine::Entity::Component(entity, name), coreMgr(coreMgr)
{
wComp = NULL;
hasInit = false;
hasUpdate = false;
hasCommand = false;
hasEvent = false;
}

void LuaComponent::initLuaExposure(Script::WrapComponent *wComp)
{
this->wComp = wComp;

hasInit = false;
LuaObject lInit = wComp->getLComp().GetByName("OnInit");
if(lInit.IsFunction())
{
hasInit = true;
}

hasUpdate = false;
LuaObject lUpdate = wComp->getLComp().GetByName("OnUpdate");
if(lUpdate.IsFunction())
{
hasUpdate = true;
}

hasCommand = false;
LuaObject lCommand = wComp->getLComp().GetByName("OnCommand");
if(lCommand.IsFunction())
{
hasCommand = true;
}

hasEvent = false;
LuaObject lEvent = wComp->getLComp().GetByName("OnEvent");
if(lEvent.IsFunction())
{
hasEvent = true;
}

if(hasInit)
{
//Make sure that the entity exist in the global state's Entities table
LuaObject lEntity = coreMgr->getScriptMgr()->getWEntityMgr()->getLEntities().GetByIndex(entity->getId());
if(lEntity.IsNil())
{
CL_String err = cl_format("Entity of id %1 has not been exposed to Lua!", entity->getId());
coreMgr->getLogMgr()->log("LuaComponent:InitLuaExposure", err, Log::L_ERROR);
return;
}

coreMgr->getScriptMgr()->doString(cl_format("%1:OnInit(Entities[%2])", name, entity->getId()));
}
}

void LuaComponent::ExecuteCommand(const CL_String &command, Engine::Player::IPlayer *player)
{
if(hasCommand)
{
//Make sure that the entity exist in the global state's Entities table
LuaObject lEntity = coreMgr->getScriptMgr()->getWEntityMgr()->getLEntities().GetByIndex(entity->getId());
if(lEntity.IsNil())
{
CL_String err = cl_format("Entity of id %1 has not been exposed to Lua!", entity->getId());
coreMgr->getLogMgr()->log("LuaComponent:ExecuteCommand", err, Log::L_ERROR);
return;
}

if(player)
coreMgr->getScriptMgr()->doString(cl_format("%1:OnCommand(Entities[%2], '%3', %4)", name, entity->getId(), command, player->getId()));
else
coreMgr->getScriptMgr()->doString(cl_format("%1:OnCommand(Entities[%2], '%3', nil)", name, entity->getId(), command));
}
}

void LuaComponent::ExecuteEvent(const Engine::Events::IEvent &event, Engine::Player::IPlayer *player)
{
if(hasEvent)
{
//Make sure that the entity exist in the global state's Entities table
LuaObject lEntity = coreMgr->getScriptMgr()->getWEntityMgr()->getLEntities().GetByIndex(entity->getId());
if(lEntity.IsNil())
{
CL_String err = cl_format("Entity of id %1 has not been exposed to Lua!", entity->getId());
coreMgr->getLogMgr()->log("LuaComponent:ExecuteEvent", err, Log::L_ERROR);
return;
}

Script::WrapIEvent wEvent(coreMgr, &event);
int fail = wEvent.init();
if(fail)
return;

//Make sure that the event exist in the global state's Events table
LuaObject lEvent = wEvent.getLEvents().GetByName(event.getType());
if(lEvent.IsNil())
{
CL_String err = cl_format("Event of type %1 has not been exposed to Lua!", event.getType());
coreMgr->getLogMgr()->log("LuaComponent:ExecuteEvent", err, Log::L_ERROR);
return;
}

if(player)
coreMgr->getScriptMgr()->doString(cl_format("%1:OnEvent(Entities[%2], Events['%3'], %4)", name, entity->getId(), event.getType(), player->getId()));
else
coreMgr->getScriptMgr()->doString(cl_format("%1:OnEvent(Entities[%2], Events['%3'], nil)", name, entity->getId(), event.getType()));
}
}

void LuaComponent::Update(double dt)
{
if(hasUpdate)
{
//Make sure that the entity exist in the global state's Entities table
LuaObject lEntity = coreMgr->getScriptMgr()->getWEntityMgr()->getLEntities().GetByIndex(entity->getId());
if(lEntity.IsNil())
{
CL_String err = cl_format("Entity of id %1 has not been exposed to Lua!", entity->getId());
coreMgr->getLogMgr()->log("LuaComponent:Update", err, Log::L_ERROR);
return;
}

coreMgr->getScriptMgr()->doString(cl_format("%1:OnUpdate(Entities[%2], %3)", name, entity->getId(), dt));
}
}






WrapComponent implementation

WrapComponent.h

#pragma once

#include
#include

namespace Engine
{
namespace Core { class CoreManager; }
namespace Entity { class Component; }
namespace Script
{
class WrapIEntity;
class WrapComponentContainer;

class WrapComponent
{
public:
WrapComponent(Core::CoreManager *coreMgr, Script::WrapIEntity *wEntity, WrapComponentContainer *wCompContainer, Entity::Component *component);
~WrapComponent();

int init();

Entity::Component *getComp() const { return component; }
LuaPlus::LuaObject getLComp() const { return lComponent; }
WrapIEntity *getWEntity() const { return wEntity; }

private:
Core::CoreManager *coreMgr;
Script::WrapComponentContainer *wCompContainer;
Script::WrapIEntity *wEntity;

Entity::Component *component;
LuaPlus::LuaObject lComponent;
};

}
}






WrapComponent.cpp

#include "WrapComponent.h"
#include "WrapIEntity.h"
#include "WrapComponentContainer.h"
#include "LuaComponent.h"
#include "ScriptManager.h"
#include
#include
#include
#include

using namespace Engine;
using namespace Script;
using namespace LuaPlus;
using namespace Entity;

WrapComponent::WrapComponent(Core::CoreManager *coreMgr, Script::WrapIEntity *wEntity, WrapComponentContainer *wCompContainer, Entity::Component *component)
{
this->coreMgr = coreMgr;
this->wEntity = wEntity;
this->wCompContainer = wCompContainer;
this->component = component;
}

WrapComponent::~WrapComponent()
{
lComponent.AssignNil(coreMgr->getScriptMgr()->GetGlobalState()->Get());
}

int WrapComponent::init()
{
LuaObject globals = (*coreMgr->getScriptMgr()->GetGlobalState())->GetGlobals();

//Check if this is a C++ component, or a scripted Lua defined component
Component::LuaComponent *luaComp = dynamic_cast(component);
if(luaComp == NULL)
{
LuaObject &lComps = wCompContainer->getLComps();
lComponent = lComps.CreateTable(component->GetName().c_str());
lComponent.SetString("id", component->GetName().c_str());

{
LuaObject lMeta = lComponent.CreateTable("MetaTable");
lMeta.SetObject("__index", lMeta);

lComponent.SetLightUserData("__object", this);
lComponent.SetMetaTable(lMeta);
}
}
else
{
lComponent = globals.GetByName(component->GetName().c_str());
lComponent.SetString("id", component->GetName().c_str());
{
LuaObject lMeta = lComponent.CreateTable("MetaTable");
lMeta.SetObject("__index", lMeta);

lComponent.SetLightUserData("__object", this);
lComponent.SetMetaTable(lMeta);
}

luaComp->initLuaExposure(this);
}
return 0;
}






WrapComponentManager implementation
WrapComponentManager.h

#pragma once

#include
#include

namespace Engine
{
namespace Core { class CoreManager; }
namespace Script
{

class WrapComponentManager
{
public:
WrapComponentManager(Core::CoreManager *coreMgr);
~WrapComponentManager();

int init();

private:
void RegisterComponent(LuaPlus::LuaObject lName);

Core::CoreManager *coreMgr;
};

}
}






WrapComponentManager.cpp

#include "WrapComponentManager.h"
#include "WrapComponent.h"
#include "LuaComponent.h"
#include "ScriptManager.h"
#include
#include
#include
#include
#include
#include

using namespace Engine;
using namespace Script;
using namespace LuaPlus;

WrapComponentManager::WrapComponentManager(Core::CoreManager *coreMgr)
{
this->coreMgr = coreMgr;
}

WrapComponentManager::~WrapComponentManager()
{
}

int WrapComponentManager::init()
{
LuaObject globals = (*coreMgr->getScriptMgr()->GetGlobalState())->GetGlobals();
globals.RegisterDirect("RegisterComponent", *this, &WrapComponentManager::RegisterComponent);

//Load all scripts in Game/Components
coreMgr->getLogMgr()->log("WrapComponentManager:Init", "Loading scripted components!", Log::L_INFO);

std::vector scripts = coreMgr->getResMgr()->getFilesInDir("/Scripts/Components/");
for(unsigned int i = 0; i {
int fail = coreMgr->getScriptMgr()->doFile(cl_format("Components/%1", scripts));
if(fail)
{
CL_String err = cl_format("Failed to load component script %1", scripts);
coreMgr->getLogMgr()->log("WrapComponentManager:Init", err, Log::L_ERROR);
}
}
coreMgr->getLogMgr()->log("WrapComponentManager:Init", "Finsihed loading scripted components!", Log::L_INFO);

return 0;
}

void WrapComponentManager::RegisterComponent(LuaObject lName)
{
if(!lName.IsString())
{
CL_String name_type = lName.TypeName();

CL_String err = cl_format("Failed to register component, because the type of name was %1 when expecting String!", name_type);
coreMgr->getLogMgr()->log("WrapComponentManager:RegisterComponent", err, Log::L_ERROR);
return;
}

CL_String name = lName.ToString();
coreMgr->getEntityMgr()->getComponentFactory()->RegisterComponent(name, &Engine::Component::LuaComponent::Create);

coreMgr->getLogMgr()->log("WrapComponentManager:RegisterComponent", cl_format("Component: %1", name), Log::L_DEBUG);
return;
}






PropertyContainer implementation
WrapPropertyContainer.h

#pragma once

#include
#include
#include
#include
#include
#include
#include

namespace Engine
{
namespace Core { class CoreManager; }
namespace Script
{
class WrapIEntity;
class WrapIPlayer;
class WrapIProperty;
class WrapIRoom;

class WrapPropertyContainer
{
public:
WrapPropertyContainer(Core::CoreManager *coreMgr, Script::WrapIEntity *wEntity);
WrapPropertyContainer(Core::CoreManager *coreMgr, Script::WrapIPlayer *wPlayer);
WrapPropertyContainer(Core::CoreManager *coreMgr, Script::WrapIRoom *wRoom);
~WrapPropertyContainer();

int init();

LuaPlus::LuaObject &getLProps() { return lProperties; }

private:
void AddProperty(LuaPlus::LuaObject self, LuaPlus::LuaObject name, LuaPlus::LuaObject defValue);
LuaPlus::LuaObject HasProperty(LuaPlus::LuaObject self, LuaPlus::LuaObject name);

void add(const CL_String &name, Entity::IProperty *prop, WrapIProperty *wProp);

Core::CoreManager *coreMgr;
Script::WrapIEntity *wEntity;
Script::WrapIPlayer *wPlayer;
Script::WrapIRoom *wRoom;

LuaPlus::LuaObject lProperties;
std::vector wProperties;

void OnPropertyAdded(const Engine::Events::EngineEvent &event);
Engine::Events::EngineEventContainer engineEvents;
};

}
}






WrapPropertyContainer.h

#include "WrapPropertyContainer.h"
#include "WrapIProperty.h"
#include "WrapIEntity.h"
#include "WrapIPlayer.h"
#include "WrapIRoom.h"
#include "ScriptManager.h"
#include
#include
#include
#include
#include
#include
#include

using namespace Engine;
using namespace Script;
using namespace LuaPlus;

WrapPropertyContainer::WrapPropertyContainer(Core::CoreManager *coreMgr, Script::WrapIEntity *wEntity)
: engineEvents(coreMgr->getEngineEventMgr())
{
this->coreMgr = coreMgr;
this->wEntity = wEntity;
this->wPlayer = NULL;
this->wRoom = NULL;
}

WrapPropertyContainer::WrapPropertyContainer(Core::CoreManager *coreMgr, Script::WrapIPlayer *wPlayer)
: engineEvents(coreMgr->getEngineEventMgr())
{
this->coreMgr = coreMgr;
this->wPlayer = wPlayer;
this->wEntity = NULL;
this->wRoom = NULL;
}

WrapPropertyContainer::WrapPropertyContainer(Core::CoreManager *coreMgr, Script::WrapIRoom *wRoom)
: engineEvents(coreMgr->getEngineEventMgr())
{
this->coreMgr = coreMgr;
this->wRoom = wRoom;
this->wPlayer = NULL;
this->wEntity = NULL;
}

WrapPropertyContainer::~WrapPropertyContainer()
{
for(unsigned int i = 0; i {
WrapIProperty *wProp = wProperties;
delete wProp;
wProp = NULL;
}
wProperties.clear();
lProperties.AssignNil(coreMgr->getScriptMgr()->GetGlobalState()->Get());
}

int WrapPropertyContainer::init()
{
LuaObject globals = (*coreMgr->getScriptMgr()->GetGlobalState())->GetGlobals();

if(wEntity)
{
lProperties = wEntity->getLEntity().CreateTable("Properties");

Engine::Entity::IEntity *entity = wEntity->getEntity();
std::map &properties = entity->GetProperties();
std::map::iterator propIt = properties.begin();
for(; propIt != properties.end(); ++propIt)
{
WrapIProperty *wProp = new WrapIProperty(coreMgr, wEntity, this, propIt->second);
int fail = wProp->init();
if(fail)
{
delete wProp;
wProp = NULL;

CL_String msg = cl_format("Failed to initialize Property Wrapper for property %1", propIt->first);
coreMgr->getLogMgr()->log("WrapPropertyContainer::Init", msg, Log::L_ERROR);
continue;
}
wProperties.push_back(wProp);
}

LuaObject lMeta = wEntity->getLMeta();
lMeta.RegisterDirect("AddProperty", *this, &WrapPropertyContainer::AddProperty);
lMeta.RegisterDirect("HasProperty", *this, &WrapPropertyContainer::HasProperty);
}
else if(wPlayer)
{
lProperties = wPlayer->getLPlayer().CreateTable("Properties");

Engine::Player::IPlayer *player = wPlayer->getPlayer();
std::map &properties = player->GetProperties();
std::map::iterator propIt = properties.begin();
for(; propIt != properties.end(); ++propIt)
{
WrapIProperty *wProp = new WrapIProperty(coreMgr, wPlayer, this, propIt->second);
int fail = wProp->init();
if(fail)
{
delete wProp;
wProp = NULL;

CL_String msg = cl_format("Failed to initialize Property Wrapper for property %1", propIt->first);
coreMgr->getLogMgr()->log("WrapPropertyContainer::Init", msg, Log::L_ERROR);
continue;
}
wProperties.push_back(wProp);
}

LuaObject lMeta = wPlayer->getLMeta();
lMeta.RegisterDirect("AddProperty", *this, &WrapPropertyContainer::AddProperty);
lMeta.RegisterDirect("HasProperty", *this, &WrapPropertyContainer::HasProperty);
}
else if(wRoom)
{
lProperties = wRoom->getLRoom().CreateTable("Properties");

Engine::Room::IRoom *room = wRoom->getRoom();
std::map &properties = room->GetProperties();
std::map::iterator propIt = properties.begin();
for(; propIt != properties.end(); ++propIt)
{
WrapIProperty *wProp = new WrapIProperty(coreMgr, wRoom, this, propIt->second);
int fail = wProp->init();
if(fail)
{
delete wProp;
wProp = NULL;

CL_String msg = cl_format("Failed to initialize Property Wrapper for property %1", propIt->first);
coreMgr->getLogMgr()->log("WrapPropertyContainer::Init", msg, Log::L_ERROR);
continue;
}
wProperties.push_back(wProp);
}

LuaObject lMeta = wRoom->getLMeta();
lMeta.RegisterDirect("AddProperty", *this, &WrapPropertyContainer::AddProperty);
lMeta.RegisterDirect("HasProperty", *this, &WrapPropertyContainer::HasProperty);
}

engineEvents.Connect("PropertyAdded", this, &WrapPropertyContainer::OnPropertyAdded);

return 0;
}

void WrapPropertyContainer::AddProperty(LuaObject self, LuaObject lName, LuaObject defValue)
{
if(!self.IsTable())
{
CL_String self_type = self.TypeName();

CL_String msg = cl_format("Failed to add property, because the type of self was %1 when expecting Table", self_type);
coreMgr->getLogMgr()->log("WrapPropertyContainer::AddProperty", msg, Log::L_ERROR);
return;
}

if(!lName.IsString())
{
CL_String name_type = lName.TypeName();

CL_String msg = cl_format("Failed to add property, because the type of name was %1 when expecting String", name_type);
coreMgr->getLogMgr()->log("WrapPropertyContainer::AddProperty", msg, Log::L_ERROR);
return;
}

CL_String name = lName.ToString();

Entity::IEntity *entity = NULL;
Player::IPlayer *player = NULL;
Room::IRoom *room = NULL;
if(wEntity)
entity = wEntity->getEntity();
else if(wPlayer)
player = wPlayer->getPlayer();
else if(wRoom)
room = wRoom->getRoom();

if(defValue.IsBoolean())
{
bool val = defValue.GetBoolean();
if(entity)
entity->AddPropertybool>(name, val);
else if(player)
player->AddPropertybool>(name, val);
else if(room)
room->AddPropertybool>(name, val);
}
else if(defValue.IsNumber())
{
float val = (float)defValue.ToNumber();
if(entity)
entity->AddPropertyfloat>(name, val);
else if(player)
player->AddPropertyfloat>(name, val);
else if(room)
room->AddPropertyfloat>(name, val);
}
else if(defValue.IsInteger())
{
int val = defValue.ToInteger();
if(entity)
entity->AddPropertyint>(name, val);
else if(player)
player->AddPropertyint>(name, val);
else if(room)
room->AddPropertyint>(name, val);
}
else if(defValue.IsString())
{
CL_String val = defValue.ToString();
if(entity)
entity->AddProperty(name, val);
else if(player)
player->AddProperty(name, val);
else if(room)
room->AddProperty(name, val);
}
else if(defValue.IsTable())
{
bool hasX = false;
bool hasY = false;
bool hasZ = false;
bool hasW = false;

LuaObject xObj = defValue.GetByName("x");
if(xObj.IsNumber())
hasX = true;

LuaObject yObj = defValue.GetByName("y");
if(yObj.IsNumber())
hasY = true;

LuaObject zObj = defValue.GetByName("z");
if(zObj.IsNumber())
hasZ = true;

LuaObject wObj = defValue.GetByName("w");
if(wObj.IsNumber())
hasW = true;

if(hasX && hasY && hasZ && hasW)
{
CL_Vec4f val = CL_Vec4f((float)xObj.ToNumber(),(float)yObj.ToNumber(),(float)zObj.ToNumber(),(float)wObj.ToNumber());
if(entity)
entity->AddProperty(name, val);
else if(player)
player->AddProperty(name, val);
else if(room)
room->AddProperty(name, val);
}
else if(hasX && hasY && hasZ)
{
CL_Vec3f val = CL_Vec3f((float)xObj.ToNumber(),(float)yObj.ToNumber(),(float)zObj.ToNumber());
if(entity)
entity->AddProperty(name, val);
else if(player)
player->AddProperty(name, val);
else if(room)
room->AddProperty(name, val);
}
else if(hasX && hasY)
{
CL_Vec2f val = CL_Vec2f((float)xObj.ToNumber(),(float)yObj.ToNumber());
if(entity)
entity->AddProperty(name, val);
else if(player)
player->AddProperty(name, val);
else if(room)
room->AddProperty(name, val);
}
}
return;
}

LuaPlus::LuaObject WrapPropertyContainer::HasProperty(LuaPlus::LuaObject self, LuaPlus::LuaObject name)
{
if(!self.IsTable())
{
CL_String msg = cl_format("Self was not a table (it's a %1)", self.TypeName());
coreMgr->getLogMgr()->log("WrapPropertyContainer::HasProperty", msg, Log::L_ERROR);
return LuaObject(coreMgr->getScriptMgr()->GetGlobalState()->Get());
}

if(!name.IsString())
{
CL_String msg = cl_format("Name was not a string (it's a %1)", name.TypeName());
coreMgr->getLogMgr()->log("WrapPropertyContainer::HasProperty", msg, Log::L_ERROR);
return LuaObject(coreMgr->getScriptMgr()->GetGlobalState()->Get());
}

bool retVal = false;
if(wEntity)
retVal = wEntity->getEntity()->HasProperty(name.ToString());
else if(wPlayer)
retVal = wPlayer->getPlayer()->HasProperty(name.ToString());
else if(wRoom)
retVal = wRoom->getRoom()->HasProperty(name.ToString());

LuaObject lRetVal;
lRetVal.AssignBoolean(coreMgr->getScriptMgr()->GetGlobalState()->Get(), retVal);
return lRetVal;
}

void WrapPropertyContainer::add(const CL_String &name, Entity::IProperty *prop, WrapIProperty *wProp)
{
if(prop == NULL)
{
CL_String msg = cl_format("Failed to add property %1, because no property was returned", name);
coreMgr->getLogMgr()->log("WrapPropertyContainer::add", msg, Log::L_ERROR);
return;
}
if(wEntity)
wProp = new WrapIProperty(coreMgr, wEntity, this, prop);
else if(wPlayer)
wProp = new WrapIProperty(coreMgr, wPlayer, this, prop);
else if(wRoom)
wProp = new WrapIProperty(coreMgr, wRoom, this, prop);

int fail = wProp->init();
if(fail)
{
delete wProp;
wProp = NULL;

CL_String msg = cl_format("Failed to initialize property wrapper for property %1", prop->GetName());
coreMgr->getLogMgr()->log("WrapPropertyContainer::add", msg, Log::L_ERROR);
return;
}
wProperties.push_back(wProp);
}

void WrapPropertyContainer::OnPropertyAdded(const Engine::Events::EngineEvent &event)
{
if(event.getArgument(1).IsEntity() && wEntity)
{
Engine::Entity::IEntity *entity = event.getArgument(1).ToEntity();
if(entity->getId() != wEntity->getEntity()->getId())
return;
}
else if(event.getArgument(1).IsIPlayer() && wPlayer)
{
Engine::Player::IPlayer *player = event.getArgument(1).ToIPlayer();
if(player->getId() != wPlayer->getPlayer()->getId())
return;
}
else if(event.getArgument(1).IsIRoom() && wRoom)
{
Engine::Room::IRoom *room = event.getArgument(1).ToIRoom();
if(room->getId() != wRoom->getRoom()->getId())
return;
}
else
{
}

Engine::Entity::IProperty *prop = event.getArgument(0).ToProperty();
WrapIProperty *wProp = NULL;
add(prop->GetName(), prop, wProp);
}






WrapIProperty implementation

WrapIProperty.h

#pragma once

#include
#include

namespace Engine
{
namespace Core { class CoreManager; }
namespace Entity { class IProperty; }
namespace Script
{
class WrapIEntity;
class WrapIPlayer;
class WrapIRoom;
class WrapPropertyContainer;

class WrapIProperty
{
public:
WrapIProperty(Core::CoreManager *coreMgr, Script::WrapIEntity *wEntity, WrapPropertyContainer *wPropContainer, Entity::IProperty *property);
WrapIProperty(Core::CoreManager *coreMgr, Script::WrapIPlayer *wPlayer, WrapPropertyContainer *wPropContainer, Entity::IProperty *property);
WrapIProperty(Core::CoreManager *coreMgr, Script::WrapIRoom *wRoom, WrapPropertyContainer *wPropContainer, Entity::IProperty *property);
~WrapIProperty();

int init();

Entity::IProperty *getProp() const { return property; }
LuaPlus::LuaObject getLProp() const { return lProperty; }

private:
LuaPlus::LuaObject Get(LuaPlus::LuaObject self);
void Set(LuaPlus::LuaObject self, LuaPlus::LuaObject value);
void AddListener(LuaPlus::LuaObject self, LuaPlus::LuaObject listener);

void initPropertyListener();

void OnPropertyChangedBool(const bool &oldValue, const bool &newValue);
void OnPropertyChangedVec2f(const CL_Vec2f &oldValue, const CL_Vec2f &newValue);
void OnPropertyChangedVec3f(const CL_Vec3f &oldValue, const CL_Vec3f &newValue);
void OnPropertyChangedVec4f(const CL_Vec4f &oldValue, const CL_Vec4f &newValue);
void OnPropertyChangedString(const CL_String &oldValue, const CL_String &newValue);
void OnPropertyChangedDouble(const double &oldValue, const double &newValue);
void OnPropertyChangedFloat(const float &oldValue, const float &newValue);
void OnPropertyChangedInt(const int &oldValue, const int &newValue);
void OnPropertyChangedUInt(const unsigned int &oldValue, const unsigned int &newValue);

templateclass T>
void OnPropertyChanged(const T &oldValue, const T &newValue);

Core::CoreManager *coreMgr;
Script::WrapPropertyContainer *wPropContainer;
Script::WrapIEntity *wEntity;
Script::WrapIPlayer *wPlayer;
Script::WrapIRoom *wRoom;

CL_String name;

Entity::IProperty *property;
LuaPlus::LuaObject lProperty;

std::vector listeners;

CL_Slot slotPropertyChanged;
};

}
}






WrapIProperty.cpp

#include "WrapIProperty.h"
#include "WrapIEntity.h"
#include "WrapEntityManager.h"
#include "WrapIPlayer.h"
#include "WrapPlayerManager.h"
#include "WrapIRoom.h"
#include "WrapRoomManager.h"
#include "WrapPropertyContainer.h"
#include "ScriptManager.h"
#include
#include
#include
#include
#include
#include
#include
#include

using namespace Engine;
using namespace Script;
using namespace LuaPlus;
using namespace Entity;

WrapIProperty::WrapIProperty(Core::CoreManager *coreMgr, Script::WrapIEntity *wEntity, WrapPropertyContainer *wPropContainer, Entity::IProperty *property)
{
this->coreMgr = coreMgr;
this->wEntity = wEntity;
this->wPlayer = NULL;
this->wRoom = NULL;
this->wPropContainer = wPropContainer;
this->property = property;
name = property->GetName();
}

WrapIProperty::WrapIProperty(Core::CoreManager *coreMgr, Script::WrapIPlayer *wPlayer, WrapPropertyContainer *wPropContainer, Entity::IProperty *property)
{
this->coreMgr = coreMgr;
this->wEntity = NULL;
this->wPlayer = wPlayer;
this->wRoom = NULL;
this->wPropContainer = wPropContainer;
this->property = property;
name = property->GetName();
}

WrapIProperty::WrapIProperty(Core::CoreManager *coreMgr, Script::WrapIRoom *wRoom, WrapPropertyContainer *wPropContainer, Entity::IProperty *property)
{
this->coreMgr = coreMgr;
this->wEntity = NULL;
this->wPlayer = NULL;
this->wRoom = wRoom;
this->wPropContainer = wPropContainer;
this->property = property;
name = property->GetName();
}

WrapIProperty::~WrapIProperty()
{
lProperty.AssignNil(coreMgr->getScriptMgr()->GetGlobalState()->Get());
}

int WrapIProp

Trefall

Trefall

 

Been a while...

So, it's been a while since my last post here... that doesn't mean I haven't been busy though!

This summer, I implemented script support with my component framework. Basically this means that I can now define new components in script. This is very powerful, since I can create new game logic while the game is running. Unfortunately the robust-gate that sits between C++ and Lua does have some overhead, since it has to make sure the data it gets from Lua is correct before pushing it to C++ classes/functions. Optimizations in C++ components, like holding a direct reference to properties it needs, can't really be done either, because I wanted a stateless component system on the Lua side. This allows me to reload scripts without worrying about corrupting state, but you have to ask the C++ side of the component for the data, and this has a bit of overhead... Still, it's a great way to prototype gameplay, if not using it in the final release. At that point, the components could always be moved into C++ components. Then again, I haven't profiled this yet or tested it in a larger game... maybe the overhead isn't so bad that you can't use it for a real game...

Together with the XML system for defining objects, that I presented in my last post, I've ended up with a truly data-driven framework which is great for rapid prototyping of games! I'm working on a couple of example apps and have moved the component system code onto google code, and will post more about this at a later point. I plan on having some very simple examples + a more advanced, functional mud example in this code-base. I'll write up a longer post about this later.

On the project side, we've had a project on visualizing a scalar-field water simulation using marching cubes. I used the xml and script approach to objects and component definitions in this project, and it worked great! The visualization isn't exactly spectacular:


But it works :P

This termine, we're doing another volumetric rendering project, plus a special effects project where we're using ERBS (Expo-Rational B-Splines).

I must admit, I miss the tower defense project! It's so much more fun making a game than optimizing volume rendering algorithms and trying to understand complex math formulas... ugh! Oh well, next semester will be all about the Master Thesis! Hopefully I will be able to find something exciting to work on! ...yeah, I haven't chosen my thesis yet, but I definitely want it to be more related to game programming than pure visualization! We'll see!

Trefall

Trefall

 

Component-based objects and XML loading

The last two days I've been doing some experimentation with the component system and object definition. The component-system lends itself very well to the data-driven model. I've always known this to be true, but I've never actually sat down and coded in support for it, until now.

So, what I first did, was to write two dummy objects in xml:

Tower.xml


Tower

Health



Health
100


MaxHealth
100






RailgunTower.xml


RailgunTower
Tower

Railgun



MaxHealth
150


DamageType
Bullet


BaseDamage
4


Accuracy
10


RateOfFire
80






So, an object definition involves defining the object's name, optionally which object it inherits from, and a list of components and property settings. This is all that's needed to define new objects entierly in xml data.

There's two stages then, to using this. First one needs to load each object definition and save the data in a structure, so that we don't need to access the xml file each time we want to make an instance of it. The second stage is on instanciation, where we make a new instance of the base object, and based on the ID, we add the list of components and property settings already loaded into our data structure.

registerObject

void ObjectFactory::registerObject(const char *fileName)
{
if(creators == 0)
creators = new std::map();

//Check if file has already been loaded, though fileName is registered as
//second value in the map, because accessing the first value is faster, and
//we need that speed in the run-time create() function more than we need that
//speed at register initialization.
std::map::iterator it = creators->begin();
for(; it != creators->end(); ++it)
{
if((*it).second == fileName)
{
return;
}
}

CL_String name = loadObject(fileName).c_str();

std::pair value(name, fileName);
creators->insert(value);
}



loadObject

CL_String ObjectFactory::loadObject(const char *fileName)
{
Common::Resource::IResource *res = manager->getResMgr()->create(cl_format("%1/%2", "Game/Objects", fileName).c_str(), "XML");

CL_String name = res->getString("Object/Name");
CL_String inherit;
try
{
inherit = res->getString("Object/Inherits");
}
catch(const CL_Exception &)
{
inherit = CL_String();
}

//If this object inherits from another, make sure that the
//parent object is already loaded, if not, load it before
//we continue
bool inheritSupported = false;
if(inherit != CL_String())
{
std::map::iterator creatorIt = creators->find(inherit);
if(creatorIt == creators->end())
{
registerObject(cl_format("%1%2", inherit, ".xml").c_str());
creatorIt = creators->find(inherit);
if(creatorIt != creators->end())
{
inheritSupported = true;
}
}
else
{
inheritSupported = true;
}
}

loadComponents(res, name, inherit, inheritSupported);
loadProperties(res, name, inherit, inheritSupported);

if(inheritSupported)
std::cout "Object: " " : public " else
std::cout "Object: "
return name;
}



loadComponents

void ObjectFactory::loadComponents(Common::Resource::IResource *res, const CL_String &name, const CL_String &inherit, bool inheritSupported)
{
std::vector compTypes;

//First add inherited components
if(inheritSupported)
{
std::map>::iterator inheritIt = object_components.find(inherit);
if(inheritIt != object_components.end())
{
std::vector inheritCompTypes = inheritIt->second;
for(unsigned int i = 0; i {
compTypes.push_back(inheritCompTypes);
}
}
}

//Then add unique components
Common::Resource::CL_XMLResource *cl_res = static_cast(res);
std::vector components = cl_res->getDoc().select_nodes("/Object/Components/Component");
for(unsigned int i = 0; i {
CL_DomElement compType = components.to_element();

int alreadyExist = -1;
for(unsigned int j = 0; j {
if(compTypes[j] == compType.get_text())
{
alreadyExist = j;
break;
}
}

if(alreadyExist == -1)
compTypes.push_back(compType.get_text());
}
object_components[name] = compTypes;
}



loadProperties

void ObjectFactory::loadProperties(Common::Resource::IResource *res, const CL_String &name, const CL_String &inherit, bool inheritSupported)
{
std::vector> propTypes;

//First add inherited properties
if(inheritSupported)
{
std::map>>::iterator inheritIt = object_properties.find(inherit);
if(inheritIt != object_properties.end())
{
std::vector> inheritPropTypes = inheritIt->second;
for(unsigned int i = 0; i {
propTypes.push_back(inheritPropTypes);
}
}
}

//Then add unique properties
Common::Resource::CL_XMLResource *cl_res = static_cast(res);
std::vector properties = cl_res->getDoc().select_nodes("/Object/Properties/Property");
for(unsigned int i = 0; i {
CL_DomElement propType = properties.to_element();
CL_String propName = propType.get_child_string("Name");
CL_String propValue = propType.get_child_string("Value");

int alreadyExist = -1;
for(unsigned int j = 0; j {
if(propTypes[j].first == propName)
{
alreadyExist = j;
break;
}
}

//If property already exist, then the last property value will always win
if(alreadyExist == -1)
propTypes.push_back(std::pair(propName, propValue));
else
propTypes[alreadyExist] = std::pair(propName, propValue);
}
object_properties[name] = propTypes;
}



That's the code used to register a new object defined in XML. Notice how we at inheritance make sure that the parten object already is loaded before continuing to extract the data, so that we don't access the XML files more often than requried.

Then for instanciation:

Somewhere we define that we wish to instanciate Tower and RailgunTower objects

Engine::Object::IObject *tower = objMgr->create("Tower");
Engine::Object::IObject *railgun_tower = objMgr->create("RailgunTower");



create

IObject *ObjectFactory::create(const char *name)
{
if(creators == 0)
throw CL_Exception("ObjectCreator map has not been instanciated!");

std::map::iterator creatorIt = creators->find(name);
if(creatorIt == creators->end())
throw CL_Exception(cl_format("%1 %2", "Unable to create object of type", name));

IObject *go = new IObject(manager, manager->getComponentFactory());

std::cout "- Adding Components" int fail = addComponents(go, name);
if(fail)
{
std::cout "- Failed adding Components" delete go;
go = NULL;
return NULL;
}
std::cout "- Finished adding Components"
std::cout "- Setting Properties" fail = addProperties(go, name);
if(fail)
{
std::cout "- Failed setting Properties" delete go;
go = NULL;
return NULL;
}
std::cout "- Finished setting Properties"
return go;
}



addComponents

int ObjectFactory::addComponents(IObject *go, const CL_String &name)
{
std::map>::iterator compIt = object_components.find(name);
if(compIt == object_components.end())
{
return 1;
}

std::vector compTypes = compIt->second;
for(unsigned int i = 0; i {
try
{
go->AddComponent(compTypes);
std::cout "-- " .c_str() }
catch(const CL_Exception &e)
{
std::cout "Failed to add component to Object of type " "\n" }
}

return 0;
}



addProperties

int ObjectFactory::addProperties(IObject *go, const CL_String &name)
{
std::map>>::iterator propIt = object_properties.find(name);
if(propIt == object_properties.end())
{
return 1;
}

std::vector> propTypes = propIt->second;
for(unsigned int i = 0; i {
CL_String name = propTypes.first;
if(!go->HasProperty(name))
continue;

CL_String value = propTypes.second;
Entity::IProperty *propInterface = go->GetIProperty(name);
propInterface->SetFromString(value);
std::cout "-- " ": " }

return 0;
}



And that's it. Here's the Output I get from my test code:

Loading Objects from XML
Object: Tower
Object: RailgunTower : public Tower
Finished loading Objects from XML
Initializing Game Logic
Adding Tower object to Scene
- Adding Components
-- Health
- Finished adding Components
- Setting Properties
-- Health: 100
-- MaxHealth: 100
- Finished setting Properties
Finished adding Tower object to Scene
Adding RailgunTower object to Scene
- Adding Components
-- Health
-- Railgun
- Finished adding Components
- Setting Properties
-- Health: 100
-- MaxHealth: 150
-- DamageType: Bullet
-- BaseDamage: 4
-- Accuracy: 10
-- RateOfFire: 80
- Finished setting Properties
Finished adding RailgunTower object to Scene
Finished initializing Game Logic



Next up is to add in Lua scripting of components, so that new components can be defined entierly in script.

The goal is that I should be able to run my application, and then start to define/edit objects and define/edit component logic, and just hotload it while the application is running. One thing I learned from the Tower Defense project, was that the last couple weeks, I probably spent 75% or more of my time just loading the game! That's about to change!

Trefall

Trefall

 

The day after...

So it's been 2 months of hard work on this beast, and now it's over. Over 2000 views on my journal since I first posted about this project, thanks a lot guys!

In the early retrospect, I think I did a lot of things right on this project, but also an aweful lot of things wrong. I started out with a sound plan, I was going to get in a fully working tower defense game before I started doing anything graphics related.

In the first month of this project, which was titled Part 1, we were to base the game on the OpenGL fixed function, which was a blessing. No need to think about shaders or doing anything fancy graphically, I could consentrate on the core elements of the engine and on the gameplay.

Unfortunately I spent way too much time on the engine :P I already had my component system, which was the core of the engine. I was very comfortable with how to structure objects and communication between them and gameplay logic using that system, which saved me a lot of time on that part. But I also set out with a goal to make everything as generic as I could.

I ended up with a IRenderer class, which had a BasicRenderer and a VBORenderer on top. With this approach I forced upon myself to enclose all VBO handling in that single class, for every single scenegraph node. Fair enough, I had to build data structures around it that the nodes could use to declair their data lists, for then to let the VBORenderer convert this into vbos. It worked pretty good, but when I had to work in new things into the system, like shaders down the line, it required quite extensive rewriting of this class. Also, when a bug would occure in the vbo related data, I would always track the bug down in this class, making it a little bit more difficult to pinpoint which node it came from, etc.

In the end, I think it would've been a good approach had I only started the OpenGL 3.1 implementation from the get-go. I spent well over a week, maybe two, of the second project (Part 2), just to rewrite the engine to OpenGL 3.1 with programmable pipeline!

In the end I was pretty satisfied with Part1, but I started crunching too late. The last day of development, I had to run overtime to get in the final gameplay pieces, going over the deadline by some 30 minutes or so. When I started Part 2, I told myself this should NOT happen again!

The first week of Part 2, I fixed some bugs and shortcomings in the engine I had found during the last couple days of Part 1. Texture Manager wouldn't load the same texture twice, Mesh loader wouldn't load the same mesh twice and the RenderInfo object, that held the data for a node (vertices, normals, texcoords, etc) was also handled this same way on the C++ side, so that nodes of the same type would share the same render data, unless explicitly told to have their own unique copy.

I also started rewriting for 3.1 this week, but that rewrite lasted a lot longer than I had anticipated. One week left until the initial deadline, I got post-processing in via FBOs and shaders. I didn't make an API for the post-processing steps, so it was all hardcoded into the SceneManager class. Of course, I intended to write the post-processing into some form of PostProcess API, but there was never time for it. At least I got time to build such APIs for shaders, uniforms and attributes. I should probably also have done the same thing with VBOs.

I hacked in the GUI. It was just simple fullscreen textures for each element. I detected mouse-hover and button presses by accessing the alpha of the textures in C++ and see if the mouse position indexed an alpha texel that gave a specific value I associated with a button. No "real" GUI handling in other words. This didn't take much time to implement though, and I doubt I would've had time to do it the "right" way. So all in all, I think it was a right decision, though it prevented me from supporting any other viewport size than the 1024x1024 I had been running with.

My biggest mistake for this project was to start on the Cuda integration so late. We got one week extension on the project to get time for Cuda, because I wasn't the only one who started late. I found out that my computer didn't support Cuda 3.0, and that Cuda 2.3 installed one year old drivers that crashed my game. So I had to do the water simulation in GLSL. Unfortunately, I spent probably three days fully coming to this conclusion, and by that time I was on the last day of development (yesterday) and had only few hours left until the deadline. I had to make the choice to let it go in favor of making the total package feel more complete.

I thus got time for doing some last minute details. In the end I got in a lot of gameplay details, even though I failed to get time for moster upgrades. I did get in tower upgrades however.

In the end, I'm quite satisfied. As an individual project done from the ground up in C++, OpenGL and GLSL, it ended up alright. It's fairly playable, though not closed to balanced of course :P It looks ok, though not close to AAA of course. There's a ton of things I'd love to get time for on this game, but with a hard deadline there just wasn't enough of the time...

I will probably continue this project though, just to make it feel more like a pollished game demo than it is right now, but at a much slower pace. Now, summer vacation starts!


Trefall

Trefall

 

Time's up!

So the deadline hit hard, and of course I didn't manage to finish completely, but that's how it should be I guess :P I made a small video of the game that I'll upload to youtube over the night. I'll post it tomorrow.

Code may be downloaded from my repository here:
Critter Defense

It's statically linked, so should be pretty easy to just open up the sln and compile. I've heard from a friend that some of the depends didn't want to work in VS2010, so you might have to fetch the depends on your own in that case. Remember that this was created for educational purposes only. Hopefully I didn't step on anyone's toes in using the assets I used.

Finally the finish line is drawn! Phew!

Youtube movie:
">Critter Defense movie


TODO:
- GLSL Water
- Animated monsters
- Fix particle system



FIXED:
- Fix camera spawnpoint
- Make perimeter only show when tower is selected
- Rotate to target heads/guns
- Simple sine-based GLSL water
- Enable game finished
- Enable score spending restricting tower building
- Allow Grid cells to be marked as occupied (f.ex the monster path)
- Add score spending for upgrades
- Monsters able to destroy base
- Add in new gun graphics (mesh + textures)


THROWN OUT:
- Cuda Water
- Rotate to direction critters
- New path and spawnpoint

Trefall

Trefall

 

1 hour until deadline!

Guybrush just headed off to dinner while I was walking the dogs for a short break. When I got back, he had tried to send me the new gun mesh/textures, but the send had timed out!!! Hopefully he'll get back before the deadline!!!

TODO:
- GLSL Water
- Animated monsters
- Fix particle system
- Add in new gun graphics (mesh + textures)


FIXED:
- Fix camera spawnpoint
- Make perimeter only show when tower is selected
- Rotate to target heads/guns
- Simple sine-based GLSL water
- Enable game finished
- Enable score spending restricting tower building
- Allow Grid cells to be marked as occupied (f.ex the monster path)
- Add score spending for upgrades
- Monsters able to destroy base


THROWN OUT:
- Cuda Water
- Rotate to direction critters
- New path and spawnpoint

Trefall

Trefall

 

2 hours to go!

TODO:
- GLSL Water
- Animated monsters
- Fix particle system
- Monsters able to destroy base
- Add in new gun graphics (mesh + textures)


FIXED:
- Fix camera spawnpoint
- Make perimeter only show when tower is selected
- Rotate to target heads/guns
- Simple sine-based GLSL water
- Enable game finished
- Enable score spending restricting tower building
- Allow Grid cells to be marked as occupied (f.ex the monster path)
- Add score spending for upgrades


THROWN OUT:
- Cuda Water
- Rotate to direction critters
- New path and spawnpoint

Trefall

Trefall

 

3 hours to go!

So, NVidia just wasted a ton of my time. Cuda 2.3 installed old drivers for my graphics card (14.07.2009 driver) that caused my game to crash. Of course, since I was in the middle of Cuda implementation, I was convinced this was due to something I was doing wrong with Cuda, not because the installer for Cuda had given me old drivers :P

So, I finally found this out, erased Cuda from my computer and got the newest video drivers for my gpu installed, and game stopped crashing!

This means I'll have to do the implementation in GLSL, with 3 hours left until I have to have a zipped down and uploaded version of my code + game! If this was a simple sine-wave equation I wouldn't be freaking out right now, but since it's the linear wave equation, that's based on 3 grids (past, present, future) and neightbouring vertices to each "current" vertex, I think this will be hard to get in on time!

Oh well, just got to run with it and see how far I get! For safety, I'll first throw in some of the simplest fixes, so that I KNOW that I'll get them in there on time. I'll then branch out and do the waves in a seperate branch, so that if I fail on completing, I won't have to backstep to get to the working code.


TODO:
- GLSL Water
- Rotate to direction critters
- New path and spawnpoint
- Enable game finished
- Enable score spending restricting tower building
- Add score spending for upgrades
- Animated monsters
- Fix particle system


FIXED:
- Fix camera spawnpoint
- Make perimeter only show when tower is selected
- Rotate to target heads/guns


THROWN OUT:
- Cuda Water

Trefall

Trefall

 

Running down the list

So I'm running down the list of features, which now looks like this:

TODO:
- Cuda Water
- Rotate to direction critters
- New path and spawnpoint
- Enable game finished
- Enable score spending restricting tower building
- Add score spending for upgrades
- Animated monsters
- Fix particle system

FIXED:
- Fix camera spawnpoint
- Make perimeter only show when tower is selected
- Rotate to target heads/guns


Here's a screeny:


Oh yeah, Guybrush finished the base and head of the turrets with new textures! He'll get me the guns later today before deadline!

Trefall

Trefall

 

Cuda, last minute details, deadline

Deadline is today at 23:55 (11:55 PM) GMT+1! And I just found out that the reason I haven't got Cuda 3.0 to work, is because my graphics card only support pre 3.0 versions of Cuda! Well, at least I'll be able to make it work!

Anyway, time is running out, again! Deadline is in approximately 14 hours, which sounds like a lot of time, but I've learned that it's really not!

Here's my TODO list:
- Animated Cuda Water
- Rotate to target, tower heads/guns
- Rotate to direction, critters
- Fix particle system
- New path and spawnpoint in map info
- Enable game finished state
- Enable score spending restricting tower building
- Add score spending for upgrades
- Animate monsters

So, it's not the greatest list of TODO ever, but there's surely enough to do!

See you all on the other side!

Trefall

Trefall

 

Static linking

Today I rewrote the game/engine to static linking. Turned out it was very difficult for anyone but myself to make the game compile and run due to the dll mess :P

So, turned also out that switching to static linking wasn't as simple as I had hoped.

Here's a list I'll make sure to go over next time:
- Configuration
- Character Set
- Runtime Library
- Preprocess definitions
->in all libs that are to be statically linked with one another.

Ok, so I got it up and running on static linking after quite a bit of work!

Afterward I went through and solved some opengl errors that was caught in the stack.

For some reason GL_LINEAR_MIPMAP_LINEAR won't work with my mipmaps, or maybe it's gluBuild2DMipmaps... perhaps it's deprecated for 3.1? Not sure, will have to check up on this in the spec when I get a chance...

Trefall

Trefall

 

GUI, Tower Upgrades, deadlines, etc, etc

Phew, so it's been quite intense the last couple of days!

I started the GUI implementation on monday, and made a request to the guys over at Empires Mod to use some of their HUD graphics. I thought it fit right into the theme I have, and also, the buttons they had for some of their GUI worked very well with what I needed. Krenzo, the artist behind the assets, was cool enough to let me use them, which I truly appreciate! Thank you so much!!!



I worked in mouse-hover awareness and clickable images through the alpha channel and texture lookup on the mouse coords on the C++ side. This means that the whole GUI for each game state was slapped onto an image the size of the game viewport, so not very flexible at all, but hey, it's about getting features in before a deadline, not to be the most impressive and awesome and flexible and and implementation in the world :P If I were to continue this project I would have to fix that though!

Once I had made the gui work like I wanted on the basic menues, I started impementing the HUD for the LevelOne gamestate, and all the logic behind it. Mainly this was regarding towers, score and lives.

I didn't want tower placement to be default all the time. I'd rather want to press the symbol for tower placement, which would then allow me to place a tower. It should sticky, so that I can place several towers, but a right-click with the mouse should un-sticky the button so that I get back to a normal mouse pointer behavior. Next, I should be able to click on a tower, so that the HUD for that specific tower came up. I should then be able to apply ugrades if I had the score to spend to do it. Right clicking should deselect the tower, or selecting a new tower should deselect the old one. A ton of small details that I had to make sure was done the right way.

Turns out, component systems are awesome :D When Damage Upgrade button is pressed, I simply send an event about this to the selected tower object. Then there's the GunView component, that has control over the looks of the gun module on the tower, it will look up on the damage level of the tower, and based on that, it will choose which gun node to attach to the tower object node. The railgun component also receives the event, and does some upgrades on the properties of the weapon, like damage value, fire rate, etc. So it was very simple to add in this kind of upgrade functionality with how the logic was layed out in my engine.

So, for the project, we were also supposed to do water in Cuda. We got an equation to compute waves and all. I didn't get time for this yesterday, but thankfully the deadline was pushed back one week, because I was certainly not the only one in my class that didn't get time for it! So, one more week of coding! This might actually turn more into a game demo than a tech demo after all :D

My friend, Guybrush, who's trees were just used in the quite awesome artistic work over at CryMod forum, was kind enough to build me some actual textures for the towers this upcoming weekend, in time for the deadline :) I'm looking forward to it so much!!!






Trefall

Trefall

Sign in to follow this  
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!