向量和矩形类的集合
项目描述
向量
用于游戏的合理、高性能的 2D 矢量对象
版权所有 2019-2020 拉里·黑斯廷斯
概述
vec
是一个模块,目前包含一个类:Vector2
,一个为游戏开发而设计的 2D 矢量对象。
特征:
Vector2
对象是不可变的。Vector2
支持所有常用功能,包括运算符重载。Vector2
尽可能延迟计算对象的属性。Vector2
对象毫不费力地支持笛卡尔坐标和极坐标。
为什么另一个向量类?
我参加了三个 PyWeek 游戏挑战。在这 3 次中的 2 次中,我在周中编写了自己的矢量类,完全出于沮丧。
大多数 Python 矢量对象的最大问题是它们是可变的。坦率地说,这种方式是疯狂的。矢量对象应该是不可变的——从 API 的角度来看它才有意义。如果您将某个游戏引擎 pawn 的位置设置为特定的矢量对象,然后修改该矢量对象会怎样?pawn 是否应该自动更新它的位置——如果是这样,它应该如何知道值改变了?
类似地,一些向量类使用度数而不是弧度表示极坐标。再次以这种方式存在疯狂。Python 模块中的三角函数在
math
弧度域中运行,并且必须跟踪某物在哪个域中——并来回转换——是一个不必要的概念复杂化。你有一个游戏要写!
(一些矢量类同时支持极坐标的弧度和度数。这简直是糟糕的 API 设计——它使 API 的表面积增加了一倍,增加了不必要的复杂性并增加了维护和测试开销。拥抱弧度,伙计们。)
与此相关的是,许多矢量类使极坐标成为二等公民。大多数向量类仅将向量存储在笛卡尔坐标中,因此程序员必须在向量对象外部执行所有极坐标操作,或者它们会产生开销和累积误差,即每次操作都转换为极坐标并再次返回。
vec.Vector2
避免了所有这些问题。 vec.Vector2
对象是不可变的,它们支持用极坐标或笛卡尔坐标定义的向量,并且它们严格使用弧度进行极坐标运算。
概念模型
vec.Vector2
对象在概念上表示一个向量。它们可以使用笛卡尔坐标或极坐标定义,并且vec.Vector2
可以查询其笛卡尔坐标和极坐标。
游戏中的大多数矢量对象都是使用笛卡尔坐标定义的。
vec.Vector2
使这很容易,支持任意数量的调用来创建一个。x
支持和y
属性的离散参数、可迭代对象和对象都可以正常工作:
vec.Vector2(0, 1)
vec.Vector2(x=0, y=1)
vec.Vector2((0, 1))
vec.Vector2([0, 1])
vec.Vector2(types.SimpleNamespace(x=0, y=1))
所有这些都定义了相同的向量。最后一个示例是为了演示vec.Vector2
可以基于任何具有x
和y
属性的对象创建向量。
一旦你有了一个矢量对象,你就可以检查它的属性。每个vec.Vector2
对象都可以查询笛卡尔坐标和极坐标:
v = vec.Vector2(0, 1)
print(v.theta, v.r)
打印1.5707963267948966 1.0
。第一个数字是 π/2(大约)。
相反,您也可以vec.Vector2
使用极坐标定义对象,然后询问其笛卡尔坐标:
v2 = vec.Vector2(r=1, theta=1.5707963267948966)
print(v2.x, v2.y)
这打印6.123233995736766e-17 1.0
。从概念上讲,这应该打印0.0, 1.0
出来——但math.pi
只是一个近似值,这意味着我们的结果与一个无穷小的数量相差甚远。
实施细节
内部vec.Vector2
对象是“笛卡尔”或“极坐标”。“笛卡尔”矢量对象根据x
和定义y
;r
“极”矢量对象是根据和定义的theta
。所有其他属性都根据需要延迟计算。
vec.Vector2
对象使用槽,并依赖于__getattr__
实现这种惰性计算。在创建向量时仅设置向量的已知值。如果用户引用了一个尚未计算的属性,Python 将调用vec.Vector2.__getattr__()
,它计算然后设置该值。未来对该属性的引用会跳过此机制并简单地返回缓存值,这仅与常规对象上的属性查找一样昂贵。
对象上的操作vec.Vector2
使用最便宜的方法计算其结果。如果您有一个vec.Vector2
使用极坐标定义的对象,并且您在其上调用.rotate()
or .scale()
,则所有数学运算都在极坐标域中完成。另一方面,添加向量总是在笛卡尔域中完成,所以如果你将一个极向量添加到任何其他向量,它的笛卡尔坐标将被计算——并且结果向量将始终使用笛卡尔坐标定义。
实际上,最后的陈述并不总是正确的。添加两个完全相同的极向量有一个特殊情况theta
:只需添加它们的r
值。这种方法比转换为笛卡尔坐标便宜得多,而且更精确,返回使用极坐标定义的向量! vec.Vector2
利用许多这样的偶然性,尽可能便宜和准确地计算你的向量。
API
vec.Vector2(x=None, y=None, *, r=None, theta=None, r_squared=None)
构造一个vec.Vector2
对象。您可以根据需要传入任意数量的这些参数;但是,您必须同时传入and
或同时传入and 。任何在构造时未传入的属性都将在评估时延迟计算。x
y
r
theta
(vec.Vector2
仅对其参数进行一些验证。它确保r
andtheta
被规范化。但是,它不会检查(x, y)
并(r, theta)
描述相同的向量。如果您传入x
and y
,并且 a theta
andr
不匹配,您将返回你要的vec.Vector2
那个。祝你好运。)
vec.Vector2
对象支持五个属性:
x
、y
、r
、theta
和r_squared
。对象是用笛卡尔坐标还是极坐标定义的都没有关系。这些都有效。 r_squared
相当于,r*r
但基于笛卡尔坐标计算要便宜得多。
vec.Vector2
对象支持迭代器协议。 您可以调用
len()
对象vec.Vector2
——它总是返回 2。您也可以遍历它们,这将按该顺序产生x
andy
属性。
vec.Vector2
对象支持序列协议。 您可以对它们下标,其行为就好像vec.Vector2
对象是一个长度为 2 的包含x
andy
属性的元组。
vec.Vector2
对象也支持布尔协议;您可以将它们与布尔运算符一起使用,并且可以调用bool()
它们。在布尔上下文中使用时,零向量的计算结果为False
,所有其他向量的计算结果为True
。
vec.Vector2
对象是可散列的。
vec.Vector2
对象支持以下运算符:
v1 + v2
将两个向量相加。v1 - v2
从左向量中减去右向量。v1 * scalar
将向量乘以一个标量,相当于v1.scale(scalar)
.v1 / scalar
将向量除以一个标量。+v1
与 完全相同v1
。-v1
返回 的相反值v1
,因此它v1 + (-v1)
应该是零向量。(由于复杂的浮点错误,情况可能并非总是如此。)v1 == v2
是True
如果两个向量完全相同,False
否则。v1 != v2
是False
如果两个向量完全相同,True
否则。
vec.Vector2
对象支持以下方法:
vec.Vector2.scaled(scalar)
返回一个新vec.Vector2
对象,相当于原始向量乘以该标量。
vec.Vector2.scaled_to_length(r)
返回一个新vec.Vector2
对象,相当于长度设置为 的原始向量r
。
vec.Vector2.normalized()
返回一个新vec.Vector2
对象,相当于缩放到长度为 1 的原始向量。
vec.Vector2.rotated(theta)
返回一个新vec.Vector2
对象,等于原始向量旋转theta
弧度。
vec.Vector2.dot(other)
返回“点积” self
• other
。这个结果是一个标量值,而不是一个向量。
vec.Vector2.cross(other)
返回“叉积” self
⨯ other
。这个结果是一个标量值,而不是一个向量。
注意:从技术上讲,没有为二维向量定义“叉积”。实际上,这会返回两个向量的“垂直点积”或“perp 点积”,因为这是人们在要求两个 2D 向量的“叉积”时真正想要的。
vec.Vector2.polar()
返回 的 2 元组(self.r, self.theta)
。
vec.Vector2.lerp(other, ratio)
根据标量比,返回表示self
和之间的线性插值的向量。 应该是(并包括)和之间的值。如果是,则返回。如果是,则返回。如果是,则返回。other
ratio
ratio
0
1
ratio
0
self
ratio
1
other
ratio
0.4
(self * 0.6) + (other * 0.4)
vec.vector2_zero
不可变的、永恒的“零”vec.Vector2
向量对象。
vec
保证每个零向量都是对该对象的引用:
>>> v = vec.Vector2(0, 0)
>>> v is vec.vector2_zero
True
从数学上讲,以极坐标表示的零向量没有定义的角度。因此vec
将其零向量定义为角度为None
。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。