窗口类
这篇文章我们来看两个和3维矢量(vector3)有关的函数。由于我们的游戏是3维的,所以当我提到“矢量”时没有特殊说明都指的是“3维矢量”。先来围观代码:
class Window(...):
...
def get_sight_vector(self):
# rotation 是画面旋转的角度,等我们讲到鼠标移动时再说
x, y = self.rotation
# 这里是一些算法,看看就行了
# 感兴趣的可以评论区讨论
m = math.cos(math.radians(y))
dy = math.sin(math.radians(y))
dx = math.cos(math.radians(x - 90)) * m
dz = math.sin(math.radians(x - 90)) * m
return (dx, dy, dz)
def get_motion_vector(self):
# strafe 是用于记录移动状态的,还记得吗?
# 等到我们讲到键盘事件之后再说
# 如果 strafe 都为0,那么不在移动,就把移动矢量设为0
if any(self.strafe):
# 这里又是一些算法
# 感兴趣的可以评论区讨论
x, y = self.rotation
strafe = math.degrees(math.atan2(*self.strafe))
x_angle = math.radians(x + strafe)
dx = math.cos(x_angle)
dz = math.sin(x_angle)
else:
dx = 0.0
dz = 0.0
return (dx, 0.0, dz)
这两个函数主要都是算法,我们只要知道它们是怎么使用的就行了。get_sight_vector
是获取画面旋转的矢量的。当玩家移动鼠标时,画面会随着鼠标的移动而旋转,旋转的角度就存储在rotation
属性中。get_motion_vector
是获取移动矢量的。只有在玩家按下移动的按键(WSAD)时,才会移动。移动的方向也取决于rotation
属性。剩下的就是一些算法了。
接着我们来讲讲collide
函数是怎么回事。collide
函数长这样:
class Window(...):
...
def collide(self, position, height):
# pad 指的是”陷进地里“能”陷”多少
# 如果为 0.5 那么碰撞体积就是半个方块
# 如果为 0 那就是完全站在方块上
pad = 0.25
# list 让 position 可变
p = list(position)
# normalize 函数还记得吗?忘了的话看一眼第二篇
np = normalize(position)
# 以下循环的本质就是挨个坐标尝试过去,看看会不会发生碰撞
for face in Settings.faces:
# 遍历 x y z 三个值
for i in range(3):
if not face[i]:
continue
# 看看现在的坐标和整数坐标差了多少
d = (p[i] - np[i]) * face[i]
# 小于 pad,不满足碰撞条件,跳过
if d < pad:
continue
# 再按高度挨个尝试过去
for dy in range(height):
# list 是为了修改它
op = list(np)
# 把高度减去,这样可以达到逐个尝试的效果
op[1] -= dy
op[i] += face[i]
# 如果方块不存在,那就不可能发生碰撞了,跳过
# 注意,list 不能作为键使用
# TypeError: unhashable type: 'list'
if tuple(op) not in self.model.world:
continue
# 发生碰撞了,那么修正坐标
p[i] -= (d - pad) * face[i]
# 如果方块在头顶上或者在脚底,那么重力和跳跃的速度就置为0
if face == (0, -1, 0) or face == (0, 1, 0):
self.dy = 0
# 既然已经碰撞了,剩下的高度就不必再尝试了
break
# 复原数据类型
return tuple(p)
我们这篇文章就讲到这里啦。代码比较复杂,不过配合注释应该是能看懂的,也欢迎评论区讨论哦~