当前位置:   article > 正文

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

ursina

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生成迷宫的算法

  1. from ursina import *
  2. from ursina.prefabs.first_person_controller import FirstPersonController
  3. from ursina.prefabs.sky import Sky
  4. from create import createMaze

接下来,创建Ursina主程序

app=Ursina()

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

在程序中引入这些材质

  1. wall_texture=load_texture("texture/brick.png")
  2. start_texture=load_texture("texture/redstoneblock.jpg")
  3. end_texture=load_texture("texture/greendiamondblock.jpg")
  4. 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的形式

  1. class Wall(Button):
  2. def __init__(self,position):
  3. super().__init__(
  4. parent=scene,
  5. model="cube",
  6. texture=wall_texture,
  7. color=color.white,
  8. position=position,
  9. origin_y=0.5
  10. )

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

  1. class Start(Button):
  2. def __init__(self,position):
  3. super().__init__(
  4. parent=scene,
  5. model="cube",
  6. texture=start_texture,
  7. color=color.white,
  8. position=position,
  9. origin_y=0.5
  10. )

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

  1. class End(Button):
  2. def __init__(self,position):
  3. super().__init__(
  4. parent=scene,
  5. model="cube",
  6. texture=end_texture,
  7. color=color.white,
  8. position=position,
  9. origin_y=0.5
  10. )
  11. class Ground(Button):
  12. def __init__(self,position):
  13. super().__init__(
  14. parent=scene,
  15. model="cube",
  16. texture=ground_texture,
  17. color=color.white,
  18. position=position,
  19. origin_y=0.5
  20. )

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

  1. class FirstPersonController(Entity):
  2. def __init__(self, **kwargs):
  3. self.cursor = Entity(parent=camera.ui, model='quad', color=color.pink, scale=.008, rotation_z=45)
  4. super().__init__()
  5. self.speed = 5
  6. self.height = 2
  7. self.camera_pivot = Entity(parent=self, y=self.height)
  8. camera.parent = self.camera_pivot
  9. camera.position = (0,0,0)
  10. camera.rotation = (0,0,0)
  11. camera.fov = 90
  12. mouse.locked = True
  13. self.mouse_sensitivity = Vec2(40, 40)
  14. self.gravity = 1
  15. self.grounded = False
  16. self.jump_height = 2
  17. self.jump_up_duration = .5
  18. self.fall_after = .35 # will interrupt jump up
  19. self.jumping = False
  20. self.air_time = 0
  21. for key, value in kwargs.items():
  22. setattr(self, key ,value)
  23. # make sure we don't fall through the ground if we start inside it
  24. if self.gravity:
  25. ray = raycast(self.world_position+(0,self.height,0), self.down, ignore=(self,))
  26. if ray.hit:
  27. self.y = ray.world_point.y
  28. def update(self):
  29. self.rotation_y += mouse.velocity[0] * self.mouse_sensitivity[1]
  30. self.camera_pivot.rotation_x -= mouse.velocity[1] * self.mouse_sensitivity[0]
  31. self.camera_pivot.rotation_x= clamp(self.camera_pivot.rotation_x, -90, 90)
  32. self.direction = Vec3(
  33. self.forward * (held_keys['w'] - held_keys['s'])
  34. + self.right * (held_keys['d'] - held_keys['a'])
  35. ).normalized()
  36. feet_ray = raycast(self.position+Vec3(0,0.5,0), self.direction, ignore=(self,), distance=.5, debug=False)
  37. head_ray = raycast(self.position+Vec3(0,self.height-.1,0), self.direction, ignore=(self,), distance=.5, debug=False)
  38. if not feet_ray.hit and not head_ray.hit:
  39. self.position += self.direction * self.speed * time.dt
  40. if self.gravity:
  41. # gravity
  42. ray = raycast(self.world_position+(0,self.height,0), self.down, ignore=(self,))
  43. # ray = boxcast(self.world_position+(0,2,0), self.down, ignore=(self,))
  44. if ray.distance <= self.height+.1:
  45. if not self.grounded:
  46. self.land()
  47. self.grounded = True
  48. # make sure it's not a wall and that the point is not too far up
  49. if ray.world_normal.y > .7 and ray.world_point.y - self.world_y < .5: # walk up slope
  50. self.y = ray.world_point[1]
  51. return
  52. else:
  53. self.grounded = False
  54. # if not on ground and not on way up in jump, fall
  55. self.y -= min(self.air_time, ray.distance-.05) * time.dt * 100
  56. self.air_time += time.dt * .25 * self.gravity
  57. def input(self, key):
  58. if key == 'space':
  59. self.jump()
  60. def jump(self):
  61. if not self.grounded:
  62. return
  63. self.grounded = False
  64. self.animate_y(self.y+self.jump_height, self.jump_up_duration, resolution=int(1//time.dt), curve=curve.out_expo)
  65. invoke(self.start_fall, delay=self.fall_after)
  66. def start_fall(self):
  67. self.y_animator.pause()
  68. self.jumping = False
  69. def land(self):
  70. # print('land')
  71. self.air_time = 0
  72. self.grounded = True
  73. def on_enable(self):
  74. mouse.locked = True
  75. self.cursor.enabled = True
  76. def on_disable(self):
  77. mouse.locked = False
  78. 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__())

  1. class Player(FirstPersonController):
  2. def __init__(self):
  3. global startPosition
  4. super().__init__()
  5. camera.fov=140
  6. self.position=startPosition
  7. self.gravity=0.01
  8. self.speed=6
  9. self.mouse_sensitivity=Vec2(160,160)
  10. def jump(self):
  11. pass

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

  1. maze=createMaze(15,15)
  2. startPosition=None
  3. endPosition=None
  4. banPostions=[]

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

  1. for y in range(1,4):
  2. for x,row in enumerate(maze):
  3. for z,value in enumerate(row):
  4. if str(value)=="s":
  5. Start((x*2,0,z*2))
  6. Start((x*2,0,z*2+1))
  7. Start((x*2+1,0,z*2))
  8. Start((x*2+1,0,z*2+1))
  9. startPosition=(x*2,3,z*2)
  10. banPostions.append([x*2,z*2])
  11. banPostions.append([x*2,z*2+1])
  12. banPostions.append([x*2+1,z*2])
  13. banPostions.append([x*2+1,z*2+1])
  14. elif str(value)=="e":
  15. End((x*2,0,z*2))
  16. End((x*2,0,z*2+1))
  17. End((x*2+1,0,z*2))
  18. End((x*2+1,0,z*2+1))
  19. endPosition=(x*2,3,z*2)
  20. banPostions.append([x*2,z*2])
  21. banPostions.append([x*2,z*2+1])
  22. banPostions.append([x*2+1,z*2])
  23. banPostions.append([x*2+1,z*2+1])
  24. elif str(value)=="0":
  25. Wall((x*2,y,z*2))
  26. Wall((x*2,y,z*2+1))
  27. Wall((x*2+1,y,z*2))
  28. Wall((x*2+1,y,z*2+1))

生成地板

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

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

  1. player=Player()
  2. sky=Sky()
  3. app.run()

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

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

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

  1. from ursina import *
  2. from ursina.prefabs.first_person_controller import FirstPersonController
  3. from ursina.prefabs.sky import Sky
  4. from create import createMaze
  5. app=Ursina()
  6. wall_texture=load_texture("texture/brick.png")
  7. start_texture=load_texture("texture/redstoneblock.jpg")
  8. end_texture=load_texture("texture/greendiamondblock.jpg")
  9. ground_texture=load_texture("texture/plank.jpg")
  10. class Wall(Button):
  11. def __init__(self,position):
  12. super().__init__(
  13. parent=scene,
  14. model="cube",
  15. texture=wall_texture,
  16. color=color.white,
  17. position=position,
  18. origin_y=0.5
  19. )
  20. class Start(Button):
  21. def __init__(self,position):
  22. super().__init__(
  23. parent=scene,
  24. model="cube",
  25. texture=start_texture,
  26. color=color.white,
  27. position=position,
  28. origin_y=0.5
  29. )
  30. class End(Button):
  31. def __init__(self,position):
  32. super().__init__(
  33. parent=scene,
  34. model="cube",
  35. texture=end_texture,
  36. color=color.white,
  37. position=position,
  38. origin_y=0.5
  39. )
  40. class Ground(Button):
  41. def __init__(self,position):
  42. super().__init__(
  43. parent=scene,
  44. model="cube",
  45. texture=ground_texture,
  46. color=color.white,
  47. position=position,
  48. origin_y=0.5
  49. )
  50. class Player(FirstPersonController):
  51. def __init__(self):
  52. global startPosition
  53. super().__init__()
  54. camera.fov=140
  55. self.position=startPosition
  56. self.gravity=0.01
  57. self.speed=6
  58. self.mouse_sensitivity=Vec2(160,160)
  59. def jump(self):
  60. pass
  61. maze=createMaze(15,15)
  62. startPosition=None
  63. endPosition=None
  64. banPostions=[]
  65. for y in range(1,4):
  66. for x,row in enumerate(maze):
  67. for z,value in enumerate(row):
  68. if str(value)=="s":
  69. Start((x*2,0,z*2))
  70. Start((x*2,0,z*2+1))
  71. Start((x*2+1,0,z*2))
  72. Start((x*2+1,0,z*2+1))
  73. startPosition=(x*2,3,z*2)
  74. banPostions.append([x*2,z*2])
  75. banPostions.append([x*2,z*2+1])
  76. banPostions.append([x*2+1,z*2])
  77. banPostions.append([x*2+1,z*2+1])
  78. elif str(value)=="e":
  79. End((x*2,0,z*2))
  80. End((x*2,0,z*2+1))
  81. End((x*2+1,0,z*2))
  82. End((x*2+1,0,z*2+1))
  83. endPosition=(x*2,3,z*2)
  84. banPostions.append([x*2,z*2])
  85. banPostions.append([x*2,z*2+1])
  86. banPostions.append([x*2+1,z*2])
  87. banPostions.append([x*2+1,z*2+1])
  88. elif str(value)=="0":
  89. Wall((x*2,y,z*2))
  90. Wall((x*2,y,z*2+1))
  91. Wall((x*2+1,y,z*2))
  92. Wall((x*2+1,y,z*2+1))
  93. y2=0
  94. for x2 in range(x*2+1):
  95. for z2 in range(z*2+1):
  96. if not ([x2,z2] in banPostions):
  97. Ground((x2,y2,z2))
  98. player=Player()
  99. sky=Sky()
  100. app.run()

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/790314
推荐阅读
相关标签
  

闽ICP备14008679号