0% found this document useful (0 votes)
81 views10 pages

Game

The document discusses using object-oriented design patterns like Strategy and Decorator patterns in game programming. It provides examples of how the Strategy pattern can be used to encapsulate monster behaviors like attacks and bravery. This avoids issues with an inheritance-only solution that leads to an exponential number of classes. The Decorator pattern is also discussed as a way to "decorate" monsters with abilities like making them more powerful in heroic dungeons, without changing the underlying monster class.

Uploaded by

trabajados
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as ODT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
81 views10 pages

Game

The document discusses using object-oriented design patterns like Strategy and Decorator patterns in game programming. It provides examples of how the Strategy pattern can be used to encapsulate monster behaviors like attacks and bravery. This avoids issues with an inheritance-only solution that leads to an exponential number of classes. The Decorator pattern is also discussed as a way to "decorate" monsters with abilities like making them more powerful in heroic dungeons, without changing the underlying monster class.

Uploaded by

trabajados
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as ODT, PDF, TXT or read online on Scribd
You are on page 1/ 10

I wanted to expand a little bit on my impromptu discussion of the relevance

to object oriented design, and specifically design patterns, on game


programming. The discipline of design isn't one that only applies to
enterprise software development; games benefit from optimal architecture
even moreso than enterprise software, in most cases, since players tend to
lose interest quickly in buggy, slow gaming experiences, and are much more
likely to badmouth, in a very vocal way to a very receptive social audience,
everything about the game when they do have a poor experience. It's in the
best interests of public companies that make games to employ very skilled
software architects up-front, because the alternative is having to spend a lot
of money on the QA teams and risking a lot of bad press at the end. Good
choices up-front can have profound effects on code quality, refactorability,
execution speed, and minimizing bug count.

Strategy Pattern
As a programmer in almost any discipline, the Strategy pattern is very likely
to be the first pattern you'll grow to learn, live, and love. As the name
implies, the Strategy pattern allows you to encapsulate behavior heuristics
through composition. In the context of game programming, I prefer to think
of them as a library of preset "moods", although they are much more flexible
than that.
Before we discuss the value of design patterns and object oriented design,
let's explore the results when we use neither. We will start with some generic
Monster object.
public class Monster {
public String name;
public int health;
public int lethality;
public Monster(...) { ... }
public void move() { ... }
public void attack(Player p) { ... }
}
Hello, monster ! We're going to use you in the dungeons of game called
World of Warquest. But wait, the dungeons in that game have two flavors: a
normal flavor, and a Heroic flavor where you'll be much more dangerous to
players. So let's implement you wrong and see what happens:
public class Monster {

String name;
int health;
int lethality;
Boolean heroic;
public Monster(...) { ... }
public void move() {
if (this.heroic) { ... }
else { ... }
}
public void attack(Player p) {
if (this.heroic) { ... }
else { ... }
}
}
Well, that doesn't look so bad. Except that every one of our Monster's
methods has this ugly if/else statement in it to distinguish normal behaviors
from heroic behaviors. Our Monster is starting to spaghettify. (Note: just as
there are patterns, there exist their evil twins, called anti-patterns, which
are just as important to identify and categorize. The presence of an antipattern suggests non-optimal code structure. Once identified, the antipattern can often suggest the correct pattern to use.) What can we do about
this ? Let's use some of our new object oriented design knowledge:

Ok, great ! We seem to be following the OO directive of encapsulating what


varies here. However, World of Warquest is going to have monsters with
many interesting fighting styles: some will want to use physical attacks, and

some will want to fight from a distance using spells. Can do !

But wait, monsters need to have more tricks in store for our players. Some
monsters are cowardly and will flee when their health gets low. Some
monsters get into a haze of battle and enrage when their health gets low,
making them especially dangerous:

Things are starting to look very bad indeed for an inheritance-only solution to

