使用pygame 编写一个3D立方体

795 阅读4分钟

让我们编写一个应用程序来练习通过在投影在 2D 屏幕上的 3D 点处绘制图像来构建场景。这将创建一个可识别的 3D 场景,并且图像在接近相机时不会改变大小。

image.png

你将看到一个立方体,由沿其边缘的许多球体图像组成。通过按光标键,您可以水平和垂直平移“相机”;按 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万现金大奖」