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

最后测试:

 

 

posted @ 2019-11-01 14:08  fly_bk  阅读(275)  评论(0编辑  收藏  举报