wxPython和pycairo练习记录3

wxPython和pycairo练习记录3

现在的感觉像是学武功的人,对着一本秘籍瞎练,没有师父,也没有参照,不知对错。之前写的有很多混乱的地方,比如主 fps 对对象 fps 的作用,比如 MovieClip 对象 surface 的宽高及对继承方法 GetWidth 的影响,等等没有影响现有结果的地方。唯一检验的标准是,实现出想要的效果。

这次想实现的是,在四个角生成敌方坦克,自动行驶,相互碰撞转向。

3.1 生成敌方坦克

手里只有一张素材图片,要区分敌我,最简单的办法就是改变颜色 ,更准确的词是改变色相。那用 mask 行吗?发现是不行,像字面意思是遮住一部分,所以得用 pycairo 的混合操作。

遮罩 mask

在这里插入图片描述

    def DrawSceneObjects(self, ctx):
        surface = cairo.ImageSurface.create_from_png("tank_T1_0.png")
        # 填充方式 https://pycairo.readthedocs.io/en/latest/reference/patterns.html
        pattern = cairo.SolidPattern(0, 1, 1, 0.8)

        # 纯色填充作为遮罩
        ctx.set_source_surface(surface, 100, 100)
        ctx.mask(pattern)

        # 图像作为遮罩
        ctx.set_source(pattern)
        ctx.mask_surface(surface, 200, 100)

混合操作 compositing operator

详见文档:https://pycairo.readthedocs.io/en/latest/reference/enums.html?highlight=operator#cairo.Operator

要改变色相,找到和色相 Hue 有关的操作,这里有 HSL_COLOR 、HSL_HUE、HSL_LUMINOSITY、HSL_SATURATION 。

HSL即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。
来源:https://baike.baidu.com/item/HSL/1443144?fr=aladdin
在这里插入图片描述

    def DrawSceneObjects(self, ctx):
        ctx.save() # 像汇编中的 pusha 保存现场
        surface = cairo.ImageSurface.create_from_png("tank_T1_0.png")
        ctx.set_source_surface(surface, 100, 100)
        ctx.paint()

        ctx.rectangle(100, 100, surface.get_width(), surface.get_height())
        ctx.set_source_rgba(0, 1, 1, 0.8)
        ctx.set_operator(cairo.Operator.HSL_HUE) # 将色相和目标图像色相混合
        ctx.fill()
        ctx.restore() # 恢复现场

3.2 自动行走和转向

玩家坦克可以由键盘事件驱动,那么没人控制的敌方坦克要怎么动起来?

单个维度很好理解,涉及多个维度,人脑很容易就混乱了。这里坦克的移动只需要修改它的xy坐标,绘制坦克的事我们之前已经交给图像容器和主程序。

转向则是分为,撞墙转向,遇到其他坦克转向。

    def ChangeDirection(self, direction):
        # 方向转换为除指定方向以外的方向,总觉得这样写不太好 加个
        # TODO
        directions = [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT]
        directions.remove(direction)
        self._direction = random.choice(directions)

    def AutoWalk(self):
        if self._direction == wx.WXK_UP:
            self.Up()
        elif self._direction == wx.WXK_DOWN:
            self.Down()
        elif self._direction == wx.WXK_LEFT:
            self.Left()
        elif self._direction == wx.WXK_RIGHT:
            self.Right()

        # 遇边界转向,坦克宽高明明是48,可是按48处理显示不对
        # TODO
        if self._x < 0:
            self._x = 0
            self.ChangeDirection(wx.WXK_LEFT)
        elif self._x > cv.BOARD_WIDTH - 68:
            self._x = cv.BOARD_WIDTH - 68
            self.ChangeDirection(wx.WXK_RIGHT)
        else:
            self._x += self._dx * self._speed

        if self._y < 0:
            self._y = 0
            self.ChangeDirection(wx.WXK_UP)
        elif self._y > cv.BOARD_HEIGHT - 96:
            self._y = cv.BOARD_HEIGHT - 96
            self.ChangeDirection(wx.WXK_DOWN)
        else:
            self._y += self._dy * self._speed

    def Update(self, times, speed):
        # 由主程序刷新时更新
        self.AutoWalk()
        super().Update(times, speed)

