Python利用3D引擎做一個(gè)太陽(yáng)系行星模擬器
這次,我們?cè)賮?lái)用Ursina引擎來(lái)做一個(gè)太陽(yáng)系行星模擬器吧!
想要了解Ursina 3D引擎的基本使用方法的話(huà),查看我的另一篇文章:詳解Python 3D引擎Ursina如何繪制立體圖形
這一次,我們要實(shí)現(xiàn)的效果如下


首先,送上本次需要用到的資源
Earth.jpg

Jupiter.jpg

Mars.jpg

Mercury.jpg

Neptune.jpg

Saturn.jpg

Sun.jpg

Uranus.jpg

Venus.jpg

現(xiàn)在,就開(kāi)始寫(xiě)代碼吧!
首先,導(dǎo)入我們需要的模塊,導(dǎo)入3D引擎ursina,數(shù)學(xué)庫(kù)math,ursina中自帶的第一人稱(chēng),sys,random隨機(jī)庫(kù)
from ursina import * from math import * from ursina.prefabs.first_person_controller import FirstPersonController import sys import random as rd
然后,創(chuàng)建app
app=Ursina()
將窗口設(shè)置為全屏,并設(shè)置背景顏色
window.fullscreen=True window.color=color.black
定義一個(gè)列表,來(lái)儲(chǔ)存生成的星
planets=[]
引入所有星球的材質(zhì)
sun_texture=load_texture("texture/Sun.png")
mercury_texture=load_texture("texture/Mercury.png")
venus_texture=load_texture("texture/Venus.png")
earth_texture=load_texture("texture/Earth.png")
mars_texture=load_texture("texture/Mars.png")
jupiter_texture=load_texture("texture/Jupiter.png")
saturn_texture=load_texture("texture/Saturn.png")
uranus_texture=load_texture("texture/Uranus.png")
neptune_texture=load_texture("texture/Neptune.png")
創(chuàng)建一個(gè)類(lèi)Planet,繼承自實(shí)體Entity,傳入_type是星的類(lèi)型,pos是位置,scale是縮放
angle:每次更新的時(shí)候行星圍繞太陽(yáng)轉(zhuǎn)的弧度
fastMode的值為1或0,表示是否讓行星圍繞太陽(yáng)公轉(zhuǎn)速度增加到200倍
rotation:星球傾斜度,這里我們隨機(jī)生成
rotspeed:星球自轉(zhuǎn)的速度
rotMode:表示沿著xyz軸的其中一條進(jìn)行旋轉(zhuǎn),自動(dòng)選擇
_type存儲(chǔ)星球類(lèi)型
texture則是材質(zhì),通過(guò)eval獲得該變量
然后進(jìn)行超類(lèi)的初始化,model是sphere,也就是球體形狀,texture表示貼圖,color顏色設(shè)置為white,position傳入坐標(biāo)
定義turn方法,傳入angle,只要不是太陽(yáng),就進(jìn)行自轉(zhuǎn)公轉(zhuǎn)操作,如果是快速模式,則速度增加到200倍,然后計(jì)算得出新的xy坐標(biāo),并用exec進(jìn)行自傳操作
最后定義input方法,接受用戶(hù)輸入,注意,這里方法名必須用input,因?yàn)樗窍到y(tǒng)自動(dòng)調(diào)用的,它總會(huì)向其傳入一個(gè)參數(shù),為按下的按鍵名字,我們就進(jìn)行判斷,如果按下回車(chē),則進(jìn)行快速模式和普通模式間的切換
class Planet(Entity):
def __init__(self,_type,pos,scale=2):
self.angle=rd.uniform(0.0005,0.01)
self.fastMode=0
self.rotation=(rd.randint(0,360) for i in range(3))
self.rotspeed=rd.uniform(0.25,1.5)
self.rotMode=rd.choice(["x","y","z"])
self._type=_type
texture=eval(f"{_type}_texture")
super().__init__(model="sphere",
scale=scale,
texture=texture,
color=color.white,
position=pos)
def turn(self,angle):
if self._type!="sun":
if self.fastMode:
angle*=200
self.x=self.x*cos(radians(angle))-self.y*sin(radians(angle))
self.y=self.x*sin(radians(angle))+self.y*cos(radians(angle))
exec(f"self.rotation_{self.rotMode}+=self.rotspeed")
def input(self,key):
if key=="enter":
self.fastMode=1-self.fastMode接下來(lái),我們定義Player類(lèi),繼承自FirstPersonController
為什么不直接用FirstPersonController呢?
因?yàn)閡rsina自帶的FirstPersonController自帶重力,我們這里只是作為第一人稱(chēng)的視角使用,不需要重力,然后還有一些功能我們不需要用到,所以我們就寫(xiě)一個(gè)類(lèi)繼承下來(lái),然后重寫(xiě)它的一部分代碼即可。首先,引入全局變量planets,超類(lèi)初始化,視野設(shè)置為90,將初始位置設(shè)置為地球的位置,重力(gravity)設(shè)置為0,表示沒(méi)有重力,vspeed表示上升下降時(shí)的速度,speed表示水平方向移動(dòng)的速度,mouse_sensitivity是鼠標(biāo)靈敏度,需要用Vec2的形式,注意,上面除了vspeed變量可以自己命名,其它的都不可以修改。接下來(lái),重寫(xiě)input,只接收esc按鍵的信息,當(dāng)我們按下esc時(shí),如果鼠標(biāo)為鎖定,則釋放,如果已經(jīng)釋放,則退出程序。然后創(chuàng)建_update方法,這里我們不重寫(xiě)ursina自動(dòng)調(diào)用的update方法,因?yàn)橄到y(tǒng)代碼里面,update方法還有很多操作,如果我們要重寫(xiě)的話(huà),可能還要加上把系統(tǒng)代碼復(fù)制過(guò)來(lái),代碼過(guò)于繁瑣,這里我們自己定義一個(gè)名字,在接下來(lái)會(huì)講到的代碼中自己調(diào)用它,在該方法中,監(jiān)聽(tīng)鼠標(biāo)左鍵、左shift和空格的事件,空格原本是跳躍,這里我們?cè)O(shè)置為上升,系統(tǒng)代碼是在input中接收空格鍵的信息的,我們已經(jīng)重寫(xiě)過(guò)了,所以這里不會(huì)觸發(fā)系統(tǒng)代碼的跳躍方法。
這里講一下input和update中進(jìn)行按鍵事件監(jiān)聽(tīng)操作的不同,input每次只接收一個(gè)按鍵,而且,如果我們一個(gè)按鍵一直按下,它不會(huì)一直觸發(fā),只會(huì)觸發(fā)一次,然后等到該按鍵釋放,才會(huì)重新對(duì)該按鍵進(jìn)行監(jiān)聽(tīng);update相當(dāng)于主循環(huán),在任何于ursina有關(guān)的地方(比如繼承自Entity、Button這樣的類(lèi),或者是主程序)寫(xiě)update方法,ursina都會(huì)進(jìn)行自動(dòng)調(diào)用,我們不需要手動(dòng)調(diào)用它,在update方法中監(jiān)聽(tīng)事件,我們用到了held_keys,不難發(fā)現(xiàn),held_keys有多個(gè)元素,只要按下就為T(mén)rue,所以每次運(yùn)行到這里,只要按鍵按下,就執(zhí)行,而input傳入的key本身就是一個(gè)元素,所以只有一個(gè),我們按下esc的操作不能連續(xù)調(diào)用,所以用input,其它移動(dòng)玩家的代碼時(shí)可以重復(fù)執(zhí)行的,所以寫(xiě)在update(應(yīng)該說(shuō)是用held_keys)中。
class Player(FirstPersonController):
def __init__(self):
global planets
super().__init__()
camera.fov=90
self.position=planets[3].position
self.gravity=0
self.vspeed=2
self.speed=600
self.mouse_sensitivity=Vec2(160,160)
self.on_enable()
def input(self,key):
if key=="escape":
if mouse.locked:
self.on_disable()
else:
sys.exit()
def _update(self):
if held_keys["left mouse"]:
self.on_enable()
if held_keys["left shift"]:
self.y-=self.vspeed
if held_keys["space"]:
self.y+=self.vspeed然后在主程序中寫(xiě)update方法,并在其中調(diào)用我們剛剛寫(xiě)的player中的_update方法,再對(duì)星球進(jìn)行自轉(zhuǎn)公轉(zhuǎn)操作
def update():
global planets,player
for planet in planets:
planet.turn(planet.angle)
player._update()
接下來(lái),我們定義兩個(gè)列表,分別表示星球名稱(chēng)和星球的大小,其實(shí)在實(shí)際的大小比例中,和這個(gè)相差很多,如果地球是1,太陽(yáng)則大約為130000,木星和圖形分別為1500多和700多,這樣相差太大,做在程序里看起來(lái)很不尋常,所以我們這里對(duì)大多數(shù)星球的大小進(jìn)行放大縮小,把它們大小的相差拉近點(diǎn)。然后遍歷并繪制,每顆星球的間隔為前一個(gè)的10倍
ps=["sun","mercury","venus","earth","mars","jupiter","saturn","uranus","neptune"]
cp=[200,15,35,42,20,160,145,90,80]
x,y,z=0,0,0
for i,p in enumerate(ps):
newPlanet=Planet(p,(x,y,z),cp[i])
planets.append(newPlanet)
x+=cp[i]*10
最后實(shí)例化player,并運(yùn)行app
player=Player()
if __name__ == '__main__':
app.run()
然后就能實(shí)現(xiàn)文章前面展示的效果啦~


