wxPython和pycairo练习记录8

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

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))

  
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值