前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Pygame学习笔记 6 —— 3D游戏

Pygame学习笔记 6 —— 3D游戏

作者头像
全栈程序员站长
发布2022-09-01 15:49:39
7640
发布2022-09-01 15:49:39
举报

大家好,又见面了,我是你们的朋友全栈君。

    pygame是是上世纪的产品,虽然不适合最3D游戏,但我可以使用pygame来绘制简单的3D图形,就像在白纸上画立体图形一样。

在这里插入图片描述
在这里插入图片描述

主要内容: 视觉上的远近、3D空间、绘制一个空间图形


一、视觉上的远近

  人的视觉总是遵循一个原则:近大远小。想象一下,如果有一天躺在草地上什么都不做,仰望星空,天空突然飞过流星雨,离我们近的流星看起来就很狭长而明亮,离我们远的则呈一个暗点状,我们完全可以用pygame模拟出来:

step1:定义Star类

  要描述一颗流星,需要有坐标和速度。在定义一个列表来存储点集:

代码语言:javascript
复制
class Star(object):
	def __init__(self, x, y, speed):
		self.x = x
		self.y = y
		self.speed = speed

stars = []

step2:生成随机点   我们需要定时的生成流星

代码语言:javascript
复制
x = float(randint(0, 639))
y = float(randint(0, 479))
speed = float(randint(10, 300))
stars.append(Star(x, y, speed))

while True:
	y = float(randint(0, 479))
	speed = float(randint(10, 300))
	stars.append(Star(640., y, speed))

step3:绘制短线   因为近的流星彗尾看起来更狭长,所以我们可以根据速度来绘制一条短线来表示流星。可以使用pygaame的time库函数Clock测量时间,根据时间和速度在不同位置绘制不同长度的短线:

代码语言:javascript
复制
while True:
	for star in stars:
		new_x = star.x - star.speed*time_passed_seconds
		pygame.draw.aaline(screen, white, (new_x, star.y), (star.x+1, star.y))
		star.x = new_x

step4:查看效果

代码语言:javascript
复制
import pygame
from pygame.locals import *
from random import randint

class Star(object):
	def __init__(self, x, y, speed):
		self.x = x
		self.y = y
		self.speed = speed
		
def run():
	pygame.init()
	screen = pygame.display.set_mode((640, 480), 0, 32)

	stars = []
	for k in range(200):
		x = float(randint(0, 639))
		y = float(randint(0, 479))
		speed = float(randint(10, 300))
		stars.append(Star(x, y, speed))

	clock = pygame.time.Clock()
	white = (255, 255, 255)

	while True:
		for event in pygame.event.get():
			if event.type == QUIT:
				pygame.quit()
				return
			elif event.type == KEYDOWN:
				return

		y = float(randint(0, 479))
		speed = float(randint(10, 300))
		stars.append(Star(640., y, speed))

		time_passed = clock.tick(60)
		time_passed_seconds = time_passed / 1000

		screen.fill((0, 0, 0))
		
		for star in stars:
			new_x = star.x - star.speed*time_passed_seconds
			pygame.draw.aaline(screen, white, (new_x, star.y), (star.x+1, star.y))
			star.x = new_x

		def on_screen(star):
			return star.x > 0
			
		stars = list(filter(on_screen, stars))

		pygame.display.update()

if __name__ == "__main__":
	run()

  这里我们需要注意一个细节,我们定义了一个on_screen函数,若一个点在屏幕上则返回值为True,使用filter函数将不在屏幕的函数过滤掉,而保留屏幕上的点(不在屏幕上的点会消耗资源)。

代码语言:javascript
复制
def on_screen(star):
			return star.x > 0
			
		stars = list(filter(on_screen, stars))

  我们不断生成长短不一运动的短线,看起来就像流星一样✨

Pygame学习笔记 6 —— 3D游戏
Pygame学习笔记 6 —— 3D游戏

二、3D空间

  首先,我们定义我们的坐标系,z粥朝向我们,x轴朝右,就和pygame窗口的x轴一样,y轴朝上,和pygame窗口的y轴方向相反。

在这里插入图片描述
在这里插入图片描述

投影:平行投影   投影最简单的办法——丢弃z坐标

代码语言:javascript
复制
def parallel_project(vector3):
	return(vector3.x, vector3.y)

  这种方法简单,但得到的效果很不理想,基本上看不出透视效果。