最后,附上代碼
from ursina import *
from math import *
from ursina.prefabs.first_person_controller import FirstPersonController
import sys
import random as rd
app=Ursina()
window.fullscreen=True
window.color=color.black
planets=[]
class Planet(Entity):
def __init__(self,_type,pos,scale=2):
self.angle=rd.uniform(0.0005,0.01)
self.fastMode=0
self.rotation=(rd.randint(0,360) for i in range(3))
self.rotspeed=rd.uniform(0.25,1.5)
self.rotMode=rd.choice(["x","y","z"])
self._type=_type
texture=eval(f"{_type}_texture")
super().__init__(model="sphere",
scale=scale,
texture=texture,
color=color.white,
position=pos)
def turn(self,angle):
if self._type!="sun":
if self.fastMode:
angle*=200
self.x=self.x*cos(radians(angle))-self.y*sin(radians(angle))
self.y=self.x*sin(radians(angle))+self.y*cos(radians(angle))
exec(f"self.rotation_{self.rotMode}+=self.rotspeed")
def input(self,key):
if key=="enter":
self.fastMode=1-self.fastMode
class Player(FirstPersonController):
def __init__(self):
global planets
super().__init__()
camera.fov=90
self.position=planets[3].position
self.gravity=0
self.vspeed=2
self.speed=600
self.mouse_sensitivity=Vec2(160,160)
self.on_enable()
def input(self,key):
if key=="escape":
if mouse.locked:
self.on_disable()
else:
sys.exit()
def _update(self):
if held_keys["left mouse"]:
self.on_enable()
if held_keys["left shift"]:
self.y-=self.vspeed
if held_keys["space"]:
self.y+=self.vspeed
def update():
global planets,player
for planet in planets:
planet.turn(planet.angle)
player._update()
sun_texture=load_texture("texture/Sun.png")
mercury_texture=load_texture("texture/Mercury.png")
venus_texture=load_texture("texture/Venus.png")
earth_texture=load_texture("texture/Earth.png")
mars_texture=load_texture("texture/Mars.png")
jupiter_texture=load_texture("texture/Jupiter.png")
saturn_texture=load_texture("texture/Saturn.png")
uranus_texture=load_texture("texture/Uranus.png")
neptune_texture=load_texture("texture/Neptune.png")
ps=["sun","mercury","venus","earth","mars","jupiter","saturn","uranus","neptune"]
cp=[200,15,35,42,20,160,145,90,80]
x,y,z=0,0,0
for i,p in enumerate(ps):
newPlanet=Planet(p,(x,y,z),cp[i])
planets.append(newPlanet)
x+=cp[i]*10
player=Player()
if __name__ == '__main__':
app.run()以上就是Python利用3D引擎做一個(gè)太陽(yáng)系行星模擬器的詳細(xì)內(nèi)容,更多關(guān)于Python太陽(yáng)系行星模擬器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)Tracert追蹤TTL值的方法詳解
Tracert命令跟蹤路由原理是IP路由每經(jīng)過(guò)一個(gè)路由節(jié)點(diǎn)TTL值會(huì)減一。本文我們將通過(guò)scapy構(gòu)造一個(gè)路由追蹤工具并實(shí)現(xiàn)一次追蹤,感興趣的小伙伴可以了解一下2022-10-10
Python調(diào)用Elasticsearch更新數(shù)據(jù)庫(kù)的操作方法
Elasticsearch是一個(gè)分布式、多租戶(hù)的全文搜索引擎,支持HTTP Web接口和無(wú)模式的JSON文檔,本文介紹Python調(diào)用Elasticsearch更新數(shù)據(jù)庫(kù)的相關(guān)操作,感興趣的朋友一起看看吧2024-12-12
解決django服務(wù)器重啟端口被占用的問(wèn)題
今天小編就為大家分享一篇解決django服務(wù)器重啟端口被占用的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07
Anaconda3中的Jupyter notebook添加目錄插件的實(shí)現(xiàn)
這篇文章主要介紹了Anaconda3中的Jupyter notebook添加目錄插件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
一文教會(huì)你用Python獲取網(wǎng)頁(yè)指定內(nèi)容
Python用做數(shù)據(jù)處理還是相當(dāng)不錯(cuò)的,如果你想要做爬蟲(chóng),Python是很好的選擇,它有很多已經(jīng)寫(xiě)好的類(lèi)包,只要調(diào)用即可完成很多復(fù)雜的功能,下面這篇文章主要給大家介紹了關(guān)于Python獲取網(wǎng)頁(yè)指定內(nèi)容的相關(guān)資料,需要的朋友可以參考下2022-03-03
Python 處理數(shù)據(jù)庫(kù)事務(wù)的操作方法
在Python中,處理數(shù)據(jù)庫(kù)事務(wù)通常涉及使用特定的數(shù)據(jù)庫(kù)驅(qū)動(dòng)如sqlite3、PyMySQL和psycopg2等,這些庫(kù)提供事務(wù)管理功能,允許開(kāi)發(fā)者手動(dòng)控制事務(wù)的提交和回滾,本文給大家介紹Python如何處理數(shù)據(jù)庫(kù)事務(wù),感興趣的朋友一起看看吧2024-10-10
Python+OpenCV實(shí)現(xiàn)閾值分割的方法詳解
閾值分割法是一種基于區(qū)域的圖像分割技術(shù),原理是把圖像像素點(diǎn)分為若干類(lèi)。本文將利用Python+OpenCV實(shí)現(xiàn)閾值分割,感興趣的可以了解一下2022-05-05

