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 fourth 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, such as a spring/mass/damper system using a design similar to ones we have been using.
If you have not already done so, mount your personal space, create a new Project11 folder and bring up TextWrangler and a Terminal. You will need the Zelle graphics package file graphics.py. You will also want to copy over 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.
The 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.
The Main Loop:
Your first task is to make a main loop. Rather than being sensitive to a mouse click, we want the main loop to terminate only when the user types q. Since we want the program to be sensitive to other keys, we have to avoid using the checkKey method more than once during our loop, and we can't use it as part of the while condition. Instead, you want to use the following structure.
# assign to a variable key the empty string # while key is not equal to the string 'q' # assign to the variable key the result of win.checkKey() # test to see what the user did (if anything) # run any updates on objects in the scene # close the window and return
To start, just implement the first three lines and the last line of the algorithm above by modifying the main function in ship-simple.py. When you run the file it should close when you hit the q key.
Next, before the main loop, create two variables, dt and frame, and assign them the values 0.01 and 0, respectively. Also, set the ship's rotational velocity to something slow, like 20.
At the end of your main loop, call the ship.update method, passing in dt as the time step and increment the frame variable. Then, add an if statement specifying that if the frame variable modulo 10 is equal to zero, then call win.update and call time.sleep with a value of dt*0.5.
# if frame modulo 10 is equal to zero # call the window's update method # call the time package's sleep function with the argument dt*0.5
Run your program. The ship should spin slowly and the program should quit when you hit the q key.
Making The Ship Rotate
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 will add or subtract from the current rotational velocity.
If the user hits the left arrow key, you want to add a certain amount of rotational velocity to the ship. If the user hits the right arrow key, you want to subtract a certain amount of rotational velocity 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 arrow key # set the rotational velocity to the old rotational velocity plus some amount gamma # call the ship's setFlickerOn method with no arguments # elif the user hits the right arrow key # set the rotational velocity to the old rotational veloity minus some amount 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.
Making the Ship Accelerate
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 we're going to add something to the ship's current velocity.
Before the main loop, create another variable delta and give it the value 1. Inside your main loop, add a case for 'space' to the if/elif structure from the prior step.
# elif the user hits the space bar # 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?
Wrapping the World
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. Note these are not the size you made the GraphWin. This is world coordinates, which by default are 10x less than window coordinates. So if you made your GraphWin 500 x 500, then the height and width of the window in world coordinates is 50 x 50.
Next, implement the following.
# assign to moveit the value False # assign to p the ship's current position. You might want to cast it to a list. # 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 # set moveit to False
Run your program and see if the ship wraps.
When you are done with the lab exercises, you may begin the project.