投影:立体投影   这种投影的效果更接近真实,在它利用透视法把远处的物体缩小了。游戏中广泛应用。

代码语言:javascript
复制
def perspective_project(vector3, d):
    x, y, z = vector3
    return (x*d/z, -y*d/z)

  由于近大远小,所以我们的结果除以一个z,同时,这里考虑了视距d(观察者到屏幕的距离),如下图。 类比:可以想象通过一个孔观察事物,当眼睛远离孔(视距增大)时,视野(fov)减小,看到的内容自然就变少了,当眼睛靠近孔时,视距减小,视野增大,看到的内容增多了。 类比:可以把z看作观察者到被观测物体之间的观测距离,可以看成照相机到物体的距离,当距离z增大时,看到的物体变小,符合透视原理。

在这里插入图片描述
在这里插入图片描述

视距:计算视距

代码语言:javascript
复制
from math import tan
def calculate_viewing_distance(fov, screen_width):
	d = (screen_width/2.0) / tan(fov/2.0)
	return d

三、绘制一个空间图形

  我们可以根据上面所学的,结合数学基础,可以绘制一个3D图形,关键代码如下:

代码语言:javascript
复制
 # 绘制点
        for point in points:
            x, y, z = point - camera_position
            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))

  在这里,我们先定义了一个相机的位置camera_position,它代表了观测者的位置,初始值为(0, 0, -700),因为要除以距离z,所以我们先将z设的比较大(因为如果z很小,如接近0,则(x, y)就接近(∞, ∞)了),之后我们将(x, y)加上center_y, center_y,目的是吧图形绘制在屏幕中央,最后根据ball的尺寸将球绘制在对于的位置。

  完整代码如下:

代码语言:javascript
复制
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 caculate_viewing_distance(fov, screen_width):
    d = (screen_width/2.0) / tan(fov/2.0)
    return d

def run():
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)

    my_font = pygame.font.SysFont("arial", 23)

    ball = pygame.image.load("ball.png")

    # 3D points
    points = []

    fov = 90.0 # Field of view
    viewing_distance = caculate_viewing_distance(radians(fov), SCREEN_SIZE[0])

    # 正方体边上的点,每条边16个点
    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)

                # 以(0, 0, 0)为顶点的正方体边的坐标性质
                if sum((edge_x, edge_y, edge_z)) >= 2:

                    # 移动到以(0, 0, 0)为中心
                    point_x = float(x) - CUBE_SIZE/2
                    point_y = float(y) - CUBE_SIZE/2
                    point_z = float(z) - CUBE_SIZE/2

                    point = Vector3(point_x, point_y, point_z)
                    points.append(point)

    # 以point_z从大到小排序
    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.0)
    camara_speed = Vector3(300.0, 300.0, 300.0)

    clock = pygame.time.Clock()

    while True:

        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_pass_seconds = time_passed / 1000.0

        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

        # fov大小有范围 0-179
        if pressed_keys[K_w]:
            fov = min(179.0, fov+0.2)
            w = SCREEN_SIZE[0]
            viewing_distance = caculate_viewing_distance(radians(fov), w)
        elif pressed_keys[K_s]:
            fov = max(1.0, fov-0.2)
            w = SCREEN_SIZE[0]
            viewing_distance = caculate_viewing_distance(radians(fov), w)

        camera_position += direction * camara_speed *  time_pass_seconds

        # 绘制点
        for point in points:

            x, y, z = point - camera_position
            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))

        # 绘制表
        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)

        # 绘制文字
        white = (255, 255, 255)
        cam_text = my_font.render("camera = "+str(camera_position), True, white)
        screen.blit(cam_text, (5, 5))
        fov_text = my_font.render("field of view = %i"%int(fov), True, white)
        screen.blit(fov_text, (5, 35))
        txt = "viewing distance = %.3f"%viewing_distance
        d_text = my_font.render(txt, True, white)
        screen.blit(d_text, (5, 65))

        pygame.display.update()

if __name__ == "__main__":
    run()

下面就是我们最终的效果啦:

在这里插入图片描述
在这里插入图片描述

第 6 篇pygame学习笔记完结 cheers! ?


参考博客:用Python和Pygame写游戏-从入门到精通 图标引用:Iconfont

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/141355.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、视觉上的远近
  • 二、3D空间
  • 三、绘制一个空间图形
相关产品与服务
图像处理
图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档