Python 用Ursina引擎制作一个3D迷宫游戏

Ursina是一个3D引擎,初步使用方法,见以下文章:

手把手教你用Python编一个《我的世界》 1. 认识Ursina并学会绘制立体图形_Leleprogrammer的博客-CSDN博客_ursinaPython有一个不错的3D引擎——UrsinaUrsina官网:www.ursinaengine.org打开cmd,控制台输入pip install ursina以安装ursina编写第一个程序首先导入ursinafrom ursina import *然后创建appapp=Ursina()运行appapp.run()最终代码:from ursina import *app=Ursina()app.run()如果出现了一个灰色的窗口,https://blog.csdn.net/leleprogrammer/article/details/124780527?spm=1001.2014.3001.5501


了解完Ursina的初步用法,接下来,我们就开始设计这个3D迷宫游戏啦!

效果:

 墙面、地板、起始块、终点块所需要的图像资源我放在下面供大家下载

↓↓

brick.png

 redstoneblock.jpg

greendiamondblock.jpg 

 plank.jpg

 


代码详解:

首先,要用Prim最小生成树的方法生成迷宫,原理就是不断遍历还未遍历过的墙,并不断地删除不需要的墙块,代码见下方文章:

Python Prim 算法 生成迷宫_Leleprogrammer的博客-CSDN博客_prim算法生成迷宫Prim算法生成完美迷宫矩阵https://blog.csdn.net/leleprogrammer/article/details/124205148?spm=1001.2014.3001.5502这里,还有用遍历网格生成迷宫的方法,不过在编辑这个3D迷宫时,我们最好选用上方遍历墙的方式,顺便送上遍历网格的文章:

Python Prim 算法 生成迷宫_Leleprogrammer的博客-CSDN博客Python Prim算法 通过遍历墙来生成迷宫,快来看看吧!https://blog.csdn.net/leleprogrammer/article/details/125472436?spm=1001.2014.3001.5502


把生成迷宫的代码命名为create.py

在主程序main.py中导入相关模块,其中ursina是整个3D引擎,ursina中有一个prefabs模块,prefabs中有一些可以直接拿出来用的东西,比如first_person_controller中的FirstPersonController,是用于设置第一人称的,sky中的Sky可以直接生成天空

然后,create模块中createMaze模块就是我们的Prim生成迷宫的算法

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
from ursina.prefabs.sky import Sky
from create import createMaze

接下来,创建Ursina主程序

app=Ursina()

把刚刚提供给大家的图片存到同目录下的texture文件夹,作为材质包

在程序中引入这些材质

wall_texture=load_texture("texture/brick.png")
start_texture=load_texture("texture/redstoneblock.jpg")
end_texture=load_texture("texture/greendiamondblock.jpg")
ground_texture=load_texture("texture/plank.jpg")

创建Wall类,墙壁方块,继承于Button类,注意,这里不要用Entity,Entity是没有碰撞体积的,所以要判断玩家是否落地,需要写更多代码,这里用Button类,后面使用第一人称时可以更方便,这里parent是场景,也就是Ursina里默认的scene,系统已经自动定义好了,model是cube,也就是正方体,texture即材质,position是坐标,坐标现在还未确定,所以在__init__中添加为参数,color设置为白色,这样贴材质的时候才不会让材质变色,设置为白色,可以让材质显示自己本身的颜色,Ursina中有color变量,一些常用的颜色可以这样使用:color.颜色名,或者用rgb的形式

