Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Tutorial doc edits

  • Loading branch information...
commit b21061469fcef07414c6a8d3a5e0222d89da6e77 1 parent b866d52
Casey Duncan caseman authored
18 doc/tutorial/tutorial1.rst
View
@@ -83,7 +83,7 @@ Below the built-in components used above are explained in more detail.
The movement component describes how an entity moves over time. It has the fields :attr:`velocity`, :attr:`accel` and :attr:`rotation`.
`Shape`
- The shape component decribes poylgonal entity shapes. It has the fields :attr:`verts` and :attr:`closed`.
+ The shape component describes polygonal entity shapes. It has the fields :attr:`verts` and :attr:`closed`.
`Renderable`
The renderable component stores some data about entity presentation. It has the fields :attr:`depth` and :attr:`color`.
@@ -138,11 +138,11 @@ We now have all the parts configured for our second goal: to create polygonal sh
Defining an Entity Class
========================
-In the abstract, entities represent the actionable items in a game. Anything that can interact or be interacted with is typically an entity. Entities are usually visible to the player and may have dynamic behavior such as movement, collision, or animation. In our example game we will be defining entities for several game objects, starting with one of the stars of the show, asteroids.
+In the abstract, entities represent the actionable items in a game. Anything that can interact or be interacted with is typically an entity. Entities are usually visible to the player and may have dynamic behavior such as movement, collision, or animation. In our example game we will be defining entities for several game objects, starting with one of the stars of the show: asteroids.
-In concrete terms, Grease entities are rather simple things. Grease entities are instances of the |Entity| class. Typically applications will define various entity types by sublclassing |Entity|. Since entity data is stored in components, they have only two instance attributes: :attr:`entity_id` and :attr:`world`. The entity id is a unique identifier for the entity in the world. This is automatically assigned when the entity is created, and is usually invisible to application. The world is of course the |World| object where the entity resides. Entities are really just typed identifiers, so they are only meaningful in the context of a world, and actually cannot be created independently of a world.
+In concrete terms, Grease entities are rather simple things. Grease entities are instances of the |Entity| class. Typically applications will define various entity types by subclassing |Entity|. Since entity data is stored in components, they have only two instance attributes: :attr:`entity_id` and :attr:`world`. The entity id is a unique identifier for the entity in the world. This is automatically assigned when the entity is created, and is usually invisible to application. The world is of course the |World| object where the entity resides. Entities are really just typed identifiers, so they are only meaningful in the context of a world, and actually cannot be created independently of a world.
-Not let's put that theory into practice and define the :class:`Asteroid` entity class. Since asteroids are fairly static objects, we just need to establish their initial state. This is done in the conventional way by defining an :meth:`__init__()` method:
+Now let's put this theory into practice and define the :class:`Asteroid` entity class. Since asteroids are fairly static objects, we just need to establish their initial state. This is done in the conventional pythonic way by defining an :meth:`__init__()` method:
.. literalinclude:: blasteroids1.py
:pyobject: Asteroid
@@ -165,13 +165,13 @@ Next let's go into the entity constructor:
:pyobject: Asteroid.__init__
:end-before: movement
-The method declaration is conventional, however the second :attr:`world` argument is mandatory. As you might imagine this is the world object that the entity has been created in. Although you must receive this argument, you do not need to do anything with it in your :meth:`__init__()` method. The :class:`Entity` base class takes care of assigning the :attr:`world` and :attr:`entity_id` attributes for you in its :meth:`__new__()` method. After the :attr:`world` argument you can define any additional arguments you need. Here we add a radius argument so that asteroids of various sizes can be created with this class.
+The method declaration is conventional, however the second :attr:`world` argument is mandatory. As you might imagine this is the world object that the entity has been created in. Although you must receive this argument, you do not need to do anything with it in your :meth:`__init__()` method. The :class:`Entity` base class takes care of assigning the :attr:`world` and :attr:`entity_id` attributes for you in its :meth:`__new__()` method, which is executed before :meth:`__init__()`. After the :attr:`world` argument you can define any additional arguments you need. Here we add a radius argument so that asteroids of various sizes can be created with this class.
-The first statement in our constructor sets the position of the asteroid. The position is random, though it avoids placing asteroids directly in the middle of the window. The position value is not stored in the asteroid instance, however. Accessing :attr:`self.position` provides a component accessor for the new entity. This accessor lets you inspect and manipulate data in the world's :attr:`position` component for this entity. The attributes of the position accessor let you read and write the field values for this entity in the position component. This lets you code using a conventional style from the point of view of the entity object, while keeping the data centralized in the components.
+The first statement in our constructor sets the position of the asteroid. The position is random, though it avoids placing asteroids directly in the middle of the window where the player's ship will spawn. The position value is not stored in the asteroid instance, however. Accessing :attr:`self.position` provides a component accessor for the new entity. This accessor lets you inspect and manipulate data in the world's :attr:`position` component for this entity. The attributes of the position accessor let you read and write the field values for this entity in the position component. This lets you code using a conventional style from the point of view of the entity object, while keeping the data centralized in the components.
-There is also another important thing happening here. Components are like sets of entities. Initially a new entity belongs to none of the components. By assigning a value to a component field, the entity is automatically added to the component if it is not already a member. So our position assignment above first adds the entity to the position component, then sets the value of the position field for the entity. There is no need to add the entity to the component separately.
+There is also another important thing happening here. Components are also like sets of entities. Initially a new entity belongs to none of the components in the world. By assigning a value to a component field of an entity, the entity is automatically added to the component if it is not already a member. So the position assignment above first adds the entity to the position component, then sets the value of the position field for the entity. There is no need to add the entity to the component separately.
-So you can read the above assignment statement as, *"add the entity to the position component and set the position field of the position component to a random 2D vector."*
+So you can read the above assignment statement as, *"add the entity to the position component and set the position field of the position component for this entity to a random 2D vector."*
Now let's tackle movement:
@@ -218,7 +218,7 @@ Before we can see our ponderous space boulders plodding through space, we need t
* Push the :class:`GameWorld`'s event handlers on the window to receive events.
* Create some :class:`Asteroid` instances.
-We'll modify the :func:`main()` function to include this functionality:
+We'll modify the :func:`main()` function to do these things:
.. literalinclude:: blasteroids1.py
:pyobject: main
26 doc/tutorial/tutorial2.rst
View
@@ -13,7 +13,7 @@ Making a Game of it
In :ref:`part 1 <tut-chapter-1>` of the tutorial the basis was laid for the *Blasteroids* game, but it is far from complete or even playable. By the end of this chapter, we'll have rectified that. To start with, let's build on the techniques we used to create the :class:`Asteroid` class, and create an entity for the player's ship.
-The start should look pretty familar, and even a bit simpler than the asteroids:
+The start should look pretty familiar, and even a bit simpler than the asteroids:
.. literalinclude:: blasteroids2.py
:pyobject: PlayerShip
@@ -22,7 +22,7 @@ The start should look pretty familar, and even a bit simpler than the asteroids:
First we have some class attributes that configure various aspects of the ship, including thrust acceleration, turn rate, shape (vertex points) and color. Separating these values out of the code makes them easier to tweak while testing the game, and also will allow us to refer to them from other code, which can be convenient.
-It's probably difficult to envision the shape from just the vertex coordinates, so here's what the ship will look like renderered:
+It's probably difficult to envision the shape from just the vertex coordinates, so here's what the ship will look like rendered:
.. figure:: ship_shape.png
:align: left
@@ -68,7 +68,7 @@ Controlling the Ship
None of the capabilities we've coded for the player's ship mean anything unless the player can control them. Here we are going to see how easy it is to wire up our game logic to the keyboard.
-To do this, we are going to create our own custom |System| to house our top-level game state, logic, and keyboard bindings. Because the example game is simple, we can easily fit all of these things into a single system class.
+To do this, we are going to create our own custom |System| to house our top-level game state, logic, and keyboard bindings. Because the example game is simple, we can easily fit all of these things into a single system class. In a more complex games, it may be more appropriate to separate these things into separate systems.
Remember that systems are behavioral aspects of our application, and are invoked each time step. So they are the perfect place to define and glue together the logic for the game.
@@ -78,7 +78,7 @@ Remember that systems are behavioral aspects of our application, and are invoked
We start by defining our :class:`GameSystem` as a subclass of :class:`~grease.controls.KeyControls`. :class:`KeyControls` is a system subclass that provides a convenient mechanism for binding its methods to keyboard events.
-The :meth:`set_world` method is overridden to include a call to create a :class:`PlayerShip` entity and store it in the game state. Since there is only one player ship, this is an easy way to keep track of it so that we can call it's methods in response to particular key presses. We make the entity here in this method -- instead of, say :meth:`__init__` -- because this method is called when the system is added to the world. Since we need a reference to the world in order to create an entity, this is the most convenient place to do so.
+The :meth:`set_world` method is overridden to include a call to create a :class:`PlayerShip` entity and store it in the system as game state. Since there is only one player ship, this is an easy way to keep track of it so that we can call it's methods in response to particular key presses. We make the entity here in this method -- instead of, say :meth:`__init__` -- because this method is called when the system is added to the world. Since we need a reference to the world in order to create an entity, this is the most convenient place to do so.
Next let's add a method to turn the ship left when either the "a" or left arrow keys are pressed:
@@ -107,7 +107,7 @@ The methods for handling turning right are the same as above with the direction
:start-after: turn(0)
:end-before: key.SPACE
-For activating thrust, we use the :meth:`~grease.controls.KeyControls.key_hold` decorator. This works differently than the key press and release decorators we used for turning. The press and release decorators configure a method to fire once for each specific key event. The key hold decorator configures a method to fire continuously, once per time step, as long as the specified key is held down. This is perfect for thrust, which needs to be adjusted continously as the ship turns, and runs a continuous animation while activated.
+For activating thrust, we use the :meth:`~grease.controls.KeyControls.key_hold` decorator. This works differently than the key press and release decorators we used for turning. The press and release decorators configure a method to fire once for each specific key event. The key hold decorator configures a method to fire continuously, once per time step, as long as the specified key is held down. This is perfect for thrust, which needs to be adjusted continuously as the ship turns, and runs a continuous animation while activated.
The :meth:`stop_thrust` method is simply bound to key release, to ensure the thrust is deactivated at the proper time.
@@ -148,7 +148,7 @@ The :class:`~grease.component.Collision` component (line 9 above) has the fields
The meaning of this field is up to the specific collision system used. For :class:`~grease.collision.Circular` systems, entities are approximated as circles for the purposes of collision detection. The radius value is simply the radius of the collision circle for an entity.
`from_mask` and `into_mask`
- Not all entities in the collision component need to be able to collide with each other. These two mask fields let you specify which entities can collide. Both mask fields are 32 bit integer bitmasks. When two entities are compared for collision, the :attr:`from_mask` value from each entity is bit-anded with the :attr:`into_mask` of the other. If this bit-and operation returns a non-zero result, then a collision is possible, if the result is zero, the entities cannot collide. Note that this happens in both directions, so a collision can occur between entity A and B if ``A.collision.from_mask & B.collision.into_mask != 0 or B.collision.from_mask & A.collision.into_mask != 0``.
+ Not all entities in the collision component need to be able to collide with each other. These two mask fields let you specify which entities can collide. Both mask fields are 32 bit integer bitmasks. When two entities are compared for collision, the :attr:`from_mask` value from each entity is bit-anded with the :attr:`into_mask` of the other. If this bit-and operation returns a non-zero result, then a collision is possible, if the result is zero, the entities cannot collide. Note that this check happens in both directions, so a collision can occur between entity A and B if ``A.collision.from_mask & B.collision.into_mask != 0 or B.collision.from_mask & A.collision.into_mask != 0``.
Let's take a closer look at how the collision system is configured above:
@@ -159,7 +159,7 @@ Let's take a closer look at how the collision system is configured above:
There are two major steps to collision handling in Grease: *collision detection* and *collision response*. The detection step happens within the collision system. A set of pairs of the currently colliding entities can be found in the :attr:`collision_pairs` attribute of the collision system. Applications are free to use :attr:`collision_pairs` directly, but they can also register one or more handlers for more automated collision response. Collision handlers are simply functions that accept the collision system they are configured for as an argument. The handler functions are called each time step to deal with collision response.
-Above we have configured :func:`~grease.collision.dispatch_events` as the collision handler. This function calls :meth:`on_collide` on all entities that are colliding. The entities' :meth:`on_collide` handler methods can contain whatever logic desired to handle the collision. This method accepts three arguments: :attr:`other_entity`, :attr:`collision_point`, and :attr:`collision_normal`. These arguments are the other entity collided with, the point where the collision occured and the normal vector at the point of collision respectively. It is up to the handler method to decide how these values are used. Note that when two entities collide, both of their :meth:`on_collide` handler methods will be called, if defined.
+Above we have configured :func:`~grease.collision.dispatch_events` as the collision handler. This function calls :meth:`on_collide` on all entities that are colliding. The entities' :meth:`on_collide` handler methods can contain whatever logic desired to handle the collision. This method accepts three arguments: :attr:`other_entity`, :attr:`collision_point`, and :attr:`collision_normal`. These arguments are the other entity collided with, the point where the collision occurred and the normal vector at the point of collision respectively. It is up to the handler method to decide how these values are used. Note that when two entities collide, both of their :meth:`on_collide` handler methods will be called, if defined.
In our game we will leverage the collision masks to make it so that the player's ship collides with asteroids, but the asteroids do not collide with each other. To do that we need to modify the :class:`Asteroid` and :class:`PlayerShip` constructors to set the collision component fields.
@@ -348,11 +348,11 @@ We start with some class attributes (line 4-5) to specify the shot's speed and t
Next we define the constructor, which in addition to the required :obj:`world` argument, takes a :obj:`shooter` entity and :obj:`angle` value. The shooter is the entity that the shot is being fired from. The angle determines the direction of the shot.
-Inside the constructor, we first determine the initial position of the shot (lines 8-10). This is done by computing an offset based on the shooter's collision radius and the input angle. This way the shot appears to come from the surface of the shooter, rather than the center. The velocity is calculated by multiplying the angle's unit vector by the shot's speed, plus the shooter's velocity. Without including the shooter's velocity, the shooter could actually outrun his own shots, and strafing would feel very unnatural.
+Inside the constructor, we first determine the initial position of the shot (lines 8-10). This is done by computing an offset based on the shooter's collision radius and the input angle. This way the shot appears to come from the surface of the shooter, rather than the center. The velocity is calculated by multiplying the angle's unit vector by the shot's speed, plus the shooter's velocity. Without including the shooter's velocity, the shooter could actually outrun his own shots, and strafing would feel very unnatural. Would-be skeptics out there should try it with and without adding the shooter's velocity to see what I mean.
The shape of the shot is set to a small triangle (line 13). This is small enough so that it will appear to be a small dot when rendered. Collision is setup with a small radius and a mask specifically designed to collide with everything except for the shooter (``~`` is Python's bit-invert operator). Setting the color ensures the shot is rendered.
-On line 17 we schedule the shot to expire at the proper time. The clock will call the expire method (defined below) when the time-to-live elapses, deleting the entity automatically. This means that we do not need to keep track of when each shot will expire ourselves, which is convenient.
+On line 17 we schedule the shot to expire at the proper time. The world's clock will call the expire method (defined below) when the time-to-live elapses, deleting the entity automatically. This means that we do not need to keep track of when each shot will expire ourselves, which is convenient.
We have two more methods to implement for :class:`Shot`: an :meth:`on_collide` handler for collision and an :meth:`expire` method for handling the shot expiration. Both will simply delete the entity:
@@ -389,11 +389,11 @@ This returns a special set of all entities in the :obj:`gun` component. It's spe
world[...].gun.firing == True
-That looks like an innocent boolean expression, but it is actually much more than that. This expression returns a set of all entities where the component field ``gun.firing`` matches the value ``True``. So this expression returns the set of all entities currently firing their gun. We can iterate this set, as we do in line 5 above, to perform the neccessary logic in our system.
+That looks like an ordinary boolean expression, but it is actually much more than that. This expression returns a set of all entities where the component field ``gun.firing`` matches the value ``True``. So this expression returns the set of all entities currently firing their gun. We can iterate this set, as we do in line 5 above, to perform the necessary logic in our system.
On line 6 we check if the entity's gun is ready to fire. ``self.world.time`` is the local timestamp of the world object, updated every time step. If the gun is ready to fire, we create a :class:`Shot` entity and update the ``last_fire_time`` so the gun can begin its cool down cycle again.
-As you may have guessed, now that we have our :class:`Gun` system implemented, we need to add it to the :class:`GameWorld` class::
+As you may have guessed, now that we have our :class:`Gun` system class implemented, we need to add an instance of it to the :class:`GameWorld`::
self.systems.gun = Gun()
@@ -418,7 +418,7 @@ With all of this in place we can finally blast those asteroids to smithereens!
Wrapping Things Up
==================
-We've now implemented all of the major game mechanics, save one. You'll notice if you fly around for a few seconds, all of the asteroids fly off the screen and disappear. And so will the player's ship if you accelerate it in one direction. We forgot to implement the all-important toroidal space topology! That's fancy-talk for making objects wrap around when they fly off the edge of the screen.
+We've now implemented all of the major game mechanics, save one. You'll notice if you fly around for a few seconds, all of the asteroids fly off the screen and disappear. And so will the player's ship if you accelerate it in one direction. We forgot to implement the all-important toroidal spatial topology! That's fancy-talk for making objects wrap around when they fly off the edge of the screen.
So, how do you suppose we're gonna fix this? Surprise! Another system! This is a textbook example of a behavioral aspect of the application:
@@ -426,7 +426,7 @@ So, how do you suppose we're gonna fix this? Surprise! Another system! This is a
:pyobject: PositionWrapper
:linenos:
-The constructor (lines 4-6) precalculates the half width and height of the window for convenience later. Remember that the origin is in centered in the window, so these values are handy for finding the edges.
+The constructor (lines 4-6) pre-calculates the half width and height of the window for convenience later. Remember that the origin is in centered in the window, so these values are handy for finding the edges.
The :meth:`step` method performs four entity extent queries, in the same spirit as in the :class:`Gun` system. Here we are querying the edges of the entities using their collision bounding boxes, comparing them to the window edges. In the first loop (line 9-10) we iterate over all entities whose right edge has moved left beyond the left edge of the window. This extent query does the job::
14 doc/tutorial/tutorial3.rst
View
@@ -25,7 +25,7 @@ First we define a constant :obj:`SCRIPT_DIR_PATH` to store the path to the scrip
The :func:`load_sound` function is a simple wrapper around :func:`pyglet.media.load` that loads sound files from the ``sfx`` directory adjacent to the script. The false default value for the :attr:`streaming` argument means that by default the files will be preloaded into memory, which is best for short sound effects.
-The :func:`load_sound` function creates fire-and-forget sounds that always play all the way through. We also add a :func:`looping_sound` function for continous-play sounds. This creates the sounds as streaming, which is suitable for longer sound effects or music. It also returns a full fledged :class:`pyglet.media.Player` object which affords us more control than a simple sound object.
+The :func:`load_sound` function creates fire-and-forget sounds that always play all the way through. We also add a :func:`looping_sound` function for continuous-play sounds. This creates the sounds as streaming, which is suitable for longer sound effects or music. It also returns a full fledged :class:`pyglet.media.Player` object which affords us more control than a simple sound object.
For a simple game like this, with relatively few sounds, we can ensure that all the sounds are preloaded by storing them as class attributes in the entities that will use them. This will work because these class attributes are evaluated right after the script, or module, is first loaded by Python. In a more complex game with more assets to load, a more sophisticated preloading mechanism that provides feedback to the player is probably warranted.
@@ -41,7 +41,7 @@ We can play the death sound when the player's ship is destroyed, by adding a lin
:pyobject: PlayerShip.on_collide
:end-before: player_died
-The thrust sound is a looping sound. It should loop continously so long as the thrust is on and stop as soon as the thrust is off. This is accomplished by some simple modifications to the :meth:`thrust_on` and :meth:`thrust_off` methods to play and pause the thrust sound:
+The thrust sound is a looping sound. It should loop continuously so long as the thrust is on and stop as soon as the thrust is off. This is accomplished by some simple modifications to the :meth:`thrust_on` and :meth:`thrust_off` methods to play and pause the thrust sound:
.. literalinclude:: blasteroids3.py
:pyobject: PlayerShip.thrust_on
@@ -121,7 +121,7 @@ There's also some changes to :meth:`on_collide` above. On line 35, we check the
Scoring and Levels
------------------
-We now know how many points the asteroids are worth, but we still need to keep track of the game score for the player. Since the score goes with the game, we can add store it in the :class:`GameSystem` where we bind the keyboard controls. if we had a game with multiple simultaneous players all with separate scores, we might choose to store the scores in a component instead.
+We now know how many points the asteroids are worth, but we still need to keep track of the game score for the player. Since the score goes with the game, we can add store it in the :class:`GameSystem` where we bind the keyboard controls. If we had a game with multiple simultaneous players all with separate scores, we might choose to store the scores in a component instead.
We refactor :class:`GameSystem` adding some attributes and methods:
@@ -165,7 +165,7 @@ This method will be called when the player's ship is destroyed. It simply decrem
:pyobject: GameSystem.player_respawn
:end-before: is_multiplayer
-This is a pretty trivial callback that creates a new :class:`PlayerShip` if there are lives remaining. There are a couple of things to note here: one is the :attr:`dt` parameter. This is needed because the clock always passes the time delta in when calling a scheduled method. We don't use it, but we need to receive it for the callback to work. The second thing of note is the :attr:`invincibility` parameter passed into the :class:`PlayerShip` constructor. This will temporarily make the ship invulnerable when it respawns so the player can avoid instant death if the ship materalizes and immediately collides with an asteroid. We will need to modify :class:`PlayerShip` to support this::
+This is a pretty trivial callback that creates a new :class:`PlayerShip` if there are lives remaining. There are a couple of things to note here: one is the :attr:`dt` parameter. This is needed because the clock always passes the time delta in when calling a scheduled method. We don't use it, but we need to receive it for the callback to work. The second thing of note is the :attr:`invincibility` parameter passed into the :class:`PlayerShip` constructor. This will temporarily make the ship invulnerable when it respawns so the player can avoid instant death if the ship materializes and immediately collides with an asteroid. We will need to modify :class:`PlayerShip` to support this::
class PlayerShip(BlasteroidsEntity):
"""Thrust ship piloted by the player"""
@@ -216,7 +216,7 @@ The methods above setup the :class:`Hud` renderer. The :meth:`set_world` method
.. note:: |Renderer| is an abstract base class much like |System| Like the latter, it not mandatory that custom renderers subclass |Renderer|, but it does serve to make the application code easier to understand.
-:meth:`create_lives_entities` describes its own purpose pretty well. It creates some stationary entities that look lke the player's ship to represent the extra lives remaining. These will be displayed at the top-left of the window.
+:meth:`create_lives_entities` describes its own purpose pretty well. It creates some stationary entities that look like the player's ship to represent the extra lives remaining. These will be displayed at the top-left of the window.
Below we get to the heart of the matter, the :meth:`draw` method. All renderers must implement this method, which is called whenever the display needs to be updated:
@@ -258,7 +258,7 @@ The basic utility of a mode stack is that the user can start in one mode, enter
Windows, Managers, Worlds, and Modes
------------------------------------
-Although it is possible to create custom modes and managers in your application, there are two concrete implementations provided by Grease that should prove convenient. One, we are already familar with: |World|. Worlds implement the mode interface so that they can be used directly as game modes. When a world is active in a mode manager, its clock receives ticks, its systems receive events, and it will invoke its renderers when the display needs to be redrawn. When a world is deactivated, it is "frozen". Its clock no longer runs, its systems no longer receive events and its renderers are no longer invoked. When the world is later reactivated, it resumes right where it left off.
+Although it is possible to create custom modes and managers in your application, there are two concrete implementations provided by Grease that should prove convenient. One, we are already familiar with: |World|. Worlds implement the mode interface so that they can be used directly as game modes. When a world is active in a mode manager, its clock receives ticks, its systems receive events, and it will invoke its renderers when the display needs to be redrawn. When a world is deactivated, it is "frozen". Its clock no longer runs, its systems no longer receive events and its renderers are no longer invoked. When the world is later reactivated, it resumes right where it left off.
Grease also provides a special mode manager, :class:`~grease.mode.ManagerWindow`. This class is a Pyglet window subclass that implements the mode manager interface. This allows you to push and pop modes, such as |World| objects, directly onto an application window. All of the OS events the window receives will go directly to the active mode. This makes implementing a user interface flow with push and pop semantics a breeze.
@@ -350,7 +350,7 @@ Grease provides a special |Multi| class we can leverage for this purpose. A |Mul
Because multi-modes themselves are just modes, they allow a whole group of submodes to be pushed onto a manager at once. They also keep track of which submode is active. Submodes can also be added and removed from the multi-mode at any time.
-To implement hotseat multiplayer using a muti-mode is quite simple. When a two-player game is initiated, a |Multi| object will be created containing 2 game worlds. When this is pushed onto the mode manager, the first game world will be activated. We will add some code to activate the next game whenever the current player loses a life. When a player's game is over, it will be removed from the multi-mode.
+To implement hotseat multiplayer using a multi-mode is quite simple. When a two-player game is initiated, a |Multi| object will be created containing 2 game worlds. When this is pushed onto the mode manager, the first game world will be activated. We will add some code to activate the next game whenever the current player loses a life. When a player's game is over, it will be removed from the multi-mode.
Let's start by seeing how we can initiate a two-player game from the title screen:
Please sign in to comment.
Something went wrong with that request. Please try again.