wxPython和pycairo练习记录10

wxpython入门第八步(画图) wxPython graphics GDI(Graphics Device Interface,图形设备接口)是一个与图形工作的接口,用于与显示器、打印机或文件等图形设备交互。它用于与显示器、打印机或文件等图形设备进行交互。GDI允许程序员在屏幕或打印机上显示数据,而不必关注特定设备的细节。GDI将程序员与硬件隔离开来。 从程序员的角度来看,GDI是一组用于处理图形的类和方法。GDI由二维矢量图形、字体和图像组成。 要开始绘制图形,我们必须创建一个设备上下文(DC)对象。在 wxPython 中,设备上 阅读详情

wxPython和pycairo练习记录10

这次写地图类和简易的地图编辑器。

10.1 地图类

简单按二维平铺地图中的障碍,地图以 json 格式保存。
在这里插入图片描述

        self.obstacles = []
        data = """{
                "cellNumX": 10,
                "cellNumY": 2,
                "cellWidth": 48,
                "cellHeight": 48,
                "data": [2, 1, 2, 1, 2, 3, 4, 5, 4, 4, 
                        2, 1, 2, 7, 0, 3, 6, 5, 4, 4,
                        2, 1, 2, 1, 2, 3, 4, 5, 4, 4]
            }"""
        map = Map(data)
        map.Show()
        self.obstacles += map.GetObjects()
# -*- coding: utf-8 -*-
# map.py

import json
from obstacle import *


class Map:
    def __init__(self, data):
        # 障碍类子类列表 [1Brick, 2Grass, 3Water, 4Ice, 5Iron, 6Blockhouse, 7Mud, 8Transmission]
        self._obstacles = [Obstacle] + Obstacle.__subclasses__() # 0为空
        self._cellNumX = 0 # 横轴网格数
        self._cellNumY = 0 # 纵轴网格数
        self._cellWidth = 0 # 网格宽
        self._cellHeight = 0 # 网格高
        self._data = [] # 地图障碍编号数据列表
        self._objects = [] # 障碍实例,用于碰撞检测
        self.Load(data)

    def Load(self, data):
        # 导入 json 格式地图数据,包括地图网格格数,网格宽高,障碍编号
        map = json.loads(data)
        self._cellNumX = map["cellNumX"]
        self._cellNumY = map["cellNumY"]
        self._cellWidth = map["cellWidth"]
        self._cellHeight = map["cellHeight"]
        self._data = map["data"]

    def Show(self):
        row = 0
        for column, x in enumerate(self._data):
            column = column % self._cellNumX
            # 实例化障碍, 0 不显示
            if x != 0:
                obstacle = self._obstacles[x](self._cellWidth * column, self._cellHeight * row)
                self._objects.append(obstacle)
            if column == self._cellNumX - 1:
                row += 1

    def GetObjects(self):
        return self._objects

10.2 地图编辑器

地图编辑器,简单实现导入素材图片,设置网格,平铺砖块,导出地图 json 。

只保留必要的功能。
在这里插入图片描述

画出简单原型图。
在这里插入图片描述

组件分析。网格可以用 wx.grid.Grid (发现 Grid 控件用在这里功能有些累赘,其实需要的只是网格的边框线);右侧砖块列表中可以用图片按钮,右侧属性用wx.ListCtrl;网格和素材的设置,地图的导入导出,放在窗口的菜单部分。

在这里插入图片描述

地图网格组件,绘图主要是网格线和单元格图片,事件主要是鼠标拖动事件、点击事件,尺寸过大时还需要滚动条。

砖块素材管理组件,绘图主要是砖块列表、属性列表,砖块列表默认编号为0的空砖块,属性列表可以直接用自带控件。砖块列表可以用图片按钮控件。

wx.Image 和 wx.Bitmap 的区别

图片数据需要根据需求作出转换。

wx.Image只是一个RGB字节的缓冲区,还有一个可选的alpha字节的缓冲区。它是所有通用的、独立于平台和图像文件格式的代码。它包括用于缩放、调整大小、剪裁和其他图像数据操作的通用代码。相反,wx.Bitmap旨在成为一个包装器,用于包装最快速/最容易绘制到DC的本地图像格式,或成为在wx.MemoryDC上执行的绘制操作的目标。通过像这样在Image/wxBitmap之间划分责任,那么就更容易使用所有平台和图像类型共享的通用代码进行通用操作,而在需要性能或兼容性的地方使用平台特定代码。

出自:https://docs.wxpython.org/wx.Bitmap.html
翻译自:https://www.deepl.com/

滚动地图网格

