practical 2 jim finnis ([email protected]) · 2016. 11. 15. · possible models 8x8 array of...
TRANSCRIPT
This week
● Solution to last week and discussion● Global variables and the model● The Main Loop pattern● States and the State Machine pattern● Random numbers● “Debounced” user input
The previous worksheet
● A common solution:
Go left using for.. loop
Go right using for.. loop
loop()
A good first solution, but...
● Difficult to modify● Each inner loop has to have its own copy of
some of the code– Clear-draw-swap
– In more complex programs this gets really nasty
● When adding new stuff it's easy to forget to add some of that framework code.
Don't repeat yourself
● If you write code that does something, don't just copy and paste it to do that thing again.
● Make the thing into a function.● If you want to do a slightly different thing, make
that function take arguments to change its behaviour.
● This makes your code much easier to modify and extend.
Use variables
● Instead of writing code that just does one thing (move left eight steps, for example),
● write one piece of code that does one thing several different ways using variables.
● Be lazy – don't write it five times, each slightly different. Write it just once, a bit more cleverly.
The Simplest Thing
● But don't be too clever – always think “What's the simplest thing that can possibly work?”
● And one piece of code is always simpler than five.
void setup(){ AberLED.begin();}
void loop(){ int x; for(int x=0;x<8;x++){ AberLED.clear(); AberLED.set(x,4,RED); AberLED.swap(); delay(300); } for(int x=7;x>=0;x){ AberLED.clear(); AberLED.set(x,4,RED); AberLED.swap(); delay(300); }}
void setup(){ AberLED.begin();}
int x=0;int dx=1;
void loop(){ AberLED.clear(); AberLED.set(x,4,RED); AberLED.swap(); x = x+dx; if(x==0 || x==7){ dx = dx; } delay(300);}
Global variables
● Are bad.● Unless you're storing data which is genuinely
global – which the whole program needs access to
● Such as the “game state” - the data about the game's “world” - also called the model.
● Object-oriented languages like C++ and Java have helpful ways of dealing with global data.
The Model
● Your game models (makes an abstract “picture” of) a virtual world – this is true of all games.
● This world might be very complex, with hundreds of entities made up of moving parts, as in a modern AAA title
● Or it might be very simple, like noughts and crosses.
● But there's always a model – the game's representation of the world as data.
The Model
● First-person shooters– The environment, consisting of 3D shapes, some of
which may be free to move
– Enemies and other creatures, which have a position and some internal state (pose data, weaponry, ammo, alert status, velocity)
– Objects in the environment, such as ammo packs, quest items etc.
– You and equipment you're carrying/wielding
Simpler models
● Chess– An 8x8 grid
– A set of pieces
● Noughts and crosses (Tic-Tac-Toe)– A 3x3 grid
– Each part of which can be marked X, O or empty
Step 1 in game development:define the model
● What is your environment (the unchanging parts of the world, the “game board”)?
● What are your entities (things in the world)?● How should you represent them as data?
– Variables, arrays, structures...
An example
● Chess is a fairly simple game, model-wise● How would you represent the state of the
game inside the computer?
Possible models
● 8x8 array of characters, each encoding a piece (e.g. “p” for pawn and “q” for queen, using upper and lower case for black and white)
● Two arrays of “piece” structures, one for each player, each containing– Whether the piece has been taken
– X,Y position if not taken
– The index of the piece in the array determines its type, e.g. whitePieces[0] is the white king, and so on.
Which is best?
● It's a good idea to design your model so that invalid states are impossible to represent. This really helps prevent bugs.– The second example is more complex, but does this quite well.
● On the other hand, it should be easy to perform the operations on the model which the game requires – moving pieces, etc.
● On the third hand, it should be easy to display the model to the user – to render it.
● For those who know chess: what is the problem with the second model?
● It's all about compromises.
Making it move: the Main Loop
● Pretty much every game has one of these, even if it's hidden inside the engine
● It's an example of a design pattern – a particular way of solving a problem, more general than an algorithm.– You'll see more design patterns in your second year.
● This pattern is very specific to real-time graphics (games etc.)
● (many engines hide the main loop from you)
The Main Loop
Read input and update model accordingly
Update model for time step
Render model
e.g. player presses FIRE, so create new missile heading in the
player's direction
e.g. for all entities, add the velocity to the position, and
do collision checks
e.g.● Clear buffer● Draw environment● Scan through entities
and draw them● Draw HUD● Flip buffers
Main Loop: handling user input
● Read switches and other inputs.● Process these inputs by modifying the model.● Must not block – that is, it must not wait for
anything (e.g. user input). It runs as fast as it can.– delays are blocking. We'll talk about these later.
– As an aside, all console games will fail product testing if the screen freezes for more than a fraction of a second.
Main loop: updating
● Change the game model (i.e. the game data variables) in other ways than user input changes.
● Typically these are changes due to time or game AI.
● Examples:– Moving objects (player, enemy, bullets..) through the
world by adding their velocity to their position
– See if any of the enemies have noticed the player and change their behaviour if so
Main loop: rendering
● Drawing things to the screen representing the model.
● Doesn't change the model
Keep everything separate
● The update and user input code shouldn't do any rendering
● The rendering code shouldn't do any updating● This helps keep your code easy to maintain
– You know where the code which draws stuff will be
– You know where the code which changes stuff will be
● An example framework for a Main Loop application.
● You need to add:
– Model data
– Set-up code
– Input, update and rendering code
void setup(){ AberLED.begin(); ... // set up initial state}
void handleInput(){ ...}
void update(){ ...}
void render(){ AberLED.clear(); ... AberLED.swap();}
// do not modify below here
void loop(){ handleInput(); update(); render();}
Timers
● You shouldn't ever use delay() - why not?● The millis() function will return the number of
milliseconds since the program started.● You can use this to find the number of
milliseconds since something happened.
Timers
● Here's some code that will make something happens 2 seconds after something else:
as a global (part of your model):
unsigned long eventTime=0L;
starting the timer:
eventTime = millis()+2000;
somewhere else:
if((0L != eventTime) && (millis() > eventTime)){eventTime=0L; // reset the timer… do something
}
States
● Your game will probably have several different phases:– menu, gameplay, winning/losing screen, high score
table, etc...
● In each of these, render(), update() and handleInput() need to do something completely different.
● The code for this can get very messy.
State Machines
● We use the idea of a state machine to deal with this.
● Each different phase of the program is called a state
● The state machine can be in one of states, and can move to other states.
State Machines
● Have a variable (usually an int) with the current state
● Code in the main loop can then look at the state variable (using if or switch) to decide what to do
● The main loop update can change the state under well-defined conditions
● Like Main Loop, it's another design pattern
Shows only the game
Shows only the menu
State Diagrams
MENU
FADE UP
FADEDOWN
GAMESTARTShow both menu and
game
On “OK” pressed On fade up complete
On game exitOn fade down
complete
Show the states, and how states move to other states
State machine code
● A #define for each state● Global state variable initially invalid (-1)● gotoState(int s) goes to state s (by setting the state
variable to s), and stores the current time● GetStateTime() returns the current time minus the time
of the last call to gotoState()● You add switches and conditional code to update() etc.
which uses the state variable to decide what to do● This code is provided on the worksheet.
Work through an example
Random Numbers
● Are very important in games!● random(max) returns a random number from 0
to max-1● So random(10) returns 0,1,2,3,4,5,6,7,8 or 9
– Note that you get numbers up to max-1, not max!
– What would you use to simulate a six-sided dice?
Random Numbers
● Performing an event sometimes, but not always:
● random(10) will return 0 one time in ten● So if(0==random(10)) { … } will run the code in
the brackets one time in ten● This is useful for doing things at random
intervals in your update● You'll find this handy for the assignment
A Bit More User Input
● Finding out when a button has been pressed● You need to compare the previous state of the
button with the current state● But buttons are bouncy!
Debouncing
Button on
Button off
We want to ignore all these
Button has been down for long enough for it to count
getButtonDown(int n)
● Luckily I've written code which does this for you!
● Call getButtonDown(int b) to find out whether a button has gone down since the last interrupt, and has been down for at least 20 consecutive interrupts.
The assignment
● You should now know everything you need to do the assignment
● Design your model● Structure the main loop as described: handleInput(),
update() and render() functions all called from loop()● Use a state machine and switch statements inside
your main loop functions● Don't use a delay – use getStateTime() or millis()
(see this week's worksheet)