工具准备:
1.IDE: PyCharm Download PyCharm: The Python IDE for data science and web development by JetBrainsDownload the latest version of PyCharm for Windows, macOS or Linux.
https://www.jetbrains.com/pycharm/download/?sectinotallow=windows2.编程语言:python 3.11
Download Python | Python.orgThe official home of the Python Programming Language
https://www.python.org/downloads/第三方库下载安装:
在整个IDE的下方找到Terminal,如下,打开它。
1.安装几个第三方库 输入以下命令回车下载
pip install pygame moderngl PyGLM numba
他们分别为
pygame:用来创建openGL上下文和事件处理和纹理加载
moderngl:用python语言重新编写并包装了openGL,使我们可以用更简单的语法和函数使用复杂的图形接口。
Numpy:可以使用各种数据类型的n维数组,并用于形成网格顶点数据
PyGLM:用于矩阵和向量相关的数学运算。
numba:提升python的性能和速度
2.创建相关资源文件夹
如图所示,创建以下文件:
用于存放 各种资源的assets文件夹
用于存放 多边形网格的meshes文件夹
用于存放 着色器的shaders文件夹
用于存放 世界物体的world_objects文件夹
主py文件和设置py文件
3.引入各种第三方包
位置:在settings.py文件中
引入以下代码
from numba import njit
import numpy as np
import glm
import math
# resolution
WIN_RES = glm.vec2(1600,900)
我做了什么?
我从numba包中引入了njit函数;
引入numpy重命名为np;
引入了glm和math包 用于数学运算
并将屏幕的分辨率设置为1600*900
位置:在main.py文件中
引入以下代码
from settings import *
import moderngl as mgl
import pygame as pg
import sys
我做了什么:
把settings文件中所有东西引入进来
引入moderngl包并重命名为mgl
引入pygame包并重命名为pg
引入sys包, sys包提供python对操作系统的访问
4.开始构造引擎
I.我们开始构造自己的引擎
位置:在main.py文件中,
class VoxelEngine:
def __init__(self):
pass
def update(self):
pass
def render(self):
pass
def handle_events(self):
pass
def run(self):
pass
这就是引擎的主体,这些方法或函数意味着什么:
__init__()为VoxelEngine类的构造方法;
update()用于更新对象的状态;
render()用于渲染图形或对象
handle_events()用于处理事件
run()用于执行游戏的主循环;众所周知,计算机会一行一行执行代码,代码执行完就会停止,所以必须将其置于不断循环中,否则游戏就停了,对吧。
II.初始化并实例化引擎
位置:main.py中,在引擎代码的下面
if __name__ == '__main__':
app = VoxelEngine()
app.run()
III. 填充引擎的构造方法
位置:在__init__()中
pg.init()
pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION,3)
pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION,3)
pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE)
pg.display.gl_set_attribute(pg.GL_DEPTH_SIZE,24)
pg.display.set_mode(WIN_RES, flags=pg.OPENGL | pg.DOUBLEBUF)
self.ctx = mgl.create_context()
self.ctx.enable(flags=mgl.DEPTH_TEST | mgl.CULL_FACE | mgl.BLEND)
self.ctx.gc_mode = 'auto'
self.clock = pg.time.Clock()
self.delta_time = 0
self.time = 0
self.is_running = True
我们一行行代码解释:从上往下
①初始化pg
②设置openGL的上下文的主版本号,主版本号表示OpenGL的主要版本,即OpenGL 3.x系列,在这里,我们将主版本号设置为3,表明我们希望使用OpenGL 3.x的特性。
③设置openGL的上下文的次版本号,次版本号表示OpenGL主要版本中的次要版本,即OpenGL 3.3,在这里,我们将次版本号设置为3,表明我们希望使用OpenGL 3.3的特性。
④设置GL_CONTEXT_PROFILE_MASK为核心配置;
设置GL_CONTEXT_PROFILE_CORE仅支持核心特性,而不支持过时的特性。
总之,我设置GL_CONTEXT_PROFILE_MASK作为OpenGL上下文,其中只包含OpenGL 3.3 核心规范中定义的特性,而不使用过时的特性。
⑤将深度缓冲区设置为24
⑦该函数用于创建一个窗口,窗口大小为一个包含了两个整数的元组变量,即WIN_RES;
flags参数用于指定窗口的特性,中间的 | 意思是将左右两个参数合并在一起用;
OPENGL参数表示创建的窗口将使用openGL渲染3D图形,该窗口拥有openGL上下文;
DOUBLEBUF参数表示使用双缓冲技术。双缓冲是一种渲染技术,它可以减少屏幕撕裂(screen tearing)现象的发生。通过使用双缓冲,我们可以在后台渲染图像,然后一次性将完整的图像显示在屏幕上,从而避免了部分图像显示在屏幕上时可能出现的撕裂现象。
⑧调用moderngl创建一个openGL上下文并赋值给ctx,使我们可以借助ctx操作openGL上下文
⑩激活moderngl的一些功能:
DEPTH_TEST:深度测试,深度测试是OpenGL中用于确定哪些像素应该被绘制在屏幕上的一种技术。它通过比较每个像素的深度值与已经绘制的像素的深度值来确定是否绘制该像素。启用深度测试可以确保远处的物体不会覆盖近处的物体
CULL_FACE:用面剔除。面剔除是OpenGL中的一种技术,用于剔除不可见的多边形面,从而提高渲染性能。启用面剔除可以使OpenGL只绘制可见的多边形面,而忽略那些不可见的面。
BLEND:启用颜色混合。混合是OpenGL中的一种技术,用于将新绘制的像素与已经存在的像素进行混合,从而实现半透明效果。启用混合可以使OpenGL绘制半透明的物体。
(11)启用垃圾回收机制,自动回收不再被使用的openGL对象
(13-15)用于计时和追踪时间
(17)标识游戏是否正在运行
IV.填充更新方法
位置在:update()里面
self.delta_time = self.clock.tick()
self.time = pg.time.get_ticks() * 0.001
pg.display.set_caption(f'{self.clock.get_fps() :.0f}')
代码解释,从上往下:
①获取上一帧和当前帧的时间间隔(以毫秒为单位),赋值给delta_time变量,这个时间间隔通常用于控制游戏的运行速度,确保游戏在不同的设备上以相同的速度运行,或者用于计算物体移动、动画等的变化
②获取自游戏开始以来的总毫秒数,然后乘以 0.001 将其转换为秒,并将结果赋值给 self.time
变量,这个时间通常用于记录游戏的运行时间。
③clock.get_fps()获取当前帧率并使用set_caption()函数显示在窗口标题栏上;
f'{self.clock.get_fps() :.0f}' 表示将当前帧率四舍五入为整数,并转换为字符串格式。
V.填充渲染方法
位置在render()方法内
self.ctx.clear()
pg.display.flip()
①清除当前的帧缓冲区和深度缓冲区,清除帧缓冲区意味着将缓冲区中的所有像素数据重置为指定的颜色值,通常是清除为背景色或透明色,以准备进行新的渲染。
②显示新的帧,前文提到了双缓冲技术,这意味着在渲染新一帧之前,首先将渲染结果绘制到后台缓冲区中,然后通过调用 display.flip()
方法将后台缓冲区的内容切换到前台缓冲区,从而更新屏幕显示。
VI.填充事件处理方法
位置:handle_events()中
for event in pg.event.get():
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
self.is_running = False
监听窗口的关闭键也就是X键和escape键,立刻将is_running状态设为False。
VII.填充运行方法
位置:run方法内
while self.is_running:
self.handle_events()
self.update()
self.render()
pg.quit()
sys.exit()
当is_running变量处于true时,不断地运行和处理handle_events()、update()和render()方法
当is_running变量处于false时,触发pg.quit()和sys.exit()方法以关闭游戏和回收资源
5.实验下游戏,是否成功
所以,的确是成功了,左上角显示有帧率,非常高,但同时也在消耗过多的资源,因为没有让他sleep,但之后会解决这个问题。
注意:可能你启动游戏后,游戏画面太大了把整个屏幕都包裹了,那么要改下settings.py里面的WIN_RES分辨率参数,改小点,哈哈。
main.py的整体代码如下
from settings import *
import moderngl as mgl
import pygame as pg
import sys
class VoxelEngine:
def __init__(self):
pg.init()
pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, 3)
pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, 3)
pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE)
pg.display.gl_set_attribute(pg.GL_DEPTH_SIZE, 24)
pg.display.set_mode(WIN_RES, flags=pg.OPENGL | pg.DOUBLEBUF)
self.ctx = mgl.create_context()
self.ctx.enable(flags=mgl.DEPTH_TEST | mgl.CULL_FACE | mgl.BLEND)
self.ctx.gc_mode = 'auto'
self.clock = pg.time.Clock()
self.delta_time = 0
self.time = 0
self.is_running = True
def update(self):
self.delta_time = self.clock.tick()
self.time = pg.time.get_ticks() * 0.001
pg.display.set_caption(f'{self.clock.get_fps() :.0f}')
def render(self):
self.ctx.clear()
pg.display.flip()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
self.is_running = False
def run(self):
while self.is_running:
self.handle_events()
self.update()
self.render()
pg.quit()
sys.exit()
if __name__ == '__main__':
app = VoxelEngine()
app.run()
settings.py的代码如下
from numba import njit
import numpy as np
import glm
import math
# resolution
WIN_RES = glm.vec2(1600, 900)