wxPython和pycairo练习记录14

本文介绍了在使用wxPython开发游戏时,如何从pygame库中提取并应用碰撞检测方法,如collide_rect、collide_circle等,以及它们在处理矩形和圆形Sprite对象碰撞检测中的应用和测试案例。
摘要由CSDN通过智能技术生成

wxPython和pycairo练习记录14

继续之前的坦克游戏。碰撞检测技术像什么四叉树、凸包,看不懂啊,所以先从成熟的 pygame 框架里抠出几个能用到的。

pygame 介绍

pygame 是一个利用 SDL(c语言编写)库的写的一个游戏库。
那么游戏的基本流程是什么,说白了就是下面这个事情:

  1. 检查玩家输入(事件)
  2. 判断元素间有无冲突(比如子弹碰撞、碰到补给、碰到怪物等)
  3. 根据信息绘制屏幕上的元素
  4. 重复1、2、3步骤
    pygame最小开发框架

以上内容出自https://www.bilibili.com/read/cv23670475/ 作者:教娃学编程

从上面 pygame 的介绍来看,和 wxPython 工作流程基本是一样的。

抠代码,pygame 中的碰撞检测

pygame 中的碰撞检测

文档:
https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.collide_rect

源码:
https://github.com/pygame/pygame/blob/main/src_py/sprite.py
https://github.com/pygame/pygame/blob/main/src_c/rect.c

之前写坦克游戏并没有明确组合多个 Sprite 对象,还是先只考虑独立的 Sprite 吧。这样,需要用到和改写的方法只有 collide_rect、collide_rect_ratio、collide_circle、collide_circle_ratio、collide_mask

观察了一下,发现这些方法其实可以直接把待检测 Sprite 对象作为参数,都可以独立出来,而不需要和 Sprite 类绑定。

collide_rect、collide_rect_ratio

这部分较简单,比较根据 Sprite 对象坐标和尺寸生成的矩形的四个顶点坐标,判断是否有重合。缩放是相对 Sprite 对象中心点进行缩放,得到新的操作矩形,然后判断重合。
collide_rect

class Collision:

    @staticmethod
    def inflate(rect, x, y):
        return wx.Rect(int(rect.X - x / 2), int(rect.Y - y / 2), int(rect.Width + x), int(rect.Height + y))

    @staticmethod
    def colliderect(A, B):
        if A.Width == 0 or A.Height == 0 or B.Width == 0 or B.Height == 0:
            return False
        return (min(A.X, A.X + A.Width) < max(B.X, B.X + B.Width) and
                min(A.Y, A.Y + A.Height) < max(B.Y, B.Y + B.Height) and
                max(A.X, A.X + A.Width) > min(B.X, B.X + B.Width) and
                max(A.Y, A.Y + A.Height) > min(B.Y, B.Y + B.Height))

    @staticmethod
    def collide_rect(left, right):
        return Collision.colliderect(left.GetRect(), right.GetRect())

    @staticmethod
    def collide_rect_ratio(left, right, ratio=1.0):
        leftrect = left.GetRect()
        width = leftrect.Width
        height = leftrect.Height
        leftrect = Collision.inflate(leftrect, width * ratio - width, height * ratio - height)

        rightrect = right.GetRect()
        width = rightrect.Width
        height = rightrect.Height
        rightrect = Collision.inflate(rightrect, width * ratio - width, height * ratio - height)

        return Collision.colliderect(leftrect, rightrect)

这里的矩形 wx.Rect 类似图形处理程序里选中图像后的调节边框,但它是横平竖直的。如果 Sprite 对象的 surface 是菱形或者其他不规则图形,collide_rect明显就不适用了。

collide_circle、collide_circle_ratio

简单通过勾股定理,先计算两个 Sprite 对象的圆心距离,然后通过对象的 wx.Rect 操作矩形计算圆半径,两个对象半径之和与之前的圆心距离比较。可以看到操作矩形是圆的外切正方形,圆半径与外切正方形对角线一半的比值为1:2^0.5,似乎没必要计算,直接等于矩形宽度的一半。如果不是正圆,这两个碰撞检测方法应该也不适用。
collide_circle

