three a simple 2d game engine: input handling and collision detection
TRANSCRIPT
Getting input from the user
We want to keep track of Where the mouse is Whether a mouse button has
been pressed What keys have been pressed
We’ll do this by storing the information in global variables
For mouse position and state And a list of states (up/down)
for each key
[define mouse-location [point 0 0]]
[define mouse-in-window? false]
[define mouse-pressed? false]
[define key-states [Array.CreateInstance Boolean 256]]
[define key-pressed?[keyvalue →
[get key-states keyvalue]]]
Event handling
The operating system tells you what the user is doing by signaling events KeyDown/KeyUp
A key went down/up on the keyboard MouseDown/MouseUp
A button went up/down on the mouse MouseMove
The mouse moved inside the window MouseEnter/MouseLeave
The user dragged the mouse into/out of the window
So we need to set up handlers for them
Setting up input handling
First, we initialize all those global variables Set all the elements of
the list of key states to false (i.e. not pressed)
Assume the mouse buttons are up
Assume the mouse is out of the window
[define initialize-input [form →
[up-to 256
[i → [[get key-states i] ← false]]]
[mouse-location ← [point 0 0]]
[mouse-down? ← false]
[mouse-in-window? ← false] … ]]
Setting up event handlers
When the MouseEnter event happens Set mouse-in-window? to
be true When MouseLead
happens Set it to be false
[define initialize-input [form → … [form.MouseEnter.Add
[ignore ignore → [mouse-in-window? ← true]]] [form.MouseLeave.Add …] [form.MouseMove.Add
[ignore e → [update-mouse e]]] [form.MouseDown.Add
[ignore e → [mouse-down? ← true] [update-mouse e]]] [form.MouseUp.Add …]]
Setting up event handlers
When MouseMove happens The OS passes a
MouseEventArgs object This object contains the
current coordinates of the mouse
So call update-mouse to read the coordinates out of the object and update the mouse-location variable
[define initialize-input [form → … [form.MouseEnter.Add
[ignore ignore → [mouse-in-window? ← true]]] [form.MouseLeave.Add …] [form.MouseMove.Add
[ignore e → [update-mouse e]]] [form.MouseDown.Add
[ignore e → [mouse-down? ← true] [update-mouse e]]] [form.MouseUp.Add …]]
update-mouse
The .X and .Y fields of the MouseEventArgs object give the location of the mouse
Make a point out of them and update mouse-location
[define update-mouse [e →
[mouse-location ← [point e.X e.Y]]]]
Setting up event handlers
MouseDown/MouseUp events are the same way The MouseEventArgs
object also has a field called Button that tells which button was pressed
For simplicity, we’ll ignore that
Update the mouse-down? Variable
Call update-mouse to update mouse-location
[define initialize-input
[form → … [form.MouseDown.Add
[ignore e → [mouse-down? ← true] [update-mouse e]]] [form.MouseUp.Add …]]
Setting up the keyboard handlers
The OS passes a KeyEventArgs object to keyboard handlers It’s KeyValue field has
a number corresponding to the key that was pressed
[define initialize-input [form →
…
[form.KeyDown.Add [ignore e → [set-key-state e.KeyValue true]]]
[form.KeyUp.Add [ignore e → [set-key-state e.KeyValue false]]] …]]
Implementing set-key-state
Basically, we Take the key number
(keyvalue) And set the corresponding
element of key-states to state (true or false)
However, we need to do the bitwise-and thingy to “fix” the number We’ll explain this later For now, trust me
[define set-key-state[keyvalue state →
[[get key-states
[bitwise-and keyvalue 255]]
← state]]]
Getting the magic key numbers
There’s an object called Keys It has fields with all the key
numbers They’re represented as a
funny kind of object So you need to do the
Convert.ToInt32 thing to make them “real” numbers
You can get any keyboard key you want this way (e.g. using Keys.A for the A key)
[define up-arrow[System.Convert.ToInt32 Keys.Up]]
[define down-arrow[System.Convert.ToInt32 Keys.Down]]
[define left-arrow[System.Convert.ToInt32 Keys.Left]]
[define right-arrow[System.Convert.ToInt32 Keys.Right]]
Example: controlling a car
A simple car that you can steer with the arrow keys Up – go forward Down – go back Left, right - steer
Example: controlling a car tank
A simple car that you can steer with the arrow keys
Unfortunately, it’s hard to tell which way the car is facing
So we’ll use a tank
The Tank class
Basically like the Obstacle class, but a slightly different draw method
[define Tank [class [Tank position]
GameObject]]
[define-method [draw [Tank t] g]
[g.DrawRectangle black-pen −12 −10 24 20]
[g.DrawRectangle black-pen 12 −2 10 4]]
Controlling speed
Simple version: If they hold down the
up-arrow key Move right at 10 pixels
per second
[define-method [tick [Tank t] Δt]
[t.velocity ←
[if [key-pressed? up-arrow]
[point 10 0]
[point 0 0]]]]]
Controlling speed
Simple version: If they hold down the
up-arrow key Move right at 10 pixels
per second Slightly fancier
Let them move backward
[define-method [tick [Tank t] Δt]
[t.velocity ←
[if [key-pressed? up-arrow]
[point 10 0]
[if [key-pressed? down-arrow] [point -10 0]
[point 0 0]]]]]
Controlling speed
Simple version: If they hold down the
up-arrow key Move right at 10 pixels
per second Slightly fancier
Let them move backward
Oh, yea. Let them steer.
[define-method [tick [Tank t] Δt]
[t.velocity ← [rotate-vector-degrees
[if [key-pressed? up-arrow] [point 10 0] [if [key-pressed? down-arrow]
[point -10 0] [point 0 0]]]
t.orientation]]]
Controlling speed
Writing it this way makes it easier to change the speed if we want to Only one number to
change
[define-method [tick [Tank t] Δt]
[t.velocity ← [× 10
[if [key-pressed? up-arrow]
1
[if [key-pressed? down-arrow]
-1
0]]
[rotate-vector-degrees [point 1 0]
t.orientation]]]]
Steering
To steer Just do the same trick
with the angular velocity
Only, remember it’s a number, not a vector
[define-method [tick [Tank t] Δt]
[t.velocity ← ...]
[t.angular-velocity ← [× 30
[if [key-pressed? left-arrow]
-1
[if [key-pressed? right-arrow]
1
0]]]]
Sticking to the mouse
We can force an object to follow the mouse by just setting its position to the mouse position
[define dragger [class [dragger position]
GameObject]]
[define-method [draw [dragger o] g]
[g.DrawEllipse black-pen −20 −20 40 40]]
[define-method [tick [dragger o] Δt]
[o.position ← mouse-location]]
Following the mouse
We can make the object follow the mouse by setting its speed to
always be some fraction of the vector between the object and the mouse
[define Mouser [class [Mouser position]
GameObject]]
[define-method [draw [Mouser o] g]
[g.DrawEllipse black-pen −20 −20 40 40]]
[define-method [tick [Mouser o] Δt]
[o.velocity ← [× [− mouse-location
o.position]
0.5]]]
Making it more interesting
We can complicate the dynamics of the ball by: Accelerating it toward
the mouse
[define SpringMouser[class [SpringMouser position]
GameObject]]
[define-method [tick [SpringMouser o] Δt]
[o.velocity ← [+ o.velocity
[× [− mouse-location
o.position]
0.1]
[× −0.001 o.velocity]]]]
Making it more interesting
We can complicate the dynamics of the ball by: Accelerating it toward
the mouse Rather than moving
straight toward it
[define SpringMouser[class [SpringMouser position]
GameObject]]
[define-method [tick [SpringMouser o] Δt]
[o.velocity ← [+ o.velocity
[× [− mouse-location
o.position]
0.1]
[× −0.001 o.velocity]]]]
Making it more interesting
We can complicate the dynamics of the ball by: Accelerating it toward
the mouse Rather than moving
straight toward it And adding some
damping to slow it down
[define SpringMouser[class [SpringMouser position]
GameObject]]
[define-method [tick [SpringMouser o] Δt]
[o.velocity ← [+ o.velocity
[× [− mouse-location
o.position]
0.1]
[× −0.001 o.velocity]]]]
Changing the appearance slightly
We can make a filled circle rather than a normal circle
By substituting FillEllipse
For DrawEllipse
And a Brush object For a Pen
[define green-brush [new SolidBrush [color “green”]]]
[define-method [draw [SpringMouser o] g]
[g.FillEllipse green-brush
−20 −20 40 40]]
Collision handling
Many games need to notice when two objects collide This is an expensive
operation It’s an on-going area
of research We’ll just talk about
the absolute dumbest version of it
[define update-objects[form →
[with* current-time = DateTime.Now Δt = [current-time.Subtract
last-time].TotalSeconds [for-all-game-objects [o → [tick o Δt]]] [for-all-game-objects
[o → [o.position ← [+ o.position [× o.velocity Δt]]] [o.orientation ← [+ o.orientation [× o.angular-
velocity Δt]]]]]
[check-collisions] [last-time ← current-time] [form.Invalidate]]]]
Naïve algorithm
Compare every object to every object
Check if they’ve collided Deal with it if they have
Notes: We haven’t said how to
check whether two objects have collided
Or what to do about it
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Computational complexity
Suppose there are n objects in the game
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Computational complexity
Suppose there are n objects in the game Then this procedure
runs n times
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Computational complexity
Suppose there are n objects in the game Then this procedure
runs n times But each time it runs,
this procedure runs n times!
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Computational complexity
Suppose there are n objects in the game Then this procedure
runs n times But each time it runs,
this procedure runs n times!
So that means we do n2 collision checks
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Computational complexity
Suppose there are n objects in the game Then this procedure
runs n times But each time it runs,
this procedure runs n times!
So that means we do n2 collision checks
Very expensive
[define check-collisions [→ [for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2] ]]]]]]]
Computational complexity
The time complexity of a program is The rate at which the running time
increases With the size of its input
A linear algorithm increases about as fast as n
We then say the algorithm is O(n) A quadratic algorithm increases as
fast as n2
We then say it’s O(n2) An exponential time algorithm
increases exponentially with n Exponential algorithms are useless
unless n is very small
0
10
20
30
40
50
60
70
80
90
100
1 2 3 4 5 6 7 8 9 10
2 n̂
n 3̂
n 2̂
n
Problems with the algorithm
This actually has some bugs
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Problem 1
This will check whether each object has collided with itself It probably has …
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Problem 1
For any two objects, x and y, it will call both [collided? x y], and [collided? y x]
[define check-collisions [→
[for-all-game-objects
[o1 → [for-all-game-objects
[o2 → [if [collided? o1 o2]
[handle-collision o1 o2]
]]]]]]]
Fancy version
This version only compares objects Against objects that are later
in the list of game objects So we only compare each
pair once
However, it turns out it still has some issues, so the version we hand out may be different
[define check-collisions[→
[if collision-detection-enabled?
[with i = 0
[while [< i [length all-game-objects]]
[with o1 = [get all-game-objects i]
j = [+ i 1]
[while [< j [length all-game-objects]]
[with o2 = [get all-game-objects j]
[if [collided? o1 o2]
[handle-collision o1 o2]]
[j ← [+ j 1]]]]]
[i ← [+ i 1]]]]]]]
Adding collision handling to the SpringBall example
Two balls have collided if
[define collided? [o1 o2 →
[< [magnitude [− o1.position
o2.position]]
10]]]]
[define-method [handle-collision [SpringBall o1] [SpringBall o2]]
[destroy o1]
[destroy o2]]
Adding collision handling to the SpringBall example
Two balls have collided if The length
[define collided? [o1 o2 →
[< [magnitude [− o1.position
o2.position]]
10]]]]
[define-method [handle-collision [SpringBall o1] [SpringBall o2]]
[destroy o1]
[destroy o2]]
Adding collision handling to the SpringBall example
Two balls have collided if The length Of the space between
them
[define collided? [o1 o2 →
[< [magnitude [− o1.position
o2.position]]
10]]]]
[define-method [handle-collision [SpringBall o1] [SpringBall o2]]
[destroy o1]
[destroy o2]]
Adding collision handling to the SpringBall example
Two balls have collided if The length Of the space between
them Is less than 10 pixels
[define collided? [o1 o2 →
[< [magnitude [− o1.position
o2.position]]
10]]]]
[define-method [handle-collision [SpringBall o1] [SpringBall o2]]
[destroy o1]
[destroy o2]]
Adding collision handling to the SpringBall example
When two ball collide [define collided? [o1 o2 →
[< [magnitude [− o1.position
o2.position]]
10]]]]
[define-method [handle-collision [SpringBall o1] [SpringBall o2]]
[destroy o1]
[destroy o2]]