00001 // /////////////////////////////////////////////////////////////////////////// 00002 // lesson05_main.cpp by Victor Dods, created 2006/08/06 00003 // /////////////////////////////////////////////////////////////////////////// 00004 // Unless a different license was explicitly granted in writing by the 00005 // copyright holder (Victor Dods), this software is freely distributable under 00006 // the terms of the GNU General Public License, version 2. Any works deriving 00007 // from this work must also be released under the GNU GPL. See the included 00008 // file LICENSE for details. 00009 // /////////////////////////////////////////////////////////////////////////// 00010 00011 00012 // /////////////////////////////////////////////////////////////////////////// 00013 // Lesson 05 - Dynamic Objects For Fun And Profit 00014 // /////////////////////////////////////////////////////////////////////////// 00015 00016 /* @endcode 00019 This lesson will show you how to make dynamic objects (game objects which can 00020 move and interact). This will be done by subclassing @ref Xrb::Engine2::Entity 00021 and providing a game-specific implementation. We will also have to subclass 00022 @ref Xrb::Engine2::World to provide the code to control its subordinate Entity 00023 objects. 00024 00025 <ul> 00026 <li>@ref lesson05_main.cpp "This lesson's source code"</li> 00027 <li>@ref lessons "Main lesson index"</li> 00028 </ul> 00029 00030 In this lesson we will be creating an animated gravitational planet/moon 00031 system. This will require us to subclass the pure virtual Engine2::Entity 00032 class with our own application-specific implementation, and to write code to 00033 update the positions of the game objects to simulate gravitation. The latter 00034 will be in a custom subclass of Engine2::World. 00035 00036 Below, in the documentation for AwesomeWorld::HandleFrame, there is an 00037 explanation of Euler Integration, which is absolutely central to game 00038 programming. Make sure not to skip it. 00039 00040 First, more detail on Object and Entity and their relationship. 00041 00042 As shown in the @ref lesson04 "previous lesson", Object is the physical, 00043 visible game object which has as its properties: position, scale and angle. 00044 By itself, it can't move or be interacted with. Its subclasses implement its 00045 Draw method -- as of Sept 2006, Sprite and Compound. Alone, this is referred 00046 to as a "static object" (as opposed to a "dynamic object"; see 00047 Xrb::Engine2::Object::IsDynamicObject). 00048 00049 Entity (Xrb::Engine2::Entity) can be thought of as "imbuing a soul" upon 00050 Object. Entity is a pure virtual class intended to be subclassed to add all 00051 the application-specific properties and code necessary for an interactive 00052 game object. An Object and Entity are both instantiated, and then the Entity 00053 instance is attached to the Object instance, and which point, the Object 00054 instance is a "dynamic object". The Entity baseclass doesn't provide any 00055 substance besides the bare minimum framework -- the actual form of the "soul" 00056 is completely up to the application-specific implementation. 00057 00058 In this particular lesson, the application-specific properties we will add 00059 to our custom subclass of Entity are mass, velocity and accumulated force. 00060 These values will be used by the gravitation simulation code to update the 00061 position of each respective dynamic object. 00062 00063 Our custom subclass of World will perform two functions: a one-time generation 00064 of the game world (a large planet and many orbiting moons), and once-per-frame 00065 calculations for the simulation of the gravitational system using the 00066 properties of each dynamic object in the World. 00067 00068 <strong>Procedural Overview</strong> -- Items in bold are additions/changes to the previous lesson. 00069 00070 <ul> 00071 <li>Global declarations</li> 00072 <ul> 00073 <li><strong>Declare subclass of Engine2::Entity specific to this app.</strong></li> 00074 <li><strong>Declare subclass of Engine2::World specific to this app.</strong></li> 00075 <ul> 00076 <li><strong>The constructor will populate the game world.</strong></li> 00077 <li><strong>Override HandleFrame to do once-per-frame gravitational 00078 simulation calculations and to update the velocities and positions 00079 of the dynamic objects.</strong></li> 00080 </ul> 00081 <li>Declare subclass of Engine2::WorldView specific to this app.</li> 00082 </ul> 00083 <li>Main function</li> 00084 <ul> 00085 <li>Initialize the Pal and game engine singletons. Create the Screen object.</li> 00086 <li>Execute game-specific code.</li> 00087 <ul> 00088 <li>Create application-specific objects and GUI elements, and make necessary signals.</li> 00089 <ul> 00090 <li>Create the game world via CreateAndPopulateWorld.</li> 00091 <li>Create the WorldViewWidget and set it as screen's main widget.</li> 00092 <li>Create the game-specific WorldView.</li> 00093 <li>Attach the WorldView to the WorldViewWidget.</li> 00094 <li>Attach the WorldView to the World.</li> 00095 </ul> 00096 <li>Run the game loop</li> 00097 <ul> 00098 <li>Calculate the Singleton::Pal().Sleep duration necessary to achieve the desired framerate.</li> 00099 <li>Handle events (user and system-generated).</li> 00100 <li>Perform off-screen processing, including game world processing.</li> 00101 <li>Draw the Screen object's entire widget hierarchy.</li> 00102 </ul> 00103 <li>Destroy application-specific objects.</li> 00104 <ul> 00105 <li>Destroy WorldViewWidget object, which will destroy WorldView object.</li> 00106 <li>Destroy World object, which will destroy all its ObjectLayers, Objects <strong>and Entities.</strong></li> 00107 </ul> 00108 </ul> 00109 <li>Delete the Screen object. Shutdown the Pal and game engine singletons.</li> 00110 </ul> 00111 </ul> 00112 00113 Comments explaining previously covered material will be made more terse or 00114 deleted entirely in each successive lesson. If something is not explained 00115 well enough, it was probably already explained in 00116 @ref lessons "previous lessons". 00117 00118 <strong>Code Diving!</strong> 00119 00120 @code */ 00121 #include "xrb.hpp" // Must be included in every source/header file. 00122 00123 #include "xrb_engine2_objectlayer.hpp" // For use of the Engine2::ObjectLayer class. 00124 #include "xrb_engine2_sprite.hpp" // For use of the Engine2::Sprite class. 00125 #include "xrb_engine2_world.hpp" // For use of the Engine2::World class. 00126 #include "xrb_engine2_worldview.hpp" // For use of the Engine2::WorldView class. 00127 #include "xrb_engine2_worldviewwidget.hpp" // For use of the Engine2::WorldViewWidget class. 00128 #include "xrb_event.hpp" // For use of the Event classes. 00129 #include "xrb_eventqueue.hpp" // For use of the EventQueue class. 00130 #include "xrb_inputstate.hpp" // For use of the InputState class (via Singleton::). 00131 #include "xrb_input_events.hpp" // For use of the EventMouseWheel class. 00132 #include "xrb_math.hpp" // For use of the functions in the Math namespace. 00133 #include "xrb_screen.hpp" // For use of the necessary Screen widget class. 00134 #include "xrb_sdlpal.hpp" // For use of the SDLPal platform abstraction layer. 00135 00136 using namespace Xrb; // To avoid having to use Xrb:: everywhere. 00137 00138 /* @endcode 00139 There really isn't much to this subclass. All we're actually doing is adding 00140 three properties and various accessors/modifiers for them. The m_mass value is 00141 a scalar value representing the first moment of inertia as defined by Newtonian 00142 mechanics (e.g. 28 kilograms); the higher this value is, the heavier the object 00143 is, and the more gravity it applies to other objects. The m_velocity value is 00144 the vector representing the change in position per second (i.e. the derivative 00145 of the position vector). Finally, the m_force vector value is used by 00146 AwesomeWorld during each game loop to calculate the total accumulated force on 00147 each object; this value isn't actually a property of a body in Newtonian 00148 mechanics -- it's just a value used by our simulation code. 00149 @code */ 00150 class AwesomeEntity : public Engine2::Entity 00151 { 00152 public: 00153 00154 // The constructor simply initializes the properties to sane values. Mass 00155 // must be greater than zero to avoid division by zero in some calculations. 00156 AwesomeEntity () 00157 : 00158 Engine2::Entity(), 00159 m_mass(1.0f), 00160 m_velocity(FloatVector2::ms_zero), 00161 m_force(FloatVector2::ms_zero) 00162 { } 00163 00164 // Trivial accessors for the properties of AwesomeEntity. 00165 inline Float Mass () const { return m_mass; } 00166 inline FloatVector2 const &Velocity () const { return m_velocity; } 00167 inline FloatVector2 const &Force () const { return m_force; } 00168 00169 // Modifiers for the properties of AwesomeEntity. The ASSERT_NAN_SANITY_CHECK 00170 // macro is used in various places in Engine2 code to quickly catch common 00171 // bugs in game code which result in NaN values being fed to the snake. 00172 inline void SetMass (Float mass) 00173 { 00174 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(mass)); 00175 ASSERT1(mass > 0.0f); 00176 m_mass = mass; 00177 } 00178 inline void SetVelocity (FloatVector2 const &velocity) 00179 { 00180 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(velocity[Dim::X])); 00181 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(velocity[Dim::Y])); 00182 m_velocity = velocity; 00183 } 00184 00185 // Procedures which will be used by the gravity calculations in AwesomeWorld. 00186 void IncrementVelocity (FloatVector2 const &velocity_delta) 00187 { 00188 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(velocity_delta[Dim::X])); 00189 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(velocity_delta[Dim::Y])); 00190 m_velocity += velocity_delta; 00191 } 00192 void IncrementForce (FloatVector2 const &force_delta) 00193 { 00194 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(force_delta[Dim::X])); 00195 ASSERT_NAN_SANITY_CHECK(Math::IsFinite(force_delta[Dim::Y])); 00196 m_force += force_delta; 00197 } 00198 void ResetForce () { m_force = FloatVector2::ms_zero; } 00199 00200 // These are pure virtual methods which have to be implemented in Entity 00201 // subclasses. Write can be left blank for our purposes. 00202 virtual void Write (Serializer &serializer) const { } 00203 // This method is called on entities which have hit the side of the 00204 // ObjectLayer. For now we'll just stop the entity's motion along the 00205 // indicated dimension(s). This method will only be called on entities 00206 // in a non-wrapped ObjectLayer. 00207 virtual void HandleObjectLayerContainment (bool component_x, bool component_y) 00208 { 00209 if (component_x) 00210 m_velocity[Dim::X] = 0.0f; 00211 if (component_y) 00212 m_velocity[Dim::Y] = 0.0f; 00213 } 00214 00215 protected: 00216 00217 // These are pure virtual methods which have to be implemented in Entity 00218 // subclasses. For our purposes, they can be left empty. 00219 virtual void HandleNewOwnerObject () { } 00220 virtual void CloneProperties (Entity const *entity) { } 00221 00222 private: 00223 00224 Float m_mass; 00225 FloatVector2 m_velocity; 00226 FloatVector2 m_force; 00227 }; // end of class AwesomeEntity 00228 00229 /* @endcode 00230 Our custom subclass of World will do the two things mentioned above: create 00231 the game world and populate it with objects, and handle per-frame gravitational 00232 simulation calculations. 00233 @code */ 00234 class AwesomeWorld : public Engine2::World 00235 { 00236 public: 00237 00238 /* @endcode 00239 The constructor will create the single ObjectLayer to contain all the 00240 objects which will be created next. The dynamic objects which will 00241 populate the game world will each be a Sprite instance and AwesomeEntity 00242 instance pair. A large, heavy "planet" and many small, light "moons" will 00243 be created. The moons' positions and velocities will be initialized to 00244 put them into circular orbit of the larger planet using Kepler's 3rd law. 00245 The scalar member value m_gravitational_constant is the symbol G in the 00246 Newtonian equation for gravitational force between two bodies. 00247 @code */ 00248 AwesomeWorld () 00249 : 00250 Engine2::World(NULL), 00251 m_gravitational_constant(60.0f) 00252 { 00253 // At this point, the world is empty. 00254 00255 // Decide some size for the ObjectLayer (the hardcoded scale factors 00256 // and translations below are loosely dependent on this value). 00257 static Float const s_object_layer_side_length = 2000.0f; 00258 // Create the ObjectLayer which will hold our game objects. 00259 Engine2::ObjectLayer *object_layer = 00260 Engine2::ObjectLayer::Create( 00261 this, // owner world 00262 false, // not wrapped 00263 s_object_layer_side_length, // side length 00264 6, // visibility quad tree depth 00265 0.0f); // z depth 00266 AddObjectLayer(object_layer); 00267 SetMainObjectLayer(object_layer); 00268 00269 Engine2::Sprite *sprite; 00270 AwesomeEntity *planet; 00271 00272 // Create a large, heavy planet. 00273 sprite = Engine2::Sprite::Create("resources/demi3_small.png"); 00274 planet = new AwesomeEntity(); 00275 sprite->SetEntity(planet); 00276 planet->SetTranslation(FloatVector2::ms_zero); 00277 planet->SetScaleFactor(250.0f); 00278 planet->SetMass(100.0f * planet->ScaleFactor() * planet->ScaleFactor()); 00279 AddDynamicObject(sprite, object_layer); 00280 00281 // Create a bunch of small, light moons. 00282 static Uint32 const s_moon_count = 50; 00283 for (Uint32 i = 0; i < s_moon_count; ++i) 00284 { 00285 sprite = Engine2::Sprite::Create("resources/shade3_small.png"); 00286 AwesomeEntity *moon = new AwesomeEntity(); 00287 sprite->SetEntity(moon); 00288 sprite->SetZDepth(-0.1f); 00289 moon->SetScaleFactor(Math::RandomFloat(10.0f, 20.0f)); 00290 moon->SetMass(0.01f * moon->ScaleFactor() * moon->ScaleFactor()); 00291 // Pick a distance to orbit the moon at. 00292 Float minimum_orbital_radius = planet->ScaleFactor() + moon->ScaleFactor() + 100.0f; 00293 Float orbital_radius = Math::RandomFloat(minimum_orbital_radius, minimum_orbital_radius + 400.0f); 00294 ASSERT1(orbital_radius > 0.0f); 00295 // The moon will be placed randomly using polar coordinates. 00296 // We've calculated the R value, now we need theta. 00297 Float angle = Math::RandomFloat(0.0f, 360.0f); 00298 // Initialize the moon's position 00299 moon->SetTranslation(orbital_radius * Math::UnitVector(angle)); 00300 // In order to figure out what speed to use to set the moon into 00301 // circular orbit, we need to know the magnitude of the gravitational 00302 // force between it and the large planet. 00303 Float gravitational_force = CalculateGravitationalForce(planet, moon); 00304 /* @endcode 00305 We will solve for the necessary orbital speed by using Kepler's Third Law; let \f$v\f$ be scalar orbital velocity (speed), \f$r\f$ be the distance between the centers of the two bodies, and \f$a\f$ be scalar acceleration. 00306 \f[ \frac{v^2}{r} = a \f] 00307 We must also replace \f$a\f$ by known quantities. This can be done using Newton's Second Law; let \f$a\f$ be scalar acceleration, \f$f\f$ be scalar force (magnitude of the force vector), and \f$m\f$ be mass (of the orbiting body, so in this case, the small moon). 00308 \f[ a = \frac{f}{m} \f] 00309 Composing the two, we get 00310 \f[ \frac{v^2}{r} = \frac{f}{m} \f] 00311 Solve for \f$v\f$: 00312 \f[ v^2 = \frac{fr}{m} \f] 00313 \f[ v = \sqrt{\frac{fr}{m}} \f] 00314 @code */ 00315 Float orbital_speed = Math::Sqrt(gravitational_force * orbital_radius / moon->Mass()); 00316 // The velocity must be perpendicular to the vector joining the 00317 // centers of the planet and the moon. 00318 moon->SetVelocity(orbital_speed * Math::UnitVector(angle+90.0f)); 00319 // Finally add it to the world. 00320 AddDynamicObject(sprite, object_layer); 00321 } 00322 } 00323 00324 protected: 00325 00326 /* @endcode 00327 In our override of HandleFrame, we put the per-frame calculations necessary 00328 for the gravitational simulation. First, we iterate through all distinct 00329 pairs of different entities and apply gravitational forces between them. 00330 Then update the velocities (apply the forces accumulated during this frame), 00331 and finally update the positions (based on the corresponding velocity values). 00332 @code */ 00333 virtual void HandleFrame () 00334 { 00335 Uint32 entity_capacity = EntityCapacity(); 00336 00337 // Apply gravitational forces between each distinct pair of entities. 00338 for (Uint32 i = 0; i < entity_capacity; ++i) 00339 { 00340 AwesomeEntity *entity0 = dynamic_cast<AwesomeEntity *>(GetEntity(i)); 00341 if (entity0 == NULL) 00342 continue; 00343 00344 for (Uint32 j = i+1; j < entity_capacity; ++j) 00345 { 00346 AwesomeEntity *entity1 = dynamic_cast<AwesomeEntity *>(GetEntity(j)); 00347 if (entity1 == NULL) 00348 continue; 00349 00350 ASSERT1(entity0 != entity1); 00351 00352 // Use the helper function to calculate the gravitational force 00353 // between the two entities. 00354 Float gravitational_force = CalculateGravitationalForce(entity0, entity1); 00355 ASSERT1(gravitational_force >= 0.0f); 00356 // If the force is zero (which can happen when the entities' 00357 // centers coincide and the gravitation equation would divide 00358 // by zero), skip this entity pair. 00359 if (gravitational_force == 0.0f) 00360 continue; 00361 00362 // The gravitational force is from entity0 to entity1 00363 FloatVector2 force_direction = (entity1->Translation() - entity0->Translation()).Normalization(); 00364 // Apply equal and opposite gravitational force to both entities. 00365 entity0->IncrementForce( gravitational_force * force_direction); 00366 entity1->IncrementForce(-gravitational_force * force_direction); 00367 } 00368 } 00369 00370 /* @endcode 00371 The calculations for velocity based on acceleration and for position based 00372 on velocity are using what's known as Euler Integration (see 00373 http://en.wikipedia.org/wiki/Euler_integration and 00374 http://en.wikipedia.org/wiki/Numerical_ordinary_differential_equations for 00375 technical descriptions). The general idea is that you have a frequently 00376 changing value (e.g. velocity) which will be referred to as the principal, 00377 and you have the rate at which the principal changes (e.g. acceleration) 00378 which will be referred to as the derivative. Euler Integration is a method 00379 for updating the principal based on the derivative, using the the time-step 00380 value (e.g. <tt>FrameDT()</tt>). In the following, the time-step is 00381 given by <tt>dt</tt>. 00382 00383 <tt>principal += derivative * dt</tt> 00384 00385 In this lesson, there are two integrations to perform: updating velocity 00386 (the principal) using acceleration (the derivative), and updating position 00387 (the principal) using velocity (the derivative). It should be noted that 00388 both of these values are vector-valued, and that the above and 00389 above-referenced descriptions of Euler Integration appear to be 00390 scalar-valued. The derivative of a vector value is defined as a 00391 component-wise derivative (the vector containing derivative of the 00392 X-component and the derivative of the Y-component). The time delta is 00393 always scalar. 00394 00395 Believe it or not, by doing this, you're actually computing numeric 00396 solutions for differential equations. Euler Integration is a very simple 00397 method for numeric integration, but is relatively inaccurate. Fortunately 00398 for the purposes of games, it works just fine. For a more accurate method, 00399 see http://en.wikipedia.org/wiki/Runge-Kutta_methods -- it is much more 00400 complicated and difficult to implement, but if accuracy is a consideration 00401 (e.g. in scientific computation) then it's worth it. 00402 @code */ 00403 // Update the velocity vector of each entity with the accumulated force 00404 // and update the position vector using the newly calculated velocity. 00405 for (Uint32 i = 0; i < entity_capacity; ++i) 00406 { 00407 AwesomeEntity *entity = dynamic_cast<AwesomeEntity *>(GetEntity(i)); 00408 if (entity == NULL) 00409 continue; 00410 00411 ASSERT1(entity->Mass() > 0.0f); 00412 // Use Euler Integration to calculate the new velocity, based on 00413 // the accumulated force during this frame. 00414 entity->IncrementVelocity(entity->Force() / entity->Mass() * FrameDT()); 00415 // Reset the accumulated force for next frame. 00416 entity->ResetForce(); 00417 // Use Euler Integration again to calculate the new position, 00418 // based on the entity's velocity. 00419 entity->Translate(entity->Velocity() * FrameDT()); 00420 } 00421 00422 /* @endcode 00423 You must always call the superclass' HandleFrame method, as it performs 00424 vital processing -- specifically of the EventQueue and PhysicsHandler, 00425 which will be covered in a later lesson. 00426 @code */ 00427 Engine2::World::HandleFrame(); 00428 } 00429 00430 private: 00431 00432 /* @endcode 00433 The following function is just a helper, useful in condensing a cluttery 00434 equation down into a nice li'l old self-documenting function call. The 00435 returned value is the computed value of Newton's Law Of Universal 00436 Gravitation: 00437 00438 \f[ F = G \frac{m_0 m_1}{r^2} \f] 00439 00440 See http://en.wikipedia.org/wiki/Gravitation for more info. In order to 00441 prevent a divide by zero, if the entities are too close (overlapping), the 00442 return value is zero. 00443 @code */ 00444 Float CalculateGravitationalForce (AwesomeEntity *entity0, AwesomeEntity *entity1) const 00445 { 00446 ASSERT1(entity0 != NULL && entity1 != NULL); 00447 FloatVector2 entity_offset(entity1->Translation() - entity0->Translation()); 00448 Float distance = entity_offset.Length(); 00449 // If they're touching, don't apply gravitational force (this 00450 // is to avoid a divide by zero if their positions coincide). 00451 if (distance < entity0->ScaleFactor() + entity1->ScaleFactor()) 00452 return 0.0f; 00453 else 00454 return 00455 m_gravitational_constant * 00456 entity0->Mass() * entity1->Mass() / 00457 (distance * distance); 00458 } 00459 00460 Float m_gravitational_constant; 00461 }; // end of class AwesomeWorld 00462 00463 /* @endcode 00464 Here is our totally awesome customized WorldView which is the same as the one 00465 explained in the @ref lesson04 "previous lesson". 00466 @code */ 00467 class AwesomeWorldView : public Engine2::WorldView 00468 { 00469 public: 00470 00471 // Trivial constructor which is just a frontend for WorldView's constructor. 00472 AwesomeWorldView (Engine2::WorldViewWidget *parent_world_view_widget) 00473 : 00474 Engine2::WorldView(parent_world_view_widget) 00475 { } 00476 00477 // Called by WorldViewWidget with all mouse wheel events for this WorldView 00478 virtual bool ProcessMouseWheelEvent (EventMouseWheel const *e) 00479 { 00480 // Rotate the view on ALT+mouse-wheel-up/down. 00481 if (e->IsEitherAltKeyPressed()) 00482 RotateView((e->ButtonCode() == Key::MOUSEWHEELUP) ? -15.0f : 15.0f); 00483 // Otherwise, we will zoom the view on mouse-wheel-up/down. 00484 else 00485 ZoomView((e->ButtonCode() == Key::MOUSEWHEELUP) ? 1.2f : 1.0f / 1.2f); 00486 // Indicates that the event was used by this method. 00487 return true; 00488 } 00489 // This method is the mouse motion analog of ProcessMouseWheelEvent. 00490 virtual bool ProcessMouseMotionEvent (EventMouseMotion const *e) 00491 { 00492 // Only do stuff if the left mouse button was pressed for this event. 00493 if (e->IsLeftMouseButtonPressed()) 00494 { 00495 // Move the view by a delta which is calculated by transforming 00496 // the screen coordinates of the event to world coordinates as used 00497 // by WorldView. 00498 MoveView( 00499 ParallaxedScreenToWorld() * FloatVector2::ms_zero - 00500 ParallaxedScreenToWorld() * e->Delta().StaticCast<Float>()); 00501 // Indicates that the event was used by this method. 00502 return true; 00503 } 00504 else 00505 // Event not used. 00506 return false; 00507 } 00508 }; // end of class AwesomeWorldView 00509 00510 void CleanUp () 00511 { 00512 fprintf(stderr, "CleanUp();\n"); 00513 // Shutdown the Pal and singletons. 00514 Singleton::Pal().Shutdown(); 00515 Singleton::Shutdown(); 00516 } 00517 00518 int main (int argc, char **argv) 00519 { 00520 fprintf(stderr, "main();\n"); 00521 00522 // Initialize engine singletons. 00523 Singleton::Initialize(SDLPal::Create, "none"); 00524 // Initialize the Pal. 00525 if (Singleton::Pal().Initialize() != Pal::SUCCESS) 00526 return 1; 00527 // Set the window caption. 00528 Singleton::Pal().SetWindowCaption("XuqRijBuh Lesson 05"); 00529 // Create Screen object and initialize given video mode. 00530 Screen *screen = Screen::Create(800, 600, 32, false); 00531 // If the Screen failed to initialize, print an error message and quit. 00532 if (screen == NULL) 00533 { 00534 CleanUp(); 00535 return 2; 00536 } 00537 00538 // Here is where the application-specific code begins. 00539 { 00540 // Create our sweet game world via a call to CreateAndPopulateWorld. 00541 AwesomeWorld *world = new AwesomeWorld(); 00542 // Create the WorldViewWidget as a child of screen. This is what will 00543 // contain an instance of WorldView and will cause it to be rendered. 00544 Engine2::WorldViewWidget *world_view_widget = new Engine2::WorldViewWidget(screen); 00545 screen->SetMainWidget(world_view_widget); 00546 // Create an instance of our AwesomeWorldView, using the newly created 00547 // WorldViewWidget as its "parent". Set the zoom factor so something 00548 // reasonable (though arbitrary). 00549 AwesomeWorldView *world_view = new AwesomeWorldView(world_view_widget); 00550 world_view->SetZoomFactor(0.002f); 00551 // Attach the newly created WorldView to the World object. 00552 world->AttachWorldView(world_view); 00553 /* @endcode 00554 Below, the game loop remains unchanged relative to the @ref lesson04 "previous lesson". 00555 @code */ 00556 // These values will be used below in the framerate control code. 00557 Float current_real_time = 0.0f; 00558 Float next_real_time = 0.0f; 00559 Float desired_framerate = 60.0f; 00560 // Run the game loop until the Screen no longer has the will to live. 00561 while (!screen->IsQuitRequested()) 00562 { 00563 // Get the current real time and figure out how long to sleep, then sleep. 00564 current_real_time = 0.001f * Singleton::Pal().CurrentTime(); 00565 Sint32 milliseconds_to_sleep = Max(0, static_cast<Sint32>(1000.0f * (next_real_time - current_real_time))); 00566 Singleton::Pal().Sleep(milliseconds_to_sleep); 00567 // Calculate the desired next game loop time 00568 next_real_time = Max(current_real_time, next_real_time + 1.0f / desired_framerate); 00569 00570 // Process events until there are no more. 00571 Event *event = NULL; 00572 while ((event = Singleton::Pal().PollEvent(screen, current_real_time)) != NULL) 00573 { 00574 // Let the InputState singleton "have a go" at keyboard/mouse events. 00575 if (event->IsKeyEvent() || event->IsMouseButtonEvent()) 00576 Singleton::InputState().ProcessEvent(event); 00577 // Give the GUI hierarchy a chance at the event and then delete it. 00578 screen->ProcessEvent(event); 00579 Delete(event); 00580 } 00581 00582 // Perform all off-screen game processing. 00583 world->ProcessFrame(current_real_time); 00584 00585 // Turn the EventQueue crank, Perform off-screen GUI processing, 00586 // turn the EventQueue crank again, and then draw everything. 00587 screen->OwnerEventQueue()->ProcessFrame(current_real_time); 00588 screen->ProcessFrame(current_real_time); 00589 screen->OwnerEventQueue()->ProcessFrame(current_real_time); 00590 screen->Draw(); 00591 } 00592 00593 // Delete world_view_widget and world, in that order. This will 00594 // automatically delete our AwesomeWorldView instance. 00595 Delete(world_view_widget); 00596 Delete(world); 00597 } 00598 00599 // Delete the Screen (and GUI hierarchy), "SHUT IT DOWN", and return success. 00600 Delete(screen); 00601 CleanUp(); 00602 return 0; 00603 } 00604 /* @endcode 00605 00606 <strong>Exercises</strong> 00607 00608 <ul> 00609 <li>In the AwesomeWorld constructor, double the orbital speed for each 00610 moon so they fly out of orbit and smash into the side of the 00611 ObjectLayer and see what happens.</li> 00612 <li>Make entities which hit the sides of the ObjectLayer bounce back by 00613 changing AwesomeEntity::HandleObjectLayerContainment and see what 00614 happens. Change it again so the entities bounce back inelastically 00615 (losing velocity after each bounce).</li> 00616 <li>Reverse the direction the moon entities orbit the planet.</li> 00617 <li>Play with the value of m_gravitational_constant and see what the 00618 effects are (taking note of where it is used in computations).</li> 00619 <li>Change the AwesomeWorld constructor so all the moon entities spawn 00620 only on either the X or Y axes so they are initially arranged in a 00621 cross pattern, and see what happens.</li> 00622 <li>Change the gravitational system to be a binary planet system (i.e. 00623 two heavy planets orbiting one another, with many lighter moons 00624 orbiting at a safe distance.</li> 00625 <li>In the AwesomeWorld constructor, spawn only a single moon and a single 00626 planet, and make the mass of the moon and planet similar to one another. 00627 The net momentum of the gravitational system will not be zero, and it 00628 will experience a drifting effect (the system's center of gravity is 00629 not stationary). Correct this drifting so that the net momentum of the 00630 system is zero.</li> 00631 <li>Add UI controls to change the world's gravitational constant and 00632 the desired framerate.</li> 00633 <li>Revert the planetary system back to the default single large planet 00634 with many moons. Change the desired framerate to something low such 00635 as 5 frames per second and see what the effect on the numerical 00636 integration is.</li> 00637 <li><strong>Extra credit:</strong> Implement Runge-Kutta Integration (see 00638 http://en.wikipedia.org/wiki/Runge-Kutta_methods for info) in addition 00639 to the less accurate Euler Integration in AwesomeWorld::HandleFrame. 00640 Make AwesomeWorld configurable as to which integration method should 00641 be used, and make the SPACE key toggle the method (you will need to 00642 override <tt>virtual bool ProcessKeyEvent (EventKey const *e)</tt> in 00643 AwesomeWorldView). See how the different integration methods perform 00644 compared to each other at different framerates, especially low ones.</li> 00645 </ul> 00646 00647 Thus concludes lesson05, you crazy almost-game-programming bastard, you. 00648 */