3.3 完整代码

class Enemy(Tank):
    def __init__(self, x, y, path="tank_T1_0.png", rect=(0, 0, 48, 48), fps=100, mixColor=(0, 1, 1, 0.8)):
        self._mixColor = mixColor # 自定义混合颜色
        super(Enemy, self).__init__(x, y, path, rect, fps)
        # 初始随机方向
        self._direction = random.choice([wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT])
        self._speed = 1
        self._isPlaying = True

    def LoadFrames(self, rect):
        # 将图像载为帧前,先改变图像的色相
        surface = cairo.ImageSurface.create_from_png("tank_T1_0.png")
        sw = surface.get_width()
        sh = surface.get_height()

        # 创建只显示坦克的 surface 作为混合层
        surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh)
        ctx = cairo.Context(surface2)
        ctx.set_source_rgba(*self._mixColor)
        ctx.mask_surface(surface)

        # 与目标图像混合
        ctx = cairo.Context(surface)
        ctx.set_source_surface(surface2)
        ctx.set_operator(cairo.Operator.HSL_HUE) # 将色相和目标图像色相混合
        ctx.paint()

        self._surface = surface
        super().LoadFrames(rect)

    def GetDirection(self):
        return self._direction

    def ChangeDirection(self, direction):
        # 方向转换为除指定方向以外的方向,总觉得这样写不太好 加个
        # TODO
        directions = [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT]
        directions.remove(direction)
        self._direction = random.choice(directions)

    def AutoWalk(self):
        if self._direction == wx.WXK_UP:
            self.Up()
        elif self._direction == wx.WXK_DOWN:
            self.Down()
        elif self._direction == wx.WXK_LEFT:
            self.Left()
        elif self._direction == wx.WXK_RIGHT:
            self.Right()

        # 遇边界转向,坦克宽高明明是48,可是按48处理显示不对
        # TODO
        if self._x < 0:
            self._x = 0
            self.ChangeDirection(wx.WXK_LEFT)
        elif self._x > cv.BOARD_WIDTH - 68:
            self._x = cv.BOARD_WIDTH - 68
            self.ChangeDirection(wx.WXK_RIGHT)
        else:
            self._x += self._dx * self._speed

        if self._y < 0:
            self._y = 0
            self.ChangeDirection(wx.WXK_UP)
        elif self._y > cv.BOARD_HEIGHT - 96:
            self._y = cv.BOARD_HEIGHT - 96
            self.ChangeDirection(wx.WXK_DOWN)
        else:
            self._y += self._dy * self._speed

    def Update(self, times, speed):
        # 由主程序刷新时更新
        self.AutoWalk()
        super().Update(times, speed)

3.4 效果

多个对象碰撞检测,还得再看看,简单写了下发现有时候会撞在一起打转,这块先放着,所以这次只添加了敌军坦克自动行走加撞墙转向。
在这里插入图片描述

# -*- coding: utf-8 -*-
# example2.py

import random
import wx
import wx.lib.wxcairo
import cairo
import board
from display import MovieClip


class cv(board.cv):
    """
    常量,用类属性避免使用全局变量
    """
    # 面板尺寸
    BOARD_WIDTH = 600
    BOARD_HEIGHT = 400


class 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 = -1
        self._dx = 0
        # 同一方向时需要需要排除,不然动画一直停留在第1帧
        if self._currentScene != 0:
            self.GotoAndPlay(0, 0)

    def Down(self):
        self._dy = 1
        self._dx = 0

        if self._currentScene != 1:
            self.GotoAndPlay(0, 1)

    def Left(self):
        self._dx = -1
        self._dy = 0

        if self._currentScene != 2:
            self.GotoAndPlay(0, 2)

    def Right(self):
        self._dx = 1
        self._dy = 0

        if self._currentScene != 3:
            self.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._speed
        self._y += self._dy * self._speed

    def OnKeyUp(self, e):
        key = e.GetKeyCode()

        if key == wx.WXK_UP or key == wx.WXK_DOWN:
            self._dy = 0
        if key == wx.WXK_LEFT or key == wx.WXK_RIGHT:
            self._dx = 0
        if self._dx == 0 and self._dy == 0:
            self.Stop()


