wxPython和pycairo练习记录8
经过障碍物的编写,发现需要停止前进并重新梳理一下。
1.新的检测项加入时,已经在向混乱发展。是时候用上封装变化的编程工具,用策略模式或状态模式重写。
2.显示,在基类引入层级和优先级,按层级绘制所有 Sprite 对象。
3.范围的碰撞和本身的碰撞的作用顺序。还有像已经实现的碉堡,只有一个外层碰撞区域,如果要多加几层呢,最外层出现坦克,射速为1,再近一层射速为5,最近一层射速达到20。
4.计时,因为 wxPython 的时间事件 wx.EVT_TIMER,产生了错误的认识,觉得必须要靠 FPS 来计时。
5.pycairo 移动原点的作用,原点在左上角,移动原点相当于移动了可视区,旋转是以原点作圆心旋转,翻转是以过原点的 x 轴 和 y 轴为对称轴。
6.相邻障碍物远程作用叠加在一起,需要排除其他障碍物所在区域。
7.把全局类独立到一个模块文件,配置项、素材。
8.垃圾绘制对象的清除,destroyed 属性为 True 的。超出边界的子弹,需要设置 destroyed 。
9.键盘控制方向处理方式。
8.1 层级管理
关于显示层级,如果能碰撞到,说明处在同一层;如果不能碰到,则要么在上层,要么在下层。就像科幻里,高维生物与低维生物,视觉上是在同一个地方,但属于不同维度,无法被攻击到。
绘制是后绘制先显示,栈是后进先出。用栈管理层级?不方便调整显示顺序啊。层级,还是树形结构比较符合。分层,把屏幕作为根节点,创建不同的层作为子节点,子节点组成父节点,例如坦克作为父节点,它的组成部件作为子节点,那么就只需要绘制组成部件。优先级,先绘制为优先,从上向下,从左向右,优先级递减。定好规则后,只需要进行先根序遍历,逐一绘制就行了。
Sprite 对象默认都加入全局变量指定的 layer 层。

引入层级管理,上下图层效果演示。看起来是跟之前差不多的,但逻辑更清楚一些。

