6. Overriding Object Properties
The example derived game object classes that we have
looked at all declared additional properties to define their custom
behavior (the downward movement speed for the box, the x and y axis
velocities, and wobble for the ball) and used them in their Update methods to set their properties to affect how they are drawn.
In many cases, this will be the most appropriate way
to control the presentation of the object, but there is an alternative
mechanism that in other cases might result in simpler code.
All the basic properties in the SpriteObject class are defined as being virtual.
This covers the sprite's texture, position coordinates, origin
coordinates, angle, scale values, source rectangle, color and layer
depth, but excludes the Vector2-typed properties (Position, Origin, and Scale) because they are just based around the underlying float
values. By default, they simply store and return the provided value.
Because they are virtual, however, the properties can be overridden in
the derived classes, and the way that they work can be modified.
For example, we could create a moon object that orbited a planet object, and override its PositionX and PositionY
properties to allow them to calculate their positions on demand, based
on the position of the planet and its rotation angle instead of having
to calculate this in the Update method. Overriding the
properties in this way guarantees that the position is up to date and
accurate, even if the planet's position has changed since the moon's
last Update call.
This scenario is demonstrated in the OrbitingSprites example project, an image from which is shown in Figure 4. It defines two sprite object classes, PlanetObject and MoonObject,
and then creates a couple of planet instances with some moons orbiting
them. Just to demonstrate the flexibility of this environment, it also
creates a moon that orbits around one of the other moons. This scenario
might be physically unlikely in the real universe but it works well for
demonstration purposes!
Within PlanetObject we just set up a simple
horizontal bounce movement pattern so that the planets gradually move
back and forth across the screen.
MoonObject is where the interesting code
resides. Its constructor includes parameters to define the moon's
target object (around which it will orbit), its orbital speed, the
distance at which it will orbit its target, and its size. The target
object, speed, and distance are stored in variables defined for the
moon, whereas the rest of the properties are passed through to the base
class.
The moon class has no Update code at all. Instead, its movement relies on the position of the target object and on the UpdateCount property implemented within the GameObjectBase class. The position of the moon is calculated on demand when its PositionX and PositionY properties are queried. The implementation is shown in Listing 9.
Example 9. The position property overrides for the moon object
/// <summary> /// Calculate the x position of the moon /// </summary> public override float PositionX { get { return _targetObject.PositionX + (float)Math.Sin(UpdateCount * _speed) * _distance; } }
/// <summary> /// Calculate the y position of the moon /// </summary> public override float PositionY { get { return _targetObject.PositionY + (float)Math.Cos(UpdateCount * _speed) * _distance; } }
|
The properties read the position of the target object and then use the Sin and Cos functions to return the positions of a circle that orbits this initial position. The UpdateCount value is multiplied by _speed variable to allow the moon to orbit at a defined rate, and the return values from Sin and Cos (which will always range between −1 and 1) are multiplied by the _distance variable to set the distance at which the moon is positioned from its target.
NOTE
As shown in Listing 9, it is quite possible to override just the get part of a property. Because the PositionX and PositionY properties in the MoonObject class make no use of the underlying value, we can simply ignore the set part and allow any attempts to access it to fall through to the base class.
MoonObject also uses the same technique, but overrides the Angle property to make each moon slowly rotate as it orbits around its target sprite.
The game objects are added to the game in the LoadContent method, as shown in Listing 10. First a planet is instantiated and added to the GameObjects
collection. A moon is then created and passed to the planet as its
target object. This is then repeated for the second planet, which is
given three moons. The last of those moons gets its own moon, too;
because the moon can orbit any object, it doesn't matter whether it is
a planet or another moon that it targets (and indeed, this
moon-of-a-moon could itself be given a moon, and so on). With this
object configuration in place, the simulation runs and produces this
comparatively complex set of object movements with an extremely small
amount of game code required.
Example 10. Initializing the objects for the OrbitingSprites project
protected override void LoadContent()
{
PlanetObject planet;
MoonObject moon;
// Create a new SpriteBatch, which can be used to draw textures.
_spriteBatch = new SpriteBatch(GraphicsDevice);
Textures.Add("Planet", Content.Load<Texture2D>("Planet"));
Textures.Add("Moon", Content.Load<Texture2D>("Moon"));
// Add a planet...
planet = new PlanetObject(this, new Vector2(150, 200),
Textures["Planet"], 0.7f);
GameObjects.Add(planet);
// ...and give it a moon
GameObjects.Add(new MoonObject(this, Textures["Moon"],
planet, 0.02f, 60, 0.3f, Color.White));
// Add another planet...
planet = new PlanetObject(this, new Vector2(300, 500),
Textures["Planet"], 1.0f);
GameObjects.Add(planet);
// ...and give it some moons
GameObjects.Add(new MoonObject(this, Textures["Moon"],
planet, 0.04f, 90, 0.2f, Color.OrangeRed));
GameObjects.Add(new MoonObject(this, Textures["Moon"],
planet, 0.025f,130, 0.4f, Color.PaleGreen));
moon = new MoonObject(this, Textures["Moon"],
planet, 0.01f, 180, 0.25f, Color.Silver);
GameObjects.Add(moon);
// Add a moon to the moon
GameObjects.Add(new MoonObject(this, Textures["Moon"],
moon, 0.1f, 25, 0.15f, Color.White));
}