wxPython和pycairo练习记录2
wxPython和pycairo练习记录2
因为要实现图像的显示和交互,今天添加两个基础的图像容器类。
2.1 前置知识
成员属性访问权限
来源:https://blog.csdn.net/weiguang102/article/details/117020112
| 说明 | public(默认) | private | protected |
|---|---|---|---|
| 同一个类中访问 | √ | √ | √ |
| 在子类中访问 | √ | × | √ |
| 在类的外部访问 | √ | × | × |
python中访问权限属性声明方式
| 权限 | 格式(_为英文下划线) | 示例 |
|---|---|---|
| public | variable | age = 18 |
| private | __variable | __money = 0 |
| protected | _variable | _hobby = [“sing”] |
2.2 Sprite
Sprite 主要源于 zetcode 高级教程。
Sprite 的属性包括坐标、尺寸、图像、边框矩形和销毁状态。它并没有什么产生实际操作的方法,仅仅是属性和属性修改查询方法的集合。
class Sprite:def __init__(self, x, y, path):self._x = xself._y = yself._destroyed = False # 销毁状态# 从文件路径加载图像try:self._surface = cairo.ImageSurface.create_from_png(path)except IOError as e:print(e.message)# 获取图像尺寸self._width = self._surface.get_width()self._height = self._surface.get_height()def GetX(self):return self._xdef SetX(self, x):self._x = xdef GetY(self):return self._ydef SetY(self, y):self._y = ydef GetSurface(self):return self._surfacedef SetSurface(self, surface):self._surface = surface# 修改图像后,更新尺寸self._width = surface.get_width()self._height = surface.get_height()def GetRect(self):# 获取边框矩形对象,可用于碰撞检测等return wx.Rect(self._x, self._y, self._width, self._height)def IsDestroyed(self):return self._destroyeddef Destroy(self):self._destroyed = True
2.3 MovieClip
参考 flash 的 MovieClip https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/MovieClip.html
MovieClip 继承自 Sprite ,因为仅仅是参考 flash ,这里仅仅保留一些需要的。根据这张坦克游戏图片素材,坦克宽高48,每行一个方向,每列一个动画帧,这里把每行作为一个场景。

