让我们编写一个应用程序来练习通过在投影在 2D 屏幕上的 3D 点处绘制图像来构建场景。这将创建一个可识别的 3D 场景,并且图像在接近相机时不会改变大小。
你将看到一个立方体,由沿其边缘的许多球体图像组成。通过按光标键,您可以水平和垂直平移“相机”;按 Q 和 A 在场景中向前和向后移动。W 和 S 键可调整透视投影的观看距离。您可以从立方体的外观和查看图表(绿色)中看到其效果。尝试观看距离和 fov 请注意,宽 fov 会使立方体看起来细长,而窄 fov 会使立方体看起来更平坦。
注意 3D 场景中的相机只是当前视点;它可能是玩家角色眼睛的视图,也可能是游戏中的任何其他视图。
首先创建一个 Vector3 对象列表,其坐标沿立方体的边缘。然后按点的 z 分量对点进行排序,以便在渲染时,首先绘制靠近查看器的点。否则,距离点可能会与靠近观看者的距离点重叠,这将打破 3D 的错觉。
在主循环中,摄像机位置会根据当前按下的键而改变。您可以看到,移动 3D 点的代码与移动 2D 精灵非常相似,只是在 3D 场景中向前和向后移动的附加 z 组件。使用基于时间的移动更新位置的代码实际上与 2D 计算相同;它只使用了 Vector3 对象而不是 Vector2 对象。
代码中的下一个是绘制场景中所有点的循环。首先,通过减去相机位置来调整点,使其相对于相机变量。如果生成的 z 分量大于 0,则表示该点位于相机前面并且可能是可见的 - 否则,绘制它没有意义。当点在相机前方时,通过将 x 和 y 分量乘以观看距离并除以 z 分量来投影。y 轴也会翻转以指向 2D 绘图功能的正确方向。最后,通过将宽度(center_x)的一半添加到x分量,将高度(center_y)的一半添加到y分量,调整2D坐标以将“世界”放置在屏幕中央。
其余代码绘制一个小图表,显示观看距离与屏幕宽度和 fov 的关系。它还会在屏幕上显示一些信息,以便您可以看到按键的效果。
如果要尝试此演示,请尝试添加创建其他对象(如金字塔和球体)的其他点列表。您可能还希望使这些“对象”在 3D 中移动,就像我们在前面对 2D 精灵所做的那样。
源代码:
import pygame
from pygame.locals import *
from gameobjects.vector3 import Vector3
from math import *
from random import randint
SCREEN_SIZE=(640,480)
CUBE_SIZE=300
def calculate_viewing_distance(fov, screen_width):
d = (screen_width/2) / tan(fov/2.0)
return d
def run():
pygame.init()
screen = pygame.display.set_mode (SCREEN_SIZE, 0)
default_font = pygame.font.get_default_font()
font = pygame.font.SysFont (default_font, 24)
ball = pygame.image.load("ball.png") .convert_alpha () # The 3D points
points=[]
fov= 90
viewing_distance= calculate_viewing_distance (radians(fov), SCREEN_SIZE[0]) # Create a list of points along the edge of a cube for x in range(0, CUBE SIZE+1, 20):
for x in range(0,CUBE_SIZE+1,20):
edge_x = x == 0 or x == CUBE_SIZE
for y in range(0,CUBE_SIZE+1,20):
edge_y = y == 0 or y == CUBE_SIZE
for z in range(0, CUBE_SIZE + 1, 20):
edge_z = z == 0 or z == CUBE_SIZE
if sum ((edge_x,edge_y,edge_z)) >=2:
point_x = float(x) - CUBE_SIZE/2
point_y = float(y) - CUBE_SIZE / 2
point_z = float(z) - CUBE_SIZE / 2
points.append(Vector3(point_x,point_y,point_z))
# Sort points in z order
def point_z(point):
return point.z
points.sort(key=point_z,reverse=True)
center_x, center_y = SCREEN_SIZE
center_x/=2
center_y/=2
ball_w,ball_h= ball.get_size()
ball_center_x = ball_w / 2
ball_center_y=ball_h/2
camera_position = Vector3(0.0, 0.0,-700.)
camera_speed = Vector3 (300.0, 300.0, 300.0)
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
quit()
screen.fill((0, 0, 0))
pressed_keys = pygame.key.get_pressed()
time_passed= clock
time_passed_seconds = time_passed/1000
direction = Vector3()
if pressed_keys[K_LEFT]:
direction.x=-1.0
elif pressed_keys[K_RIGHT]:
direction.x=+1.0
if pressed_keys[K_UP]:
direction.y=+1.0
elif pressed_keys [K_DOWN]:
direction.y = -1.0
if pressed_keys[K_q]:
direction.z=+1.0
elif pressed_keys[K_a]:
direction.z=-1.0
if pressed_keys[K_w]:
fov = min(179., fov + 1.)
w = SCREEN_SIZE[0]
viewing_distance= calculate_viewing_distance(radians(fov), w)
elif pressed_keys[K_s]:
fov = max(1., fov - 1.)
w = SCREEN_SIZE[0]
viewing_distance=calculate_viewing_distance(radians(fov), w)
camera_position += direction * camera_speed*time_passed_seconds
# Draw the 3D points
for point in points:
x, y, z = point - camera_position
if z > 0:
x = x*viewing_distance / z
Y = -y * viewing_distance / z
x += center_x
Y += center_y
screen.blit(ball, (x - ball_center_x, y - ball_center_y))
# Draw the field of view diagram
diagram_width = SCREEN_SIZE[0]/ 4
col=(50, 255, 50)
diagram_points = []
diagram_points.append((diagram_width / 2, 100 + viewing_distance / 4))
diagram_points.append((0, 100))
diagram_points.append((diagram_width, 100))
diagram_points.append((diagram_width / 2, 100 + viewing_distance / 4))
diagram_points.append((diagram_width / 2, 100))
pygame.draw.lines(screen, col, False, diagram_points, 2)
# Draw the text
white = (255, 255, 255)
cam_text = font.render("camera ="+str(camera_position), True, white)
screen.blit(cam_text,(5, 5))
fov_text = font.render("field of view = 응i" % int(fov),True, white)
screen.blit(fov_text, (5, 35))
txt = "viewing distance = %.3f"%viewing_distance
d_text= font.render(txt, True, white)
screen.blit(d_text, (5, 65))
pygame.display.update()
if __name__ == '__main__':
run()
总结
具有3D视觉效果的游戏最有可能吸引玩家并让他们保持开心。这不是因为图形更逼真 - 早期的3D游戏实际上看起来很粗糙 - 与2D游戏相比 - 而是因为它们感觉更自然。3D 游戏中的对象可以旋转并以不同的角度查看,就像在现实世界中一样。
本文正在参加「金石计划 . 瓜分6万现金大奖」