3D 移動

Jason990420發表於2020-05-02

建立日期: 2020/05/02

修訂日期: None

相關軟體資訊:

Win 10 Python 3.7.6 PySimpleGUI 4.18.2 PIL/Pillow 7.1.1

Tool

說明: 本文請隨意引用或更改,只須標示出處及作者,作者不保證內容絕對正確無誤,如造成任何後果,請自行負責.

前言

一個3D 移動的程式, 本來樹有比例放大的功能, 因為速度太慢就取消掉了; PySimpleGUI 本來就不是為遊戲而建立, 所以大量的資料處理會造成作變慢以及間斷的感覺, 因此這裡就不作樹的遠近尺寸變化.

內容說明

  1. 固定的遠景
  2. 馬路
  3. 分路線
  4. 路邊
  5. 其他綠地
  6. 向前移動
  7. 3D

輸出畫面

Python

程式碼及說明

  1. 庫的匯入
import PySimpleGUI as sg
import math
from pathlib import Path
from Tool import Read_File, Read_URL
from PIL import Image
from io import BytesIO
  1. GUI class 的定義
    • 螢幕寛度及高度
    • 分路線, 馬路, 路邊的寛度及分格的長度
    • 攝像頭及螢幕位置
    • 背景/樹的圖片網址及檔名
    • GUI 定義及生成
class GUI():

    def __init__(self):
        self.width, self.height = self.size = (1080, 400)
        self.boxes = 51
        self.x0, self.y0, self.z0 = 400, 150, -50
        self.z1 = 0
        self.box_distance = 100
        self.w0 = 20
        self.w1 = 2000
        self.w2 = 2060
        self.c1 = 0
        self.count = 0
        self.offset = 0.5
        self.background_url = 'https://ae01.alicdn.com/kf/HTB18XRYXBv0gK0jSZKb762K2FXaF.png'
        self.background_file = 'background.png'
        self.Get_Image(self.background_url, self.background_file, (self.width, 300))
        self.background_image = Image.open(self.background_file)
        self.tree_url = 'https://www.vippng.com/png/full/458-4585828_fall-tree-png.png'
        self.tree_file = 'tree.png'
        self.Get_Image(self.tree_url, self.tree_file, (50, 50))
        self.tree_data, self.w, self.h = self.Get_Tree()
        self.window = sg.Window('Racing', layout=[[self.Graph()]],
            return_keyboard_events=True, finalize=True)
        self.draw = self.window.FindElement('Graph')
        self.figures = []
        self.Draw_Background()
  1. 3D 座標轉換成螢幕 2D 座標
def Convert_To_Screen(self, x, y, z):
    scale = (z-self.z1)/(z-self.z0)
    X = x + (self.x0 - x)*scale - self.x0
    Y = y + (self.y0 - y)*scale
    return [X, Y]
  1. 多點之座標轉換
def Convert_points(self, points):
    return [self.Convert_To_Screen(*point) for point in points]
  1. 左下角座標/寛度及長度轉換成方框四點的座標

    除背景及樹以外都是以小方框組合成的

def point_to_points(self, x, y, z, dx, dz):
    return [[self.c1+x, y+self.y1 ,z], [self.c1+x+dx, y+self.y1, z], [self.c2+x+dx, y+self.y2 , z+dz], [self.c2+x, y+self.y2, z+dz]]
  1. 畫上背景
def Draw_Background(self):
    with BytesIO() as output:
        self.background_image.save(output, format='PNG')
        data = output.getvalue()
    self.background_figure = self.draw.DrawImage(data=data, location=(-self.width/2, self.height))
  1. 畫方框
def Draw_Box(self, points, color):
    self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
  1. 畫馬路
def Draw_Road(self, i):
    points = self.point_to_points(-self.w1/2, 0, self.offset+i*self.box_distance, self.w1, self.box_distance)
    points = self.Convert_points(points)
    self.Draw_Box(points, color='grey')
  1. 畫分路線
def Draw_Horse(self, i):
    color = ['grey', 'white']
    points = self.point_to_points(-self.w0/2, 0, self.offset+i*self.box_distance, self.w0, self.box_distance)
    points = self.Convert_points(points)
    self.Draw_Box(points, color=color[(self.count+i)%2])
  1. 畫路邊
def Draw_Gap(self, i):
    color = ['black', 'darkgrey']
    points = self.point_to_points(-self.w2/2, 0, self.offset+i*self.box_distance, self.w2, self.box_distance)
    points = self.Convert_points(points)
    self.Draw_Box(points, color=color[(self.count+i)%2])
  1. 畫綠地
def Draw_Ground(self, i):
    points = self.point_to_points(-self.w2-50, 0, self.offset+i*self.box_distance, 50, self.box_distance)
    points = self.Convert_points(points)
    points[0][0] = points[3][0] = -self.width/2
    points[1][0] = points[2][0] = self.width/2
    self.Draw_Box(points, color='green')
  1. 樹圖檔轉換成可顯示用的圖片資料
def Get_Tree(self):
    im = Image.open(self.tree_file)
    with BytesIO() as output:
        im.save(output, format='PNG')
        data = output.getvalue()
    return data, im.width//2, im.height
  1. 畫左右兩邊的樹
def Draw_Tree(self, i):
    if (i+self.count)%3 == 0:
        x, y = self.Convert_To_Screen(self.c1-self.w2//2-200, self.y1, self.offset+i*self.box_distance)
        figure = self.draw.DrawImage(data=self.tree_data, location = (x-self.w, y+self.h))
        self.figures.append(figure)
        x, y = self.Convert_To_Screen(self.c1+self.w2//2+200, self.y1, self.offset+i*self.box_distance)
        figure = self.draw.DrawImage(data=self.tree_data, location = (x-self.w, y+self.h))
        self.figures.append(figure)
  1. 從網址載入圖片, 按要求尺寸縮放後, 存檔備用.
def Get_Image(self, url, file, size):
    if not Path(file).is_file():
        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)
  1. PySimpleGUI 圖形元件
def Graph(self):
    return sg.Graph(self.size, (-self.width//2, 0), (self.width//2, self.height), key='Graph')
  1. 更新螢幕

    除畫上馬路的所有元素, 同時更改路的方向, 並前進.

    實際上, 不是前進, 是更新馬路的所有元素, 使得看起來像是前進.

def Update(self):
    for figure in self.figures:
        self.draw.DeleteFigure(figure)
    self.figures = []
    for i in range(self.boxes-1, -1, -1):
        self.c1 = -2000*math.sin((i+G.count)*2/self.boxes*math.pi)
        self.c2 = -2000*math.sin((i+G.count+1)*2/self.boxes*math.pi)
        self.y1, self.y2 = self.c1//5-400, self.c2//5-400
        self.Draw_Ground(i)
        self.Draw_Gap(i)
        self.Draw_Road(i)
        self.Draw_Horse(i)
        self.Draw_Tree(i)
    self.x0 = G.c1 # + self.w1//4
    self.count += 1
    if self.count == self.boxes*10:
        self.count = 0
  1. GUI 事件處理

    這裡的按鍵, 雖有動作, 但會被還原, 但仍保留在這裡.

G = GUI()
while True:

    G.Update()
    event, values = G.window.Read(timeout=100)

    if event == None:
        break
    elif event == 'Left:37':
        G.x0 -= G.box_distance
    elif event == 'Right:39':
        G.x0 += G.box_distance
    elif event == 'Up:38':
        pass
    elif event == 'Down:40':
        pass

G.window.close()
本作品採用《CC 協議》,轉載必須註明作者和本文連結

Jason Yang

相關文章