Python利用3D引擎制作一個(gè)3D迷宮游戲
Ursina是一個(gè)3D引擎,初步使用方法,見(jiàn)文章:詳解Python 3D引擎Ursina如何繪制立體圖形
了解完Ursina的初步用法,接下來(lái),我們就開(kāi)始設(shè)計(jì)這個(gè)3D迷宮游戲啦!
效果:

墻面、地板、起始?jí)K、終點(diǎn)塊所需要的圖像資源我放在下面供大家下載
↓↓
brick.png

redstoneblock.jpg

greendiamondblock.jpg

plank.jpg

代碼詳解:
首先,要用Prim最小生成樹(shù)的方法生成迷宮,原理就是不斷遍歷還未遍歷過(guò)的墻,并不斷地刪除不需要的墻塊,代碼見(jiàn)文章:Python Prim算法通過(guò)遍歷墻實(shí)現(xiàn)迷宮的生成
這里,還有用遍歷網(wǎng)格生成迷宮的方法,不過(guò)在編輯這個(gè)3D迷宮時(shí),我們最好選用上方遍歷墻的方式,順便送上遍歷網(wǎng)格的文章:Python利用Prim算法生成迷宮
把生成迷宮的代碼命名為create.py
在主程序main.py中導(dǎo)入相關(guān)模塊,其中ursina是整個(gè)3D引擎,ursina中有一個(gè)prefabs模塊,prefabs中有一些可以直接拿出來(lái)用的東西,比如first_person_controller中的FirstPersonController,是用于設(shè)置第一人稱的,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
接下來(lái),創(chuàng)建Ursina主程序
app=Ursina()
把剛剛提供給大家的圖片存到同目錄下的texture文件夾,作為材質(zhì)包
在程序中引入這些材質(zhì)
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")
創(chuàng)建Wall類,墻壁方塊,繼承于Button類,注意,這里不要用Entity,Entity是沒(méi)有碰撞體積的,所以要判斷玩家是否落地,需要寫更多代碼,這里用Button類,后面使用第一人稱時(shí)可以更方便,這里parent是場(chǎng)景,也就是Ursina里默認(rèn)的scene,系統(tǒng)已經(jīng)自動(dòng)定義好了,model是cube,也就是正方體,texture即材質(zhì),position是坐標(biāo),坐標(biāo)現(xiàn)在還未確定,所以在__init__中添加為參數(shù),color設(shè)置為白色,這樣貼材質(zhì)的時(shí)候才不會(huì)讓材質(zhì)變色,設(shè)置為白色,可以讓材質(zhì)顯示自己本身的顏色,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
)
再創(chuàng)建一個(gè)類Start,用作起始方塊,這里參數(shù)都差不多,只改了材質(zhì)
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(結(jié)束點(diǎn))的類也差不多,都只改了個(gè)材質(zhì)
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
)接下來(lái)是Player,我們這里不直接使用Ursina的FirstPersonController,因?yàn)樗到y(tǒng)已經(jīng)幫你設(shè)置好了行走速度、重力加速度、跳躍等等,這里在Pycharm編輯器里,ctrl+鼠標(biāo)點(diǎn)FirstPersonController即可看到對(duì)應(yīng)的FirstPersonController的系統(tǒng)代碼,系統(tǒng)代碼如下:
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我們只需要修改里面一些參數(shù),__init__是一定要被修改的,跳躍的時(shí)候有時(shí)候會(huì)卡墻,然后跳到迷宮頂端,所以咱們這里禁用跳躍,重寫jump函數(shù),用pass代替即可。
gravity是重力,我們準(zhǔn)備這樣設(shè)計(jì):一開(kāi)始,我們是在半空中,然后緩緩下降,進(jìn)入迷宮初始點(diǎn),所以要緩緩下降就需要改到重力,將self.gravity改為0.01,也就是默認(rèn)的1%,然后將玩家移動(dòng)速度設(shè)置為6,也就是將self.speed設(shè)置為6,self.position是坐標(biāo),暫時(shí)未知,用全局變量代替,camera像scene和color一樣,也是Ursina系統(tǒng)代碼中已經(jīng)幫我們定義好的了,我們無(wú)需重新定義,直接使用即可,camera中的fov是視角,咱們?cè)O(shè)置大一點(diǎn),改為140,也差不多算廣角了,然后設(shè)置self.mouse_sensitivity(鼠標(biāo)敏感度),這個(gè)要像系統(tǒng)代碼一樣用Vec2,而且要傳兩個(gè)參數(shù),我也不知道為啥這樣,反正系統(tǒng)怎么寫,咱們格式就盡量跟系統(tǒng)一樣。其實(shí)這個(gè)靈敏度,就是鼠標(biāo)移動(dòng)視角的時(shí)候的速度,原來(lái)是40,40,這里我們?cè)O(shè)置為原來(lái)的4倍,160,160。
別忘了要執(zhí)行父類的初始化函數(shù)哦!(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
接下來(lái),生成迷宮,然后定義參數(shù),banPositions存儲(chǔ)起始?jí)K和結(jié)束塊的坐標(biāo),生成地板的時(shí)候要跳過(guò),因?yàn)樵谶@兩個(gè)塊的地板是與其他不同的
maze=createMaze(15,15) startPosition=None endPosition=None banPostions=[]
這里,因?yàn)橐粋€(gè)格寬度的路很窄,顯得不寬敞,而且有時(shí)候行走也有些不方便,這里,我們把每個(gè)塊放大為2x2,然后再創(chuàng)建場(chǎng)景
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))
然后實(shí)例化player和sky,最后運(yùn)行程序
player=Player() sky=Sky() app.run()
這樣就可以實(shí)現(xiàn)文章一開(kāi)始圖片中的效果啦!
不過(guò)我們走到終點(diǎn)也沒(méi)有啥效果,這個(gè)就留給大家自己嘗試和拓展啦!在這里就不再講解~
最后,附上main.py的參考代碼(迷宮生成的代碼到我文章開(kāi)頭給出的鏈接中查看復(fù)制):
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()以上就是Python利用3D引擎制作一個(gè)3D迷宮游戲的詳細(xì)內(nèi)容,更多關(guān)于Python 3D迷宮游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Python實(shí)現(xiàn)智能停車場(chǎng)車牌識(shí)別計(jì)費(fèi)系統(tǒng)
這篇文章主要為大家介紹了如何利用Python實(shí)現(xiàn)一個(gè)智能停車場(chǎng)車牌識(shí)別計(jì)費(fèi)系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手嘗試一下2022-04-04
基于Python?OpenCV和?dlib實(shí)現(xiàn)眨眼檢測(cè)
這篇文章主要介紹了基于Python?OPenCV及dlib實(shí)現(xiàn)檢測(cè)視頻流中的眨眼次數(shù)。文中的代碼對(duì)我們的學(xué)習(xí)和工作有一定價(jià)值,感興趣的同學(xué)可以參考一下2021-12-12
python調(diào)用DLL與EXE文件截屏對(duì)比分析
這篇文章主要為大家介紹了python調(diào)用DLL與EXE文件截屏對(duì)比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-10-10
Python實(shí)現(xiàn)對(duì)特定列表進(jìn)行從小到大排序操作示例
這篇文章主要介紹了Python實(shí)現(xiàn)對(duì)特定列表進(jìn)行從小到大排序操作,涉及Python文件讀取、計(jì)算、正則匹配、排序等相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
Python變量和數(shù)據(jù)類型和數(shù)據(jù)類型的轉(zhuǎn)換
這篇文章主要介紹了Python變量和數(shù)據(jù)類型和數(shù)據(jù)類型的轉(zhuǎn)換,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
Python while 循環(huán)使用的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Python while 循環(huán)使用的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06

