-
Notifications
You must be signed in to change notification settings - Fork 2
Sprites in Motion
This is a tutorial for using the sprite functions; as an example we will write a game.
Press shift-return on each example to follow along.
Lets start with a static scene; the hero at the bottom of the screen.
The invaders are in a line at the top.
(clear-image 0.0 0.0 0.0 1.0)
(load-sprites "images/bg1.jpg" 0)
(load-sprites "images/h1.png" 1)
(load-sprites "images/D-17.png" 20)
(draw-sprite 0 0.0 10.0)
(draw-sprite 1 400.0 460.0)
(for x from 120.0 to 700.0 step 88.0
(draw-scaled-rotated-sprite
20 x 20.0 0.0 0.65) )
(show 1)
That is essentially a picture; there is nothing animated.
We certainly need the hero ship to move from left to right when the player presses a key.
Arranging things into functions helps.
(define load-all-sprites
(lambda ()
(load-sprites "images/bg1.jpg" 0)
(load-sprites "images/h1.png" 1)
(load-sprites "images/D-17.png" 20)))
(define clear-scene
(lambda ()
(clear-image 0.0 0.0 0.0 1.0)
(draw-sprite 0 0.0 10.0)))
(define draw-invaders
(lambda (x y)
(for x-pos from x to 700.0 step 88.0
(draw-scaled-rotated-sprite
20 x-pos y 0.0 0.65))))
(define draw-hero
(lambda (x)
(draw-sprite 1 x 460.0)))
The same scene is now drawn using the more meaningful functions.
(load-all-sprites)
(clear-scene)
(draw-invaders 120.0 20.0)
(draw-hero 400.0)
(show 0)
We can see that drawing the scene will require the hero and invaders positions at least.
(define draw-scene
(lambda (hx ix iy )
(clear-scene)
(draw-invaders ix iy)
(draw-hero hx)))
Now it is easy to try the scene out with different positions.
(draw-scene 140.0 168.0 20.0)
(show 0)
It is easy now to test the aliens sweeping straight down the screen
(for y from 20.0 to 400.0 step 0.5
(draw-scene 400.0 100.0 y ) (show 0))
The position of the hero though is going to depend on a key press by the player.
So we should define a variable for that.
(define h-x 400.0)
h-x will be the hero placement.
As we set through each frame of the game; we will need to read the keys the player pressed.
Look at the results from (graphics-keys)
(graphics-keys)
Click on the image pane at the top so it has focus and press left arrow.
See that graphics keys now shows
(left . #t).
A way to check for this and change the h-x variable is as follows
(when
(and (< (cdr (assq 'recent keys)) 50) (cdr (assq 'left keys)))
(set! h-x (- h-x 1.0)))
In english "when the left key was pressed in the last 50 ms move the hero to the left."
It makes sense to write a function to check for the keys we can add more checks later.
To recap a bit our program now looks like this
;; example 1 - move hero with arrow keys.
(define h-x 400.0)
(define load-all-sprites
(lambda ()
(load-sprites "images/bg1.jpg" 0)
(load-sprites "images/h1.png" 1)
(load-sprites "images/D-17.png" 20)))
(define clear-scene
(lambda ()
(clear-image 0.0 0.0 0.0 1.0)
(draw-sprite 0 0.0 10.0)))
(define draw-invaders
(lambda (x y)
(for x-pos from x to 700.0 step 88.0
(draw-scaled-rotated-sprite
20 x-pos y 0.0 0.65))))
(define draw-hero
(lambda (x)
(draw-sprite 1 x 460.0)))
(define draw-scene
(lambda (hx ix iy )
(clear-scene)
(draw-invaders ix iy)
(draw-hero hx)))
(define check-keys
(lambda (keys)
(when (and (< (cdr (assq 'recent keys)) 50)
(cdr (assq 'left keys)))
(set! h-x (- h-x 1.0)))
(when (and (< (cdr (assq 'recent keys)) 50)
(cdr (assq 'right keys)))
(set! h-x (+ h-x 1.0)))))
(load-all-sprites)
(define game-step
(lambda ()
(check-keys (graphics-keys))
(draw-scene h-x 100.0 20.0)))
(game-step)(show 0)
We have a definition for game-step that moves the hero when the arrow keys are pressed.
To test this we place game step into a timer so it gets repeated every 60 ms.
(set-every-function 1000 33 0
(lambda ()
(game-step)(gc)))
This really appears to do nothing; however it is redrawing the scene every 33 ms.
If you now focus on the image pane the hero ship moves left and right when you use the arrow keys.
You may spot that seems a bit slow.
You can tune that by changing this function; lets make it move by 4 instead of by 1.
(define check-keys
(lambda (keys)
(when (and (< (cdr (assq 'recent keys)) 50)
(cdr (assq 'left keys)))
(set! h-x (- h-x 4.0)))
(when (and (< (cdr (assq 'recent keys)) 50)
(cdr (assq 'right keys)))
(set! h-x (+ h-x 4.0)))))
It seems like it would be cheating to let the player move off the screen; away from any future bombs.
Lets restrict the players movement now.
Move the player as far as reasonable to the left; and then look at the h-x variable.
h-x
It looks like it should go no lower than 1.0
The other side is less obvious; move as far as reasonable to the right; and look at h-x again.
I make that 713.0 on the right.
(define check-hx
(lambda ()
(when (< h-x 1.0) (set! h-x 1.0))
(when (> h-x 713.0) (set! h-x 713.0))))
In the game-step after checking the keys we can check h-x.
Before we draw the scene.
(define game-step
(lambda ()
(check-keys (graphics-keys))
(check-hx)
(draw-scene h-x 100.0 20.0)))
The movement is now checked.
Be nice to animate the enemy ships a little.
These deserve to be animated individually rather than as a row.
So we need to track them; perhaps in a list.
First lets define an enemy by creating a new-enemy.
(define new-enemy
(lambda (x y xn yn s)
(list (list (list x y) (list xn yn) s))))
Where
x y are initial position
xn yn are offsets to move the sprite
s is the sprite bank to plot
A list of enemies is needed; we can start with two
(define enemies '())
(set! enemies
(append enemies
(new-enemy 10.0 20.0 1.0 0.0 20)))
(set! enemies
(append enemies
(new-enemy 120.0 20.0 1.0 0.0 20)))
We added one enemy to test.
We want to display the list of enemies.
To do that we use map to run the draw-scaled-rotated-sprite function to each enemy in the list.
(define draw-invaders
(lambda ()
(map (lambda (e)
(apply
draw-scaled-rotated-sprite
(list (caddr e) (caar e) (cadar e) 0.0 0.65)))
enemies)))
(define draw-scene
(lambda ( hx )
(clear-scene)
(draw-invaders)
(draw-hero hx)))
(define game-step
(lambda ()
(check-keys (graphics-keys))
(check-hx)
(draw-scene h-x)))
Changes to draw-scene and game-step are needed as draw-scene no longer needs three arguments.
This should now draw two enemies and the hero.
(game-step)(show 0)
We actually want to move the enemies.
A function can move an enemy by updating the x and y position
(define move-enemy
(lambda (e)
(let* ([xy (car e)]
[xn (cadr e)]
[newxy (map (lambda (x y) (+ x y)) xy xn)]
[s (caddr e)])
(list newxy xn s))))
xn and s are not changed
newxy is updated from xy + xn.
If we can move one enemy we can move them all.
(define move-enemies
(lambda ()
(set! enemies
(map (lambda (e)
(move-enemy e)) enemies))))
(define game-step
(lambda ()
(check-keys (graphics-keys))
(check-hx)
(move-enemies)
(draw-scene h-x)))
Time to recap again; our code now looks like this.
;; example 2 - enemy list
(define h-x 400.0)
(define load-all-sprites
(lambda ()
(load-sprites "images/bg1.jpg" 0)
(load-sprites "images/h1.png" 1)
(load-sprites "images/D-17.png" 20)))
(define clear-scene
(lambda ()
(clear-image 0.0 0.0 0.0 1.0)
(draw-sprite 0 0.0 10.0)))
(define new-enemy
(lambda (x y xn yn s)
(list (list (list x y) (list xn yn) s))))
(define enemies '())
(define enemy-wave-one
(lambda ()
(for y from -80.0 to -1200.0 step -300.0
(set! enemies
(append enemies
(new-enemy 10.0 y 0.0 1.0 20)))
(set! enemies
(append enemies
(new-enemy 110.0 y 0.0 1.2 20)))
(set! enemies
(append enemies
(new-enemy 210.0 y 0.0 1.0 20)))
(set! enemies
(append enemies
(new-enemy 350.0 y 0.0 1.0 20)))
(set! enemies
(append enemies
(new-enemy 450.0 y 0.0 1.2 20)))
(set! enemies
(append enemies
(new-enemy 550.0 y 0.0 1.0 20)))
))
(define draw-invaders
(lambda ()
(map (lambda (e)
(apply
draw-scaled-rotated-sprite
(list (caddr e) (caar e) (cadar e) 0.0 0.65)))
enemies)))
(define move-enemy
(lambda (e)
(let* ([xy (car e)]
[xn (cadr e)]
[newxy (map (lambda (x y) (+ x y)) xy xn)]
[s (caddr e)])
(list newxy xn s))))
(define move-enemies
(lambda ()
(set! enemies
(map (lambda (e)
(move-enemy e)) enemies))))
(define draw-hero
(lambda (x)
(draw-sprite 1 x 460.0)))
(define check-hx
(lambda ()
(when (< h-x 1.0) (set! h-x 1.0))
(when (> h-x 713.0) (set! h-x 713.0))))
(define draw-scene
(lambda ( hx )
(clear-scene)
(draw-invaders)
(draw-hero hx)))
(define check-keys
(lambda (keys)
(when (and (< (cdr (assq 'recent keys)) 50)
(cdr (assq 'left keys)))
(set! h-x (- h-x 1.0)))
(when (and (< (cdr (assq 'recent keys)) 50)
(cdr (assq 'right keys)))
(set! h-x (+ h-x 1.0)))))
(load-all-sprites)
(enemy-wave-one)
(define game-step
(lambda ()
(check-keys (graphics-keys))
(check-hx)
(move-enemies)
(draw-scene h-x)))
And can be run with the timer function.
(set-every-function 1000 33 0
(lambda ()
(game-step)(gc)))