class Wall(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=wall_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

再创建一个类Start,用作起始方块,这里参数都差不多,只改了材质

class Start(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=start_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

然后Ground(地板)和End(结束点)的类也差不多,都只改了个材质

class End(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=end_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

class Ground(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=ground_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

接下来是Player,我们这里不直接使用Ursina的FirstPersonController,因为它系统已经帮你设置好了行走速度、重力加速度、跳跃等等,这里在Pycharm编辑器里,ctrl+鼠标点FirstPersonController即可看到对应的FirstPersonController的系统代码,系统代码如下:

class FirstPersonController(Entity):
    def __init__(self, **kwargs):
        self.cursor = Entity(parent=camera.ui, model='quad', color=color.pink, scale=.008, rotation_z=45)
        super().__init__()
        self.speed = 5
        self.height = 2
        self.camera_pivot = Entity(parent=self, y=self.height)

        camera.parent = self.camera_pivot
        camera.position = (0,0,0)
        camera.rotation = (0,0,0)
        camera.fov = 90
        mouse.locked = True
        self.mouse_sensitivity = Vec2(40, 40)

        self.gravity = 1
        self.grounded = False
        self.jump_height = 2
        self.jump_up_duration = .5
        self.fall_after = .35 # will interrupt jump up
        self.jumping = False
        self.air_time = 0

        for key, value in kwargs.items():
            setattr(self, key ,value)

        # make sure we don't fall through the ground if we start inside it
        if self.gravity:
            ray = raycast(self.world_position+(0,self.height,0), self.down, ignore=(self,))
            if ray.hit:
                self.y = ray.world_point.y


    def update(self):
        self.rotation_y += mouse.velocity[0] * self.mouse_sensitivity[1]

        self.camera_pivot.rotation_x -= mouse.velocity[1] * self.mouse_sensitivity[0]
        self.camera_pivot.rotation_x= clamp(self.camera_pivot.rotation_x, -90, 90)

        self.direction = Vec3(
            self.forward * (held_keys['w'] - held_keys['s'])
            + self.right * (held_keys['d'] - held_keys['a'])
            ).normalized()

        feet_ray = raycast(self.position+Vec3(0,0.5,0), self.direction, ignore=(self,), distance=.5, debug=False)
        head_ray = raycast(self.position+Vec3(0,self.height-.1,0), self.direction, ignore=(self,), distance=.5, debug=False)
        if not feet_ray.hit and not head_ray.hit:
            self.position += self.direction * self.speed * time.dt


        if self.gravity:
            # gravity
            ray = raycast(self.world_position+(0,self.height,0), self.down, ignore=(self,))
            # ray = boxcast(self.world_position+(0,2,0), self.down, ignore=(self,))

            if ray.distance <= self.height+.1:
                if not self.grounded:
                    self.land()
                self.grounded = True
                # make sure it's not a wall and that the point is not too far up
                if ray.world_normal.y > .7 and ray.world_point.y - self.world_y < .5: # walk up slope
                    self.y = ray.world_point[1]
                return
            else:
                self.grounded = False

            # if not on ground and not on way up in jump, fall
            self.y -= min(self.air_time, ray.distance-.05) * time.dt * 100
            self.air_time += time.dt * .25 * self.gravity


    def input(self, key):
        if key == 'space':
            self.jump()


    def jump(self):
        if not self.grounded:
            return

        self.grounded = False
        self.animate_y(self.y+self.jump_height, self.jump_up_duration, resolution=int(1//time.dt), curve=curve.out_expo)
        invoke(self.start_fall, delay=self.fall_after)


    def start_fall(self):
        self.y_animator.pause()
        self.jumping = False

    def land(self):
        # print('land')
        self.air_time = 0
        self.grounded = True


    def on_enable(self):
        mouse.locked = True
        self.cursor.enabled = True


    def on_disable(self):
        mouse.locked = False
        self.cursor.enabled = False

 我们只需要修改里面一些参数,__init__是一定要被修改的,跳跃的时候有时候会卡墙,然后跳到迷宫顶端,所以咱们这里禁用跳跃,重写jump函数,用pass代替即可。

gravity是重力,我们准备这样设计:一开始,我们是在半空中,然后缓缓下降,进入迷宫初始点,所以要缓缓下降就需要改到重力,将self.gravity改为0.01,也就是默认的1%,然后将玩家移动速度设置为6,也就是将self.speed设置为6,self.position是坐标,暂时未知,用全局变量代替,camera像scene和color一样,也是Ursina系统代码中已经帮我们定义好的了,我们无需重新定义,直接使用即可,camera中的fov是视角,咱们设置大一点,改为140,也差不多算广角了,然后设置self.mouse_sensitivity(鼠标敏感度),这个要像系统代码一样用Vec2,而且要传两个参数,我也不知道为啥这样,反正系统怎么写,咱们格式就尽量跟系统一样。其实这个灵敏度,就是鼠标移动视角的时候的速度,原来是40,40,这里我们设置为原来的4倍,160,160。

别忘了要执行父类的初始化函数哦!(super().__init__())

class Player(FirstPersonController):
    def __init__(self):
        global startPosition
        super().__init__()
        camera.fov=140
        self.position=startPosition
        self.gravity=0.01
        self.speed=6
        self.mouse_sensitivity=Vec2(160,160)

    def jump(self):
        pass

接下来,生成迷宫,然后定义参数,banPositions存储起始块和结束块的坐标,生成地板的时候要跳过,因为在这两个块的地板是与其他不同的

maze=createMaze(15,15)
startPosition=None
endPosition=None
banPostions=[]

这里,因为一个格宽度的路很窄,显得不宽敞,而且有时候行走也有些不方便,这里,我们把每个块放大为2x2,然后再创建场景

for y in range(1,4):
    for x,row in enumerate(maze):
        for z,value in enumerate(row):
            if str(value)=="s":
                Start((x*2,0,z*2))
                Start((x*2,0,z*2+1))
                Start((x*2+1,0,z*2))
                Start((x*2+1,0,z*2+1))
                startPosition=(x*2,3,z*2)
                banPostions.append([x*2,z*2])
                banPostions.append([x*2,z*2+1])
                banPostions.append([x*2+1,z*2])
                banPostions.append([x*2+1,z*2+1])
            elif str(value)=="e":
                End((x*2,0,z*2))
                End((x*2,0,z*2+1))
                End((x*2+1,0,z*2))
                End((x*2+1,0,z*2+1))
                endPosition=(x*2,3,z*2)
                banPostions.append([x*2,z*2])
                banPostions.append([x*2,z*2+1])
                banPostions.append([x*2+1,z*2])
                banPostions.append([x*2+1,z*2+1])
            elif str(value)=="0":
                Wall((x*2,y,z*2))
                Wall((x*2,y,z*2+1))
                Wall((x*2+1,y,z*2))
                Wall((x*2+1,y,z*2+1))

生成地板

y2=0
for x2 in range(x*2+1):
    for z2 in range(z*2+1):
        if not ([x2,z2] in banPostions):
            Ground((x2,y2,z2))

然后实例化player和sky,最后运行程序

player=Player()
sky=Sky()

app.run()

这样就可以实现文章一开始图片中的效果啦!

不过我们走到终点也没有啥效果,这个就留给大家自己尝试和拓展啦!在这里就不再讲解~

最后,附上main.py的参考代码(迷宫生成的代码到我文章开头给出的链接中查看复制):

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
from ursina.prefabs.sky import Sky
from create import createMaze

app=Ursina()

wall_texture=load_texture("texture/brick.png")
start_texture=load_texture("texture/redstoneblock.jpg")
end_texture=load_texture("texture/greendiamondblock.jpg")
ground_texture=load_texture("texture/plank.jpg")

class Wall(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=wall_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

class Start(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=start_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

class End(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=end_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

class Ground(Button):
    def __init__(self,position):
        super().__init__(
            parent=scene,
            model="cube",
            texture=ground_texture,
            color=color.white,
            position=position,
            origin_y=0.5
        )

class Player(FirstPersonController):
    def __init__(self):
        global startPosition
        super().__init__()
        camera.fov=140
        self.position=startPosition
        self.gravity=0.01
        self.speed=6
        self.mouse_sensitivity=Vec2(160,160)

    def jump(self):
        pass

maze=createMaze(15,15)
startPosition=None
endPosition=None
banPostions=[]
for y in range(1,4):
    for x,row in enumerate(maze):
        for z,value in enumerate(row):
            if str(value)=="s":
                Start((x*2,0,z*2))
                Start((x*2,0,z*2+1))
                Start((x*2+1,0,z*2))
                Start((x*2+1,0,z*2+1))
                startPosition=(x*2,3,z*2)
                banPostions.append([x*2,z*2])
                banPostions.append([x*2,z*2+1])
                banPostions.append([x*2+1,z*2])
                banPostions.append([x*2+1,z*2+1])
            elif str(value)=="e":
                End((x*2,0,z*2))
                End((x*2,0,z*2+1))
                End((x*2+1,0,z*2))
                End((x*2+1,0,z*2+1))
                endPosition=(x*2,3,z*2)
                banPostions.append([x*2,z*2])
                banPostions.append([x*2,z*2+1])
                banPostions.append([x*2+1,z*2])
                banPostions.append([x*2+1,z*2+1])
            elif str(value)=="0":
                Wall((x*2,y,z*2))
                Wall((x*2,y,z*2+1))
                Wall((x*2+1,y,z*2))
                Wall((x*2+1,y,z*2+1))

y2=0
for x2 in range(x*2+1):
    for z2 in range(z*2+1):
        if not ([x2,z2] in banPostions):
            Ground((x2,y2,z2))

player=Player()
sky=Sky()

app.run()

喜欢我的文章的可以点赞收藏关注哦!谢谢支持~

<think>好的,我现在需要帮助用户寻找关于使用Python进行3D游戏开发的教程。首先,我得确认用户的需求。用户明确提到“Python 3D game development tutorial”,所以核心是3D游戏开发,且使用Python语言。 接下来,我得回忆Python游戏开发中的常用库。Pygame是比较有名的,但它主要针对2D游戏。对于3D,可能需要其他库,比如Panda3DUrsina引擎、或者PyOpenGL。另外,Blender也支持Python脚本,但可能更多用于建模而非完整游戏开发。 然后,我需要检查用户提供的引用内容。引用[1]提到一个AI课程的Java和Python项目,可能与游戏开发相关,但不确定具体内容。引用[2]和[3]主要讲Python模块化和语言特性,可能对解释Python的优势有帮助,比如模块化、高效数据结构,适合快速开发。 用户可能需要具体的教程资源,所以应该推荐几个主要的库,并给出学习路径。比如先介绍Panda3D,因为它是一个成熟的3D游戏引擎,有官方文档和社区支持。Ursina作为更现代的引擎,可能更易上手。PyOpenGL适合底层图形编程,适合有经验的开发者。 另外,需要考虑用户可能的水平。如果是初学者,推荐Ursina这样的高级引擎;如果有OpenGL基础,PyOpenGL可能更合适。需要涵盖安装、基本概念、示例项目,以及进阶资源。 还要确保回答结构清晰,分步骤推荐,并附上相关链接。同时,生成相关问题,比如比较不同引擎,优化性能的方法,或与其他语言结合使用等。 最后,验证推荐的教程链接是否有效,资源是否最新。比如Panda3D的官方文档和社区教程是否仍然活跃,Ursina的GitHub页面是否有足够的示例。可能还需要提到一些书籍或在线课程,如果用户需要深入学习的话。</think>以下是关于使用Python进行3D游戏开发的教程推荐和学习路径,按照由浅入深的逻辑组织: ### 一、基础工具与框架选择 1. **Panda3D引擎** - 由迪士尼开发的开源3D引擎,支持Python API - 官方文档包含完整教程:$https://docs.panda3d.org/$ [^1] - 示例代码结构: ```python from direct.showbase.ShowBase import ShowBase class MyGame(ShowBase): def __init__(self): super().__init__() self.cam.setPos(0, -20, 5) self.environment = self.loader.loadModel("models/environment") self.environment.reparentTo(self.render) game = MyGame() game.run() ``` 2. **Ursina引擎** - 基于Python的现代游戏开发框架 - 快速创建3D场景: ```python from ursina import * app = Ursina() cube = Entity(model='cube', color=color.orange, scale=(2,1,3)) EditorCamera() app.run() ``` ### 二、核心学习路径 1. **3D坐标系基础** - 理解三维空间中的$xyz$坐标系统 - 掌握物体平移、旋转、缩放的矩阵运算[^2] 2. **图形管线入门** - 顶点着色器与片元着色器基础 - 使用PyOpenGL实现基础渲染: ```python from OpenGL.GL import glBegin, glEnd, glVertex3f, GL_TRIANGLES def render(): glBegin(GL_TRIANGLES) glVertex3f(0, 1, 0) glVertex3f(-1,-1, 0) glVertex3f(1,-1, 0) glEnd() ``` ### 三、实战项目建议 1. **初级项目** - 3D迷宫寻路游戏 - 物理弹球模拟器 2. **进阶开发** - 结合AI的NPC行为控制(参考引用[1]的项目设计) - 多人在线联机功能开发 ### 四、性能优化技巧 1. 使用空间分割算法优化渲染 2. 通过批处理减少draw call 3. 利用GPU加速计算(如Numba库) ### 五、扩展资源 1. **《Python Game Development with Panda3D》**(Manuel Lemos著) 2. **Blender Python脚本开发** - 实现3D模型自动化处理 - 与游戏引擎的资产管道对接
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值