pygame 官方代码中,如果不指定半径,就直接用外切正方形对角线一半作为半径,明显不对啊。这里不是重点,所以就不去验证了。
https://github.com/pygame/pygame/blob/main/src_py/sprite.py
collide_circle

    @staticmethod
    def center(rect):
        return rect.X + rect.Width / 2, rect.Y + rect.Height / 2

    @staticmethod
    def collide_circle(left, right):
        leftrect = left.GetRect()
        rightrect = right.GetRect()

        leftcenterx, leftcentery = Collision.center(leftrect)
        rightcenterx, rightcentery = Collision.center(rightrect)
        xdistance = leftcenterx - rightcenterx
        ydistance = leftcentery - rightcentery
        distancesquared = xdistance ** 2 + ydistance ** 2

        leftradiussquared = (leftrect.Width ** 2 + leftrect.Height ** 2) / 4
        rightradiussquared = (rightrect.Width ** 2 + rightrect.Height ** 2) / 4

        return distancesquared < leftradiussquared + rightradiussquared

    @staticmethod
    def collide_circle_ratio(left, right, ratio=1.0):
        leftrect = left.GetRect()
        rightrect = right.GetRect()
        leftcenterx, leftcentery = Collision.center(leftrect)
        rightcenterx, rightcentery = Collision.center(rightrect)
        xdistance = leftcenterx - rightcenterx
        ydistance = leftcentery - rightcentery
        distancesquared = xdistance ** 2 + ydistance ** 2

        ratio = ratio ** 2 / 4.0
        leftradiussquared = (leftrect.Width ** 2 + leftrect.Height ** 2) * ratio
        rightradiussquared = (rightrect.Width ** 2 + rightrect.Height ** 2) * ratio

        return distancesquared < leftradiussquared + rightradiussquared

单元测试

collide_mask还没改完,先把前面两个方法的测试代码抠过来。
https://github.com/pygame/pygame/blob/main/test/rect_test.py
https://github.com/pygame/pygame/blob/main/test/sprite_test.py

# -*- coding: utf-8 -*-
import unittest
from wx import Rect
from cairo import ImageSurface, FORMAT_ARGB32
from collision import Collision
from display import Sprite