一种方案是地图网格用不带滚动的面板实现,外面再加一层可滚动的容器,这样绘制时不用自己计算鼠标相对位置,没有特别要注意的地方;一种方案是网格继承 wx.ScrolledWindow 或 wx.lib.scrolledpanel.ScrolledPanel ,绘制的时候需要调用 self.PrepareDC(dc) ,不然滚动不会绘制,滚动后鼠标相对坐标获取需要调用 self.CalcUnscrolledPosition(event.GetPosition()) ,鼠标在窗口拖动时自动滚动滚动条可以参考 wxPython demo 中的 DragScroller 。最好滚动还是单独作为一个操作,测试的时候边滚动边绘制体验并不好。

参考:evt.GetPosition() in wx.ScrolledWindow https://groups.google.com/g/wxpython-users/c/0VlpIcBYs04

可编辑 ListCtrl

点击砖块素材显示砖块相关属性,可直接编辑 ID 和图片路径。这里没写。

参考:https://www.blog.pythonlibrary.org/2011/01/04/wxpython-wx-listctrl-tips-and-tricks/

鼠标光标

选中砖块素材后,鼠标光标变为砖块缩略图。创建光标实例 wx.Cursor(image:wx.Image) ,然后调用控件方法 SetCursor 。

wx.GridBagSizer

用 wx.GridBagSizer 设置布局,就像是在 Excel 画好格子,然后放置控件。因为是弹性布局,格子的尺寸直接受控件尺寸影响,比如静态文本字太多的话,和它同一列的控件就不好控制位置,就需要把静态文本所占的列划分成很多列。

最后

一开始考虑不全面,后面写的时候就只有一个心思,烂代码,完成就好。代码创建控件实例是个痛苦的过程,因为做的基本都是重复的事,能用循环的最好写进循环,熟悉控件后推荐用 wxFromBuilder 。EXE文件下载地址:https://download.youkuaiyun.com/download/SmileBasic/86507349
在这里插入图片描述

# -*- coding: utf-8 -*-
# https://blog.youkuaiyun.com/SmileBasic
import os
import sys
from collections import OrderedDict
import json
import wx
import wx.lib.wxcairo
import wx.lib.scrolledpanel as scrolled
import wx.lib.dragscroller


class cv:
    BOARD_WIDTH = 1000
    BOARD_HEIGHT = 750
    MAP_GAP = 10  # 网格留白
    MAP_TILE_SIZE = (48, 48)  # 砖块素材列表砖块尺寸
    CURSOR_TILE_SIZE = (32, 32)  # 鼠标箭头砖块尺寸
    tiles = OrderedDict()  # 砖块对象字典,以 ID 为键,有序字典便于素材列表顺序排列
    currentTileId = 0  # 当前砖块 ID


def get_resource_path(relative_path):
    # https://www.bilibili.com/read/cv12744142
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)