class Enemy(Tank):
    def __init__(self, x, y, path="tank_T1_0.png", rect=(0, 0, 48, 48), fps=100, mixColor=(0, 1, 1, 0.8)):
        self._mixColor = mixColor # 自定义混合颜色
        super(Enemy, self).__init__(x, y, path, rect, fps)
        # 初始随机方向
        self._direction = random.choice([wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT])
        self._speed = 1
        self._isPlaying = True

    def LoadFrames(self, rect):
        # 将图像载为帧前,先改变图像的色相
        surface = cairo.ImageSurface.create_from_png("tank_T1_0.png")
        sw = surface.get_width()
        sh = surface.get_height()

        # 创建只显示坦克的 surface 作为混合层
        surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh)
        ctx = cairo.Context(surface2)
        ctx.set_source_rgba(*self._mixColor)
        ctx.mask_surface(surface)

        # 与目标图像混合
        ctx = cairo.Context(surface)
        ctx.set_source_surface(surface2)
        ctx.set_operator(cairo.Operator.HSL_HUE) # 将色相和目标图像色相混合
        ctx.paint()

        self._surface = surface
        super().LoadFrames(rect)

    def GetDirection(self):
        return self._direction

    def ChangeDirection(self, direction):
        # 方向转换为除指定方向以外的方向,总觉得这样写不太好 加个
        # TODO
        directions = [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT]
        directions.remove(direction)
        self._direction = random.choice(directions)

    def AutoWalk(self):
        if self._direction == wx.WXK_UP:
            self.Up()
        elif self._direction == wx.WXK_DOWN:
            self.Down()
        elif self._direction == wx.WXK_LEFT:
            self.Left()
        elif self._direction == wx.WXK_RIGHT:
            self.Right()

        # 遇边界转向,坦克宽高明明是48,可是按48处理显示不对
        # TODO
        if self._x < 0:
            self._x = 0
            self.ChangeDirection(wx.WXK_LEFT)
        elif self._x > cv.BOARD_WIDTH - 68:
            self._x = cv.BOARD_WIDTH - 68
            self.ChangeDirection(wx.WXK_RIGHT)
        else:
            self._x += self._dx * self._speed

        if self._y < 0:
            self._y = 0
            self.ChangeDirection(wx.WXK_UP)
        elif self._y > cv.BOARD_HEIGHT - 96:
            self._y = cv.BOARD_HEIGHT - 96
            self.ChangeDirection(wx.WXK_DOWN)
        else:
            self._y += self._dy * self._speed

    def Update(self, times, speed):
        # 由主程序刷新时更新
        self.AutoWalk()
        super().Update(times, speed)


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) // 2
        y = (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)

        # 坦克在四个角的坐标
        coordinates = [(0, 0),
                       (cv.BOARD_WIDTH - 48, 0),
                       (0, cv.BOARD_HEIGHT - 48),
                       (cv.BOARD_WIDTH - 48, cv.BOARD_HEIGHT - 48)]
        # 生成10个敌军坦克
        for i in range(10):
            coord = random.choice(coordinates)
            enemy = Enemy(x=coord[0], y=coord[1], mixColor=(random.random(), random.random(), random.random(), 0.8))
            self.sceneObjects.append(enemy)

    def DrawSceneObjects(self, ctx):
        for so in self.sceneObjects:
            ctx.set_source_surface(so.GetSurface(), so.GetX(), so.GetY())
            ctx.paint()

    def OnKeyDown(self, e):
        self.player.OnKeyDown(e)
        self.Refresh()

    def OnKeyUp(self, e):
        self.player.OnKeyUp(e)

猜你喜欢

转载自blog.csdn.net/SmileBasic/article/details/126345944