our beastiary. Luckily, there is a higher truth than pure inheritance, but
before we talk about that higher truth, let's discuss the real source of the
problem here. At each iteration where we attempted to solve our problems
with inheritance, we applied the ``solution'' to each of the classes we came
up with the previous iteration. This gives rise to an exponential explosion in
the number of classes we need to cover all combinations of Monster
behaviors. When we're talking classes of complexity, exponential anything is
a red flag that something isn't right with our approach, and the longer it takes
to realize that, the more painful it's going to be to correct.
Let's look more closely at that last diagram. At each level of iteration where
we used inheritance, we note that the behaviors are independent of each
other. That is, that our monster could prefer physical attacks or magical
attacks had nothing to do with whether our monster was cowardly or brave.
That independence suggests that there's another level of encapsulation to
exploit here: let's put all of the bravery logic (green) into its own strategy
class, and all of the attack (purple) logic into its own strategy class.

The picture seems more complicated than it needs to because the sample
code (yellow) is cluttering the image up, but we immediately see that things
are much more manageable. By not using inheritance to cover all of the
possible cases, we've cut our original problem off at the knees. We're still
using inheritance within each of the strategies, but that inheritance stays
contained in the individual Strategy classes and doesn't bleed out into the
Monster class. Rather, we're using composition to associate each monster
with an attack behavior and a bravery behavior. Lastly, we've added a couple
of concrete Monster classes in red a warlock and a warrior to show how

we can create a wide variety of monsters just by changing the combination of


concrete strategies they're composed of.
One thing to keep in mind here, though, is that while the Strategy pattern is
great for heuristics, don't mistake that with artificial intelligence. At no point
so far is the monster observing or analyzing the behavior of the players; in
fact, given knowledge of the monster's general strategy class, we can
consistently predict the monster's behavior from its current state and its
environment. However, by providing enough of these strategies, we can
provide, with a miminal amount of work, a wide-enough variety of behavior
combinations to provide a challenging gaming experience without having to
resort to AI, which is devilishly difficult to do right.
Also, by virtue of the fact that the Strategy pattern associates behaviors with
a monster without tightly coupling that behavior to that monster, the
behavior of a monster can be changed at runtime. A monster that would
otherwise be cowardly might become relentlessly brave when under the
affect of a magical effect or when in proximity to another monster. A casting
monster might temporarily resort to melee attacks if interrupted or silenced.
In my experience writing Quake bots, the Strategy pattern was particularly
good for setting difficulty levels on the bots. All bots performed exactly the
same tasks namely:

seeking other players when no other player is visible

selecting their present weapon

aiming and shooting at visible players

The Strategy pattern was ideal for implementing the difficulty levels of the
bots. Novice bots did all of the above randomly and with considerable
amount of slop (i.e., they would frequently miss their targets, change their
minds, make deliberately bad decisions); expert bots had this amount of
slop dialed down to the point where they would never miss a shot, so you
had to be ready for them. However, another interesting use of the Strategy
pattern was what the bots did when there were no other players in sight.
Novice bots would often just sit there, but the strategies for smarter bots
would have them look at their inventories and health, prioritizing gathering
health and ammo (when they needed it) over hunting down other players.
Additionally, my favorite bots to implement were ones that had objectionable
personalities: some of them were singleminded about finding their favorite
weapon (usually a sniper rifle) and camping it so that nobody else could have
it. Some bots would look at human players' inventories, see what they were
using, and go after that particular kind of ammo in an attempt to starve the
players out of their preferred weapon.

Notice, though, that for now I've left the NormalMonster and HeroicMonster
classes hanging. The reason for that is that the Strategy pattern may not be
the best way of handling the distinction between monster strengths.
Decorator Pattern
We've talked about this in class quite a bit, but it has powerful uses in game
programming as well. Let's get back to our Monster class. Just like in class,
we've seen that a decorated beverage both is-a beverage and has-a
beverage. The same goes with our normal/heroic monsters. Inside every
heroic monster is a greatly-reduced normal monster. It's hard to see from the
class diagram:

but if we sketch up a funky little sequence diagram, things become clearer:

