Pulling it all together (again)Now that we've got these tasks, let's head back to our game-specific CApplication object, and try them out. We'll need to decide on priorities for each one - the priorities determine the order in which the tasks are run - and we'll need a simple task of our own to add to the mix, otherwise the app won't really do anything at all (including exit). Here's the order of execution - the 'pipeline:' CGlobalTimer (priority: 10) CInputTask (priority: 20) CSoundTask (priority: 50) COurTestTask (priority: 100) CVideoUpdate (priority: 10000) You can see that the tasks are fairly well spaced out; an app could add at least 9 tasks between the system ones, and the gap between the sound task and the video update is large enough for anything. Before we set up the pipeline itself, here's the test task: class COurTestTask : public ITask { public: bool Start() {return true;} void Update() { glClear(GL_COLOR_BUFFER_BIT); if(CInputTask::mouseDown(SDL_BUTTON_LEFT)) CKernel::GetSingleton().KillAllTasks(); } void Stop(){}; AUTO_SIZE; }; Very simple. It'll just cause all tasks to shutdown when you press the left mouse button, and clears the screen in the meantime. So, now we go back to CApplication::Run, and just before calling CKernel::Execute(), we create tasks and put them into the pipeline: //it's probably a good idea to have all the system tasks together. //The priority system means the tasks can officially be created in //any order (though bear in mind that the CVideoUpdate task must be //added to the kernel before any task using GL functions in its //Start() method, because SDL_VIDEO will not have been initialized). //We'll create the system tasks first and then our game-specific ones //afterwards. It also ensures that when we get to game-specific tasks, //things like FSOUND_Init() have been called. CMMPointer<CGlobalTimer> globalTimer = new CGlobalTimer(); globalTimer->priority=10; //the CMMPointer<ITask> expression here is used to typecast //the pointer from CGlobalTimer* to ITask* CKernel::GetSingleton().AddTask(CMMPointer<ITask>(globalTimer)); CMMPointer<CInputTask> inputTask = new CInputTask(); inputTask->priority=20; CKernel::GetSingleton().AddTask(CMMPointer<ITask>(inputTask)); CMMPointer<CSoundTask> soundTask = new CSoundTask(); soundTask->priority=50; CKernel::GetSingleton().AddTask(CMMPointer<ITask>(soundTask)); videoTask = new CVideoUpdate(); videoTask->priority=10000; CKernel::GetSingleton().AddTask(CMMPointer<ITask>(videoTask)); //game-specific tasks: CMMPointer<COurTestTask> tt=new COurTestTask(); tt->priority=100; CKernel::GetSingleton().AddTask(CMMPointer<ITask>(tt)); Build and test that - you should get a blank screen, which exits when you click the mouse. In the word of many millions of people, 'Yes!' The CodeThe code for this article contains a bit more than what we've seen here - I've written a very (and I mean very) basic implementation of Pong. Move your paddle using the mouse; click (or just lose the game :P ) to exit. See what you can do with it - if you need ideas, I'd suggest getting the ball to come off the paddle at different angles depending on where you hit it, or maybe adding sound. The relevant code is in CPongTask, in main.cpp; I recognize that you can't do much impressive stuff without texturing, which is coming soon. Still, consider it an exercise in pure gameplay - if you can make that Pong game fun, without using any fancy graphics and effects further than shaded polygons, then major kudos; I'll be truly impressed. Maybe it should be a lounge mini-contest. There are also some updates to code from previous articles, based on feedback I've had from people (mostly minor bugfixes). The most important change is probably in the memory manager - previously, I'd overlooked stack objects, which could have lead to *serious* problems: CSomeIMMObjectDerivedClass obj; CMMPointer<CSomeIMMObjectDerivedClass> ptr=&obj; ptr=0; IMMObject::CollectGarbage(); //heap fault - obj has a reference count of zero, but we //shouldn't call delete() because we didn't allocate it using new()! The memory manager has now been updated to handle them. I believe I've commented the code; the best documentation, however, is the discussion that lead to the discovery (and later fixing) of the problem, in the discussion thread for Enginuity part 2. Indeed, all the discussion threads have been rich sources of information and ideas for me (and others too, they tell me :) ). ConclusionWell, that's a basic (and I mean basic) engine finished. You could stop reading now, and just work with what we've built up together; it's a pretty stable base for any project. Maybe you'd care to rewrite it with DirectX or change some other fundamental feature; I hope my articles have given you enough understanding of the way the engine works to allow you to do that. However, as much as you can stop reading, doesn't mean I'm going to stop writing. After all, I haven't met my specification yet - there's still the networking system to be implemented, along with the beginnings of a 3D graphics system... but more importantly, there's no games built on this engine yet! It's no good if games can't actually *use* it. I'd just like to take this opportunity to thank the people who've supported me so far - eldee, my loyal proofreader; Oluseyi, my seems-loyal-enough-but-I-reckon-has-a-hidden-agenda-yeah-buddy-I'm-onto-you proofreader; and all the many people who gave me their comments and feedback, through email, the forums, and IRC. I'll try not to let you down as I progress. :) So, I'm far from finished. Next article I plan to cover textures and fonts, as well as the mysterious Interpolators and Triggers systems. In the meantime, I recommend you visit the 'Discuss this article' link to point out all my mistakes and pick apart my methods; or, of course, you can still email me (rfine at tbrf dot net).
|
|