class MapGrid(scrolled.ScrolledPanel):
    """
    地图网格控件,网格线、单元格砖块绘制及鼠标事件处理
    """
    def __init__(self, parent, cellNumX=20, cellNumY=15, cellWidth=48, cellHeight=48):
        self._width = cellNumX * cellWidth + cv.MAP_GAP * 2  # 网格总宽度
        self._height = cellNumY * cellHeight + cv.MAP_GAP * 2   # 网格总高度

        super(MapGrid, self).__init__(parent=parent, size=(self._width, self._height), style=wx.SIMPLE_BORDER)

        self._cellNumX = cellNumX  # 横轴网格数
        self._cellNumY = cellNumY  # 纵轴网格数
        self._cellWidth = cellWidth  # 网格宽
        self._cellHeight = cellHeight  # 网格高
        self._cells = []  # 地图数据二维数组

        self._cursorPosition = (0, 0)  # 鼠标坐标,用于更新笔刷

        self.InitUI()
        self.BindEvents()

    def InitUI(self):
        self.SetDoubleBuffered(True)  # 双缓冲,防闪烁
        # TODO 诡异的地方,每次打开软件初始的滚动条位置都不对
        self.SetScrollbars(1, 1, self._width, self._height, 0, 0)  # 设置滚动条

        # 初始化地图数据二维数组,全部填充0
        self.ClearMap()

    def BindEvents(self):
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnDrag)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

    def OnPaint(self, event):
        # TODO 鼠标拖动绘制有时会出现断线
        dc = wx.PaintDC(window=self)
        self.PrepareDC(dc)
        ctx = wx.lib.wxcairo.ContextFromDC(dc)

        self.DrawGrid(ctx)
        self.DrawCell(ctx)

    def DrawGrid(self, ctx):
        # 绘制网格
        ctx.set_source_rgb(0.8, 0.8, 0.8)
        ctx.set_dash([2, 2])  # 虚线,2像素线,2像素空

        # 绘制竖线
        for i in range(self._cellNumX + 1):
            x = i * self._cellWidth + cv.MAP_GAP
            ctx.move_to(x , cv.MAP_GAP)
            ctx.line_to(x, self._height - cv.MAP_GAP)
            ctx.stroke()

        # 绘制横线
        for j in range(self._cellNumY + 1):
            y = j * self._cellHeight + cv.MAP_GAP
            ctx.move_to(cv.MAP_GAP, y)
            ctx.line_to(self._width - cv.MAP_GAP, y)
            ctx.stroke()

    def DrawCell(self, ctx):
        for i in range(self._cellNumY):
            for j in range(self._cellNumX):
                tile = cv.tiles.get(self._cells[i][j])
                if tile and tile.id != 0:
                    surface = tile.Scale((self._cellWidth, self._cellHeight))  # 缩放到地图设置砖块尺寸
                    ctx.set_source_surface(surface, self._cellWidth * j + cv.MAP_GAP, self._cellHeight * i + cv.MAP_GAP)
                    ctx.paint()

    def _SetCell(self, position):
        x, y = position
        if (x > cv.MAP_GAP and x < self._width - cv.MAP_GAP) and (y > cv.MAP_GAP and y < self._height - cv.MAP_GAP):
            # 鼠标拖动时所在行和列
            column = (x - cv.MAP_GAP) // self._cellWidth
            row = (y - cv.MAP_GAP) // self._cellHeight

            # 将鼠标拖动经过单元格砖块编号设为笔刷砖块编号
            self._cells[row][column] = cv.currentTileId
            self.Refresh()  # 重绘

    def OnDrag(self, event):
        # 响应鼠标拖动事件
        self._cursorPosition = self.CalcUnscrolledPosition(event.GetPosition())
        if event.Dragging():
            if event.LeftIsDown():
                self._SetCell(self._cursorPosition)
            elif event.RightIsDown():
                # 右键拖动清除砖块
                tmp = cv.currentTileId
                cv.currentTileId = 0
                self._SetCell(self._cursorPosition)
                cv.currentTileId = tmp

        self.Refresh()

    def OnLeftDown(self, event):
        position = self.CalcUnscrolledPosition(event.GetPosition())
        self._SetCell(position)

    def OnRightDown(self, event):
        # 右键单击清除砖块
        position = self.CalcUnscrolledPosition(event.GetPosition())

        tmp = cv.currentTileId
        cv.currentTileId = 0
        self._SetCell(position)
        cv.currentTileId = tmp

    def ClearMap(self):
        self._cells.clear()
        for i in range(self._cellNumY):
            row = []
            for j in range(self._cellNumX):
                row.append(0)
            self._cells.append(row)
        self.Refresh()

    def CreateMap(self):
        dlg = NewMapDialog(parent=self, title="新建地图")
        dlg.CenterOnScreen()  # 居中显示
        val = dlg.ShowModal()  # 模态窗口

        if val == wx.ID_OK:
            textCtrls = [x for x in dlg.Children if isinstance(x, wx.TextCtrl)]
            self._cellNumX = int(textCtrls[0].GetValue().strip())
            self._cellNumY = int(textCtrls[1].GetValue().strip())
            self._cellWidth = int(textCtrls[2].GetValue().strip())
            self._cellHeight = int(textCtrls[3].GetValue().strip())
            self._width = self._cellNumX * self._cellWidth + cv.MAP_GAP * 2
            self._height = self._cellNumY * self._cellHeight + cv.MAP_GAP * 2
            self.SetScrollbars(1, 1, self._width, self._height, 0, 0)
            self.ClearMap()

        dlg.Destroy()

    def ExprotData(self):
        data = {}
        data["cellNumX"] = self._cellNumX
        data["cellNumY"] = self._cellNumY
        data["cellWidth"] = self._cellWidth
        data["cellHeight"] = self._cellHeight
        data["data"] = [x for y in self._cells for x in y]
        return data

    def ImportData(self, data):
        self._cellNumX = data["cellNumX"]
        self._cellNumY = data["cellNumY"]
        self._cellWidth = data["cellWidth"]
        self._cellHeight = data["cellHeight"]
        self._width = self._cellNumX * self._cellWidth + cv.MAP_GAP * 2
        self._height = self._cellNumY * self._cellHeight + cv.MAP_GAP * 2
        self.SetScrollbars(1, 1, self._width, self._height, 0, 0)

        self._cells.clear()
        for i in range(self._cellNumY):
            row = []
            for j in range(self._cellNumX):
                row.append(data["data"][i * self._cellNumX + j])
            self._cells.append(row)

        self.Refresh()


class NewMapDialog(wx.Dialog):
    def __init__(self, *args, **kwargs):
        super(NewMapDialog, self).__init__(*args, **kwargs)

        grid = wx.GridBagSizer(vgap=5, hgap=5)
        lst = ["横轴块数", "纵轴块数", "砖块宽度", "砖块高度"]
        for i, x in enumerate(lst):
            grid.Add(wx.StaticText(parent=self, label=x + ":"),
                     pos=(i, 0),
                     span=(1, 2),
                     flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.LEFT | wx.TOP,
                     border=10)
            grid.Add(wx.TextCtrl(parent=self),
                     pos=(i, 2),
                     span=(1, 5),
                     flag=wx.EXPAND | wx.ALIGN_LEFT | wx.TOP | wx.RIGHT,
                     border=5)

        grid.Add(wx.Button(self, wx.ID_OK),
                 pos=(4, 1),
                 span=(1, 2),
                 flag=wx.TOP | wx.BOTTOM | wx.RIGHT,
                 border=10)
        grid.Add(wx.Button(self, wx.ID_CANCEL),
                 pos=(4, 3),
                 span=(1, 2),
                 flag=wx.TOP | wx.BOTTOM | wx.LEFT,
                 border=10)

        self.SetSizer(grid)
        grid.Fit(self)


