建立日期: 2020/05/02
修訂日期: 2020/05/08 (Revised for Tool.py PEP8)
相關軟體資訊:
Win 10 | Python 3.7.6 | PySimpleGUI 4.18.2 | PIL/Pillow 7.1.1 |
說明: 本文請隨意引用或更改,只須標示出處及作者,作者不保證內容絕對正確無誤,如造成任何後果,請自行負責.
前言
一個3D 移動的程式, 169 行.
內容說明
- 固定的遠景
- 馬路
- 分路線
- 路邊
- 路樹
- 其他綠地
- 向前移動
- 3D
輸出畫面
程式碼及說明
- 庫的匯入
import PySimpleGUI as sg
import math
from pathlib import Path
from Tool import read_URL, mapping
from PIL import Image
from io import BytesIO
- 其本引數設定: 物件長寛, 顏色及數量, 攝像頭位置, 螢幕位置,
class GUI():
def __init__(self):
self.boxes = 51
self.box_height = 50
self.x0, self.y0, self.z0 = 0, 150, -50
self.z1 = 0
self.gap_width = 860
self.road_width = 800
self.line_width = 20
self.offset = 0.5
self.road_color = 'grey'
self.gap_color = 'black'
self.line_color = 'white'
self.figures = []
self.count = 0
self.Create_Window()
- 建立畫布物件
def Create_Canvas(self):
self.width = 1080
self.height = 400
return sg.Graph((self.width, self.height), (-self.width//2, 0),
(self.width//2, self.height), key='Graph')
- 建立 GUI 視窗, 從網路上下載景及樹木的圖片, 並畫上背景
def Create_Window(self):
self.window = sg.Window('3D Road', layout=[[self.Create_Canvas()]],
return_keyboard_events=True, finalize=True)
self.draw = self.window.FindElement('Graph')
self.Load_Background()
self.Load_Tree()
self.Draw_Background()
- 下載圖片, 並以要求的尺寸存檔
def Download_Picture(self, url, file, size):
if not Path(file).is_file():
sg.popup(f'First time to load {file} from web ...', no_titlebar=True,
auto_close=True, auto_close_duration=2)
response, data = read_URL(url, byte=True)
if data:
with open(file, 'wb') as f:
f.write(data)
im = read_file(file)
im = im.resize(size)
im.save(file)
sg.popup(f'{file} loaded ...', no_titlebar=True,
auto_close=True, auto_close_duration=1)
else:
sg.popup(f'{file} load failed...', no_titlebar=True,
auto_close=True, auto_close_duration=2)
self.window.close()
quit()
- 轉換圖片檔案成適合顯示的資料
def Load_Data(self, file):
im = Image.open(file)
with BytesIO() as output:
im.save(output, format='PNG')
data = output.getvalue()
return (data, im.width, im.height)
- 載入背景圖片
def Load_Background(self):
url = 'https://ae01.alicdn.com/kf/HTB18XRYXBv0gK0jSZKb762K2FXaF.png'
file = 'background.png'
size = (self.width, 300)
self.Download_Picture(url, file, size)
self.background, self.background_width, self.background_height = (
self.Load_Data(file))
- 載入樹木圖片
def Load_Tree(self):
url = 'https://www.vippng.com/png/full/458-4585828_fall-tree-png.png'
file = 'tree.png'
size = (250, 250)
self.Download_Picture(url, file, size)
self.tree = Image.open(file)
self.tree_width, self.tree_height = self.tree.size
- 轉換 3D 座標成 2D 座標
def Point_To_2D(self, point):
x, y, z = point
s1 = (z-self.z1)/(z-self.z0)
X = x + (self.x0 - x)*s1 - self.x0
Y = y + (self.y0 - y)*s1
return [X, Y]
- 根據左下角的標及其變化, 生成框的四個角座標
def Point_To_Box(self, x, y, z, dx, dy, dz, dx2):
return [[x, y, z], [x+dx, y, z],
[x+dx+dx2, y+dy, z+dz], [x+dx2, y+dy, z+dz]]
- 顯示背景圖片
def Draw_Background(self):
self.background_figure = self.draw.DrawImage(data=self.background,
location=(-self.background_width//2, self.height))
- 顯示一個填色的框
def Draw_Box(self, x, y, z, dx, dy, dz, dx2, color):
points = self.Point_To_Box(x, y, z, dx, dy, dz, dx2)
points = mapping(self.Point_To_2D, points)
self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
- 顯示綠地
def Draw_Grass(self, x, y, z, dx, dy, dz, dx2, color):
points = self.Point_To_Box(x, y, z, dx, dy, dz, dx2)
points = mapping(self.Point_To_2D, points)
points[0][0] = points[3][0] = -self.width//2
points[1][0] = points[2][0] = self.width//2
self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
- 顯示左右兩邊的樹木, 並按比率縮小
def Draw_Tree(self, x, y, z, dx, dy, dz, scale=1):
w = max(1, int(self.tree_width*scale))
h = max(1, int(self.tree_height*scale))
im = self.tree.resize((w, h))
with BytesIO() as output:
im.save(output, format='PNG')
data = output.getvalue()
X, Y = self.Point_To_2D([x, y, z])
figure = self.draw.DrawImage(data=data,
location = (X-self.tree_width*scale/2, Y+self.tree_height*scale))
self.figures.append(figure)
X, Y = self.Point_To_2D([x+dx, y+dy, z+dy])
figure = self.draw.DrawImage(data=data,
location = (X-self.tree_width*scale/2, Y+self.tree_height*scale))
self.figures.append(figure)
- 刪除記錄中所有已顯示的物件
def Delete_Figures(self):
for figure in self.figures:
self.draw.DeleteFigure(figure)
self.figures = []
- 螢幕更新
- 先刪除已畫的物件
- 畫綠地
- 由遠到近把路邊, 馬路, 中線以及樹畫出
- 馬路的中心位置由sin函式來調整
- 馬路高低也由sin函式來調整
- 樹的放大比率也隨著距離變化
def Update_All(self):
self.Delete_Figures()
self.Draw_Grass(-self.width//2, 0, self.offset,
self.width, 0, self.boxes*self.box_height, 0, 'green')
for box in range(self.boxes-1, -1, -1):
center = -500*math.sin(2*(box+self.count)/self.boxes*math.pi)
dx = -500*math.sin(2*(box+self.count+1)/self.boxes*math.pi)-center
dy = center/2-250
z = box*self.box_height
if (box+self.count)%3 == 0:
self.Draw_Box(center-self.gap_width//2, 0, z, self.gap_width,
0, self.box_height, dx, self.gap_color)
self.Draw_Box(center-self.road_width//2, 0, z, self.road_width,
0, self.box_height, dx, self.road_color)
if (box+self.count)%3 == 0:
self.Draw_Box(center-self.line_width//2, 0, z, self.line_width,
0, self.box_height, dx, self.line_color)
if (box+self.count)%10 == 0:
scale = 1- (z-self.z1)/(z-self.z0)
self.Draw_Tree(center-self.gap_width//2-50, 0, z,
self.gap_width+100, 0, 0, scale)
self.count += 1
if self.count == self.boxes:
self.count = 0
self.x0 = center
- 簡單的GUI事件處理迴路
G = GUI()
while True:
G.Update_All()
event, values = G.window.Read(timeout=100)
if event == None:
break
elif event in ['Left:37', 'Right:39', 'Up:38', 'Down:40']:
pass
G.window.close()
本作品採用《CC 協議》,轉載必須註明作者和本文連結