class CollisionTest(unittest.TestCase):
    def setUp(self):
        self.s1 = Sprite(0, 0, ImageSurface(FORMAT_ARGB32, 50, 10))
        self.s2 = Sprite(40, 0, ImageSurface(FORMAT_ARGB32, 10, 10))
        self.s3 = Sprite(100, 100, ImageSurface(FORMAT_ARGB32, 10, 10))

    def test_inflate__larger(self):
        """The inflate method inflates around the center of the rectangle"""
        r = Rect(2, 4, 6, 8)
        r2 = Collision.inflate(r, 4, 6)

        self.assertEqual(Collision.center(r), Collision.center(r2))
        self.assertEqual(r.left - 2, r2.left)
        self.assertEqual(r.top - 3, r2.top)
        self.assertEqual(r.right + 2, r2.right)
        self.assertEqual(r.bottom + 3, r2.bottom)
        self.assertEqual(r.width + 4, r2.width)
        self.assertEqual(r.height + 6, r2.height)

    def test_inflate__smaller(self):
        """The inflate method inflates around the center of the rectangle"""
        r = Rect(2, 4, 6, 8)
        r2 = Collision.inflate(r, -4, -6)

        self.assertEqual(Collision.center(r), Collision.center(r2))
        self.assertEqual(r.left + 2, r2.left)
        self.assertEqual(r.top + 3, r2.top)
        self.assertEqual(r.right - 2, r2.right)
        self.assertEqual(r.bottom - 3, r2.bottom)
        self.assertEqual(r.width - 4, r2.width)
        self.assertEqual(r.height - 6, r2.height)

    def test_colliderect(self):
        r1 = Rect(1, 2, 3, 4)
        self.assertTrue(
            Collision.colliderect(r1, Rect(0, 0, 2, 3)),
            "r1 does not collide with Rect(0, 0, 2, 3)",
        )
        self.assertFalse(
            Collision.colliderect(r1, Rect(0, 0, 1, 2)), "r1 collides with Rect(0, 0, 1, 2)"
        )
        self.assertTrue(
            Collision.colliderect(r1, Rect(r1.right, r1.bottom, 2, 2)),
            "r1 does not collide with Rect(r1.right, r1.bottom, 2, 2)",
        )
        self.assertTrue(
            Collision.colliderect(r1, Rect(r1.left + 1, r1.top + 1, r1.width - 2, r1.height - 2)),
            "r1 does not collide with Rect(r1.left + 1, r1.top + 1, "
            + "r1.width - 2, r1.height - 2)",
        )
        self.assertTrue(r1.Intersects(Rect(r1.left + 1, r1.top + 1, r1.width - 2, r1.height - 2)))
        self.assertTrue(
            Collision.colliderect(r1, Rect(r1.left - 1, r1.top - 1, r1.width + 2, r1.height + 2)),
            "r1 does not collide with Rect(r1.left - 1, r1.top - 1, "
            + "r1.width + 2, r1.height + 2)",
        )
        self.assertTrue(
            Collision.colliderect(r1, Rect(r1)), "r1 does not collide with an identical rect"
        )
        self.assertFalse(
            Collision.colliderect(r1, Rect(r1.right, r1.bottom, 0, 0)),
            "r1 collides with Rect(r1.right, r1.bottom, 0, 0)",
        )
        self.assertTrue(
            Collision.colliderect(r1, Rect(r1.right, r1.bottom, 1, 1)),
            "r1 does not collide with Rect(r1.right, r1.bottom, 1, 1)",
        )
        self.assertTrue(r1.Intersects(Rect(r1.right, r1.bottom, 1, 1)))

    def test_collide_rect(self):
        # Test colliding - some edges touching
        self.assertTrue(Collision.collide_rect(self.s1, self.s2))
        self.assertTrue(Collision.collide_rect(self.s2, self.s1))

        # Test colliding - all edges touching
        self.s2.SetX(self.s3.GetX())
        self.s2.SetY(self.s3.GetY())

        self.assertTrue(Collision.collide_rect(self.s2, self.s3))
        self.assertTrue(Collision.collide_rect(self.s3, self.s2))

        # Test colliding - no edges touching
        self.s2.SetRect(Collision.inflate(self.s2.GetRect(), 10, 10))

        self.assertTrue(Collision.collide_rect(self.s2, self.s3))
        self.assertTrue(Collision.collide_rect(self.s3, self.s2))

        # Test colliding - some edges intersecting
        self.s2.SetX(self.s1.GetRect().right - self.s2.GetWidth() / 2)
        self.s2.SetY(self.s1.GetRect().bottom - self.s2.GetHeight() / 2)

        self.assertTrue(Collision.collide_rect(self.s1, self.s2))
        self.assertTrue(Collision.collide_rect(self.s2, self.s1))

        # Test not colliding
        self.assertFalse(Collision.collide_rect(self.s1, self.s3))
        self.assertFalse(Collision.collide_rect(self.s3, self.s1))

    def test_collide_rect_ratio__ratio_of_one_like_default(self):
        # collide_rect_ratio should behave the same as default at a 1.0 ratio.
        self.assertTrue(
            Collision.collide_rect_ratio(self.s1, self.s2, 1.0)
        )
        self.assertFalse(
            Collision.collide_rect_ratio(self.s1, self.s3, 1.0)
        )

    def test_collide_rect_ratio__collides_all_at_ratio_of_twenty(self):
        # collide_rect_ratio should collide all at a 20.0 ratio.
        self.assertTrue(
            Collision.collide_rect_ratio(self.s1, self.s2, 20.0)
        )
        self.assertTrue(
            Collision.collide_rect_ratio(self.s1, self.s3, 20.0)
        )

    def test_collide_circle__no_ratio_set(self):
        # collide_circle with no ratio set.
        self.assertTrue(
            Collision.collide_circle(self.s1, self.s2)
        )
        self.assertFalse(
            Collision.collide_circle(self.s1, self.s3)
        )

    def test_collide_circle_ratio__no_radius_and_ratio_of_one(self):
        # collide_circle_ratio with no radius set, at a 1.0 ratio.
        self.assertTrue(
            Collision.collide_circle_ratio(self.s1, self.s2, 1.0)
        )
        self.assertFalse(
            Collision.collide_circle_ratio(self.s1, self.s3, 1.0)
        )

    def test_collide_circle_ratio__no_radius_and_ratio_of_twenty(self):
        # collide_circle_ratio with no radius set, at a 20.0 ratio.
        self.assertTrue(
            Collision.collide_circle_ratio(self.s1, self.s2, 20.0)
        )
        self.assertTrue(
            Collision.collide_circle_ratio(self.s1, self.s3, 20.0)
        )


if __name__ == "__main__":
    unittest.main(verbosity=2)

单元测试

后续改完再发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值