class Tile:
    """
    砖块对象,存储砖块ID、尺寸、图像
    """
    def __init__(self, id, x, y, image):
        self.id = id
        self.x = x
        self.y = y
        self.image = image
        self.bitmap = wx.Bitmap(image, wx.BITMAP_TYPE_PNG)
        self.surface = wx.lib.wxcairo.ImageSurfaceFromBitmap(self.bitmap)  # 原始尺寸 surface
        self.width = self.surface.get_width()
        self.height = self.surface.get_height()
        self.surfaceCursor = self.Scale(cv.CURSOR_TILE_SIZE)
        self.imageCursor = wx.lib.wxcairo.BitmapFromImageSurface(self.surfaceCursor).ConvertToImage()
        self.surfaceMap = self.Scale(cv.MAP_TILE_SIZE)
        
    def Scale(self, destSize=(16, 16)):
        img = self.bitmap.ConvertToImage()  # 转换成 wx.Image 对象
        img = img.Scale(*destSize)  # 缩放到目标尺寸
        bitmap = wx.Bitmap(img, wx.BITMAP_SCREEN_DEPTH)
        surface = wx.lib.wxcairo.ImageSurfaceFromBitmap(bitmap)
        return surface


class MapTile(wx.Panel):
    """
    地图砖块素材控件,管理和显示砖块素材
    """
    def __init__(self, parent):
        super(MapTile, self).__init__(parent=parent, style=wx.SIMPLE_BORDER)
        self._tiles = {}  # 砖块对象
        self.InitUI()
        self.BindEvents()

    def InitUI(self):
        self._lastPosition = (0, 0)  # 最近一个 tile 坐标
        self._lastId = 0  # 最近一个 tile id

        self.topScrollPanel = scrolled.ScrolledPanel(parent=self, style=wx.BORDER_RAISED)
        self.topScrollPanel.SetupScrolling(scroll_x=False, scroll_y=True)

        self.topPanel = wx.Panel(parent=self.topScrollPanel)
        vbox = wx.BoxSizer(orient=wx.VERTICAL)
        vbox.Add(self.topPanel, proportion=1, flag=wx.EXPAND)
        self.topScrollPanel.SetSizer(vbox)

        # TODO 使用可编辑 ListCtrl
        self.listCtrl = wx.ListCtrl(parent=self, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)

        self.listCtrl.InsertColumn(0, '参数', width=60)
        self.listCtrl.InsertColumn(1, '值', width=120)
        self.idIndex = self.listCtrl.InsertItem(0, "ID")
        self.widthIndex = self.listCtrl.InsertItem(1, "宽度")
        self.heightIndex = self.listCtrl.InsertItem(2, "高度")
        self.imageIndex = self.listCtrl.InsertItem(3, "图片")

        # 默认放置置空砖块
        tile = Tile(0, 0, 0, get_resource_path("delete.png"))
        cv.tiles[0] = tile
        wx.BitmapButton(parent=self.topPanel, bitmap=tile.bitmap, style=wx.BORDER_NONE,
                        name="0", pos=(0, 0), size=cv.MAP_TILE_SIZE)

        vbox = wx.BoxSizer(orient=wx.VERTICAL)
        vbox.Add(self.topScrollPanel, proportion=3, flag=wx.EXPAND | wx.ALL, border=10)
        vbox.Add(self.listCtrl, flag=wx.EXPAND | wx.ALL, border=10)

        self.SetSizer(vbox)

        self._SetCursor()  # 设置鼠标样式

    def BindEvents(self):
        self.Bind(wx.EVT_BUTTON, self.OnButton)

    def OnButton(self, event):
        # 鼠标左击按按钮事件,设置当前砖块
        cv.currentTileId = int(event.GetEventObject().GetName())
        tile = cv.tiles.get(cv.currentTileId)
        self._SetCursor()

        self.listCtrl.SetItem(self.idIndex, 1, str(tile.id))
        self.listCtrl.SetItem(self.widthIndex, 1, str(tile.width))
        self.listCtrl.SetItem(self.heightIndex, 1, str(tile.height))
        self.listCtrl.SetItem(self.imageIndex, 1, str(tile.image))

    def OnRightDown(self, event):
        btn = event.GetEventObject()  # 获取右键点击对象
        cv.tiles.pop(int(btn.GetName()))  # 从砖块字典删除
        btn.Destroy()  # 清除按钮

        # 将 topPanel 子控件按钮重新排序
        x = 0
        y = 0
        for button in self.topPanel.Children:
            button.SetPosition((x, y))
            self._lastPosition = (x, y)
            x += cv.MAP_TILE_SIZE[0] + 2
            if x > self.topPanel.Size[0] - (cv.MAP_TILE_SIZE[0] + 2):
                x = 0
                y += cv.MAP_TILE_SIZE[1] + 2

        self.Refresh()
        self.Parent.mapGrid.Refresh()  # 刷新网格,网格中相应已绘制砖块也清除

    def _SetCursor(self):
        tile = cv.tiles.get(cv.currentTileId)
        cursor = wx.Cursor(tile.imageCursor)
        # 设置网格和砖块素材面板的鼠标样式
        self.Parent.mapGrid.SetCursor(cursor)
        self.topPanel.SetCursor(cursor)

    def AddTile(self, path):
        self._lastId += 1
        x, y = self._lastPosition
        x += cv.MAP_TILE_SIZE[0] + 2
        if x > self.topPanel.Size[0] - (cv.MAP_TILE_SIZE[0] + 2):
            x = 0
            y += cv.MAP_TILE_SIZE[1] + 2
        self._lastPosition = (x, y)
        tile = Tile(self._lastId, x, y, path)
        cv.tiles[tile.id] = tile
        # 将砖块实例化为图片按钮,并绑定右键事件
        btn = wx.BitmapButton(parent=self.topPanel,
                              bitmap=tile.bitmap,
                              style=wx.BORDER_NONE,
                              name=str(tile.id),
                              pos=(tile.x, tile.y),
                              size=cv.MAP_TILE_SIZE)
        btn.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

    def ImportTile(self):
        wildcard = "图片 (*.bmp;*.gif;*.jpg;*.jpeg;*.png)|*.bmp;*.gif;*.jpg;*.jpeg;*.png|" \
                   "All files (*.*)|*.*"
        dlg = wx.FileDialog(
            self, message="Choose a file",
            defaultDir=os.getcwd(),
            defaultFile="",
            wildcard=wildcard,
            style=wx.FD_OPEN | wx.FD_MULTIPLE |
                  wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST |
                  wx.FD_PREVIEW
            )
        if dlg.ShowModal() == wx.ID_OK:
            for path in dlg.GetPaths():
                self.AddTile(path)
        dlg.Destroy()
        self.Refresh()

    def ClearTile(self):
        # 清除除空以外的所有砖块
        for button in self.topPanel.Children:
            if button.GetName() != "0":
                cv.tiles.pop(int(button.GetName()))
                button.Destroy()

        self._lastPosition = (0, 0)
        self._lastId = 0

        self.Refresh()
        self.Parent.mapGrid.Refresh()

    def ExportData(self):
        data = []
        tiles = [cv.tiles.get(int(x.GetName())) for x in self.topPanel.Children][1:]
        for tile in tiles:
            tmp = {}
            tmp["id"] = tile.id
            tmp["width"] = tile.width
            tmp["height"] = tile.height
            tmp["image"] = tile.image
            data.append(tmp)
        return data

    def ImportData(self, data):
        self.ClearTile()

        for tileData in data["tiles"]:
            self._lastId += 1
            x, y = self._lastPosition
            x += cv.MAP_TILE_SIZE[0] + 2
            if x > self.topPanel.Size[0] - (cv.MAP_TILE_SIZE[0] + 2):
                x = 0
                y += cv.MAP_TILE_SIZE[1] + 2
            self._lastPosition = (x, y)
            tile = Tile(self._lastId, x, y, tileData["image"])
            cv.tiles[tile.id] = tile
            btn = wx.BitmapButton(parent=self.topPanel,
                                  bitmap=tile.bitmap,
                                  style=wx.BORDER_NONE,
                                  name=str(tile.id),
                                  pos=(tile.x, tile.y),
                                  size=cv.MAP_TILE_SIZE)
            btn.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)


