Time for exploration of advanced animation. I gave you a taste for it in the lesson where I covered iteration. is not that hard – actually very easy – until you want to get more than one object interacting in an animated world.
In this lesson, you will learn
- how to do something when a key is pressed.
- how to use multiple turtles to do multiple things in parallel, so that they appear to be performed at the same time.
We will write a program that consists of a cannon, a few (reusable) projectiles, and a flying saucer – each implemented with its own turtle. As you will see, the graphics is easy, getting them to work together in our simulated environment is the hard part.
|alt=”Your browser understands the <APPLET> tag but isn’t running the applet, for some reason.” Your browser is completely ignoring the <APPLET> tag!|
Use the left- and right-arrow keys to move the cannon (the equilateral triangle) and the up-arrow key to fire.
Animate a Projectile
Start entering this lesson’s program; I called my program SpaceWar.tt. First write a procedure named tracer which has two inputs: an x coordinate and a y coordinate which are the starting point for the projectile. It should animate a box moving from x,yup and off the visible graphics area.
If you are having trouble, review the animation part of the lesson on iteration.
Responding to Keyboard Events – the keyPressed Procedure
Similar to the way mouseClicked events are available to you, the TurtleGraphics applet and application receive events when a key on the keyboard is pressed. If you enter a procedure with the name keyPressedthat has one input (a number), it will be performed when a key is pressed.
Use the TurtleGraphics application (TG) or go back up to the applet and type in the following definition of keyPressed.
to keyPressed :num if equal? :num 129 [ println "left-arrow stop ] if equal? :num 130 [ println "right-arrow stop ] println :num end
Once you have this entered, remember to click the mouse in the graphics window, and then try out the left- and right-arrow keys. What happens when you press the up- and down-arrow keys?
Combining keyPressed Events and Animation
Back to the program we are writing for this lesson – use keyPressedevents to move an object (the cannon) right/left across the graphics window. An equilateral triangle is easy to draw. Using the midpoint of the base as the current point, draw a triangle that’s 20 turtle-steps on all sides.
Now, write a keyPressed procedure that moves the triangle left 5 or 10 turtlesteps when the left-arrow is pressed. Once you have this working, enhance keyPressed to move the triangle to the right each time the right-arrow key is pressed.
Finally, add an invocation of the tracer procedure you wrote earlier in this lesson. Change keyPressed so that when the up-arrow key is pressed, a projectile appears to come from the tip of your triangle and travels off the top of the graphics window.
The Turtle Can Only Do One Thing at a Time
So, you got everything I’ve asked you to do working. There’s a very annoying thing that happens… When you press the up-arrow key and a projectile works its way up the graphics window, you can no longer move the triangle. The problem is that the turtle can only do one thing at a time – and it’s in a loop in tracer:
repeat quotient :distance 10 [ fd 10 wait 200 setc 7 bk 10 fd 10 setc 4 ]
- the repeat command completes, at which point
- tracer is complete, and execution of instructions continues back in
- keyPressed, which also is now complete,
will the program be ready to accept another key event. Well, this problem is certainly an annoyance. But worse, our next step is to animate a target across the graphics window. If we animate the target the same way as we did the projectile, we have a real serious problem – the projectile will not move until the target is done moving!
What we need is multiple turtles that each do things independently.
Multiple Turtles – Doing More Than One Thing at a Time
Well, there are two commands that provide interaction with multiple turtles.
The syntax of the newturtle and talkto commands is:
- the command name: “newturtle” or “talkto”
- the <name> of the turtle (note: the name of the initial turtle is “t1”), and
- an optional <List-of-Instructions>
And, the syntax of a <List-of-Instructions> is the same as for the repeat and if commands. To refresh your memory:
- an open square bracket,
- <Instructions>, and
- a close square bracket
Use the TurtleGraphics application (TG) or go back up to the applet and type in the following example which demonstrates the use of the newturtle command.
to c1 repeat 72 [ fd 10 rt 10 wait 1800 ] end to c2 repeat 108 [ fd 10 lt 10 wait 900 ] end to c3 repeat 216 [ fd 10 rt 10 wait 600 ] end newturtle "t2 setc 1 newturtle "t3 setc 4 seth 90 talkto "t1 [c1] talkto "t2 [c2] c3
What the above does is
- define three procedures which draw circles at different speeds, one (c2) in a counter-clockwise direction
- a new turtle named “t2” is created and it becomes the current turtle – the one which will act on input
- the color of our new turtle is changed to blue
- a third turtle is created (named “t3”) and it becomes the current turtle
- its color is changed to red and its heading is changed such that it is facing east,
- since the current turtle is t3, the talkto command is used to tell turtle t1 to invoke c1
- similarly tell turtle t2 to invoke c2
- finally tell the current turtle, t3, to invoke c3
So, you end up with three turtles going round in circles. When they all stop, you can tell them to draw the circles again by typing
clean talkto "t1 [ c1 ] talkto "t2 [ c2 ] c3
Back to this lesson’s SpaceWar program. Create a new turtle in your program to drive the projectile. Please name it “p1” (which is the name I gave mine), an abbreviation for projectile-1. When the up-arrow key event is handled, have this new turtle invoke your tracerprocedure.
Communicating Between Turtles
So, there was one tricky thing you needed to do in the last exercise – how did you tell tracerwhere is should start? In other words, how did you pass it the x,y position of the top tip of the triangle?
If you tried:
talkto "p1 [ tracer xcor ycor ]
it didn’t work! No matter where the cannon (the triangle) was moved to, the first projectile would start at 0,0. The reason is that the xcor and ycor operations return the current location of the current turtle – the projectile turtle (“p1”) – NOT the turtle that’s the cannon (“t1”).
What you need to do is use global variables to pass information from one turtle to another. Here is what I did in my fireCannon procedure (my keyevent procedure invokes fireCannon when it gets an up-arrow event, see Figure 13.1).
to fireCannon make "p1x xcor make "p1y sum ycor 20 talkto "p1 [ tracer :p1x :p1y ] end
What this does is to put the coordinates of the tip of the triangle into a couple of global variables, named p1x and p1y. The turtle performing the fireCannon is the same one that receives the key events and animates the triangle (the cannon). It then tells the projectile-1 turtle (“p1“) to invoke tracerwith references to the contents of these global variables.
Since I’m more concerned here with teaching you programming than anything else, I picked the first object that I thought of that would make a good target (easy to draw) – a flying saucer. And, since you’ve already animated both a triangle (the cannon) and a box (the projectile), I’m going to give you all of my code for the flying saucer.
Just for your information, the first thing I always do in the source code for my programs is to declare all global variables. By doing this, it is obvious to anyone reading my programs what global variables the programs contain. Following the global variables, I declare/create the additional turtles I am going to use. Here’s the new code:
make "p1x 0 ;projectile 1's starting x postion make "p1y 0 ; and its starting y position newturtle "fs [hideturtle] ;flying saucer turtle to black output 0 end to white output 7 end to drawSaucer setpensize 10 penup setpencolor black repeat 12 [forward 10 pendown forward 10 penup back 20 right 30] setpensize 14 pendown forward 50 back 100 end to advanceSaucer setpensize 42 setpencolor white back 2 forward 124 back 42 drawSaucer end to saucerJourney penup setxy -300 100 pendown setheading 90 drawSaucer repeat 60 [ advanceSaucer wait 800 ] end
With this code, all you need to do is invoke saucerJourneyand the saucer will traverse the graphics window. Try it out…
Hitting the Target
What we need to do now is figure out some way to determine when a projectile hits the saucer. There are many ways to do this. When I was originally writing my version of the program, I already had multiple projectiles working before I added a target. So, I decided to
- have the flying saucer update global variables identifying its current location
- have the projectile check this information to determine if it has hit the target after each move forward
- If the projectile has hit the target, it sets a global variable (hit) to true
- finally, the procedure that controls movement of the flying saucer checks hit before moving forward to see if it is now true, and if so, invokes splat and stops.
Got all that? If so, go and write the code. If not, keep reading, I’ll go into the solution in a bit more detail.
A Detailed Look at my Version of the Program
Here’s the structure of my program.
Although it looks pretty simple, there is a lot of information in this diagram. You have everything here you need to write the program. If you are having trouble reading any of the labels in the diagram, click on it to get a full-sized version of it. Let me walk you through some of the parts/pieces.
First of all, I’ve put three kinds of things in the diagram. The global variables for the program are shown as boxes; there are five in the diagram labeled (left to right) p1y, p1x, hit, saucerX, and saucerY. The procedures that make up the program are drawn as labeled bubbles/clouds. I’ve colored the bubbles so that you can see which procedures are performed by the different turtles. Specifically, the green bubble procedures are performed by the initial turtle (t1). The blue bubble procedures are for the flying saucer turtle (fs) And, the red bubble procedures are for the projectile turtle (p1).
And then there are all of the arrows. The simple black line arrows show flow of control, i.e., procedure invocation. As with all of my programs that are GUI-based, I have a procedure named init which initializes everything. In this program, it just happens to use one of each type of arrow, so I’ll use it to explain the arrows.
Checkout init; it has a solid black line arrow pointing at the bubble labeled drawCannon. This denotes init invoking drawCannon. Above this is a big yellow arrow pointing to the hit box. Flow of data into the global variables and accessing the contents of the data that is in them is shown with these big yellow arrows. In the arrow is the word false. So, in this case, init is storing false into the global variable named hit. Above the yellow line is a dashed black line pointing at the saucerJourney bubble. Dashed lines in the drawing indicate that a talkto command was used to tell the destination to do something. In this case, init told the fs turtle to perform the saucerJourney procedure.
When init completes, the program just waits for some event. In this program, it waits for a key event, which will cause the keyPressed procedure to be performed.
There is one other arrow that I want to make sure you understand. Find traceHelper, a red bubble at the bottom. The arrow pointing to it (coming from tracer) has the word “count” in it. Count is an input expected by traceHelper. The arrow I want you to understand is the one that originates at the right hand side and loops around to point back at itself. This signifies that traceHelper is a recursive procedure – it invokes itself. Here is part of the its code, enough for you to see the flow of control.
to traceHelper :count if equal? :count 0 [ stop ] ... traceHelper difference :count 1 end
When tracer invokes traceHelper, it specifies a number of times it wants traceHelper to do what it does – move the projectile. So, think of traceHelper as the instructions-list in a repeatcommand.
A Detailed Look at Detecting a Hit
The global variable hit is used to signal that a projectile has struck the target. The procedure init sets it to the value false. The procedure saucerJourney accesses hit and if its value is true it invokes splat and stops. If you look at Figure 13.1 you will see that it is the procedure traceHelper that sets hit to true.
traceHelper is the procedure that animates a projectile. After the projectile is moved forward, traceHelper has the following if command.
if and xInSaucer? xcor yInSaucer? ycor [make "hit "true stop]
This if command combines the results of two procedures which return boolean results. If you need to review the and operator, I first covered it in the Built-in Operators For Combining/Manipulating Boolean Valuessection in the Predicates lesson.
Figure 13.1 shows this interaction. If you look at the traceHelper bubble, you’ll see it’s interaction with both xInSaucer? and yInSaucer?. The invocation of xInSaucer? consists of passing it the current X coordinate, which is what the xcor primitive returns, and getting back either true or false. The invocation of yInSaucer? is similar.
So, if the coordinates of the projectile are in the saucer, the projectile has collided with the flying saucer. So, what does the if command above do in this case? It sets the global variable hit to true. Figure 13.1 shows this as a big yellow arrow with true in it leading out of traceHelper and pointing to the hit box.
The next time the flying saucer turtle looks at hit (as it performs saucerJourney), it will see that it has been hit!
Getting More Than One Shot – Multiple Projectiles
All right, let’s add a few more projectiles – a few more turtles – to our arsenal. If you think about it, that’s the reason why there’s a projectile turtle named p1 and a “1” in the variable names p1X and p1Y. So, is adding another projectile as simple as creating a new turtle named p2, creating a couple more global variables: p2X and p2Y, and sprinkling more code everywhere we do something with projectile 1?
Not quite… Let’s take another look at fireCannon. Here’s a new version of it with new code that controls a second projectile, a copy of what we did with projectile 1.
to fireCannon make "p1x xcor make "p1y sum ycor 20 talkto "p1 [ tracer :p1x :p1y ] make "p2x xcor make "p2y sum ycor 20 talkto "p2 [ tracer :p2x :p2y ] end
What’s wrong with this new version of fireCannon?
Well, everytime that it’s invoked, it tells both projectile turtles to perform tracer. We need to know if a projectile turtle is already doing something. If it is, it can’t be given anything else to do until it’s done. And, we only want one projectile turtle to perform tracer. If we tell p1 do it, that’s it, we’re done.
Time for a couple more global variables, i named mine p1Avl and p2Avl. They are used to determine whether or not a projectile turtle is available.
Here’s the idea:
- in init you set their contents to true,
- in fireCannon we check to see if a projectile turtle is available before we try to use it; if it’s available, we change the pnAvl variable to false (to indicate the projectile is in-use) and use talkto to have its associated turtle invoke tracer.
- the last thing that tracer does is to change the projectile’s pnAvl variable to true to indicate it’s once again available. But, this means we need a seperate tracer for each projectile, e.g.,to tracer1 :xcor :ycor … make “p1Avl “true end
Here’s the start of the new fireCannon
to fireCannon if :p1Avl [make "p1Avl "false make "p1x xcor make "p1y sum ycor 20 talkto "p1 [tracer1 :p1x :p1y] stop]
That should do it. Add a few more projectiles to your program. Don’t get too carried away, there is a limit to the number of turtles that the TurtleGraphics applet/application lets you create. I’ve chosen to give you 12 turtles – if you need more you’ll need to get me to up this number…
- Extend the program to have more than one flying saucer trip. Keep track of hitsand speed up the flying saucer if it keeps getting hit – to make hitting it harder.NOTE: for performance reasons, you should include a cg or clean command as part of wrap-up of each flying saucer journey. This is an easy way to clean up the mess that splat makes. But, even if the flying saucer did not get hit, cg and clean free up a lot stuff the TurtleGraphics environment saves in case you re-size the graphics area.
An alternative is to turn off collection of graphics stuff with the norefresh command.
- Add a second flying saucer turtle so that multiple targets can be in the air at the same time. The flying saucers should go from left to right and right to left.
- Keep score. Count the number of hits and award points based on the level of difficulty of each hit. As the speed a flying saucer travels at increases, hitting it should be worth more points.
- Give the flying saucers projectiles of their own.
- When you get tired of playing with your Spacewar application, try writing an application that plays Racquetball, like this one:
alt=”Your browser understands the <APPLET> tag but isn’t running the applet, for some reason.” Your browser is completely ignoring the <APPLET> tag! Racquetball Applet
Animation is easy! You were able to animate a triangle very easily. Hopefully, interacting with keyboard events was easy for you too. Writing a keyPressed procedure is pretty much like writing a mouseClickedprocedure.
You also learned how to create multiple turtles with the newturtle command and give them commands with the talkto command. These commands by themselves are not hard.
The difficult part of the program we wrote was figuring out how the turtles should communicate. How does one turtle know where another turtle is, or in our case where an object being drawn by the turtle is? How do you make sure a turtle available/ready to do something you want it to do?
|New jLogo Procedures Used In This Lesson|
|KEYPRESSED||keyNumber||When a keyboard key is pressed, TG receives an event. If the key is one that TG is interested in, a user- defined procedure with the name KEYPRESSED (expecting one input) is invoked if it has been defined.
|TO keyPressed :num
|Creates a new turtle with the specified name and directs future input to it.If an optional instructionList is supplied, it is given to the new turtle to do and future input remains directed to the current turtle.||NEWTURTLE “t2 [ HT ]|
|Directs future input to the specified turtle or, if the optional instructionList id provided, it is given to the specified turtle and future input remains directed to the current turtle.||TALKTO “t2 [ FD 10 ]|