https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Advanced-Graphics-with-Classes
https://github.com/tiggerntatie/brython-server
For this tutorial, return to your ggame-tutorials repository that you forked earlier. Create a new file called tutorial4.py and paste the following code into it to get started:
from ggame import App, RectangleAsset, ImageAsset, Sprite, LineStyle, Color, Frame myapp = App() # Background black = Color(0, 1) noline = LineStyle(0, black) bg_asset = RectangleAsset(myapp.width, myapp.height, noline, black) bg = Sprite(bg_asset, (0,0)) myapp.run()
This snippet should look familiar, as it is a "cut down" version of the last tutorial that you worked on (tutorial3.py). Notice that we have removed the step function entirely. In this tutorial, we will add the step function back, but in an entirely different way!
For the first part of this tutorial, we would like to customize the behavior of the standard App class by creating an entirely new application class called MyApp that inherits its basic behavior from the standard App class.
Paste the following snippet in just before the myapp = ... line:
class SpaceGame(App): """ Tutorial4 space game example. """ def __init__(self): super().__init__()
Then cut the four lines that create the background and paste them in below the super()... line and indent them to match. Finally, change the parts that say myapp.width and myapp.height to be self.width and self.height, respectively. Now your new piece of code should look like this:
class SpaceGame(App): """ Tutorial4 space game example. """ def __init__(self): super().__init__() # Background black = Color(0, 1) noline = LineStyle(0, black) bg_asset = RectangleAsset(self.width, self.height, noline, black) bg = Sprite(bg_asset, (0,0))
Yes, you could have pasted this in from the get-go, but I want you to be very clear about where this code is coming from.
Things to notice about the change:
class SpaceGame(App):
defines a new class, called SpaceGame
, that inherits all of the functionality of the standard App
class.__init__
method for the class. In this case it expects no arguments.super().__init__(...
line forces the new SpaceGame class to call the standard App class' __init__
function before beginning its own initialization. Always do this if you want your new class to fully inherit the behavior of the parent class.__init__
method of the game class.As it stands, your program is broken. To make the new SpaceGame class take effect, we have to instantiate it instead of instantiating the App class. Change the next to last line of the program from
myapp = App()
to myapp = SpaceGame()
.
Try running the program. You should see a black background.
Just above your SpaceGame class definition, paste this new code:
class SpaceShip(Sprite): """ Animated space ship """ asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png", Frame(227,0,65,125), 4, 'vertical') def __init__(self, position): super().__init__(SpaceShip.asset, position)
Run your program. It should not do anything different from before. Creating a new Sprite class does not actually create any sprites. All it does is create a blueprint for making sprites.
Add a single SpaceShip sprite by adding the following line to the end of the SpaceGame __init__
method (properly indented, of course):
SpaceShip((100,100))
Now run your code. Cool. Try adding a few more SpaceShip instances at the end of your SpaceGame __init__
method:
SpaceShip((150,150)) SpaceShip((200,50))
It looks like you are building a fleet!
The code we added is very simple, but there is one line that needs some explanation:
asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png", Frame(227,0,65,125), 4, 'vertical')
The asset
variable is created within the class, but outside of any methods. This makes it a classattribute that will be available to all instances of the class. We used this to call the parent Sprite class __init__
method using the syntax: SpaceShip.asset
. This approach allows us to create as many instances of the SpaceShip as we want, but without creating multiple assets. There is one object representing the spaceship image, but multiple objects representing the sprites.
This call to create an ImageAsset
has more arguments than we used in the previous tutorial. Here's what they are about:
Frame(227,0,65,125)
argument specifies a rectangular section within the image file. If you look at the image file in Github you will notice that it actually consists of sixteen different spacecraft images, some with rocket thrust and some without. The frame arguments refer to the horizontal and vertical location of the upper left hand corner of the sub-image we want (227 and 0 pixels), followed by the width and height of it (65 and 125 pixels).4
argument means that the asset will actually include four sub-images of the same size as the first, and...'vertical'
argument means that those four images are arranged vertically in the image file. Go back to the github repository and look at this image file to see what I mean by "four images ... arranged vertically."All of this additional information means that this asset is ready to animate! It consists of a single spaceship without thrust, and three spaceship images that include a blast of thrust. By selecting which of these frames we want to show at any given time, we can give the appearance of motion within the sprite itself.
First, let's add some attributes to the SpaceShip class. Add the following lines at the end of the SpaceShip class __init__
method:
self.vx = 1 self.vy = 1 self.vr = 0.01
These will set an initial horizontal, vertical and rotational velocity.
Then, add a step
method to the SpaceShip class. This should appear after the SpaceShip class __init__
method (but leave a space between them):
def step(self): self.x += self.vx self.y += self.vy self.rotation += self.vr
This will just add the velocities to the sprite's position attributes, x
, y
, and rotation
, which are built-in attributes of the Sprite class that were automatically inherited by the SpaceShip class.
If you are unsure about where to paste these code snippets, check the full listing at the end of this page.
Unfortunately, just adding a step
method to a sprite class does not mean that it will be called. So we have to add a step
method to the application itself. Add the following code below the __init__
method of the SpaceGame class (but leave a space between them):
def step(self): for ship in self.getSpritesbyClass(SpaceShip): ship.step()
You are expected to add a step
method to your own customizations of the standard App class. This step
method is automatically called with every video frame update in the game.
This method body uses a for
loop to access every instance of the SpaceShip class, then calls its step
method (ship.step()
). Since the SpaceGame step
function is called with every video frame update, this means that every SpaceShip step
function will also be called with every video frame update.
Now to the animation details. We want the thrust images to animate when the user presses the space key. So here are the things we have to do:
step
method to change the sprite image, depending on whether the space is down or released.First, let's add code for managing the state of the thrusting, and listen for the appropriate keys. Add the following inside the end of the SpaceShip __init__
method:
self.thrust = 0 self.thrustframe = 1 SpaceGame.listenKeyEvent("keydown", "space", self.thrustOn) SpaceGame.listenKeyEvent("keyup", "space", self.thrustOff)
Then add the thrustOn
and thrustOff
methods to the SpaceShip class. Add the following immediately after the SpaceShip step
method:
def thrustOn(self, event): self.thrust = 1 def thrustOff(self, event): self.thrust = 0
These simple functions will keep track of whether the space key is down (thrust is 1) or up (thrust is 0).
Finally, add the following inside the end of the SpaceShip step
method:
# manage thrust animation if self.thrust == 1: self.setImage(self.thrustframe) self.thrustframe += 1 if self.thrustframe == 4: self.thrustframe = 1 else: self.setImage(0)
If self.thrust
is set to 1, it means the space button is depressed and the sprite image is set to whatever self.thrustframe
is (remember we initialized it to 1 in the class __init__
method). Image number 0 is the first image, 1 is the second, and so on. The next three lines increment the thrustframe attribute, checking to see if it has gone beyond the end of our list of images (there are only three of them) and setting it back to 1 if necessary.
Finally, if self.thrust
is set to 0, it means the space button is released, and we should just display the thrustless spaceship image, which is done with self.setImage(0)
.
There are many more improvements to make to our game, but these are left as exercises for the student!
You may have noticed that the spaceship sprites rotate in a very strange way. This is because the default "center" of a sprite is actually its upper left corner. You can change the center by setting the fxcenter
and fycenter
attributes of the Sprite (or in our case the SpaceShip) class. Do this by adding this final line to the SpaceShip __init__
method:
self.fxcenter = self.fycenter = 0.5
Run your program again and revel in its awesomeness!
Find more information about the Sprite and App classes by examining the detailed ggame documentation.
'left arrow'
and 'right arrow'
keys to rotate the ships left and right.Blast
sprite that uses the blast.png
image included in the /images
folder. Use the 'enter'
key to create Blast sprites on the screen at random locations (or "fire" them from the spaceship sprites).Blast
sprite.""" tutorial4.py by E. Dennison """ from ggame import App, RectangleAsset, ImageAsset, Sprite, LineStyle, Color, Frame class SpaceShip(Sprite): """ Animated space ship """ asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png", Frame(227,0,65,125), 4, 'vertical') def __init__(self, position): super().__init__(SpaceShip.asset, position) self.vx = 1 self.vy = 1 self.vr = 0.01 self.thrust = 0 self.thrustframe = 1 SpaceGame.listenKeyEvent("keydown", "space", self.thrustOn) SpaceGame.listenKeyEvent("keyup", "space", self.thrustOff) self.fxcenter = self.fycenter = 0.5 def step(self): self.x += self.vx self.y += self.vy self.rotation += self.vr # manage thrust animation if self.thrust == 1: self.setImage(self.thrustframe) self.thrustframe += 1 if self.thrustframe == 4: self.thrustframe = 1 else: self.setImage(0) def thrustOn(self, event): self.thrust = 1 def thrustOff(self, event): self.thrust = 0 class SpaceGame(App): """ Tutorial4 space game example. """ def __init__(self): super().__init__() # Background black = Color(0, 1) noline = LineStyle(0, black) bg_asset = RectangleAsset(self.width, self.height, noline, black) bg = Sprite(bg_asset, (0,0)) SpaceShip((100,100)) SpaceShip((150,150)) SpaceShip((200,50)) def step(self): for ship in self.getSpritesbyClass(SpaceShip): ship.step() myapp = SpaceGame() myapp.run()