class MapPanel(wx.Panel):
    """
    主面板,布局管理
    """
    def __init__(self, parent):
        super(MapPanel, self).__init__(parent=parent)
        self.InitUI()

    def InitUI(self):
        self.mapGrid = MapGrid(parent=self)
        self.mapTile = MapTile(parent=self)

        hbox = wx.BoxSizer(orient=wx.HORIZONTAL)
        hbox.Add(self.mapGrid, proportion=1, flag=wx.EXPAND | wx.ALL, border=25)
        hbox.Add(self.mapTile, flag=wx.EXPAND | wx.ALL ^ wx.LEFT, border=25)
        self.SetSizer(hbox)


class MapFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(MapFrame, self).__init__(*args, **kwargs)
        self.map = MapPanel(parent=self)
        self.Center()
        self.SetMenu()

        self.Bind(wx.EVT_MENU, self.OnMenu)

    def SetMenu(self):
        # TODO 菜单整理
        menuBar = wx.MenuBar()
        # 1st menu from left
        menu1 = wx.Menu()
        menu1.Append(101, "&新建地图")
        menu1.Append(102, "&导出地图")
        menu1.Append(103, "&导入地图")
        menuBar.Append(menu1, "&地图(M)")

        menu2 = wx.Menu()
        menu2.Append(201, "&导入素材")
        menuBar.Append(menu2, "&砖块(T)")

        menu3 = wx.Menu()
        menu3.Append(301, "&清空网格")
        menu3.Append(302, "&清空素材")
        menuBar.Append(menu3, "&清空(C)")

        self.SetMenuBar(menuBar)

        self.grid = self.map.mapGrid
        self.tile = self.map.mapTile
        self.menus = {
            101: self.grid.CreateMap,
            102: self.ExportMap,
            103: self.ImportMap,
            201: self.tile.ImportTile,
            301: self.grid.ClearMap,
            302: self.tile.ClearTile
        }

    def OnMenu(self, event):
        id = event.GetId()
        self.menus[id]()

    def ExportMap(self):
        gridData = self.grid.ExprotData()
        tileData = self.tile.ExportData()
        gridData["tiles"] = tileData
        result = json.dumps(gridData)

        wildcard = "地图 (*.map)|*.map|" \
                   "All files (*.*)|*.*"
        dlg = wx.FileDialog(
            self, message="导出地图", defaultDir=os.getcwd(),
            defaultFile="", wildcard=wildcard, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
            )

        if dlg.ShowModal() == wx.ID_OK:
            with open(dlg.GetPath(), "w") as f:
                f.write(result)

    def ImportMap(self):
        wildcard = "地图 (*.map)|*.map|" \
                   "All files (*.*)|*.*"
        dlg = wx.FileDialog(
            self, message="导入地图",
            defaultDir=os.getcwd(),
            defaultFile="",
            wildcard=wildcard,
            style=wx.FD_OPEN |
                  wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST |
                  wx.FD_PREVIEW
            )
        if dlg.ShowModal() == wx.ID_OK:
            with open(dlg.GetPath(), "r") as f:
                data = json.loads(f.read())
            self.grid.ImportData(data)
            self.tile.ImportData(data)
        dlg.Destroy()