和 Sprite 一样,构造参数需要初始绘制坐标,文件路径(每行一个场景的图片),还需要每帧图像显示的初始区域矩形(后面的帧只需要改变显示坐标)。
class MovieClip(Sprite):def __init__(self, x, y, path, rect, fps):super(MovieClip, self).__init__(x, y, path)self._scenes = [] # 保存所有场景,每个场景保存当前场景所有帧self._frames = [] # 保存当前场景所有帧self._currentScene = 0 # 当前场景索引self._totalScenes = 0 # 总场景数self._currentFrame = 0 # 当前帧索引self._totalFrames = 0 # 当前场景总帧数self._isPlaying = False # 播放状态self._fps = 1000 // fps # 动画可能需要不同于主程序的FPS,这里换算成定时器的时间单位self.LoadFrames(rect)
2.4 完整代码
# -*- coding: utf-8 -*-
# display.pyimport wx
import cairoclass Sprite:def __init__(self, x, y, path):self._x = xself._y = yself._destroyed = False # 销毁状态# 从文件路径加载图像try:self._surface = cairo.ImageSurface.create_from_png(path)except IOError as e:print(e.message)# 获取图像尺寸self._width = self._surface.get_width()self._height = self._surface.get_height()def GetX(self):return self._xdef SetX(self, x):self._x = xdef GetY(self):return self._ydef SetY(self, y):self._y = ydef GetSurface(self):return self._surfacedef SetSurface(self, surface):self._surface = surface# 修改图像后,更新尺寸self._width = surface.get_width()self._height = surface.get_height()def GetRect(self):# 获取边框矩形对象,可用于碰撞检测等return wx.Rect(self._x, self._y, self._width, self._height)def IsDestroyed(self):return self._destroyeddef Destroy(self):self._destroyed = Trueclass MovieClip(Sprite):def __init__(self, x, y, path, rect, fps):super(MovieClip, self).__init__(x, y, path)self._scenes = [] # 保存所有场景,每个场景保存当前场景所有帧self._frames = [] # 保存当前场景所有帧self._currentScene = 0 # 当前场景索引self._totalScenes = 0 # 总场景数self._currentFrame = 0 # 当前帧索引self._totalFrames = 0 # 当前场景总帧数self._isPlaying = False # 播放状态self._fps = 1000 // fps # 动画可能需要不同于主程序的FPS,这里换算成定时器的时间单位self.LoadFrames(rect)def LoadFrames(self, rect):# 按显示区域载入场景和帧x, y, width, height = rect# 每行表示一个场景scene,scene中每列表示一个frametry:for j in range(self._height // height):frames = []for i in range(self._width // width):frame = self._surface.create_for_rectangle(x + width * i, y + height * j, width, height)frames.append(frame)self._scenes.append(frames)except Exception as e:print(str(e))# 更新初始化变量self._surface = self._scenes[self._currentScene][self._currentFrame]self._totalScenes = len(self._scenes)self._totalFrames = len(self._scenes[self._currentScene])# create_for_rectangle 得到的 surface 获取到的宽高为0,这里直接使用矩形宽高self._width = widthself._height = heightdef Play(self):self._isPlaying = Truedef Stop(self):self._isPlaying = Falsedef PrevScene(self):self._currentScene -= 1self._isPlaying = Falsedef NextScene(self):self._currentScene += 1self._isPlaying = Falsedef PrevFrame(self):self._currentFrame -= 1self._isPlaying = Falsedef NextFrame(self):self._currentFrame += 1self._isPlaying = Falsedef GotoAndPlay(self, frame, scene):self._currentScene = sceneself._currentFrame = frameself._isPlaying = Truedef GotoAndStop(self, frame, scene):self._currentScene = sceneself._currentFrame = frameself._isPlaying = Falsedef Update(self, times, speed):# 交给主程序执行,用于刷新 MovieClip 对象数据,主程序需要增加 times 用于记录主程序刷新次数# 主程序刷新速度要比 MovieClip 对象刷新快print("刷新次数:", times, "当前帧索引:", self._currentFrame)if times % (self._fps // speed) == 0 and self._isPlaying:self._currentFrame += 1def __setattr__(self, name, value):self.__dict__[name] = value# 更新 _currentFrame 和 _currentScene 时,同时更新相应变量if self.__dict__.get("_scenes"):if self.__dict__.get("_frames") and name == "_currentFrame":self._surface = self._frames[self._currentFrame % self._totalFrames]elif name == "_currentScene":self._frames = self._scenes[self._currentScene % self._totalScenes]self._totalFrames = len(self._frames)
# -*- coding: utf-8 -*-
# board.py
# 增加刷新次数记次 timesimport wx
import wx.lib.wxcairoclass cv:"""常量,用类属性避免使用全局变量"""# 刷新定时器 idTIMER_ID = 1# 刷新次数times = 0# 刷新间隔时间 毫秒, FPS (frames per second) 就是 1000 // SPEEDSPEED = 10# 面板尺寸BOARD_WIDTH = 800BOARD_HEIGHT = 600class Board(wx.Panel):def __init__(self, parent):super(Board, self).__init__(parent=parent, style=wx.WANTS_CHARS) # wx.WANTS_CHARS 可接收键盘事件self.SetDoubleBuffered(True) # 双缓冲,防止闪烁self.InitVariables()self.BindEvent()def InitVariables(self):# 初始化变量self.InitSceneObjects()# 设置定时器self.timer = wx.Timer(owner=self, id=cv.TIMER_ID)self.timer.Start(cv.SPEED)def InitSceneObjects(self):# 初始化场景中对象变量,如坦克实例、计分栏初始分数等self.sceneObjects = []def BindEvent(self):# 绑定事件self.Bind(wx.EVT_TIMER, self.OnTimer, id=cv.TIMER_ID)self.Bind(wx.EVT_PAINT, self.OnPaint)self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)def OnTimer(self, e):# 处理更新事件,定时更新变量,刷新重绘cv.times += 1for so in self.sceneObjects:so.Update(cv.times, cv.SPEED)self.CheckStrategies()self.Refresh() # 重绘,执行OnPaintdef CheckStrategies(self):# 碰撞检测等passdef OnPaint(self, e):# 处理重绘事件dc = wx.PaintDC(window=self) # device context,设备上下文,相当于画布或虚拟的屏幕,ctx = wx.lib.wxcairo.ContextFromDC(dc) # 获取 cairo.Context 对象,同上self.DrawBackground(ctx)self.DrawSceneObjects(ctx)def DrawBackground(self, ctx):# 填充黑色背景ctx.set_source_rgb(0, 0, 0)ctx.paint()def DrawSceneObjects(self, ctx):# 绘制要显示的对象,如坦克、计分栏等passdef OnKeyDown(self, e):# 处理键盘按下事件passdef OnKeyUp(self, e):# 处理键盘弹起事件pass
2.5 效果
这里写了一个可控制方向的坦克,不同方向播放不同场景动画。并不完善,比如方向控制,边界检测,但基本达到要求。

