Python 用Ursina引擎制作一个3D迷宫游戏
Posted Leleprogrammer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 用Ursina引擎制作一个3D迷宫游戏相关的知识,希望对你有一定的参考价值。
Ursina是一个3D引擎,初步使用方法,见以下文章:
了解完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迷宫时,我们最好选用上方遍历墙的方式,顺便送上遍历网格的文章:
把生成迷宫的代码命名为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()
喜欢我的文章的可以点赞收藏关注哦!谢谢支持~
3D 模型未在 Ursina 中加载颜色(python 3)
【中文标题】3D 模型未在 Ursina 中加载颜色(python 3)【英文标题】:3D Model not loading with colors in Ursina (python 3) 【发布时间】:2021-11-10 20:13:27 【问题描述】:我正在尝试加载我在 Blender 中制作的低多边形 FPS 手臂。我使用我设置颜色的材料给它们颜色,它在 Blender 中看起来很好。但是当我将它们加载到 Ursina 时,它们完全是白色的,没有颜色!我清楚地认为问题在于我应用颜色的“方式”,这可能是出于某种原因的问题。
这是我导入文件的方式:
class Hand(Entity):
def __init__(self):
super().__init__(model = 'ARMS.obj',
scale = (0.1, 0.1, 0.1),
rotation = (0, -20, 0),
color = color.white,
position = (0, 2, 0))
(对于最小可复制示例,如果不发布我的整个代码将很难做到)
所以是的,它以我在颜色参数中设置的颜色显示(白色以避免影响我在 Blender 中应用的颜色......那不显示)。请问有人知道该怎么做吗?我的意思是,有人有将 3D 模型加载到 Ursina/等价物的经验,并且知道我做错了什么吗?我尽我所能,遵循以下:https://blender.stackexchange.com/questions/75872/not-showing-colors-in-material-mode。我将加入我分配给搅拌机的材料属性的图像:
我真的很想知道如何将我的颜色/未来纹理加载到 ursina 中!
【问题讨论】:
我试试这个,谢谢! 对不起,我弄糊涂了。 Ursina 不能使用 .mtl 文件。相反,您必须将其加载到 Blender(当您导入 .obj 网格时会自动发生)并将 UV 映射纹理导出到 PNG 文件。 是的,这就是我从尝试中看到的:文件没有加载或任何东西......我将 UV 纹理导出为 PNG,按照教程,我得到了图像,看起来都很好,但是当我在代码中将纹理指定为该图像时,UV 展开似乎被遗忘了:纹理没有像预期的那样加载,造成了一种混乱...... 【参考方案1】:OBJ 模型无法在 ursina 中加载颜色。你要做的是
-
在 Blender 顶部单击
Texture Paint
选项卡
在屏幕的左上角,您会看到一个名为 Image
的按钮。
点击它,如果你已经加载了一张图片(看起来像你这样做),点击Save
并将它保存到你的项目文件夹中
在手类中,包含texture
参数
class Hand(Entity):
def __init__(self):
super().__init__(
model = 'ARMS.obj',
scale = (0.1, 0.1, 0.1),
rotation = (0, -20, 0),
color = color.white,
position = (0, 2, 0),
texture = "ARMS.png"
)
【讨论】:
我已经试过了,但是UV展开有点奇怪?并且纹理没有正确加载... @QuantumSushi 你用搅拌机打开你的模型了吗?如果不是:在搅拌机中进入对象的编辑模式。点击顶部的“UV”。然后点击展开。而且,.mtl 文件在 Ursina 中不起作用 我尝试将纹理导出为 PNG,将其加载到 ursina 中,是的,我展开了模型,但是在加载对象时,展开似乎被忘记了,并且加载了纹理,但是做了一团糟以上是关于Python 用Ursina引擎制作一个3D迷宫游戏的主要内容,如果未能解决你的问题,请参考以下文章
请问用unity3d来制作MMO类型网络游戏,服务端该如何制作?听说用Photon网络引擎可以制作,有详细的教程吗?