def main():
    app = wx.App()
    frame = MapFrame(parent=None, title="地图编辑器", size=(cv.BOARD_WIDTH, cv.BOARD_HEIGHT))
    frame.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()

3ds smilebasic3号说明书 3ds 编程开发,主要介绍用法和语法 立即下载

相关推荐

otyaSMILEBASIC:兼容SMILEBASIC 3.x的BASIC

otyaSMILEBASIC 兼容SMILEBASIC 3.x的BASIC 速度 比3DS更快,具体取决于执行环境。 SYS / EX8TEC DEMO SPEED TEST括号是旧的3DS 不喜欢 得分(kazu越大越快) 添加 732293(153860) 打印 351160(34391) 雪碧 623998(67960) 线 394808(17720) 如何建造 git clone git@github.com:otya128/otyaSMILEBASIC.git cd otyaSMILEBASIC dub run 您需要编译什么 D编译器 配音 废弃的SDL2 废弃的GL3 卷曲 运行时需要什么 SDL2 实现功能 工作代码示例 FOR I=0 TO 100 IF I MOD 3==0 AND I MOD 5==0 THEN ?"FIZZBUZZ" ELSE I

wxpython编写的简单划线工具

import wx class SketchWindow(wx.Window):     def __init__(self, parent, ID):         wx.Window.__init__(self, parent, ID)         self.SetBackgroundColour("White")         self.color = "Black

sileiam的专栏 1721

3ds smilebasic3

可以在3ds 编程

wxPython和pycairo练习记录2

因为要实现图像的显示和交互,今天添加两个基础的图像容器类。

SmileBasic的博客 971

smilehax-IIe:Smilebasic 3.6.0 EURUSA(最新版本)的3DS用户级主要帐户。 3.3.2通过应用降级对JPN的支持从3.3.3以上

Smilehax-IIe 介绍 这是3DS Smilebasic 3.6.0 USA / EUR(和3.3.2 JPN)的新主要漏洞。 目前,美国/欧元是最新的应用,而JPN可以从3.6.3-> 3.5.0降级为3.3.2。 方向 发行档案中提供了指导。 您需要美国,欧元或JPN 3DS / 2DS以及eshop下载的Smilebasic才能开始。 利用 可以将Utf-16字符分配给下标的TIME $ / DATE $解释器sysvars,从而可以对用户区中的所有DATA和某些BSS进行只写访问。 TIME $ [0] / DATE $ [0]实际上指向rodata中的某个位置,并且过大的下标

磁带库Scalar i6000界面基本操作步骤

