Phase one - Multiple rocks
For this phase, you will keep a set of rocks and spawn new rocks into this set. This requires the following steps:
a_rock
and replace it with rock_group
. Initialize the rock group to an empty set. Modify your rock spawner to create a new rock (an instance of a Sprite object) and add it to rock_group
.process_sprite_group
. This function should take a set and a canvas and call the update and draw methods for each sprite in the group.process_sprite_group
function on rock_group
in the draw handler.For this phase, you will detect collisions between the ship and a rock. Upon a collision, the rock should be destroyed and the player should lose a life. To implement ship-rock collisions, you need to do the following:
collide
method to the Sprite class. This should take an other_object
as an argument and return True
if there is a collision or False
otherwise. For now, this other object will always be your ship, but we want to be able to use this collide method to detect collisions with missiles later, as well. Collisions can be detected using the radius of the two objects. This requires you to implement methods get_position
and get_radius
on both the Sprite and Ship classes.group_collide
helper function. This function should take a set group
and an a sprite other_object
and check for collisions betweenother_object
and elements of the group. If there is a collision, the colliding object should be removed from the group. To avoid removing an object from a set that you are iterating over (which can cause you a serious debugging headache), iterate over a copy of the set created via set(group)
. This function should return the number of collisions. Be sure to use the collide
method from part 1 on the sprites in the group to accomplish this task.group_collide
helper to determine if the ship hit any of the rocks. If so, decrease the number of lives by one. Note that you could have negative lives at this point. Don't worry about that yet.Phase three - Missiles
For this phase, you will keep a set of missiles and spawn new missiles into this set when firing using the space bar. This requires the following steps:
a_missile
and replace it with missile_group
. Initialize the missile group to an empty set. Modify your shoot method of my_ship
to create a new missile (an instance of the Sprite class) and add it to the missile_group
. If you use our code, the firing sound should play automatically each time a missile is spawned.process_sprite_group
to process missile_group
. While you can now shoot multiple missiles, you will notice that they stick around forever. To fix this, we need to modify the Sprite class and the process_sprite_group
function.update
method of the Sprite class, increment the age of the sprite every time update
is called. If the age is greater than or equal to the lifespan of the sprite, then we want to remove it. So, return False
(meaning we want to keep it) if the age is less than the lifespan and True
(meaning we want to remove it) otherwise.process_sprite_group
to check the return value of update
for sprites. If it returns True
, remove the sprite from the group. Again, you will want to iterate over a copy of the sprite group in process_sprite_group
to avoid deleting from the same set over which you are iterating.group_collide
, because we want to check for collisions between two groups. All we need to do is add one more helper function:
group_group_collide
that takes two groups of objects as input. group_group_collide
should iterate through the elements of a copy of the first group using a for
-loop and then call group_collide
with each of these elements on the second group. group_group_collide
should return the number of elements in the first group that collide with the second group as well as delete these elements in the first group.group_group_collide
in the draw handler to detect missile/rock collisions. Increment the score by the number of missile collisions.You now have a mostly working version of RiceRocks!!! Let's add a few final touches.
started
to False
, destroy all rocks and prevent any more rocks for spawning until the game is restarted.Bonus
The following will not be graded. Feel free to try this, but do not break any of the other game functionality. We strongly recommend that you save your work before doing this and keep track of it, so you can submit a working version of the first five phases if you end up breaking your game trying to add more features.
One thing that is missing in your game is explosions when things collide. We have provided a tiled explosion image that you can use to create animated explosions. To get things working, you will need to do a few things:
self.animated
is True
. If so, then choose the correct tile in the image based on the age. The image is tiled horizontally. If self.animated
is False
, it should continue to draw the sprite as before.explosion_group
global variable and initialize it to an empty set.group_collide
, if there is a collision, create a new explosion (an instance of the Sprite class)and add it to the explosion_group
. Make sure that each explosion plays the explosion sound.process_sprite_group
to process explosion_group
.You should now have explosions working!
代码链接:点击打开链接
2013年秋更新版:点此链接
代码如下:
# implementation of Spaceship - program template for RiceRocks import simplegui import math import random # globals for user interface WIDTH = 800 HEIGHT = 600 score = 0 lives = 3 time = 0 started = False class ImageInfo: def __init__(self, center, size, radius = 0, lifespan = None, animated = False): self.center = center self.size = size self.radius = radius if lifespan: self.lifespan = lifespan else: self.lifespan = float('inf') self.animated = animated def get_center(self): return self.center def get_size(self): return self.size def get_radius(self): return self.radius def get_lifespan(self): return self.lifespan def get_animated(self): return self.animated # art assets created by Kim Lathrop, may be freely re-used in non-commercial projects, please credit Kim # debris images - debris1_brown.png, debris2_brown.png, debris3_brown.png, debris4_brown.png # debris1_blue.png, debris2_blue.png, debris3_blue.png, debris4_blue.png, debris_blend.png debris_info = ImageInfo([320, 240], [640, 480]) debris_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png") # nebula images - nebula_brown.png, nebula_blue.png nebula_info = ImageInfo([400, 300], [800, 600]) nebula_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/nebula_blue.f2013.png") # splash image splash_info = ImageInfo([200, 150], [400, 300]) splash_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/splash.png") # ship image ship_info = ImageInfo([45, 45], [90, 90], 35) ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png") # missile image - shot1.png, shot2.png, shot3.png missile_info = ImageInfo([5,5], [10, 10], 3, 50) missile_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/shot2.png") # asteroid images - asteroid_blue.png, asteroid_brown.png, asteroid_blend.png asteroid_info = ImageInfo([45, 45], [90, 90], 40) asteroid_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png") # animated explosion - explosion_orange.png, explosion_blue.png, explosion_blue2.png, explosion_alpha.png explosion_info = ImageInfo([64, 64], [128, 128], 17, 24, True) explosion_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/explosion_alpha.png") # sound assets purchased from sounddogs.com, please do not redistribute # .ogg versions of sounds are also available, just replace .mp3 by .ogg soundtrack = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/soundtrack.mp3") missile_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/missile.mp3") missile_sound.set_volume(.5) ship_thrust_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/thrust.mp3") explosion_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/explosion.mp3") # helper functions to handle transformations def angle_to_vector(ang): return [math.cos(ang), math.sin(ang)] def dist(p, q): return math.sqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2) def process_sprite_group(canvas,rock_group): for tmp in rock_group: tmp.draw(canvas) if tmp.update(): rock_group.remove(tmp) def group_collide(group,other_object): numcol=0 for tmp in set(group): if tmp.collide(other_object): group.remove(tmp) numcol+=1 return numcol def group_group_collide(group1,group2): numcol=0 for tmp in set(group1): tmpcol=group_collide(group2,tmp) if tmpcol: numcol+=tmpcol group1.remove(tmp) return numcol # Ship class class Ship: def __init__(self, pos, vel, angle, image, info): self.pos = [pos[0], pos[1]] self.vel = [vel[0], vel[1]] self.thrust = False self.angle = angle self.angle_vel = 0 self.image = image self.image_center = info.get_center() self.image_size = info.get_size() self.radius = info.get_radius() def draw(self,canvas): if self.thrust: canvas.draw_image(self.image, [self.image_center[0] + self.image_size[0], self.image_center[1]] , self.image_size, self.pos, self.image_size, self.angle) else: canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle) # canvas.draw_circle(self.pos, self.radius, 1, "White", "White") def update(self): # update angle self.angle += self.angle_vel # update position self.pos[0] = (self.pos[0] + self.vel[0]) % WIDTH self.pos[1] = (self.pos[1] + self.vel[1]) % HEIGHT # update velocity if self.thrust: acc = angle_to_vector(self.angle) self.vel[0] += acc[0] * .1 self.vel[1] += acc[1] * .1 self.vel[0] *= .99 self.vel[1] *= .99 def set_thrust(self, on): self.thrust = on if on: ship_thrust_sound.rewind() ship_thrust_sound.play() else: ship_thrust_sound.pause() def increment_angle_vel(self): self.angle_vel += .05 def decrement_angle_vel(self): self.angle_vel -= .05 def shoot(self): global missile_group forward = angle_to_vector(self.angle) missile_pos = [self.pos[0] + self.radius * forward[0], self.pos[1] + self.radius * forward[1]] missile_vel = [self.vel[0] + 6 * forward[0], self.vel[1] + 6 * forward[1]] missile_group.append(Sprite(missile_pos, missile_vel, self.angle, 0, missile_image, missile_info, missile_sound)) def get_position(self): return self.pos def get_radius(self): return self.radius # Sprite class class Sprite: def __init__(self, pos, vel, ang, ang_vel, image, info, sound = None): self.pos = [pos[0],pos[1]] self.vel = [vel[0],vel[1]] self.angle = ang self.angle_vel = ang_vel self.image = image self.image_center = info.get_center() self.image_size = info.get_size() self.radius = info.get_radius() self.lifespan = info.get_lifespan() self.animated = info.get_animated() self.age = 0 if sound: sound.rewind() sound.play() def draw(self, canvas): if self.animated: tmpcenter=[self.image_center[0]+self.age*self.image_size[0],self.image_center[1]] canvas.draw_image(self.image, tmpcenter, self.image_size, self.pos, self.image_size,self.angle) else: canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size,self.angle) def update(self): # update angle self.angle += self.angle_vel # update position self.pos[0] = (self.pos[0] + self.vel[0]) % WIDTH self.pos[1] = (self.pos[1] + self.vel[1]) % HEIGHT self.age+=1 if self.age>self.lifespan: return True else: return False def get_position(self): return self.pos def get_radius(self): return self.radius def collide(self,other_object): #global explosion_group if dist(self.get_position(),other_object.get_position())<self.get_radius()+other_object.get_radius(): foward=angle_to_vector(self.angle) tmppos=[self.pos[0]+self.get_radius(),self.pos[1]+self.get_radius()] a_explosion=Sprite(tmppos, [0,0], 0, 0, explosion_image, explosion_info, explosion_sound) explosion_group.append(a_explosion) return True else: return False def decrement_angle_vel(self): self.angle_vel-=0.08 def increment_angle_vel(self): self.angle_vel+=0.08 # key handlers to control ship def keydown(key): if key == simplegui.KEY_MAP['left']: my_ship.decrement_angle_vel() elif key == simplegui.KEY_MAP['right']: my_ship.increment_angle_vel() elif key == simplegui.KEY_MAP['up']: my_ship.set_thrust(True) elif key == simplegui.KEY_MAP['space']: my_ship.shoot() def keyup(key): if key == simplegui.KEY_MAP['left']: my_ship.increment_angle_vel() elif key == simplegui.KEY_MAP['right']: my_ship.decrement_angle_vel() elif key == simplegui.KEY_MAP['up']: my_ship.set_thrust(False) # mouseclick handlers that reset UI and conditions whether splash image is drawn def click(pos): global started,lives,score,my_ship,rockth center = [WIDTH / 2, HEIGHT / 2] size = splash_info.get_size() inwidth = (center[0] - size[0] / 2) < pos[0] < (center[0] + size[0] / 2) inheight = (center[1] - size[1] / 2) < pos[1] < (center[1] + size[1] / 2) if (not started) and inwidth and inheight: started = True lives=3 score=0 rockth=1 my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 0, ship_image, ship_info) soundtrack.rewind() soundtrack.play() def draw(canvas): global time, started, lives, rock_group, missile_group, explosion_group,score # animiate background time += 1 wtime = (time / 4) % WIDTH center = debris_info.get_center() size = debris_info.get_size() canvas.draw_image(nebula_image, nebula_info.get_center(), nebula_info.get_size(), [WIDTH / 2, HEIGHT / 2], [WIDTH, HEIGHT]) canvas.draw_image(debris_image, center, size, (wtime - WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT)) canvas.draw_image(debris_image, center, size, (wtime + WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT)) # draw UI canvas.draw_text("Lives", [50, 50], 22, "White") canvas.draw_text("Score", [680, 50], 22, "White") canvas.draw_text(str(lives), [50, 80], 22, "White") canvas.draw_text(str(score), [680, 80], 22, "White") # draw ship and sprites my_ship.draw(canvas) process_sprite_group(canvas,rock_group) process_sprite_group(canvas,missile_group) process_sprite_group(canvas,explosion_group) # update ship and sprites my_ship.update() # draw splash screen if not started if not started: canvas.draw_image(splash_image, splash_info.get_center(), splash_info.get_size(), [WIDTH / 2, HEIGHT / 2], splash_info.get_size()) return if group_collide(rock_group,my_ship): lives-=1 if lives==0: started=False rock_group=[] explosion_group=[] #canvas.draw_image(splash_image,splash_info.get_center(), splash_info.get_size(), [WIDTH / 2, HEIGHT / 2], splash_info.get_size()) if group_group_collide(rock_group,missile_group): score+=1 # timer handler that spawns a rock def rock_spawner(): global rock_group,rockth rockth*=1.05 if len(rock_group) >= 12 or not started: return rock_pos = [random.randrange(0, WIDTH), random.randrange(0, HEIGHT)] while dist(my_ship.get_position(),rock_pos)<200: rock_pos=[random.randrange(0, WIDTH), random.randrange(0, HEIGHT)] rock_vel = [rockth*random.random() * .6 - .3, rockth*random.random() * .6 - .3] rock_avel = random.random() * .2 - .1 rock_group.append( Sprite(rock_pos, rock_vel, 0, rock_avel, asteroid_image, asteroid_info)) # initialize stuff frame = simplegui.create_frame("Asteroids", WIDTH, HEIGHT) # initialize ship and two sprites my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 0, ship_image, ship_info) rock_group = []#Sprite([WIDTH / 3, HEIGHT / 3], [1, 1], 0, .1, asteroid_image, asteroid_info) #a_missile = Sprite([2 * WIDTH / 3, 2 * HEIGHT / 3], [-1,1], 0, 0, missile_image, missile_info, missile_sound) missile_group=[] explosion_group=[] rockth=1 # register handlers frame.set_keyup_handler(keyup) frame.set_keydown_handler(keydown) frame.set_mouseclick_handler(click) frame.set_draw_handler(draw) timer = simplegui.create_timer(1000.0, rock_spawner) # get things rolling timer.start() frame.start()