About this blog
Works In Stagnant Progress
Entries in this blog
So it's been a bit since my last journal entry, at least relative to the pace that I wanted to maintain. I got concerned about design issues again, largely because I was thinking about how to integrate scripting into my design. I had always wanted scripting to be in it, but I didn't try to integrate it immedately. However, after all this work with states, it started to be clear that I wanted states to be as fully scriptable as possible. I felt that this would be an important place where scripting could allow radip development.
So I was thinking about that, but didn't care to put it in the journal in case I changed my mind. I don't want to look like I keep starting over; that's so stereotypical (and so typical of my own past). So I was going to wait until I came to a firm decision and justification. I was learning about various scripting lanuages, since I hadn't really used any much before. Mostly Python and Lua. I decided on Lua, and was working on compiling Lua, Luabind, and getting them to work in my program.
And then politics interrupted. Since around the 14th or 15th, I've been spending a whole lot of time with stuff dealing with Republican/Libertarian Presidential candidate Ron Paul. Not a single ounce of work on programming or game design. I'll let you know when I get back to it, though, for anyone who is interested. I wanted to get into the 6th 4 Elements contest, but it's hard to do when you're distracted.
And yes, I had to plug Ron Paul. Research him. And then vote for him, regardless of your party affiliation (he'd make a far better uniter than Bush), both in the primaries (if you can), and in the general election. Do it!
I did some refactoring today. And created a bug that took far too long to track down, and amounted to one single line that I forgot to add/copy during the refactoring. Typical.
But it seems to be working now. I attempted to make it easier to make states, by creating a couple of base states from which other states could inherit. One is just a basic state, has a base essence state with some things I plan on using in just about all states, as well as handles activating and deactivating a state, such as when a gameplay state goes into the background while the main menu is active. The other base state expands on this, and adds some standard GUI stuff, and is a basic menu state. All full-screen menus can inherit from this.
I also scrapped the single Process() call on states, and went back to two functions, PreProcess() and PostProcess(). It was getting way too messy otherwise. The PreProcess() primarily gets a list of components that need processing. These components are processed by the main loop, and then PostProcess() is called on the active state, which does anything it needs to in response to the processed components, and optionally sets the new state. I did it this time so that I still don't need boost::enable_shared_from_this, however, which is good.
In the way of progress, rather than the semi-progress of refactoring, I got the CEGUI component working pretty well. It followed the previous prototype's pattern pretty much, but I tweaked it here and there, and am happy with the changes.
I got the main menu actually displaying with CEGUI as well. I also added the options menu, not that it provides any options yet. I'm still mostly just copying from the old prototype, which did very little. Perhaps tomorrow (rather than my hoped-for last night) I'll have all the four menus available.
Not so much progress today as I desired. Poo.
I made a modification to my timer component, and added a refinement timer component to replace the functionality. Previously, I had a timer that was based off of the performance counter in Windows, and would only update its time at the beginning of the frame (in its Process() function). This way, every component could read the exact same time for the entire duration of the frame.
What I decided to do instead is make the performance timer component an instantaneous timer, so that calls to get the time would immediately query the performance timer and return the result. I then made a discrete timer component that, as part of its constructor, took a shared_ptr to any other timer, and would only query that timer's time during the Process() function, providing the functionality that my single timer component provided previously. This way, I now have access to instantaneous time or discrete time, and I think the code allows the two functionalities to be separated in an appropriate manner. I can now easily use other timers (based on different system/library calls, or combinations of those calls to smooth out discontinuities), and still get discrete timing without having to rewrite it for every different timing method.
My progress with CEGUI so far has been trivial, so I'll just wait until after I get it working (which may be tonight, but most likely tomorrow) to comment on it.
I just got done running my new prototype for the first time, with no runtime errors (yet). It doesn't do much, but I added a lot of code today.
I added a timer component which currently just gets the time from the Windows performance counter. It does a little bit extra, like remembering a starting point, translating into seconds (as a double), and so forth, but is relatively simple. I also have time iterator component that stores a desired frequency for iterations, and as time passes, it will say how many complete iterations were passed during that time, and the fraction of the way the time is toward the next iteration. This can be used for fixed-rate logic updates, for example. In a game-logic component, I can simply query the time iterator for complete iterations, and simply loop that many times doing the update. (This could be zero times, too, if a frame went by too quickly.) And the fraction toward the next iteration can be used for interpolation purposes.
I also added an Ogre component. I was undecided this morning on how precisely I'd do this, but I decided to keep it real simple. It's primary purpose is to expose a shared_ptr of Ogre::Root, and to do some minimal processing. When this component gets processed, it iterates through all render windows that had started processing from the previous frame, if any, and calls swapBuffers() on them, to present the image to the screen. It then goes through all the render windows again and calls update() on each of them. I separate the update() and swapBuffers() to hopefully get the parallelism benefits available from letting the CPU do other stuff while the GPU does its stuff between update() and swapBuffers(). The Process() function also handles whether I'm using an immediate present mode, v-sync present mode, or a fixed (but unrelated to v-sync) present mode. In the last mode, I use a time iterator to manage it. If the time iterator says 0 complete iterations have occurred, I don't do the update step. If one or more iterations have completed, then I do tell the render windows to update, once. No reason to tell them to update more than once, all one after another in quick succession. Basically, if we missed a few frames, just forget them.
In addition, I provided some helper functions to easily wrap Ogre pointers in a shared_ptr, along with a functor to call the appropriate member function of the appropriate owner object for cleanup. (To destroy an Ogre::RenderWindow, you need to call Ogre::Root::detachRenderTarget(), along with a pointer to an Ogre::Root. And so forth.)
Finally, I added a main menu state. Not that it shows a main menu yet, but it provides a state for the startup state to transition to, once it's done creating everything. Doing this alerted me to some problems with my use of the essence pattern, so I provided a little bit of template codery to help cut down on code that I'd have to write, while not losing any flexibility in design. It now shouldn't be too hard to create a new state, and even easier to write code to switch from one state to another.
I also changed the way my states specify the next state. Previously, at the end of a state's Process() function (which completes before any components are processed that frame), it would return the next state, or itself, if nothing changes. One minor problem is that this required using boost::enable_shared_from_this; no big deal, but I'd rather not have to if I could help it. A more serious problem is that I really don't know if I need to transition to another state until after the components have been processed, especially components like input and the GUI. I had intended allowing a state to inject its own component in the list of components that need processing (since a state gets to specify what goes in the list anyway), and this way, the state could do some processing after all the other components have had their turn. However, this still didn't allow me to specify a new state at that point. I could go back to my previous prototype's design, where I called PreProcess() on a state to get the list of components, I processed all the components, and then I called PostProcess() on the state to let it do its thing and tell me the next state. But I liked having just a single Process() function, so I decided to pass in a "next-state setter" into Process(). It has a function to set what the next state is. If you don't call this function at all, then nothing changes, and the current state remains active. I can then also pass in this object to the internal state component that gets processed at the end, and if it concludes that the state needs to change, it can go right ahead and do that. And since I don't need to do anything if the state doesn't change, then I can usually avoid boost::enable_shared_from_this. I'm happy, more or less.
So this main menu state merely tells a few components to process (timer, graphics time iterator, input, graphics, and internal state component), displays an 800x600 black window created by Ogre, and at the end checks all the keyboard events that occurred this frame. If one of them is a key-down on the escape key, I change set the next state to be an empty shared_ptr, thus terminating the program. Nothing fancy, but it shows that the updated design is working up to this point.
I should be able to get CEGUI working tomorrow pretty easily, assuming I work as well as I did today, and get all the other menu states written. Then I'll once again have interactivity of a semi-meaningful sort.
Today I decided to use a std::pair to return a range of elements to solve my collection problems. I can make it either const or non-const, without affecting the fact that the container itself cannot be modified. I then quickly remembered vaguely that Boost might contain something like this. I shortly thereafter rediscovered Boost.Range, and boost::iterator_range. That solved that problem. After making a few typedefs, and changing a bunch of return value types throughout the code, I think I'm now in a pretty good position. At any pointer in the future, I should be able to easily make a container-agnostic wrapper and use that instead of the current hard-coded std::vector. Due to the way boost::iterator_range is designed, no source code outside of the input component itself should need to change, which is nice.
Basically, it looks something like this:
class i_Input : public i_Component
typedef boost::iterator_range<:vector> >::const_iterator> t_ConstEventRange;
typedef boost::iterator_range<:vector> >::const_iterator> t_EventRange;
virtual t_ConstEventRange Events() const = 0;
virtual t_EventRange Events() = 0;
The Direct Input component that implements the i_Input interface has an implementation of those two functions that looks like this (where mEvents is a std::vector<:shared_ptr> >):
i_Input::t_ConstEventRange c_DirectInput::Events() const
return boost::make_iterator_range(mEvents.begin(), mEvents.end());
return boost::make_iterator_range(mEvents.begin(), mEvents.end());
Using this would then look like the following, regardless of whether I use std::vector or std::list or some custom wrapper (if all my assumptions are correct; I haven't test it yet, though):
i_Input::t_ConstEventRange events = mInput->Events();
for (eventIterator = events.begin(); eventIterator != events.end(); ++eventIterator)
I also added to the Startup State class, so that it actually initializes an input component with a Direct Input instance, and finds the primary keyboard, mouse, and joystick, to be passed on to other states once all the other necessary components are also initialized.
Next up is deciding what I want to do with making an Ogre component. After that will be CEGUI, and I should at that point be able to add a few more states (menu states) and see something again, though it won't look any different than my previous prototype. But I think the code and design should be somewhat cleaner.
I moved my previous Direct Input code over to my new project, making a few ultimately minor changes in the process. I also switched from using an abstracted collection class to just exposing std::vector directly. I do still have a problem with providing both a const and non-const version of the functions that return collections, however. Normally I just return a const reference or a non-const reference, but in this case, the container still needs to remain const in both versions, but the elements need to be non-const in the non-const version of the function. I might want to write some form of abstraction anyway, and will likely be tempted to reintroduce the container agnosticism, supporting any container that provides bidirectional iterators. For now, I just return a non-const reference, and will just depend on my responsibility to not actually modify the container, just elements.
So I have begun my next prototype.
I first of all updated SubVersion and TortoiseSVN, the software I try to motivate myself to use for version control. It's good stuff (especially when I was using it for larger projects at work, back when I had a job), but I tend to be lazy, and for small projects, it takes a bit of effort. Once directory layouts, filenames, and so forth have stabilized, it's perfect, but I think I get tired of having to manage the versioning of all the stuff other than the contents of files. But if I don't use it at this stage of the project, I probably won't use it later either, so I'll deal with it. It probably isn't so bad anyway.
I started with my core game loop, which is actually quite simple, and leaves all the non-loop processing to state and component objects. The main loop maintains a state variable that is the active state. I call a Process() function on this state, and receive back a pointer to the next state (or null to end the loop and thus the program), as well as a list of components to process. I then process all these components in order (making sure I don't process a component twice), and do it all over again, with whatever state is now active.
I will describe my ideas on handling timing within components once I get to that section of code, but I will mention now that despite the simplicity of the loop, I do intend to be able to support fixed-frequency game logic, optional variable-frequency graphics, and the potential to Sleep() or do whatever else in order to not hog the CPU if not needed or not desired.
I decided to use the essence pattern that I just recently read about to handle the creation of states. I had a problem of state constructors requiring too many parameters. Since there is no central repository of components (graphics, physics, input, et cetera), the states themselves needed to keep track of such lists. When one state transitioned to another, it typically needed to past most, sometimes all, of these components on to the next state that it created, and it ended up looking rather messy. Something about that just seemed inappropriate.
In the previous prototype, I merely made a concrete base state class from which all other state inherited. It contained pointers for all the components and other objects that might need to be passed between transitioning states, and a copy constructor that handled this copying. When creating a new state, I simply passed in a reference to the current state, and the new state could then just pass that up to its parent's constructor, and all the objects that were available from the last state would be copied to this state. It cut down on code, and made things look a little cleaner.
However, the problem was that this base state meant that all states included this same set of components, even if they didn't use them at all. It was definitely a temporary hack, and needed to be fixed. I now intend to make all constructors private, provide a public essence class for each state that can be initialized with state-specific objects, and can then create its corresponding state, since it and the state are friends of each other. Additionally, I plan to support an interface on all essence classes that exposes an associative view of the data, mapping string names to objects. Any state constructed with an essence can save that essence object, and then when it creates a new class, can pass a reference of this first essence to the new state's essence. It can look up objects by name, saving a lot of hassle of manually specifying each item.
I suppose I could just build this into the state class itself, but I think the essence pattern will provide a separation of roles that is appropriate, or so I hope.
I'm sort of in the middle of a project currently, but that's no good excuse for starting making some journal entries on it. So this will be my first use of GDNet's journal feature.
In fact, now might be a very good time to start journaling this project. I tend to most often work within a prototype style of development, and I just got done with my first prototype. I think now is a good time to determine what things went well, and what needs improvement.
This for all practical purposes my first real attempt at a game of any significant size. I'm trying very carefully to put a lot of effort into design this time, since I've had bad experiences (at work and on my own) with projects getting very difficult to maintain or expand upon due to a lack of design after a certain point.
Early on in the brainstorming stage of starting this project, I read Jeff Plummer's Masters Thesis, A Flexible and Expandable Architecture for Computer Games. I then attempted to expand on this and produce a design that would meet the criteria that he described. In the end, I discarded this work, because it was starting to look like a highly fancified way of organizing globals, and many of the problems that globals introduce seemed to be leaking in around the edges. Nonetheless, many of the concepts from the paper and from my experiment still remained on my mind.
I decided to cut back on the demands I was placing on myself, and just try to get a prototype up and running, with a few of the ideas in place, without requiring that it be the "perfect" design. One of the elements I wanted to keep was a concept of a list of components, the primary purpose of each is to provide a Process() function that the game engine can call to make it do its work. Each frame, Process() should be called on each component in turn, and they do whatever it is they should do.
In addition, I decided also to use a system of state objects. The game engine is at any one time aware of the active state, and tells that state to do its thing. To support many of the ways that states tend to work in games, I decided to provide a PreProcess() function and a PostProcess() function on states. The PreProcess() function primarily adds a list of components to a master list that should be processed this frame. Within the PreProcess() function, a state call call PreProcess() on states that are considered secondary. (For example, if in the middle of a game the user goes to the main menu, the normal gameplay state might still be semi-active in the background. In this case, the main menu state stores a reference to the gameplay state, and tells the gameplay state to PreProcess() and PostProcess() as necessary. This way, the gameplay state still gets to add to the list of components that need processing, and also doesn't need to get destroyed/recreated as the user enters/exits the main menu.) Within the PostProcess() function, the state determines what things need to change based on the results of the various components' processing. Additionally, it returns a reference to the state that should become active. In most cases, this is a reference to itself, but can be a reference to a newly created state (such as starting a new game, and transitioning from the main menu to the menu where the user chooses the new game properties), or a reference to an internally stored state (such as the choosing "Resume Game" from the main menu).
The final purpose of this prototype was to simply get different components working together. I have done very very little 3D work up until now, and this was to be a 3D game. (I have worked a lot in Direct3D, but it was doing 2D stuff almost exclusively, orthographic projections, axis aligned cameras, and all that.) External libraries I decided to use were Direct Input, Ogre, CEGUI, and Ageia PhysX. I wanted to see all of these working together. I also ended up learning a bit of Blender in order to make models.
Of course, so far I haven't mentioned anything about the game itself, only architecture. The game is a space sim. I wish to eventually have a rather sweet ship builder with real physics being influenced by the design (location of thrusters, distribution of mass, et cetera), along with the necessary pseudo-physics stuff necessary to make it playable (inertial dampeners or what-have-you). Try to add a true sense of ownership to the ship and parts that a player can create during the course of the game. Further, less developed ideas include a randomly generated universe with some form of an actual simulation of governments, economics, wars, and so forth to really provide a decent sense of immersion and believable/meaningful missions, rather than either the fixed storyline or the sets of dull missions that are practically all the same. But I'll worry about that later. I'm trying to do this in stages, so as not to give up like I have on so many projects in the past.
So what I've accomplished with this first prototype is to indeed get all the components working together. My first work was with input. I managed to wrap up Direct Input all nice to my liking, as well as make it fit the component idea that I am trying to create. Calling Process() on the input component will check the buffered events and add those to a list that can later be examined by other parts of the program, as well as update the unbuffered state of any device that is currently active. I added the ability to set virtual ranges for Joystick data, as well as deadzones, and intend to eventually also add modifiers to buttons (any type of button) to allow reading the button in an auto-repeat mode as well as the normal raw mode, whichever is appropriate at the moment.
I then moved on to graphics. I went straight for getting both Ogre and CEGUI working at once, not bothering to first get Ogre working alone. That turned out well enough, and I then began making states to represent the various menus. For now, each menu only has buttons, which is definitely incomplete. The options menu, for example, has Okay, Apply, and Cancel buttons, but no actual options. It simply returns to the main menu if you choose Okay or Cancel, and does nothing if you choose Apply. Regardless, it felt good being able to navigate through the menus, and get to the actual gameplay state (which at this point was merely a black screen that waited for the Escape key to be pressed, at which point it went back to the main menu).
So next I struggled through the process of getting an actual 3D model displayed in the gameplay state. This was complicated, mostly due to the process of getting familiar with a new library, though my limited experience with 3D caused some problems as well. I have a lot of book-ish knowledge on 3D applications, but first-hand experience does wonders for ones ability to know which settings or function calls may have been overlooked or used inappropriately. I finally got a poorly modeled ship actually drawn. Then a skybox (after I realized that my far clip distance was too short to allow the skybox to be visible, arg!) and finally some ogre heads, so I would be able to see how I was moving, and not just how I was turning.
Then I finally began on physics. I first of all managed to model my ship as a sphere in physical terms, and set gravity to the standard -9.8y. Excitingly, this worked just as I wanted. Then I proceeded to tie joystick input to forces applied at different locations of the ship, and after many complications with confused orientations and such, got standard Newtonian physics to work. (Navigating in a space ship with such physics is a bitch!) One of my biggest problems with the orientations was that Ogre expects positions and optionally orientations, relative to parent nodes, whereas PhysX provides positions and orientations according to absolute space. Figuring out how to make those two work together properly (while also trying to compensate for Blender's different axis orientation, without writing an exporter or something similar myself) caused some problems, but I finally got it working. The last step was to provide some sense of better control over the ship. I turned on a bit of angular dampening to stop the crazy rolling. I also applied two forces of equal magnitude to the center of gravity of the ship: The first force was in the exact opposite direction of the ship's velocity, and the second force was in the direction that the ship was facing. The magnitude of the force was proportional to the magnitude of the velocity. This causes the ship to generally want to go in the direction in which the ship is facing, cancelling out velocity when appropriate, but doing nothing if the ship is facing the exact same direction in which it is moving.
So that's what I have now, and I think it is a good time to move on, having learned quite a bit already. There are some things I definitely woudl like to rework (there were some things that I purposely didn't design very thoroughly, since I simply wanted to get something working, and didn't have the appropriate experience to know how to design it well anyway), and some things that I'm quite pleased with.
First, things I'm pleased with. There have been some discussions I've seen saying that using state objects aren't really necessary or very useful, but I'm liking them quite a bit thus far. They haven't gotten in my way any, and have allowed me to very easily work on one part at a time. They seem to be a very effective way of separating responsibilities in an appropriate manner. Maybe that'll change as things get larger and more complicated, though. We'll see.
I also like my wrapping up of Direct Input. I should be able to quite easily swap out Direct Input for some other input library if need be, and everything is objectified in a way that I find useful and intuitive. However, in the process of trying to encapsulate and hide implementation details, I've found that providing access to containers is a little annoying. I don't really want the outside to know if my list of events is a vector or a deque or a list or whatnot, for example. So I wrote a container wrapper that simply provides a begin(), end(), and bidirectional iterators, and uses some virtual functions and templates internally to hide all the details of the specific container used. But in the end, I think this has complicated things more than anything, and I might decide to simply make publicly visible the container that I choose to use for each collection of items.
Another problem with encapsulation was that I had initially organized all my files to assume that graphics, GUI, and physics would all also have interfaces that were library-agnostic. In the end, though, in order to get the prototype doing stuff, rather than mucking about with designing good interfaces and wrapping up the libraries to fit the interfaces, I decided to just expose the library objects directly. I will probably end up ditching the interface entirely, other than to wrap the library up so that it works with the component model's Process() concept. If experience leads to an idea of a good library-agnostic design for some particular component, I might follow that lead later on, but not initially. The input is already designed pretty well, but the others can simply maintain the design decided on by the respective library writers.
This next time through I am also going to need to write some model converters. I would like the z-up axis system in Blender to be converted to a y-up system in the process. Additionally, I'd like to be able to model the collision objects, and physics objects in Blender, along with the model, and then convert them from graphical data (associated with materials and such) to a purely physical form. Both objects need to be convex, for reducing computation and playing nice with PhysX. The collision objects need to be as simple as possible, since they're relevant at runtime, but the physics objects need to modeled so that they more accurately represent the ship, since these objects will end up being reduced down into precomputed mass, center of mass, and inertia tensor. They also need to be arranged so that I can effectively assign physical materials to them in a way that makes sense (making certain parts of a ship heavier than others, stronger, and so forth). I also need to create a system of attaching ship components to each other, and attaching functionality to some of them (engines are things that can apply forces, guns fire stuff, et cetera). Right now I merely hard-coded the locations and strengths of forces.
By the end of this next stage of prototyping, I hope to be able to choose a scenario (flying around, shooting stationary objects, shooting moving objects, facing hostile ships that fire back, and so on), choose a predefined ship that is made from components that have their graphical and physical properties specified in files, and then play through those scenarios. There won't be any winning conditions, this is still just a testbed for software architecture, as well as the the gameplay style of using different ships that have real physics. I will also want to include at least a few options in the options menu (resolution, fullscreen/windowed, perhaps a few others). At this stage, if all is going well, then I will probably begin to more seriously think about the overall gameplay, not just the flying around shooting part.
I'll provide some screenshots of my current progress later, as basic and boring as they may be, just for historical purposes or something. [smile]