(注:磁带库的前面的门不要打开,日常维护:主要就是简单看下磁带库的状态;绿色代表正常;黄色代表报警但不影响磁带库正常使用;红色代表报警,不能正常使用,及时和厂家联系报修。) 一、磁带库管理页面 1、首先,磁带库前面(具体作用如图) 2、查看状态和管理页面 3、管理页面登陆。用户名:service 密码:xxxxxx 4、磁带机背面 二、电脑连接磁带库进入管理页面 1.用网线把电脑和磁带库的维护接口(磁带库背面)进行连接 2.电脑环境:JAVA。把电脑的IP设置成自...

杀神lwz的博客 5359

wxPython和pycairo练习记录8

经过障碍物的编写,发现需要停止前进并重新梳理一下。1.新的检测项加入时,已经在向混乱发展。是时候用上封装变化的编程工具,用策略模式或状态模式重写。2.显示,在基类引入层级和优先级,不同层低层级先绘制,同层高优先级先绘制。3.范围的碰撞和本身的碰撞的作用顺序。还有像已经实现的碉堡,只有一个外层碰撞区域,如果要多加几层呢,最外层出现坦克,射速为1,再近一层射速为5,最近一层射速达到20。

SmileBasic的博客 387

Quest 3开发第一步:搞定Meta账号登录和Oculus软件连不上设备的那些坑(2024实测)

本文详细介绍了Quest 3开发初期的关键步骤,包括Meta账号注册、Oculus软件安装与设备连接等常见问题的解决方案。针对国内开发者遇到的邮箱限制、验证码接收等难题,提供了实用的应对策略,并分享了开发环境配置和常见问题排查技巧,帮助开发者快速搭建Quest 3开发环境。

weixin_30333885的博客 498

wxPython和pycairo练习记录1

学习,分为学和习,学完了不练习,让大脑觉得不重要,会忘记。所以,我决定练习并记录 wxPython 和 pycairo 的代码编写,如果你也想试试请自行配置环境。wxPython官网:https://www.wxpython.org/pycairo官网:https://cairographics.org/pycairo/本机环境:WIN10因为没有相关完整项目经验,所以不能把全局都设计好,先从基本的迭代。...

SmileBasic的博客 283

ESP32-CAM搭建Arduino及简单使用

配置环境 方法一 文件 → 首选项 http://wechat.doit.am/package_esp8266com_index.json https://dl.espressif.com/dl/package_esp32_index.json 工具 → 开发板 → 开发板管理器 等待安装 方法二 https://github.com/espressif/arduino-esp32 Arduino安装目录下,找到hardware文件夹 建立文件夹espressif,然后在里面再建立一个esp32文件夹

Ying的博客 2万+

Notion卡顿救星:3分钟搞定DNS优化,手机电脑全平台加速指南

本文针对Notion访问慢和卡顿问题,提供了全平台DNS优化与Hosts文件修改的详细指南。通过更换优质DNS服务器或建立静态IP映射,可有效解决因网络延迟和域名解析导致的加载缓慢,实现手机与电脑端的快速加速,提升Notion使用流畅度。

v6b7n8m9q0的博客 1306

wxPython和pycairo练习记录6

这次想要实现障碍物。

SmileBasic的博客 330

ORACLE 12C RAC 启动报错,错误代码 ORA-27300,ORA-27301,ORA-27302

非一体机,12.2 无任何补丁,2套X86服务器,1节点集群异常(数据库正常,还有会话),2节点正常工作,负载不高,硬件、空间均未检查到异常,多路径存在掉线的情况,但通过Mulitpath -ll 看又是正常的,2根私有网络,其中一根不同,sysctl中配置的稀疏模式。5、再次加成crs alert ohas等集群日志,以及根据现象和错误再次到后台sr中查找可用信息,发现有一个类似问题,12.2 RAC 无任何补丁,报同样错误,解决方案为重启两套服务器。查询MOS文档以及后台文档,排除上面指出的问题。

weixin_42344665的博客 1465

wxPython和pycairo练习记录3

现在的感觉像是学武功的人,对着一本秘籍瞎练,没有师父,也没有参照,不知对错。之前写的有很多混乱的地方,比如主 fps 对对象 fps 的作用,比如 MovieClip 对象 surface 的宽高及对继承方法 GetWidth 的影响,等等没有影响现有结果的地方。唯一检验的标准是,实现出想要的效果。这次想实现的是,在四个角生成敌方坦克,自动行驶,相互碰撞转向。...

SmileBasic的博客 354

Quartus II设计程序计数器PC、由PC产生ROM的地址(含仿真)

设计程序计数器PC,正常工作模式下,系统启动,设置PC的初始值,之后每来一个clk,PC的输出值应该增加1;当程序发生跳转时,PC的值应该发生改变,级将跳转的地址值赋进行输出。PC后端连接一个ROM,由PC来产生ROM的地址,仿真进行验证。

m0_73700621的博客 1816

硬件压力测试工具使用方法

