Listing 8-8. Simple 3D Engine (simple3d.py)
SCREEN_SIZE = (640, 480) POINT_COUNT = 300 CUBE_SIZE = 300
import pygame from pygame.locals import *
from gameobjects.vector3 import Vector3
from math import *
from random import randint def calculate_viewing_distance(fov, screen_width):
d = (screen_width/2.0) / tan(fov/2.0) return d def run():
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()
fov = 90. # Field of view viewing_distance = calculate_viewing_distance(radians(fov), SCREEN_SIZE)
# Create a list of points along the edge of a cube for x in xrange(0, CUBE_SIZE+1, 20):
for y in xrange(0, CUBE_SIZE+1, 20): edge_y = y == 0 or y == CUBE_SIZE
for z in xrange(0, CUBE_SIZE+1, 20): edge_z = z == 0 or z == CUBE_SIZE
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()
for event in pygame.event.get(): if event.type == QUIT: return screen.fill((0, 0, 0))
pressed_keys = pygame.key.get_pressed()
time_passed = clock.tick() 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
direction.z = +1.0 elif pressed_keys[K_a]: direction.z = -1.0
viewing_distance = calculate_viewing_distance(radians(fov), w) elif pressed_keys[K_s]: fov = max(1., fov-1.) w = SCREEN_SIZE
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 = 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 / 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)
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)
txt = "viewing distance = %.3f" % viewing_distance d_text = font.render(txt, True, white)
Listing 8-8 starts out by creating a list of Vector3 objects with coordinates along the edges of a cube. The points are then sorted by their z components, so that when they are rendered, the points nearer the viewer are drawn first. Otherwise, distance points may overlap those that are close to the viewer and it will break the illusion of 3D.
Inside the main loop, the camera position is changed depending on which keys are currently pressed. You can see that the code to move a 3D point is very similar to moving a 2D sprite, with only an additional z component that moves forward and back in the 3D scene. The code to update the position with time-based movement is actually identical to the 2D calculation; it just used Vector3 objects rather than Vector2 objects.
Next in the code is a loop that draws all the points in the scene. First, the point is adjusted so that it is relative to the camera by subtracting the camera_position variable. If the resulting z component is greater than 0, it means that the point is in front of the camera and may be visible—otherwise, there is no point in drawing it. When the point is in front of the camera, it is projected by multiplying the x and y components by the viewing distance and dividing by the z component. The y axis is also flipped to point in the right direction for the 2D drawing functions. Finally the 2D coordinate is adjusted to place the "world" in the center of the screen by adding half the width (center_x) to the x component and half the height (center_y) to the y component.
The remaining code draws a small diagram that shows how the viewing distance relates to the width of the screen and the fov. It also displays a few pieces of information on screen so that you can see what effect the key presses have.
If you want to experiment with this demonstration, try adding other lists of points that create other objects, such as pyramids and spheres. You might also want to make these "objects" move in 3D in the same way as we have done with 2D sprites in the preceding chapters.
Was this article helpful?