he luck vs. skill aspect of games is one which is fairly central to good design—indeed, it's something we've covered before.
But before we worry about trying to balance luck and skill, we really
need to ask: what is chance, and to what extent is it necessary in a
game? Furthermore, how can we implement chance in a way that feels
rewarding rather than punishing, and use it to improve rather than
detract from the overall playing experience?
Is Chance Required?
It's nearly impossible to create a game without luck. A game without
luck isn't really a game—something like "who's the tallest" or "who has
the most fingers" doesn't really involve any sort of challenge. These
are simply measurements that the players are unable to change, and so
are unlikely to provide much entertainment. A game must have an element
of uncertainty—something like "who can balance on one leg the longest",
while not terribly in depth, is at least not predetermined. Even when
one player is better, their success is not always guaranteed.
For many games, we use cards, dice, or a random number generator to
create this unpredictability. But not all games use randomisation tools,
and a serious strategy game like chess still requires an element of
randomness: this element comes from the players themselves. Players are
unpredictable, and will often adjust strategies and tactics on the fly,
based on what they consider the best probable outcome. This is why,
despite being a fairly static game, chess games can vary wildly: no two
players approach the game the same way.
The reason humans can provide chance to chess is because chess is incredibly complex – in fact, we can describe chess as a complex game.
Unfortuntely, unlike concepts such as "flow" or "zero sum game", the
term "complex game" isn't a recognised term. Since we'll be talking
about complexity a lot, we should probably define what we mean by it.
Understanding Complexity
So what is a complex game?
If we look at Tic-tac-toe, we can see a game with fairly simple
rules. There are nine spaces, players place an X or an O, trying to make
a straight line, and the game is always over in nine moves or fewer.
It's fairly easy to predict the results of a Tic-tac-toe game, even
before the first move is made—assuming the two players play "correctly",
then the game will always end in a draw.
We can say then, that tic-tac-toe is hard to justify as a complex
game. In fact, Tic-tac-toe has been solved, which is to say that we've
calculated every set of possible moves, and essentially proven the best moveset. To make matters worse, humans are capable of "solving" a tic-tac-toe game without much mental agility.
Compare this to chess, which has 64 pieces and six different types of
piece, each with their own moveset, special moves such as castling and
en passant, and a ruleset that means a game could (technically) last
forever. Given these conditions, it's perhaps unsurprising that chess
has never been solved, even by the most powerful computers.
So, essentially, a complex game is one which has not been solved, or that cannot be solved by the players.
This addendum "cannot be solved by the players" is important. It
means that games can continue to be fun, assuming the players are
incapable of solving them. This is why Four-in-a-row (also known as
Connect 4) remains a fairly popular game; although computers have solved
it, when players sit down they are unlikely to be capable of
calculating the perfect game in their heads, so they play non-optimally.
Tic-tac-toe, while trivial for most players, is still a good game for
young children who are unable to plot out every move in their head.
Complexity is subjective.
So why is this important? Because a non-complex game (a simple game)
is a boring game. If the game is not complex, then it is solvable. If it
is solvable, then the outcome is predetermined; all the player is
required to do is work out the best moveset, and they've won. And at
that point, they may as well go back to playing "who has the most
fingers".
A small sidenote here, a solvable game can be better described as a
puzzle. And while puzzles are popular (many newspaper print daily
crossword puzzles), a puzzle is only fun up until it's been solved –
which is why crossword enthusiasts generally don't sit and solve the
same crossword over and over. Theres certainly nothing wrong in deciding
to make a puzzle game, but be aware of what it is you're aiming for,
and how that will impact replayability.
Tools of Fate
So, when we look at games like chess or Tic-tac-toe, we can see they
are all strategy games with no inbuilt randomisation: basic strategy
games. There are, however, many games which do use dice, cards or other
tools as an inbuilt mechanic, like snakes and ladders or poker. As most
of these games can't be considered complex, the inclusion of these dice
or cards is necessary to prevent the game from being solvable. If, in
the game of Snakes and Ladders, rather than rolling, players chose a
number between one and six spaces to move every turn, then even children
would quickly work out that "always choose six unless you land on a
snake" is an optimal strategy.
Of course, adding randomness does not automatically make a solvable
game good. In fact, you simply change the aim from "find the solution"
to "find the best probable outcome". You still essentially have a puzzle
game, except that the win condition is not guaranteed. Too much
randomness is just as bad as none; here again Snakes and Ladders is an
obvious example. Almost no-one other than children plays the game, as it
lacks any sort of interactive challenge and, therefore, people see it
as ultimately pointless.
So why does a game like poker continue to work? Poker is,
essentially, a series of mini puzzles. You are given a hand, and you
have to "solve" how probable it is for you to win. You can then bet on
your hand, based on how likely you are to win the game.
This is an oversimplified view of poker, and if this was all there
was to the game, it would be fairly boring. It would be trivial to write
a program to calculate the odds (although people do that anyway), and
simply run it to maximise your wins.
The fun of poker comes from player interaction: from bluffing and
confidence. You are not required to bet on a good hand, and you are able
to bet on junk. In fact, this is arguably what the game of poker is
truly about; the cards are simply there to facilitate this, and to
provide a fresh round of lying every few minutes. By adding the random
element, we've eliminated player knowledge, which means that we can use
uncertainty as a game mechanic. Players are required to perform based on
what they know, and it is the combination of calculating winning odds
and outfoxing other players that lets poker maintain its fanbase.
Bored of Boards
So, although we've been talking about about traditional gaming,
computer games use exactly the same principles of design. Games like
Tetris or Bejewelled can be considered simple (with added randomness),
and games like Starcraft or Team Fortress can be considered complex.
In almost all games, there is a certain puzzle-like quality. Even in
an RTS or FPS, players are constantly making decisions based on optimal
play: should I build tanks or planes? Should I choose the machine gun or
grenade launcher? Should I turn left or right? Like in chess, the
player attempts to make decisions based on what they think will result
in the best outcome. The randomness isn't (generally) provided by
computer dice, but by the choices of the players in the game. Players
are trying to outwit each other, as well as outskill them.
In fact, its possible to argue that the only randomness in a PvP game
(like an FPS or RTS) should come from the players themselves. As we've talked about before, crits in TF2 are a subject of much contention in the playerbase—to summarise, any shot in TF2 has the possibility of being a crit
shot. Crit shots require nothing more than the roll of a dice, and any
damage dealt by a critical bullet will be twice or three times as much
normal, which causes crits to be frequently lethal. While new players
may enjoy the thrill of randomly getting a kill, "pro" players will see
the crit mechanic as unnecessarily spoiling their skills.
The range of numbers we use for randomness also play a large effect
on how things pan out. If a rifle deals 90-110 damage a shot, then if we
have 150 health the random element is really a flavour effect: no
matter what happens, we need to be shot twice to die. However, if we
have 100 health, than a rifle will randomly kill us in one shot half of
the time. Despite there only being a small range in randomness, the
numbers used matter a great deal.
The Effects of Randomness
So why is it that "pro" players bemoan crit systems (and improperly
implemented damage ranges) whereas "casual" players don't? The answer,
simply, is player expectation.
A pro player will have played their game of choice a lot. They will
know it inside and out. They will know what damage they can take, what
they can deal out, and what the outcome of any situation should be. And
while they may sometimes judge things poorly, it is generally due to
underestimating the opponent's skill level, or making bad split-second
decisions.
So when a pro player enters a battle, and they are instantly gibbed
by a bullet for no reason other than luck, they might feel cheated. They
knew what they wanted to happen, but because of an electronic dice
roll, they were instantly killed instead.
New players will generally not feel this sting as sharply; they don't
know the game as well, they have fewer expectations of what should
happen, and so they can enter a battle not really expecting to win. To
them, battles are as much a learning experience as a test of skill.
This randomisation destroying expectation is something that can
happen in almost any game with randomness. When you're waiting for a
line block in Tetris, and the computer instead gives you six S blocks in
a row, the player might feel a little cheated. The popular game Puzzle
Quest (essentially a bejewelled clone with RPG elements) received many
player complaints about "cheating AI"; there are enough forum threads
about it that the developers had to specifically come out and say that the AI doesn't cheat.
So why does it feel this way? Why are so many players upset over
randomly falling jewel colours? Because the randomness is subverting
player expectations. When a player goes into a game, they are
(generally) expecting to be challenged, but they're also expecting that if they play well, they can win.
When the game randomly throws some bad numbers at you, and you
immediately lose, then you can feel cheated. You had an idea of how the
game was going to play, and despite your best efforts, you were
defeated—not by your own lack of skill, or superior opponent strategy,
but by electronic dice. This, for most players, is incredibly
infuriating.
This "luck subverting player expectation" extends into all sorts of
game. In fact, the more luck involved in a game, the more likely it is
to be frustrating. RPGs are a notable example, especially because of
crit systems. Crits systems often seem like a fun little addition, but
by the numbers they will almost always punish the players. This is
because:
Players are, generally, expected to defeat most enemies.
Crits add randomness to battles.
Randomness in battle means unpredictable results.
Therefore, players will (occasionally) win battles they should have lost, but more often:
Players will lose battles they should have won.
This is, of course, assuming that the encounters are designed or
tailored towards the player. Some RPGs simply throw the player at giant
monster and be done with it; however, as professional designers, we
should be looking to ensure that the game is tailored towards our
players, rather than just throwing some dragons in and calling it a day.
The other problem then, assuming we have designed our combats
carefully, is that a crit system over-favours the player. Imagine if,
after a harrowing journey through time and space, the hero of our game
walks up to the ancient demon terrorising the planet and kills him in
one (critical) blow. Its not quite the epic battle of legend, and is
likely to leave the player feeling underwhelmed and unsatisfied. A
player wants a challenge, and denying them that challenge because of
randomness is unlikely to provide satisfaction.
In the case of Puzzle Quest, whether or not the AI actually was
cheating isn't actually important: what is important is that to some
players, it felt like the AI was cheating. The lucky streaks
gotten by the player are likely to be ignored (due to their expectation
of winning anyway), but having your victory snatched away by a series of
unfortunate dice rolls may seem unfair and punishing.
Fixing Things
So how do we fix things? It might seem like so far all we've really
said is "randomness is bad". And essentially, that's true. We're
professional games designers; we shouldn't be doing things randomly.
Every decision the player makes should be the result of a carefully
crafted experience, and putting in randomness can endanger that.
When we look more closely at it, we realise that randomness can be added for two primary reasons:
To make the outcome unpredictable, or
to generate content.
Let's examine these:
Unpredictable Outcomes
As we talked about in our previous article, players enjoy winning.
As we talked about here, randomness somewhat replaces the need for skill.
Therefore, adding randomness to a game allows (in some sense) bad
players to win against good players. In a game with no randomness, a
good player will always win against a bad player.
Because of this, having this unpredictability can be an important
part of a game: it allows bad players to influence the game, and
(hopefully) become better. If a player is constantly matched up against
superior opponents and is losing, chances are they will quickly lose
interest.
However, good players will often dislike this randomness, and will often be put off by a game which "punishes" their skill.
So how can we fix this?
Well, one option is to have an Elo rating system.
This essentially gives players a number based on their skill level:
beat a grandmaster, and your Elo rating goes up; lose games to newbies,
and your score will probably go down. It originated as a way for chess
players to measure their skill, but many MOBAs (Multiplayer Online
Battle Arenas, such as League of Legends and Defense of the Ancients) do
this, so that when you enter a battle you are (theoretically) placed
with people around the same skill level. Some first-person shooters have
also attempted this, allowing players to rebalance the teams if one
side is continually getting crushed.
Another option would be to have a handicap system.
Not too dissimilar to an Elo system, a handicap system allows players
to give themselves an artificial advantage based on their skill level.
Fighting games will often do this, giving the weaker player a variable
health and damage output bonus. Although a handicap system might not
solve all skill imbalance issues (it's easy to imagine online players
abusing a system like this), it's a good way to allow casual players to
compete more equally with their hardcore friends.
In both these cases, you can reduce the randomness of innate game
elements, leaving the players as the only randomness generators.
Generating Content
The problem with generating content randomly (or gameplay elements,
like in the game of Tetris's falling blocks) isn't so much that we're
generating unpredictable content; it's that, often, certain sequences of
random elements are extremely punishing to the player.
If, in Tetris, the player is waiting for a line block, but we only
generate S blocks for the rest of the game, then the player will have
every right to be annoyed. And, while it's improbable, it can happen.
In other games, such as a dungeon crawl RPG, we might have a 1%
chance of generating a boss every time a monster spawns. If, by chance,
we generate three bosses in a row, then the player might find themselves
in an unwinnable battle.
By the same token, we might go through the game and never generate a
boss. This could make the game incredibly easy, or (if the bosses drop
equipment upgrades) incredibly hard. In either case, randomness has
essentially destroyed our players' enjoyment of the game.
In certain cases, randomness can completely remove a player from the
game. In the popular collectible card game Magic: The Gathering, players
build decks that require a combination of lands (power sources) and
spells to defeat their opponents. The use of lands is an important
balancing mechanism: a simple goblin might require one land in play,
while a mighty dragon might require ten. However, if the player happens
to draw no land cards, then they are unable to play anything; they are
essentially forced to sit there with zero options until their opponent
defeats them. While it's possible to mitigate this to some extent, it's a
serious design flaw that a non-insignificant number of game losses are
the result of bad luck, rather than being outplayed.
People feel loss more strongly than they feel gain. It's an interesting psychological phenomenon; consider these two scenarios:
You are given $1,000. I ask if you want to gamble on a coin toss:
heads you win an extra $1,000, tails you don't. Alternatively, you can
just have an extra $500 (no coin toss required).
You are given $2,000. I ask if you want to gamble on a coin toss: tails you lose $1,000, heads you don't. Alternatively, you can just give back $500 (no coin toss required).
In general,
people tend to take the guaranteed extra $500 in the first case, but
gamble on the coin toss in the second... even though the outcomes for
gambling are the same in each scenario! (Do the maths: whether you
choose to flip the coin or not, and whether the coin comes up heads or
not, the amount of money you end up with in the end is the same
regardless of whether we're talking about Option 1 or Option 2.)
This means that, if you have a mechanic in game which randomly
rewards or punishes they players, the losses will, psychologically,
outweigh the gains. If the gamble is optional then it opens up extra
avenues of gameplay, but a forced gamble will mostly feel like
punishment. A final problem with generating content randomly is that it can be
very difficult to generate content which is interesting. A great example
of this is MMOs; World of Warcraft has dozens upon dozens of dungeons,
each of which can take hours to complete, and weeks to successfully
master. However, once the dungeons are mastered, they (arguably) offer
few variations and little replayability, save for the obvious grind for
equipment. In Anarchy Online, the number of designed dungeons was tiny:
however, players could enter randomised dungeons. In theory, no two
dungeons the players encountered would ever be the same: however, in
practise, every dungeon felt the same. Because dungeons were
randomised, they had no narrative structure or overall design concept.
Instead of feeling unique, every dungeon felt the same.
A lot of this is down to how many rules are put in place and how the
generation is implemented: Nethack and Spelunky both use randomly
generated levels, and have massive fan bases. The generation of rules
for interesting map design is, however, a slightly different issue from
randomly generating gameplay elements, and is probably best left for
another discussion; it suffices to say that a good designer should be
aware of the limitations of generating maps. We can still apply much of
this randomness discussion to the generation of these maps, however.
A More Serious Solution
So where does this leave us? Well, sometimes we can actually just
remove randomness from a game entirely. In the case of an RPG, instead
of spawning a boss 1% of a time, we can spawn them after every 100
kills. In the case of collectible card games, one of Magic's competitors
(Versus system) solved the issue of players needing land by making
every card playable, face-down, as a land instead of a spell; this meant
that you would never find yourself "stuck", while maintaining the
momentum that lands crucially provided.
However, a total removal of randomness can often be an overzealous
case of throwing the baby out with the bath water. In the example of the
RPG, making a boss spawn every 100 kills exactly is likely to make them
too predictable, and when a game gets too predictable, it becomes a
puzzle. A better option would be making a boss spawn somewhere between
every 50 and 150 kills. This means that bosses are still within a random
range (making them hard to predict), but aren't so random you can get
attacked by three at once.
This use of carefully controlled numbers is pseudorandom generation.
There are many ways to do it: in Tetris, if we spawn an L block, then
for the next three blocks we "re-roll the dice" once if an L block is
spawned again. Normally, the L block has a one-in-seven chance of
spawning, but giving it a re-roll makes it a 1/49 chance for those
threee turns. It can still happen, but is much less likely.
This isn't the best way, of course: there are many ways to generate
numbers randomly, ranging from simple re-rolls to weighted random
numbers; plus, sometimes, in a case like Tetris, just leaving it as a
one-in-seven chance to generate any block might be the best option.
If we do use randomness, we also have the opportunity to introduce seeds.
This simply means that the random numbers we use in our game aren't
actually random; the "random" sequence is entirely defined by a number
called a seed. In Tetris, it appears that we can't predict what blocks
are going to fall; however, if we seed a tetris game with the number 42,
and we start off with square, L block, T block, square, then every game
that uses the 42 seed will begin with those blocks. Seeds aren't used
often in gaming (Minecraft and FreeCell being two notable examples), but
can be a nice addition.
The ability to seed randomness comes from the fact that computers
aren't actually capable of generating random numbers: often, they simply
take a base number (such as the time in milliseconds), and then perform
a calculation to get a "random" number. By ensuring the base number is
the same every time, the calculations will give us the same "random"
numbers. On Windows, FreeCell game #11982 is the only impossible game.The alternative to removing randomness is to use more of it. This
might seem crazy initially, but can actually be extremely effective:
roll two dice, add them together, and you have a one in six chance of
getting a seven. Roll 2,000 dice, add them together, divide by 1,000
(and round), and you will almost always get a seven. In this case, using
so much randomness has almost entirely removed randomness.
Summing Up
At the end of the day, randomness isn't inherently evil; it all comes
down to perception. Players want to be be challenged, or to have an
interesting experience, and there's nothing challenging or interesting
about throwing a bunch of random monsters at a player, with little
regard to whether they live or die. By tempering the randomness, we can
craft the results we want, and hopefully make a game which interacts
with the player, rather than ignoring them.
A Few Final Notes About Randomness
Randomness is generally a bad way to solve conflict, because (by its
very nature) it creates unpredictable results. Randomness can also be a
poor way to generate content, but is often the only sensible way to
approach it; imagine designing a Tetris game which had a list of every
block that should drop, in order.
When randomness does occur, it should generally favour the player
(which is hard to achieve in a player vs player environment). If game
elements are generated randomly, they should allow the player to react
to a worst case scenario in a way that still allows a reasonable chance
of success.
However, we have to accept that sometimes randomness is necessary.
RPGs would be a lot less exciting without dice to determine combat.
Board games, in particular, are unlikely to resolve the issue anytime
soon: throwing 1,000 dice and averaging out just isn't practical for a
game of Snakes and Ladders.
And of course, the final big caveat: if we remove randomness entirely, are we making a game, or a puzzle?
Many
gamedevs find that their first game is the hardest one to finish. I've
worked as a consultant on almost a dozen "first" games with budding
independent development companies, and gathered all of the lessons
learned from those experiences here. After going over each question,
I'll also give you a quick anecdote about how I (or someone I worked
with) learned that lesson the hard way. Preview image icon: Question by Henry Ryder, from The Noun Project.
1. What Is My Game's Scope?
Picking too big scope is the most common pitfall that I've seen
developers fall into. You think up this great game idea, eagerly get
started, and soon find yourself with too many features left to add, not
enough of the main game done, and very little time left.
Having a large game scope isn't necessarily a bad thing; if you are
developing for fun, or have a highly organized design system, it can be
great. But for your typical small team that hopes to make a profit from
their pursuit, trying to build a large game can be a early death
sentence.
The easiest way for new developers to get past this phase is to take the time to break down their game design into layers.
When working with developers, I like to have them create a to-do list
of the core game. This consists of everything necessary for the game to
be finished in an acceptable state - no additional features, unneeded
side quests, or extras. This list serves as the absolute minimal amount
that needs to be done by the release date and still allow you to feel
proud of your game.
Then, I have them create a "polish" list. This includes everything
needed to make the game more complete, but not required to make it
playable and fun. Typically these are things such as additional
graphics, transition animations, more animation frames, additional or
ambient sound effects, more audio tracks, improved UI, particle effects,
advanced lighting, and more advanced AI.
The final list, "extras", consists of anything that they would like
to add that can wait to be added in at the end, or skipped if there is
not enough time to implement it. These items typically consist of
unlockables, social or multiplayer features (unless they are an integral
part of the game), additional side quests, and so on.
Lesson Learned: The Fallen
The first major game that I worked on was called The Fallen. I was 16, and two of my friends and myself decided we were going to make the best MMO ever.
Our idea was to have multiple persistent servers with shared
character and enemy instances (so that each server would be a different,
massive area, and herds of the enemies, called "fallen", could migrate
between them), in a giant FPS-meets-RPG built on the Torque engine.
We put a little more than a year's worth of work into the project
before giving up, managing to get two servers up and running, and having
as many as 27 players per day during our fever pitch.
The project was way too large for the three of us to handle on our
own, and the game's quality suffered. There was no audio and barely any
dialogue; the enemies were terribly modeled 3d atrocities; and all of
the players were the standard Torque 3D model.
Every player was the standard Torque model, with different colors for different classes. (Image from Torque's FPS tutorial.)
Had we planned things out prior to getting started, and kept the
scope of our game smaller at the beginning, we might have stood a chance
of finishing it. Instead, we quickly became overwhelmed by a behemoth
of files and badly programmed (and terribly commented) code that only a
group of teenagers can create.
2. Is This Something I Can Do?
The second most common pitfall for projects is being over-ambitious.
When preparing to start a project, ask yourself: is this something
that I can do? Am I able to do tons of 3D programming and collision
detection? Do I know enough about 3D modeling and lighting? Can I make
this within a reasonable time frame?
If you find that you can't, don't be disheartened! Do your research
and see if there are any libraries, APIs, tools, or anything at all that
would make it possible for you to actually finish your game. If you
can't find anything to make your game's creation easier, then consider
simplifying or modifying the design so that it is.
Personally, I like to take more complicated game designs and use them
as end goals, designing a few smaller games leading up to it, each
allowing me to learn a new skill needed to complete the final game. I
urge developers that I work with to do the same. If you want to make a
MMORPG, first try making a standalone RPG, then move onto a game with
basic multiplayer, and then take a stab it. Having those needed skills
under your belt will make developing your game much easier, and means
you're much more likely to finish it!
Lesson Learned: Tiny Hero
EvolvingPoet's first game, Tiny Hero.
When I finally decided to try to make game development into a
full-time pursuit by starting an LLC, I planned on making a game that
was not possible within our current constraints. The problem wasn't that
it was technically challenging; it was more that there was a huge
amount of content that needed to be created and very little time in
which to do so. This ultimately lead to a solid week of crunching and
the production of a sub-par game.
It took weeks for me to try and bury this game, and I still couldn't completely wipe it from the internet.
To make matters worse, having too little time to complete the game
led to us having to spend even more time doing damage control so that we
wouldn't be hated forever in the indie dev community. (That doesn't
actually happen - most everyone involved is super nice.)
3. Can I Afford to Make This?
When making your first game as an independent developer, finances
often get overlooked. For hobbyist developers or those who have another
job to support themselves, this isn't a huge deal, but developers that
quickly jump into being full-time game makers often find themselves in
over their heads.
Assuming that your first game is going to make enough for you to live
on is not a safe bet, and the same goes for your second game as well
(and maybe even a few after that). Being an indie dev means that you are
running a business, and, on average, small businesses take around two
years to become profitable. If you have two years' worth of living saved
up, then this isn't too big of a problem, but for most people, this is a
huge hurdle. It is a good idea to have a part-time or full-time job to
supplement your gamedev income for a while, if only for the safety net
it provides.
Having realistic sales targets is another important caveat. Unless
you are extremely skilled and extremely lucky, you will not be selling
thousands of copies of your game. Depending on the market and scope of
the project your sales targets will vary, but I find that most commonly
developers I work with will be aiming to get around 700 to 1,000 sales.
While these may sound like low numbers, once you actually release your
game you will find out just how difficult those targets are to reach.
If your game has a price point of $10 and you sell it through the
typical online stores, you can expect to bring in around $5,600 from
those 700 sales. Considering that most developers that I work with are
two-person teams who create a game every two to three months, you can
expect maybe $1,250/month per person after expenses. In that time, the
teams are typically working full-time hours (most of the time, many
more). That works out to around $7.80/hour, or just above minimum wage -
and again, that's if you're lucky.
It isn't all doom and gloom, though; you can make a living off of
making independent games, just don't expect to do so with your very
first one. On average, I see that developers start to make a decent wage
from their work at some point around the release of their third game.
Lesson Learned: Zombie MMO
This particular lesson ended up ruining a client of ours. They aimed to create a huge Zombie-based MMO (think DayZ),
and the prototype that they had was great - fun to play, technically
sound, immersive, and all that jazz - but they hadn't done all of their
financial homework. Screenshot from Day-Z, not Zombie MMO.
Through a lack of planning they managed to find themselves ten months
into a project with very little to show, very little money left, ten
angry employees, and one irate investor. They brought me in to try to
clean up the situation, and switching to a more need-based development
approach, the game started to make headway very fast, and even earned a
bit of money through its alpha.
(They then decided that an unrelated factor was the reason for the
improvement, not the change in development systems, and upon changing
back to their previous method of development, quickly found themselves
out of business.
Don't Give Up; Just Be Realistic
These questions are here to help you be realistic in the pursuit of
your first game, not to dissuade you from making it. A lot of would-be
game makers whom I've met hear me talk about these things and decide
that the whole pursuit is just too hard - but it's not! You just need to
prepare yourself and plan ahead so that you can actually finish your
game.
Some relate game development to running a marathon, and that's an apt
metaphor.Running is not something that I have any experience in (most
people would say that I'm phobic to most exercise in general), and if I
tried to run a 5K, there is an extremely small chance that I would
actually finish. Game development works the same way: if you don't build
up your endurance and skill over time, it is going to be an arduous
journey, and you will find yourself either giving up part way through,
or reaching the end exasperated and out of breath.
Conclusion
Making your first game is a monumental step in your development
career, and hopefully getting it done will be a little easier now. If
you keep the game's size reasonable, its complexity within your ability,
and your financial expectations realistic, then there is no reason that
you shouldn't be able to finish.
Now go make some games!
In
this tutorial, we're going to simulate a dynamic 2D body of water using
simple physics. We will use a mixture of a line renderer, mesh
renderers, triggers and particles to create our effect. The final result
comes complete with waves and splashes, ready to add to your next game.
A Unity (Unity3D) demo source is included, but you should be able to
implement something similar using the same principles in any game
engine.
Here's what we're going to end up with. You'll need the Unity browser plugin to try it out. Click to create a new object to drop into the water.
Setting Up Our Water Manager
In his tutorial, Michael Hoffman demonstrated how we can model the surface of water with a row of springs.
We're going to render the top of our water using one of Unity's line
renderers, and use so many nodes that it appears as a continuous wave.
We'll have to keep track of the positions, velocities and
accelerations of every node, though. To do that, we're going to use
arrays. So at the top of our class we'll add these variables:
1
2
3
4
5
float[] xpositions;
float[] ypositions;
float[] velocities;
float[] accelerations;
LineRenderer Body;
The LineRenderer will store all our nodes and outline our body of water. We still need the water itself, though; we'll create this with Meshes. We're going to need objects to hold these meshes too.
1
2
GameObject[] meshobjects;
Mesh[] meshes;
We're also going to need colliders so that things can interact with our water:
1
GameObject[] colliders;
And we'll store all our constants as well:
1
2
3
4
constfloatspringconstant = 0.02f;
constfloatdamping = 0.04f;
constfloatspread = 0.05f;
constfloatz = -1f;
These constants are the same kind as Michael discussed, with the exception of z—this is our z-offset for our water. We're going to use -1
for this so that it gets displayed in front of our objects. (You might
want to change this depending on what you want to appear in front and
behind of it; you're going to have to use the z-coordinate to determine
where sprites sit relative to it.)
Next, we're going to hold onto some values:
1
2
3
floatbaseheight;
floatleft;
floatbottom;
These are just the dimensions of the water.
We're going to need some public variables we can set in the editor,
too. First, the particle system we're going to use for our splashes:
1
publicGameObject splash:
Next, the material we'll use for our line renderer (in case you want
to reuse the script for acid, lava, chemicals, or anything else):
1
publicMaterial mat:
Plus, the kind of mesh we're going to use for the main body of water:
1
publicGameObject watermesh:
These are all going to be based on prefabs, which are all included in the source files.
We want a game object that can hold all of this data, act as a
manager, and spawn our body of water ingame to specification. To do
that, we'll write a function called SpawnWater().
This function will take inputs of the left side, the width, the top, and the bottom of the body of water.
(Though this seems inconsistent, it acts in the interest of quick level design when building from left to right).
Creating the Nodes
Now we're going to find out how many nodes we need:
1
2
intedgecount = Mathf.RoundToInt(Width) * 5;
intnodecount = edgecount + 1;
We're going to use five per unit width, to give us smooth motion that
isn't too demanding. (You can vary this to balance efficiency against
smoothness.) This gives us all our lines, then we need the + 1 for the extra node on the end.
The first thing we're going to do is render our body of water with the LineRenderer component:
1
2
3
4
5
Body = gameObject.AddComponent<LineRenderer>();
Body.material = mat;
Body.material.renderQueue = 1000;
Body.SetVertexCount(nodecount);
Body.SetWidth(0.1f, 0.1f);
What we've also done here is select our material, and set it to
render above the water by choosing its position in the render queue.
We've set the correct number of nodes, and set the width of the line to 0.1.
You can vary this depending on how thick you want your line. You may have noticed that SetWidth() takes two parameters; these are the width at the start and the end of the line. We want that width to be constant.
Now that we've made our nodes, we'll initialise all our top variables:
01
02
03
04
05
06
07
08
09
10
11
12
xpositions = newfloat[nodecount];
ypositions = newfloat[nodecount];
velocities = newfloat[nodecount];
accelerations = newfloat[nodecount];
meshobjects = newGameObject[edgecount];
meshes = newMesh[edgecount];
colliders = newGameObject[edgecount];
baseheight = Top;
bottom = Bottom;
left = Left;
So now we have all our arrays, and we're holding on to our data.
Now to actually set the values of our arrays. We'll start with the nodes:
Here, we set all the y-positions to be at the top of the water, and
then incrementally add all the nodes side by side. Our velocities and
accelerations are zero initially, as the water is still.
We finish the loop by setting each node in our LineRenderer (Body) to their correct position.
Creating the Meshes
Here's where it gets tricky.
We have our line, but we don't have the water itself. And the way we
can make this is using Meshes. We'll start off by creating these:
1
2
3
for(inti = 0; i < edgecount; i++)
{
meshes[i] = newMesh();
Now, Meshes store a bunch of variables. The first variable is pretty simple: it contains all the vertices (or corners).
The diagram shows what we want our mesh segments to look like. For
the first segment, the vertices are highlighted. We want four in total.
Now, as you can see here, vertex 0 is the top-left, 1 is the top-right, 2 is the bottom-left, and 3 is the top-right. We'll need to remember that for later.
The second property that meshes need is UVs.
Meshes have textures, and the UVs choose which part of the textures we
want to grab. In this case, we just want the top-left, top-right,
bottom-left, and bottom-right corners of our texture.
1
2
3
4
5
Vector2[] UVs = newVector2[4];
UVs[0] = newVector2(0, 1);
UVs[1] = newVector2(1, 1);
UVs[2] = newVector2(0, 0);
UVs[3] = newVector2(1, 0);
Now we need those numbers from before again. Meshes are made up of
triangles, and we know that any quadrilateral can be made of two
triangles, so now we need to tell the mesh how it should draw those
triangles.
Look at the corners with the node order labelled. Triangle A connects nodes 0, 1 and 3; Triangle B connects nodes 3, 2 and 0. Therefore, we want to make an array that contains six integers, reflecting exactly that:
1
int[] tris = newint[6] { 0, 1, 3, 3, 2, 0 };
This creates our quadrilateral. Now we set the mesh values.
1
2
3
meshes[i].vertices = Vertices;
meshes[i].uv = UVs;
meshes[i].triangles = tris;
Now, we have our meshes, but we don't have Game Objects to render them in the scene. So we're going to create them from our watermesh prefab which contains a Mesh Renderer and Mesh Filter.
Here, we're making box colliders, giving them a name so they're a bit
tidier in the scene, and making them each children of the water manager
again. We set their position to be halfway between the nodes, set their
size, and add a WaterDetector class to them.
Now that we have our mesh, we need a function to update it as the water moves:
You might notice that this function just uses the code we wrote
before. The only difference is that this time we don't have to set the
tris and UVs, because these stay the same.
Our next task is to make the water itself work. We'll use FixedUpdate() to modify them all incrementally.
1
2
voidFixedUpdate()
{
Implementing the Physics
First, we're going to combine Hooke's Law with the Euler method to find the new positions, accelerations and velocities.
So, Hooke's Law is [Math Processing Error], where [Math Processing Error] is the force produced by a spring (remember, we're modelling the surface of the water as a row of springs), [Math Processing Error] is the spring constant, and [Math Processing Error] is the displacement. Our displacement is simply going to be the y-position of each node minus the base height of the nodes.
Next, we add a damping factor proportional to the velocity of the force to dampen the force.
The Euler method is simple; we just add the acceleration to the velocity and the velocity to the position, every frame.
Note: I just assumed the mass of each node was 1 here, but you'll want to use:
1
accelerations[i] = -force/mass;
if you want a different mass for your nodes.
Tip: For precise physics, we would use Verlet integration,
but because we're adding damping, we can only use the Euler method,
which is a lot quicker to calculate. Generally, though, the Euler method
will exponentially introduce kinetic energy from nowhere into your
physics system, so don't use it for anything precise.
Now we're going to create wave propagation. The following code is adapted from Michael Hoffman's tutorial.
Here, we create two arrays. For each node, we're going to check the
height of the previous node against the height of the current node and
put the difference into leftDeltas.
Then, we'll check the height of the subsequent node against the height of the node we're checking, and put that difference into rightDeltas. (We'll also multiply all values by a spread constant).
We can change the velocities based on the height difference
immediately, but we should only store the differences in positions at
this point. If we changed the position of the first node straight off
the bat, by the time we looked at the second node, the first node will
have already moved, so that'll ruin all our calculations.
01
02
03
04
05
06
07
08
09
10
11
for(inti = 0; i < xpositions.Length; i++)
{
if(i > 0)
{
ypositions[i-1] += leftDeltas[i];
}
if(i < xpositions.Length - 1)
{
ypositions[i + 1] += rightDeltas[i];
}
}
So once we've collected all our height data, we can apply it at the
end. We can't look to the right of the node at the far right, or to the
left of the node at the far left, hence the conditions i > 0 and i < xpositions.Length - 1.
Also, note that we contained this whole code in a loop, and ran it
eight times. This is because we want to run this process in small doses
multiple times, rather than one large calculation, which would be a lot
less fluid.
Adding Splashes
Now we have water that flows, and it shows. Next, we need to be able to disturb the water!
For this, let's add a function called Splash(), which
will check the x-position of the splash, and the velocity of whatever is
hitting it. It should be public so that we can call it from our
colliders later.
1
2
publicvoidSplash(floatxpos, floatvelocity)
{
First, we need to make sure that the specified position is actually within the bounds of our water:
We take the position of the splash relative to the position of the left edge of the water (xpos).
We divide this by the position of the right edge relative to the position of the left edge of the water.
This gives us a fraction that tells us where the splash is. For
instance, a splash three-quarters of the way along the body of water
would give a value of 0.75.
We multiply this by the number of edges and round this number, which gives us the node our splash was closest to.
1
velocities[index] = velocity;
Now we set the velocity of the object that hit our water to that node's velocity, so that it gets dragged down by the object.
Note: You could change this line to whatever suits
you. For instance, you could add the velocity to its current velocity,
or you could use momentum instead of velocity and divide by your node's
mass.
Now we want to make a particle system that'll produce the splash. We
defined that earlier; it's called "splash" (creatively enough). Be sure
not to confuse it with Splash(). The one I'll be using is included in the source files.
First, we want to set the parameters of the splash to change with the velocity of the object.
Here, we've taken our particles, set their lifetime so they won't die
shortly after they hit the surface of the water, and set their speed to
be based on the square of their velocity (plus a constant, for small
splashes).
You may be looking at that code and thinking, "Why has he set the startSpeed twice?", and you'd be right to wonder that. The problem is, we're using a particle system (Shuriken,
provided with the project) that has its start speed set to "random
between two constants". Unfortunately, we don't have much access over
Shuriken by scripts, so to get that behaviour to work we have to set the
value twice.
Now I'm going to add a line that you may or may not want to omit from your script:
1
2
Vector3 position = newVector3(xpositions[index],ypositions[index]-0.35f,5);
Shuriken particles won't be destroyed when they hit your objects, so
if you want to make sure they aren't going to land in front of your
objects, you can take two measures:
Stick them in the background. (You can tell this by the z-position being 5).
Tilt the particle system to always point towards the center of your
body of water—this way, the particles won't splash onto the land.
The second line of code takes the midpoint of the positions, moves
upwards a bit, and points the particle emitter towards it. I've included
this behaviour in the demo. If you're using a really wide body of
water, you probably don't want this behaviour. If your water is in a
small pool inside a room, you may well want to use it. So, feel free to
scrap that line about rotation.
Now, we make our splash, and tell it to die a little after the
particles are due to die. Why a little afterwards? Because our particle
system sends out a few sequential bursts of particles, so even though
the first batch only last till Time.time + lifetime, our final bursts will still be around a little after that.
Yes! We're finally done, right?
Collision Detection
Wrong! We need to detect our objects, or this was all for nothing!
Remember we added that script to all our colliders before? The one called WaterDetector?
Well we're going to make it now! We only want one function in it:
1
2
voidOnTriggerEnter2D(Collider2D Hit)
{
Using OnTriggerEnter2D(), we can specify what happens whenever a 2D Rigid Body enters our body of water. If we pass a parameter of Collider2D we can find more information about that object.
Now, all of our colliders are children of the water manager. So we just grab the Water component from their parent and call Splash(), from the position of the collider.
Remember again, I said you could either pass velocity or momentum, if
you wanted it to be more physically accurate? Well here's where you
have to pass the right one. If you multiply the object's y-velocity by
its mass, you'll have its momentum. If you just want to use its
velocity, get rid of the mass from that line.
Finally, you'll want to call SpawnWater() from somewhere. Let's do it at launch:
1
2
3
4
voidStart()
{
SpawnWater(-10,20,0,-10);
}
And now we're done! Now any rigidbody2D with a collider that hits the water will create a splash, and the waves will move correctly.
Advertisement
Bonus Exercise
As an extra bonus, I've added a few lines of code to the top of SpawnWater().
These lines of code will add a box collider to the water itself. You
can use this to make things float in your water, using what you've
learnt.
You'll want to make a function called OnTriggerStay2D() which takes a parameter of Collider2D Hit.
Then, you can use a modified version of the spring formula we used
before that checks the mass of the object, and add a force or velocity
to your rigidbody2D to make it float in the water.
Make a Splash
In this tutorial, we implemented a simple water simulation for use in
2D games with simple physics code and a line renderer, mesh renderers,
triggers and particles. Perhaps you will add wavy bodies of fluid water
as an obstacle to your next platformer, ready for your characters to
dive into or carefully cross with floating stepping stones, or maybe you
could use this in a sailing or windsurfing game, or even a game where
you simply skip rocks across the water from a sunny beach. Good luck!