# -*- coding: utf-8 -*-
# example2.pyimport wx
import wx.lib.wxcairo
import cairo
import board
from display import MovieClipclass cv(board.cv):"""常量,用类属性避免使用全局变量"""# 面板尺寸BOARD_WIDTH = 600BOARD_HEIGHT = 400class Tank(MovieClip):def __init__(self, *args, **kwargs):super(Tank, self).__init__(*args, **kwargs)self._speed = 10 # 移动速度self._dx = 0 # x 轴方向self._dy = 0 # y 轴方向def Up(self):self._dy = -1self._dx = 0self.GotoAndPlay(0, 0)def Down(self):self._dy = 1self._dx = 0self.GotoAndPlay(0, 1)def Left(self):self._dx = -1self._dy = 0self.GotoAndPlay(0, 2)def Right(self):self._dx = 1self._dy = 0self.GotoAndPlay(0, 3)class Player(Tank):def __init__(self, x, y, path="tank_T1_0.png", rect=(0, 0, 48, 48), fps=100):super(Player, self).__init__(x, y, path, rect, fps)def OnKeyDown(self, e):key = e.GetKeyCode()if key == wx.WXK_UP:self.Up()if key == wx.WXK_DOWN:self.Down()if key == wx.WXK_LEFT:self.Left()if key == wx.WXK_RIGHT:self.Right()self._x += self._dx * self._speedself._y += self._dy * self._speeddef OnKeyUp(self, e):key = e.GetKeyCode()if key == wx.WXK_UP or key == wx.WXK_DOWN:self._dy = 0if key == wx.WXK_LEFT or key == wx.WXK_RIGHT:self._dx = 0if self._dx == 0 and self._dy == 0:self.Stop()class Board(board.Board):def DrawBackground(self, ctx):super().DrawBackground(ctx)text = "SmileBasic"ctx.set_font_size(40)_, _, w, h, _, _ = ctx.text_extents(text)x = (cv.BOARD_WIDTH - w) // 2y = (cv.BOARD_HEIGHT - h) // 2# 文字是以首个字左下角坐标定位,而矩形是以左上角定位,y轴相差文字的高度,不需要考虑线条宽度。# 另外PaintDC是不含标题栏的。ctx.rectangle(x - 10, y - 10 - h, w + 20, h + 20)ctx.set_source_rgb(1, 0, 0)ctx.stroke()ctx.move_to(x, y)ctx.set_source_rgb(1, 1, 1)ctx.show_text(text)def InitSceneObjects(self):super().InitSceneObjects()self.player = Player(50, 50) # 实例化一个玩家坦克self.sceneObjects.append(self.player)def DrawSceneObjects(self, ctx):ctx.set_source_surface(self.player.GetSurface(), self.player.GetX(), self.player.GetY())ctx.paint()def OnKeyDown(self, e):self.player.OnKeyDown(e)self.Refresh()def OnKeyUp(self, e):self.player.OnKeyUp(e)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