# -*- coding: utf-8 -*-
# layer.py
class Layer:
def __init__(self, data="Screen"):
self.data = data
self.parent = None
self.left = None
self.right = None
self.firstChild = None
self.lastChild = None
def Append(self, tree):
# 向最右添加子节点
tree.parent = self
if self.lastChild:
self.lastChild.right = tree
tree.left = self.lastChild
self.lastChild = tree
else:
self.lastChild = self.firstChild = tree
return self
def PreAppend(self, tree):
# 向最左添加子节点
tree.parent = self
if self.firstChild:
self.firstChild.left = tree
tree.right = self.firstChild
self.firstChild = tree
else:
self.firstChild = self.lastChild = tree
return self
def Insert(self, tree, left):
# 在指定节点右插入节点
tree.parent = self
tree.left = left
tree.right = left.right
if left is self.lastChild:
self.lastChild = tree
else:
left.right.left = tree
left.right = tree
return self
def PreInsert(self, tree, right):
# 在指定节点左插入节点
tree.parent = self
tree.right = right
tree.left = right.left
if right is self.firstChild:
self.firstChild = tree
else:
right.left.right = tree
right.left = tree
return self
def Delete(self, tree):
# 删除指定节点
if tree.parent is not self:
return
if tree.left:
tree.left.right = tree.right
if tree is self.lastChild:
self.lastChild = tree.left
if tree.right:
tree.right.left = tree.left
if tree is self.firstChild:
self.firstChild = tree.right
if tree is self.firstChild and tree is self.lastChild:
self.firstChild = None
self.lastChild = None
tree.parent = None
tree.left = None
tree.right = None
return self
def Exchange(self, tree1, tree2):
# 交换位置
if (tree1.parent is not self) or (tree2.parent is not self) or (not self.firstChild) or (tree1 is tree2):
return
# 逻辑有点混乱,直接暴力交换
tmp = Layer("tmp")
self.Insert(tmp, tree1)
self.Delete(tree1)
self.Insert(tree1, tree2)
self.Delete(tree2)
self.Insert(tree2, tmp)
self.Delete(tmp)
del tmp
def Clear(self):
self.parent = None
self.left = None
self.right = None
self.firstChild = None
self.lastChild = None
return self
def SetParent(self, parent):
# 设置父节点,移动,没解决parent首尾相连构成循环的问题
self.parent.Delete(self)
parent.Append(self)
return self
def __Draw(self, node, ctx):
# 绘制
if hasattr(node.data, "GetSurface"):
sprite = node.data
ctx.set_source_surface(sprite.GetSurface(), sprite.GetX(), sprite.GetY())
ctx.paint()
def Display(self, ctx):
# 显示全部 Sprite 对象
if (not self.parent) and self.data == "Screen" and self.firstChild:
self.Traverse(self, self.__Draw, ctx)
@classmethod
def Traverse(cls, root, func, *args):
# 遍历子节点
node = root.firstChild
while node:
# 只对没有子节点的节点执行操作
if not node.firstChild:
func(node, *args)
cls.Traverse(node, func, *args)
node = node.right
8.2 修改配置文件
主要是把素材和图层配置放到公共文件。
# -*- coding: utf-8 -*-
# const.py
import os
import glob
from cairo import ImageSurface
from layer import Layer
class cv:
"""
常量,用类属性避免使用全局变量
"""
# 刷新定时器 id
TIMER_ID = 1
# 刷新次数
times = 0
# 刷新间隔时间 毫秒, FPS (frames per second) 就是 1000 // SPEED
SPEED = 10
# 面板尺寸
BOARD_WIDTH = 600
BOARD_HEIGHT = 400
# 全局图层,管理对象显示
screen = Layer()
layer0 = Layer("layer0") # 背景
layer1 = Layer("layer1") # 水面、冰面、传送阵
layer2 = Layer("layer2") # 坦克、子弹、碉堡、砖墙
layer3 = Layer("layer3") # 草地
layer4 = Layer("layer4") # 碉堡感应范围、障碍作用解除触发范围
screen.Append(layer0).Append(layer1).Append(layer2).Append(layer3).Append(layer4)
layer = layer2 # 默认图层
# 素材文件路径
img_path = "./resource/img/"
# 图片直接加载为 pycairo 的 surface
surfaces = {
}
for path in glob.glob(img_path + "*.png"):
surfaceName = os.path.basename(path).split(".")[0]
surfaces[surfaceName] = ImageSurface.create_from_png(path)
8.3 修改图像容器
引入图层属性,值为 Layer 对象,可通过它修改显示的层级。
# -*- coding: utf-8 -*-
# display.py
import wx
import cairo
from const import cv
from layer import Layer
class Sprite:
def __init__(self, x, y, surface, parentLayer=cv.layer):
self._x = x
self._y = y
self._destroyed = False # 销毁状态
# 创建 surface 副本, 没找到简单一点的方法
self._width = surface.get_width()
self._height = surface.get_height()
self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._width, self._height)
ctx = cairo.Context(self._surface)
ctx.set_source_surface(surface, 0, 0)
ctx.paint()
# 默认加入默认图层
self._layer = Layer(self)
parentLayer.Append(self._layer)
def GetX(self):
return self._x
def SetX(self, x):
self._x = x
def GetY(self):
return self._y
def SetY(self, y):
self._y = y
def GetWidth(self):
return self._width
def SetWidth(self, width):
self._width = width
def GetHeight(self):
return self._height
def SetHeight(self, height):
self._height = height
def GetSurface(self):
return self._surface
def SetSurface(self, surface):
self._surface = surface
def GetRect(self):
# 获取边框矩形对象,可用于碰撞检测等
return wx.Rect(self._x, self._y, self._width, self._height)
def IsDestroyed(self):
return self._destroyed
def Destroy(self):
self._destroyed = True
def GetLayer(self):
return self._layer
def SetParentLayer(self, layer):
cv.layer.SetParent(layer)
class MovieClip(Sprite):
def __init__(self, x, y, surface, rect, fps):
super(MovieClip, self).__init__(x, y, surface)
self._scenes = [] # 保存所有场景,每个场景保存当前场景所有帧
self._frames = [] # 保存当前场景所有帧
self._currentScene = 0 # 当前场景索引
self._totalScenes = 0 # 总场景数
self._currentFrame = 0 # 当前帧索引
self._totalFrames = 0 # 当前场景总帧数
self._isPlaying = False # 播放状态
self._fps = fps # 动画可能需要不同于主程序的FPS
self.LoadFrames(rect)
def LoadFrames(self, rect):
# 按显示区域载入场景和帧
x, y, width, height = rect
# 每行表示一个场景scene,scene中每列表示一个frame
try:
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))

这个博客详细记录了一个使用wxPython和pycairo库开发的2D游戏框架的实现过程。作者通过封装变化,引入了策略模式和状态模式,优化了图层管理,实现了图层的层级和优先级控制,以及对象的移动、显示和销毁。此外,还介绍了如何处理键盘控制、图像加载、碰撞检测和对象更新等核心功能。
最低0.47元/天 解锁文章
387

被折叠的 条评论
为什么被折叠?



