Lab Exercise 11: Designing Interaction
This project completes the semester with a design task that requires a multi-stage interaction with the user combined with some type of simulation. This is the fifth and final part of a multi-part project, and the primary challenge is designing and then implementing a more complex user interface and interaction than prior projects. Alternatively, you can choose to simulate a phenomenon of your choice.
Video: Making a Spaceship
If you have not already done so, create a new project11 folder. You will need the Zelle graphics package file graphicsPlus.py. You will also want a copy of your physics_objects.py, collision.py, and the file containing your rotating block class from project 10 (if it is in a separate file).
The Zelle documentation, is also available.
- Examine a Simple Ship Class
Go ahead and download a simple Ship class. The ship will act like a ball, with respect to collisions and motion, but it also has the capability to rotate around its center. Open up the file and go through the various parts to get an understanding of how it works.
- The __init__ method looks similar to the RotatingBlock class, except that the Ship uses two polygons (triangles). The first triangle is the body of the ship. The second triangle is the exhaust. They are defined in model coordinates in the __init__, with the ship body triangle centered on the origin.
- The next set of methods--draw, undraw, getAngle, setAngle, getRotVelocity, setRotVelocity, and rotate--are identical to the equivalent methods in the RotatingBlock class. A smart coder would create a parent rotator class an inherit these rather than having duplicate code in multiple classes.
The final four functions are unique to the Ship class, though the mathematics and process of rendering are almost identical to the RotatingBlock class, but slightly simpler because the anchor is at the center of the ship. In the render method, note that all of the points from both polygons are rotated, then the first three points make up the ship polygon and the second three points make up the exhaust polygon. Finally, the ship is colored.
In the update method, the only difference from the RotatingBlock is the code designed to make the exhaust flicker. This, combined with the setFlickerOn and setFlickerOff enabled the main loop to turn on flickering for a period of time after the user hits a key.
Test the ship-simple.py file by running it. It doesn't do anything except make a window, render the ship, and wait for a mouse click.
- Create a Main Loop
Remove the win.getMouse() from the main function. Then set up a main loop that runs until the user types 'q'. Since the program will be responding to many different keys, assign the result of win.checkKey() to a variable before testing the value returned. Make sure you have a win.update() either at the start or end of your main loop.
Test your program to make sure it quits when you type q.
Before the main loop, create the variable dt and assign it the values 0.02. Also, set the ship's rotational velocity to something slow, like 20 (deg/s).
At the end of the main loop, call the ship.update() method, passing in dt as the time step. Then call win.update(). Finally, call time.sleep with a value of dt*0.5. You may be able to leave out the sleep call.
Run your program. The ship should spin slowly and the program should quit when you hit the q key.
- Make The Ship Rotate On Command
The next step is to make the ship rotate when the user hits the right or left arrow keys. If you want to simulate an actual space vehicle, then firing attitude rockets should add or subtract from the current rotational velocity. If you want somewhat simpler control, then have left and right call the rotate function with a fixed angle (positive for Left, negative for Right).
If the user hits the left arrow key, you want to add a certain amount of rotational velocity (or angle if using the simpler model) to the ship. If the user hits the right arrow key, you want to subtract a certain amount of rotational velocity (or angle) from the ship. When the user hits the left arrow, the key variable will have the value 'Left'. When the user hits the right arrow, the key variable will have the value 'Right'.
Before the main loop, create another variable gamma and give it the value 10. Inside your main loop, implement the following code.
# if the user hits the 'Left' key # set the rotational velocity to the old rotational velocity plus gamma # call the ship's setFlickerOn method with no arguments # elif the user hits the 'Right' key # set the rotational velocity to the old rotational veloity minus gamma # call the ship's setFlickerOn method with no arguments
Run your program. You should be able to rotate the ship left and right. Don't hold the arrow keys down too long.
- Make the Ship Accelerate on Command
The next step is to make the ship move forward when the user hits the 'space' bar. This action depends on the current angle of the ship. As with the rotational velocity, each time the user hits the space bar the program should add something to the ship's current velocity.
Before the main loop, create another variable delta and give it the value 10. Inside the main loop, add a case for the 'space' key to the if/elif structure from the prior step.
# elif the user types 'space' # assign to a the ship's current angle (getAngle) # assign to theta the result of multiplying a by math.pi and dividing by 180 # assign to v the ship's current velocity (getVelocity) # set the ship's velocity to it's new values # The new X velocity is v_new_x = v_old_x + cos(theta) * delta # The new Y velocity is v_new_y = v_old_y + sin(theta) * delta # call the ship's setFlickerOn method with no arguments
Run your program. You should be able to rotate (arrow keys) and accelerate (space bar). How long can you keep the spaceship in the window?
- Make the World Wrap
The final task is to make the ship's world wrap from top to bottom and left to right. The basic idea is to figure out if the ship has gone out of bounds. If it has, add or subtract a value from its current position to make it look like the world wraps around the window.
First, create two more variables, winWidth and winHeight, prior to the loop that hold the window width and height.
Next, implement the following in the main loop just before the call to ship.update()
# assign to moveit the value False # assign to p the ship's current position # if the x coordinate is less than 0 # add winWidth to the x coordinate # assign to moveit the value True # elif the x coordinate is greater than winWidth # subtract winWidth from the x coordinate # assign to moveit the value True # if the y coordinate is less than 0 # add winHeight to the y coordinate # assign to moveit the value True # elif the y coordinate is greater than winHeight # subtract winHeight from the y coordinate # assign to moveit the value True # if moveit: # set the ship's position to p
Run your program and test if the ship wraps.
When you are done with the lab exercises, you may begin the project.
If you are working on a mac, there is a simple way to play sound. The first step is to import a package called os.
The os package has a method called system, that lets you send a string to the terminal as though you typed a command and hit return.
Download a sound. Once you have downloaded it to your working directory, open a terminal and cd to that directory. Then type the following command.
afplay lasergun.wav &
It should play the sound. Now run the python shell by typing python3. Then import the os package. Finally, use the os.system function to execute the command you just typed into the terminal.
os.system( 'afplay lasergun.wav &' )
When you type and run the above line in the python interpreter shell, it should play the sound. You can use the same line of code when you want to play a sound in your python program. Just make sure the sound file is in the same directory as your code.