B, --bind host# 绑定到主机的多个地址中的一个。当客户端到服务器端的测试结束时,服务器端通过-l选项指定的端口(或默认为客户端连接到服务器端的端口),反向连接至客户端。-c, --client host#如果Iperf运行在服务器模式,并且用-c参数指定一个主机,那么Iperf将只接受指定主机的连接。-C, --compatibility# 与低版本的Iperf使用时,可以使用兼容模式。-b, --bandwidth #[KM] UDP模式使用的带宽,单位bits/sec。

weixin_53303772的博客 8220

wxPython和pycairo练习记录9

想要实现的效果是,同时按下多个方向,无论松开或按下其他键,总是响应剩余最近按下的键,这里直接用列表存储处在按下状态的按键代码。测试 wxPython 按下多个方向后松开最近按下的,之前保持按住的会失效。那就不能靠按键直接改变坐标,只改变速度方向,然后坐标的计算移动交给定时器刷新。射击按键行为也交给定时器,只改变状态,不执行操作。效果看起来还不错。之后可以看到,坦克和砖墙碰撞时的回弹问题也没有了。...

SmileBasic的博客 449

突发E层(Es层):短波通信中的“意外惊喜”与“隐形杀手”全解析

本文深入解析了突发E层(Es层)在短波通信中的双重作用,既可能带来超远距离通信的“意外惊喜”,也可能成为通信链路的“隐形杀手”。通过分析Es层的形成机制、时空分布规律及实战应用技巧,帮助通信爱好者与专业人员更好地利用或应对这一电离层现象,提升短波通信的可靠性与效率。

weixin_30443813的博客 826

wxPython和pycairo练习记录17

按理说素材图片虽然是位图但因为是纯色,手动缩放显示都没问题,不知道为什么会这样,网上解释说是"Cairo 默认使用线性插值算法进行缩放操作,而线性插值会导致图像像素的颜色被平滑地混合,从而造成图像的模糊效果。不要当过程完美主义者,很容易自我攻击。素材中的 characters.gif,用 wx.Image 加载后,调用 GetAlpha 方法发现得到不正确的透明通道值,而且 HasAlpha 得到的结果是 False,因为没有正确解释透明通道数据,RGB 数据也错了,加载后整个图片都变成了粉色。

SmileBasic的博客 897

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

wxPython和pycairo练习记录13

之前虽然通过设置窗口样式去掉了标题栏,但是背景看起来还是有些碍眼。通过 SetTransparent 只能调整整体透明度,文字和背景都透明了。希望实现的效果是背景透明,而文字不透明。

SmileBasic的博客 560

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

wxPython和pycairo练习记录19

pygame.mixer.Channel 这里的声道和音频文件的声道含义不一样,这里的声道是指播放通道,0 为第一个声道。在 wxPython 中 wx.adv.Sound 只能播放 WAV 格式的文件,或者在 UI 中调用 wx.media.MediaCtrl 控件,但素材里的音频还有 OGG 格式的,没法用 wxPython 在后台播放。测试的时候,只有设置同步播放 wx.adv.SOUND_SYNC 才有声音,后面再修改,所有代码都完成了如果还是这样就再创建一个线程来播放声音。

SmileBasic的博客 1044

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

wxPython和pycairo练习记录12

过年在家刷视频号直播时发现弹幕互动游戏,挺有意思的,刚好诠释了反射的用法。想要自己也做一个弹幕游戏,于是就有了这个基本的需求,先让弹幕滚动显示出来,直播时可以当作小挂件。

SmileBasic的博客 434

wxPython和pycairo练习记录4

继续写碰撞检测。

SmileBasic的博客 306

wxPython和pycairo练习记录11

了解事件机制是学习 wxPython 绕不过去的坎,结合各种资料和个人理解整理了一下,如果有问题请留言指出。

SmileBasic的博客 587

wxPython和pycairo练习记录14

继续之前的坦克游戏。碰撞检测技术像什么四叉树、凸包,看不懂啊,所以先从成熟的 pygame 框架里抠出几个能用到的。之前写坦克游戏并没有明确组合多个 Sprite 对象,还是先只考虑独立的 Sprite 吧。这样,需要用到和改写的方法只有 `collide_rect、collide_rect_ratio、collide_circle、collide_circle_ratio、collide_mask`。

SmileBasic的博客 263

wxPython和pycairo练习记录15

像素:是指在由一个数字序列表示的图像中的一个最小单位,称为像素。这里的像素并非我们平时所说的眼睛看到的点,它更像是屏幕上的一个网格,不同的显示设备可能有不同大小的格子。在 Python 中,我们可以方便地通过一些库读取图像的某个像素值。这个例子中,返回的像素值是 RGBA 数值元组。

SmileBasic的博客 416
上一篇: wxPython和pycairo练习记录9
下一篇: 怎样学习wxPython
SmileBasic
博客等级 码龄5年 347粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值