The normal monster lives on within the heroic monster, and the player that's
battling the heroic monster is fighting the decorated monster, but the
normal monster inside the heroic monster is unchanged. Why in the
world would we want that ? In World of Warquest, suppose there is a fight
mechanic wherein one of our party members becomes mind-controlled and
starts attacking the player's party, and that the successful fight strategy is
that the party must ignore the original monster and turn their attention to the
mind-controlled party member, damaging them until the mind-control
mechanic is broken. When the mind-control mechanic is broken, the
Decorator pattern allows us to strip away the mind-controlled decoration
and recover the original state of the party member before the mind-control
mechnic.
There are yet other uses of the Decorator pattern; many of them are evident
in real-time strategy games that involve technological upgrades. Many RTSes
have upgrade paths for their units; as the player unlocks certain
technological abilities or proceeds into later eras, less powerful units can be
upgraded to more powerful ones, as in this example from the game
Civilization IV:

We have the same basic idea here: each weapon upgrade, for the most part,
simply improves the speed, offensive and defensive capabilities, and range of
the unit it replaces, generally without providing new functionality. In this
case, the Decorator pattern is ideal for representing units, especially when it

comes time to upgrade units. When the player achieves and activates the
unit upgrade, the code is dead simple. For instance, when the player
upgrades all of their Catapult objects, the associated code is something like:
procedure upgradeCatapults:
for each unit in player's army:
if unit is catapult:
unit = new Cannon(unit);
And that's it. On the next call to the screen-painting method, all of the tiles
for catapults will automatically be replaced by cannons.
Observer Pattern
One more pattern I'd like to mention. The dungeons in World of Warquest
would be very simple for parties if they could just pick off one monster at a
time, so we can't permit them to do that. We'll increase the challenge level
by arranging the dungeons into groups of monsters, and attacking any single
monster in the group will result in the entire group coming after the player
that initiated the attack. Typically, we call such groups codebound,
because the game client treats them as a group regardless of their distance
from one another.
There are a couple of ways we can do this. One way might be that when we
attack a monster, it makes an area-of-effect shout and any other monster in
that area-of-effect acts as though it, too, had been attacked by the player.
We'll definitely want to do that, in order to discourage players from picking
fights in the middle of crowded rooms.
However, let's generalize our approach. In addition to aggressive action
against any one monster drawing the attention of multiple monsters, perhaps
we also want to draw the attention of monsters (who may not be visible to
the players) when the user interacts with inanimate objects, either by their
own volition (stealing a treasure, lighting a wall sconce, pressing a button) or
not (stepping on a trapped floor tile, trying and failing to disarm a trap).
The Observer pattern is ideal for the purpose of notifying other objects when
actions are performed on an object. Let's consider a couple such scenarios.
In this scenario, we'll set up a room with a treasure and two guard monsters.
When either monster is attacked, it notifies the other monster, passing a
reference to the player that initiated the attack; the result is that both
monsters begin attacking the player. In the second scenario, the treasure is a
trap that, when disturbed by the player, notifies both of the guards, who then
in turn begin attacking the player.

The Observer pattern is the basis for almost all event-driven applications, and
is common in both games, business software, and development frameworks
(jQuery, for example, makes extensive use of this pattern, but also the
javascript DOM event model is one huge Observer pattern). The alternative
to the Observer pattern is polling, which involves objects constantly querying
each other about their statuses (Have you been attacked yet ? How about
now ? Now ?); in general, polling is a monumental waste of processor power.
The Observer replaces polling with a much more efficient don't call me, I'll
call you approach.
One of the implementation details at the heart of the Observer pattern may
be an unfamiliar one. It has a number of names: callback, function pointer,
delegate method; the easiest way to grok it is through the name callback.
Basically, the caller tells the callee, I want to be notified when some
condition is true. Here's a function with some blank parameter values. When
the condition is true, plug in the parameter values you see and call that
function. In addition to event-driven programming, callbacks are common in
asynchronous programming. If you're interested in implementing the
Observer pattern in Java, look up the EventObject and EventListener
classes.
Real-time strategy games employ the Observer pattern frequently as well.
One such case is when the player instructs some production unit (such as a

factory or barracks) to create a new unit (such as a tank or an archer).


Production of new units generally takes some number of rounds, and effective
players generally have many such units in production at a given time. When
control transfers to a player, they will generally want to know which of their
production units have completed production so that they can get started
producing another unit. The determination for how the player is to be
notified (message written to a chat log, sound, flashing light) is delegated to
the caller.

You might also like