04.碰撞反应
碰撞检查 两精灵之间的距离<=精灵显示图像宽度一半的和,就说明碰撞了
实施碰撞功能:
我们将两点间的距离等常用函数抽取成一个util模块
import math def distance(point_1=(0, 0), point_2=(0, 0)): """计算两点间的距离""" return math.sqrt((point_1[0] - point_2[0]) ** 2 + (point_1[1] - point_2[1]) ** 2) def center_image(image): """锚点居中""" image.anchor_x = image.width / 2 image.anchor_y = image.height / 2
在Physicalobject类构造方法中增加属性dead表示是否死亡
# 是否死亡 self.dead = False
接着添加两个方法:
def collides_with(self, other_object): """是否与other_object碰撞""" collision_distance = self.image.width / 2 + other_object.image.width / 2 actual_distance = util.distance(self.position, other_object.position) return (actual_distance <= collision_distance) def handle_collision_with(self, other_object): """碰撞处理 每个对象在碰到另一个对象时就死掉 """ self.dead = True
接下来就是对游戏对象列表遍历移除死亡对象game_objects,在修改启动模块的update()
def update(dt): for obj in game_objects: obj.update(dt) # 调用Physicalobject的更新 # 检查所有对象对 for i in range(len(game_objects)): for j in range(i + 1, len(game_objects)): obj_1 = game_objects[i] obj_2 = game_objects[j] if not obj_1.dead and not obj_2.dead: if obj_1.collides_with(obj_2): obj_1.handle_collision_with(obj_2) obj_2.handle_collision_with(obj_1) for to_remove in [obj for obj in game_objects if obj.dead]: to_remove.delete() game_objects.remove(to_remove)
现在可以运行查看,两精灵碰撞会都消失
碰撞响应:
现在需要我们在游戏过程中开始将内容添加到game_objects列表中,并让对象检查彼此的类型以决定是否应该死亡
在循环下方声明一个to_add列表,然后向其中添加新对象。在update()最底部,在删除对象代码之后,将to_add中的对象添加到game_objects中:
def update(dt): # 检查所有对象对 for i in range(len(game_objects)): for j in range(i + 1, len(game_objects)): obj_1 = game_objects[i] obj_2 = game_objects[j] if not obj_1.dead and not obj_2.dead: if obj_1.collides_with(obj_2): obj_1.handle_collision_with(obj_2) obj_2.handle_collision_with(obj_1) to_add = [] for obj in game_objects: obj.update(dt) # 调用Physicalobject的更新 to_add.extend(obj.new_objects) obj.new_objects = [] # 添加完置空 for to_remove in [obj for obj in game_objects if obj.dead]: # 如果垂死的对象产生了任何新对象,请稍后将它们添加到game_objects列表中 to_add.extend(obj.new_objects) to_remove.delete() game_objects.remove(to_remove) game_objects.extend(to_add)
在Physicalobject类构造中添加new_objects属性:
self.new_objects = []
下面来完成子弹模块:
"""子弹bullet.py""" import pyglet from let import physicalobject3, resources class Bullet(physicalobject3.Physicalobject): def __init__(self, *args, **kwargs): super().__init__(resources.bullet_image, *args, **kwargs) # 执行一次 pyglet.clock.schedule_once(self.die, 0.5) self.is_bullet = True # 是子弹 def die(self, dt): self.dead = True
发射子弹:
Player类,构造中定义子弹的速度:
# 子弹速度 self.bullet_speed = 700.0
发射子弹的按键控制:
def on_key_press(self, symbol, modifiers): if symbol == key.SPACE: self.fire()
我们需要在船头而不是在船头放出子弹。我们还需要将飞船的现有速度添加到子弹的新速度中,否则,如果玩家走得足够快,子弹的运行速度最终会比飞船慢
def fire(self): """将飞船的现有速度添加到子弹的新速度中""" # 子弹的大小 angle_radians = -math.radians(self.rotation) # 转换为弧度并反转方向 ship_radius = self.image.width / 2 bullet_x = self.x + math.cos(angle_radians) * ship_radius bullet_y = self.y + math.sin(angle_radians) * ship_radius # 子弹 new_bullet = bullet.Bullet(bullet_x, bullet_y, batch=self.batch) # 飞船的现有速度+子弹的速度=子弹的新速度 bullet_vx = self.velocity_x + math.cos(angle_radians) * self.bullet_speed bullet_vy = self.velocity_y + math.sin(angle_radians) * self.bullet_speed new_bullet.velocity_x, new_bullet.velocity_y = bullet_vx, bullet_vy self.new_objects.append(new_bullet)
player构造中新增event_handlers属性如下,这样就包含了放子弹事件与KeyStateHandler
self.event_handlers = [self, self.key_handler]
主模块中修改原来的推送事件
# 将其推送到事件堆栈中 for handler in play_ship.event_handlers: game_window.push_handlers(handler)
碰撞时不应破坏相同类型的对象,修改Physicalobject类中的handle_collision_with:
def handle_collision_with(self, other_object): """碰撞处理 碰撞时不应破坏相同类型的对象 """ if other_object.__class__ == self.__class__: self.dead = False else: self.dead = True
现在还有一个问题是,子弹与玩家碰撞,我们在Physicalobject构造中定义reacts_to_bullets,表示是否对子弹有反应碰撞,并默认为不是子弹:
self.is_bullet = False # 不是子弹
self.reacts_to_bullets = True # 对子弹有反应
显然,玩家构造中设置为False
self.reacts_to_bullets = False # 对子弹无反应
然后我们修改collides_with碰撞检测:
def collides_with(self, other_object): """是否与other_object碰撞""" if not self.reacts_to_bullets and other_object.is_bullet: return False if self.is_bullet and not other_object.reacts_to_bullets: return False collision_distance = self.image.width / 2 + other_object.image.width / 2 actual_distance = util.distance(self.position, other_object.position) return (actual_distance <= collision_distance)
ok,现在让我们让子弹击中小行星吧
下面我们增加游戏难度,击中小行星,小行星会分裂:
编写小行星类:
"""小行星类aster.py""" import random from let import resources, physicalobject4 class AsteroidSmall(physicalobject4.Physicalobject): def __init__(self, *args, **kwargs): super(AsteroidSmall, self).__init__(resources.asteroid_image, *args, **kwargs) self.rotate_speed = random.random() * 100.0 - 50.0 # 随机旋转 def handle_collision_with(self, other_object): # 碰撞处理 super(AsteroidSmall, self).handle_collision_with(other_object) if self.dead and self.scale > 0.25: # 当小行星的大小是新小行星的1/4时,小行星应该停止分裂 num_asteroids = random.randint(2, 3) for i in range(num_asteroids): new_asteroid = AsteroidSmall(x=self.x, y=self.y, batch=self.batch) new_asteroid.rotation = random.randint(0, 360) new_asteroid.velocity_x = (random.random() * 70 + self.velocity_x) new_asteroid.velocity_y = (random.random() * 70 + self.velocity_y) new_asteroid.scale = self.scale * 0.5 self.new_objects.append(new_asteroid) def update(self, dt): super(AsteroidSmall, self).update(dt) self.rotation += self.rotate_speed * dt
接下来需要做的最后一件事是转到load.py并让asteroid()方法创建一个新的Asteroid而不是PhysicalObject:
new_asteroid = aster.AsteroidSmall(x=asteroid_x, y=asteroid_y, batch=batch) def asteroids(num_asteroids, player_position, batch=None): ... for i in range(num_asteroids): ... new_asteroid = aster.AsteroidSmall(x=asteroid_x, y=asteroid_y, batch=batch) ... return asteroids
最后测试: