wxPython和pycairo练习记录7
继续写障碍,选几个典型的来写。有个问题,有调用图片文件的类,每次生成新实例,都重新加载一次图片。应该把素材统一加载为全局变量,具体使用只需要引用素材变量。积重难返,记住这个教训,以后写其他的再注意吧。
7.1 铁块
刚想起障碍尺寸还有四分之一块的,之前的就不改了,这里铁块基础尺寸写成24*24大小。
class Iron(Obstacle):
# 铁块
def __init__(self, x, y):
surface = self.Draw()
super(Iron, self).__init__(x, y, surface)
self._canCross = False # 不可穿行
self._canDestroy = False # 不可打破
self._layer = 0 # 层级,0 和坦克同一层
self._canBulletCross = False # 子弹不可穿行
self._buff = False # 没有特殊效果
def Draw(self):
# 尺寸24*24
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, 24, 24)
ctx = cairo.Context(surface)
# 底色
ctx.set_source_rgb(0.7, 0.7, 0.7)
ctx.paint()
# 阴影
ctx.move_to(0, 24)
ctx.line_to(24, 24)
ctx.line_to(24, 0)
ctx.close_path()
ctx.set_source_rgb(0.5, 0.5, 0.5)
ctx.fill()
# 高光
ctx.rectangle(6, 6, 12, 12)
ctx.set_source_rgb(1, 1, 1)
ctx.fill()
return surface
7.2 碉堡
它不同于普通障碍,检测到坦克到达一定范围就开始射击。不能无限制射击,不然就打成一条直线了,给武器类加上开火间隔时间。给障碍类新加 isRemote 属性,专门用于有远程作用的障碍。
因为是四方向的,只检测上下左右四个方向正方向是否有坦克经过。左上角坐标定位,实际检测坐标范围需要计算尺寸。
测试一下,碉堡图片找了两张图拼了一下。
class Blockhouse(Obstacle):
# 碉堡
def __init__(self, x, y, path="wall_blockhouse.png", radius=150):
# 从文件加载 surface
try:
surface = cairo.ImageSurface.create_from_png(path)
except IOError as e:
print(e.message)
super(Blockhouse, self).__init__(x, y, surface)
self._canCross = False # 不可穿行
self._canDestroy = True # 可打破
self._layer = 0 # 层级,0 和坦克同一层
self._canBulletCross = False # 子弹不可穿行
self._buff = True # 有特殊效果
self._isRemote = True # 有远程作用
self._radius = radius # 火力覆盖半径
self._direction = wx.WXK_UP # 射击方向
# 加载武器和子弹
self._weapon = Weapon(capacity=50, speed=1, interval=500)
self._weapon.Loaded(self)
for i in range(50):
self._weapon.AddBullet(Bullet(type=3))
def LoadWeapon(self, weapon):
weapon.Loaded(self)
self._weapon = weapon
def GetWeapon(self):
return self._weapon
def GetRadius(self):
return self._radius
def GetDirection(self):
return self._direction
def Act(self, tank=None):
# 向坦克调整射击方向并射击
direction = tank.GetDirection()
tx, ty, tw, th = tank.GetRect()
x, y, w, h = self.GetRect()
if tx > x - tw and tx < x + w:
if ty < y:
# print("up")
self._direction = wx.WXK_UP
else:
# print("down")
self._direction = wx.WXK_DOWN
self._weapon.Fire()
if ty > y - th and ty < y + h:
if tx < x:
# print("left")
self._direction = wx.WXK_LEFT
else:
# print("right")
self._direction = wx.WXK_RIGHT
self._weapon.Fire()
7.3 泥坑
坦克经过泥坑速度就减为1,离开后速度恢复,需要给坦克父类添加速度读写方法。在哪存储原始速度呢?给坦克父类再加个原始速度属性。
那么怎么判断离开的动作?试试像碉堡一样给它也添加一个远程作用,作用半径设为它本身的尺寸1.5倍。也许远程作用可以独立出一个雷达…
找的图片有点像沙地,修修补补又三年。两块泥坑相邻,作用相互影响,把探测半径减到极小,也不太理想,暂时没想到怎么解决。
class Mud(Obstacle):
# 泥坑
def __init__(self, x, y, path="wall_mud.png"):
# 从文件加载 surface
try:
surface = cairo.ImageSurface.create_from_png(path)
except IOError as e:
print(e.message)
super(Mud, self).__init__(x, y, surface)
self._canCross = True # 可穿行
self._canDestroy = False # 不可打破
self._layer = -1 # 子弹可以在泥坑上层飞过
self._canBulletCross = True # 子弹可穿行
self._buff = True # 有特殊效果
self._isRemote = True # 有远程作用
self._radius = self._width * 1.5 # 作用半径
def GetRadius(self):
return self._radius
def Act(self, tank=None):
# 离开范围
tx, ty, tw, th = tank.GetRect()
x, y, w, h = self.GetRect()
if tank.GetSpeed() == 1 and (tx < x - tw or tx > x + w or ty < y - th or ty > y + h):
tank.ResetSpeed()
def SecondAct(self, tank=None):
# 进入范围
if tank.GetSpeed() > 1:
tank.SetSpeed(1)
7.4 传送阵
两个传送阵,碰撞其中一个,传送到另一个传送阵位置。在传送阵上需要等待时间才能传送,不然无限互传。出传送阵范围,等待时间归零。
class Transmission(Obstacle):
# 传送阵
def __init__(self, x, y):
surface = self.Draw()
super(Transmission, self).__init__(x, y, surface)
self._canCross = True # 可穿行
self._canDestroy = False # 不可打破
self._layer = -1 # 层级,0 和坦克同一层
self._canBulletCross = True # 子弹可穿行
self._buff = True # 没有特殊效果
self._isRemote = True # 有远程作用
self._radius = self._width * 1.5 # 作用半径
self._waitTime = 3000 # 等待时间3秒
self._dest = None # 传送目的法阵
def GetRadius(self):
return self._radius
def Draw(self):
# 画个六芒星
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, 48, 48)
ctx = cairo.Context(surface)
# 填充紫色底色
ctx.set_source_rgb(0.27, 0.1, 0.47)
ctx.paint()
ctx.save()
ctx.set_source_rgb(1, 1, 1)
for i in range(2):
if i == 1:
# 对称翻转
ctx.scale(1, -1) # (x * dx, y * dy)
ctx.translate(0, -48) # 设置坐标轴原点
# 画直角三角形
ctx.move_to(0, 12)
ctx.line_to(48, 12)
ctx.line_to(24, 48)
ctx.close_path()
ctx.stroke()
ctx.restore()
# 紫色底不太明显,画四个角,标识下边缘
ctx.set_source_rgb(0.6, 0.6, 0.6)
for i in range(2):
if i == 1:
# 关于 x 轴对称
ctx.scale(1, -1)
ctx.translate(0, -48)
for j in range(2):
if j == 1:
# 关于 y 轴对称
ctx.scale(-1, 1)
ctx.translate(-48, 0)
ctx.move_to(0, 6)
ctx.line_to(0, 0)
ctx.line_to(6, 0)
ctx.close_path()
ctx.fill()
return surface
def Act(self, tank=None):
# 离开范围,等待时间归零
tx, ty, tw, th = tank.GetRect()
x, y, w, h = self.GetRect()
if tx < x - tw or tx > x + w or ty < y - th or ty > y + h:
tank.SetTransTime(0)
def SecondAct(self, tank=None):
# 进入法阵范围
# 新进入,设置进入时间
if tank.GetTransTime() == 0:
tank.SetTransTime(cv.times * cv.SPEED)
# 符合时间,开始传送
if tank.GetTransTime() + self._waitTime <= cv.times * cv.SPEED:
tank.SetX(self._dest.GetX())
tank.SetY(self._dest.GetY())
tank.SetTransTime(0)
def Bind(self, transmission):
# 法阵双向绑定
self._dest = transmission
transmission.SetDest(self)
def GetDest(self):
return self._dest
def SetDest(self, dest):
self._dest = dest
7.5 代码
# -*- coding: utf-8 -*-
# obstacle.py
import math
import wx
import cairo
from display import Sprite
from weapon import Weapon, Bullet
import board
class cv(board.cv):
"""
常量,用类属性避免使用全局变量
"""
# 面板尺寸
BOARD_WIDTH = 600
BOARD_HEIGHT = 400
class Obstacle(Sprite):
def __init__(self, x, y, surface):
# 重写 Sprite 的构造函数, 直接传入 surface
self._x = x
self._y = y
self._destroyed = False # 销毁状态
self._surface = surface
# 获取图像尺寸
self._width = self._surface.get_width()
self._height = self._surface.get_height()
self._canCross = False # 是否可穿行
self._canDestroy = False # 是否可打破
self._layer = 0 # 层级,0 和坦克同一层,-1 下层, 1 上层
self._canBulletCross = False # 是否子弹可穿行
self._buff = False # 是否有为有特殊效果的
self._isRemote = False # 可远程作用
def CanCross(self):
return self._canCross
def CanDestroy(self):
return self._canDestroy
def GetLayer(self):
return self._layer
def CanBulletCross(self):
return self._canBulletCross
def HasBuff(self):
return self._buff
def IsRemote(self):
return self._isRemote
def Act(self, tank=None):
# 与坦克的交互行为,比如加滑行 _buff,比如碉堡直接开火
pass
def Update(self, times, speed):
# 用于被定时器处理函数调用刷新,静态障碍不需要重写
pass
class Brick(Obstacle):
# 砖墙
def __init__(self, x, y):
surface = self.Draw() # 直接绘制砖墙
super(Brick, self).__init__(x, y, surface)
self._canCross = False # 不可穿行
self._canDestroy = True # 可打破
self._layer = 0 # 层级,0 和坦克同一层
self._canBulletCross = False # 子弹不可穿行
self._buff = False # 没有特殊效果
def Draw(self):
# 绘制砖墙
# 按坦克尺寸48创建图像,颜色不需要透明
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, 48, 48)
# 砖块是错落砌在一起的,先建一个2倍障碍宽度的矩形,作为一行砖块
rect1 = cairo.ImageSurface(cairo.FORMAT_RGB24, 96, 12)
# 画一个小24*12的矩形砖块,然后重复平铺
rect2 = cairo.ImageSurface(cairo.FORMAT_RGB24, 24, 12)
ctx = cairo.Context(rect2)
# 填充灰色作底,水泥
ctx.set_source_rgb(0.4, 0.4, 0.4)
ctx.paint()
# 画矩形,填充砖红色,砖
ctx.set_source_rgb(0.6, 0.3, 0)
ctx.rectangle(2, 2, 20, 8)
ctx.fill()
# 画线,更深的砖色,阴影
ctx.set_source_rgb(0.42, 0.02, 0)
ctx.set_line_width(4)
ctx.move_to(3, 10)
ctx.line_to(3, 3)
ctx.line_to(22, 3)
ctx.stroke()
# 以 rect2 重复填充 rect1
ctx = cairo.Context(rect1)
pattern = cairo.SurfacePattern(rect2)
pattern.set_extend(cairo.Extend.REPEAT)
ctx.set_source(pattern)
ctx.paint()
# 把 rect1 错位绘制到 surface
ctx = cairo.Context(surface)
ctx.set_source_surface(rect1, -12, 0)
ctx.paint()
ctx.set_source_surface(rect1, 0, 12)
ctx.paint()
ctx.set_source_surface(rect1, -12, 24)
ctx.paint()
ctx.set_source_surface(rect1, 0, 36)
ctx.paint()
return surface
class Grass(Obstacle):
# 草地
def __init__(self, x, y, path="wall_grass.png"):
# 从文件加载 surface
try:
surface = cairo.ImageSurface.create_from_png(path)
except IOError as e:
print(e.message)
super(Grass, self).__init__(x, y, surface)
self._canCross = True # 可穿行
self._canDestroy = False # 不可打破
self._layer = 1 # 层级,0 和坦克同一层
self._canBulletCross = True # 子弹可穿行
self._buff = False # 没有特殊效果
class Water(Obstacle):
# 水面
def __init__(self, x, y, path="wall_water.png"):
# 从文件加载 surface
try:
surface = cairo.ImageSurface.create_from_png(path)
except IOError as e:
print(e.message)
super(Water, self).__init__(x, y, surface)
self._canCross = False # 不可穿行
self._canDestroy = False # 不可打破
self._layer = -1 # 子弹可以在水面上层飞过
self._canBulletCross = True # 子弹可穿行
self._buff = False # 没有特殊效果
class Ice(Obstacle):
# 冰面
def __init__(self, x, y):
surface = self.Draw()
super(Ice, self).__init__(x, y, surface)
self._canCross = True # 可穿行
self._canDestroy = False # 不可打破
self._layer = -1 # 层级,0 和坦克同一层
self._canBulletCross = True # 子弹可穿行
self._buff = True # 有特殊效果
self._distance = 20 # 滑行距离
def Draw(self):
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, 48, 48)
ctx = cairo.Context(surface)
# 填充底色
ctx.set_source_rgb(0.1, 0.85, 0.95)
ctx.paint()
# 以中心逆向旋转45度
ctx.translate(24, 24)
ctx.rotate(math.pi * 3 / 4)
ctx.translate(-24, -24)
# 画几条表示冰面纹理的线
ctx.set_source_rgb(0, 0.64, 0.8)
for i in range(10):
if i % 2 == 0:
ctx.set_dash([5, 1])
else:
ctx.set_dash([10, 15])
ctx.move_to(-10, 6 * i)
ctx.line_to(70, 6 * i)
ctx.stroke()
return surface
def Act(self, tank=None):
# 与坦克的交互行为,比如加滑行 _buff,比如碉堡直接开火
direction = tank.GetDirection()
x = tank.GetX()
y = tank.GetY()
if direction == wx.WXK_UP:
tank.SetY(y - self._distance)
elif direction == wx.WXK_DOWN:
tank.SetY(y + self._distance)
elif direction == wx.WXK_LEFT:
tank.SetX(x - self._distance)
elif direction == wx.WXK_RIGHT:
tank.SetX(x + self._distance)
class Iron(Obstacle):
# 铁块
def __init__(self, x, y):
surface = self.Draw()
super(Iron, self).__init__(x, y, surface)
self._canCross = False # 不可穿行
self._canDestroy = False # 不可打破
self._layer = 0 # 层级,0 和坦克同一层
self._canBulletCross = False # 子弹不可穿行
self._buff = False # 没有特殊效果
def Draw(self):
# 尺寸24*24
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, 24, 24)
ctx = cairo.Context(surface)
# 底色
ctx.set_source_rgb(0.7, 0.7, 0.7)
ctx.paint()
# 阴影
ctx.move_to(0, 24)
ctx.line_to(24, 24)
ctx.line_to(24, 0)
ctx.close_path()
ctx.set_source_rgb(0.5, 0.5, 0.5)
ctx.fill()
# 高光
ctx.rectangle(6, 6, 12, 12)
ctx.set_source_rgb(1, 1, 1)
ctx.fill()
return surface
class Blockhouse(Obstacle):
# 碉堡
def __init__(self, x, y, path="wall_blockhouse.png", radius=150):
# 从文件加载 surface
try:
surface = cairo.ImageSurface.create_from_png(path)
except IOError as e:
print(e.message)
super(Blockhouse, self).__init__(x, y, surface)
self._canCross = False # 不可穿行
self._canDestroy = True # 可打破
self._layer = 0 # 层级,0 和坦克同一层
self._canBulletCross = False # 子弹不可穿行
self._buff = True # 有特殊效果
self._isRemote = True # 有远程作用
self._radius = radius # 火力覆盖半径
self._direction = wx.WXK_UP # 射击方向
# 加载武器和子弹
self._weapon = Weapon(capacity=50, speed=1, interval=500)
self._weapon.Loaded(self)
for i in range(50):
self._weapon.AddBullet(Bullet(type=3))
def LoadWeapon(self, weapon):
weapon.Loaded(self)
self._weapon = weapon
def GetWeapon(self):
return self._weapon
def GetRadius(self):
return self._radius
def GetDirection(self):
return self._direction
def Act(self, tank=None):
# 向坦克调整射击方向并射击
direction = tank.GetDirection()
tx, ty, tw, th = tank.GetRect()
x, y, w, h = self.GetRect()
if tx > x - tw and tx < x + w:
if ty < y:
# print("up")
self._direction = wx.WXK_UP
else:
# print("down")
self._direction = wx.WXK_DOWN
self._weapon.Fire()
if ty > y - th and ty < y + h:
if tx < x:
# print("left")
self._direction = wx.WXK_LEFT
else:
# print("right")
self._direction = wx.WXK_RIGHT
self._weapon.Fire()
class Mud(Obstacle):
# 泥坑
def __init__(self, x, y, path="wall_mud.png"):
# 从文件加载 surface
try:
surface = cairo.ImageSurface.create_from_png(path)
except IOError as e:
print(e.message)
super(Mud, self).__init__(x, y, surface)
self._canCross = True # 可穿行
self._canDestroy = False # 不可打破
self._layer = -1 # 子弹可以在泥坑上层飞过
self._canBulletCross = True # 子弹可穿行
self._buff = True # 有特殊效果
self._isRemote = True # 有远程作用
self._radius = self._width * 1.5 # 作用半径
def GetRadius(self):
return self._radius
def Act(self, tank=None):
# 离开范围
tx, ty, tw, th = tank.GetRect()
x, y, w, h = self.GetRect()
if tank.GetSpeed() == 1 and (tx < x - tw or tx > x + w or ty < y - th or ty > y + h):
tank.ResetSpeed()
def SecondAct(self, tank=None):
# 进入范围
if tank.GetSpeed() > 1:
tank.SetSpeed(1)
class Transmission(Obstacle):
# 传送阵
def __init__(self, x, y):
surface = self.Draw()
super(Transmission, self).__init__(x, y, surface)
self._canCross = True # 可穿行
self._canDestroy = False # 不可打破
self._layer = -1 # 层级,0 和坦克同一层
self._canBulletCross = True # 子弹可穿行
self._buff = True # 没有特殊效果
self._isRemote = True # 有远程作用
self._radius = self._width * 1.5 # 作用半径
self._waitTime = 3000 # 等待时间3秒
self._dest = None # 传送目的法阵
def GetRadius(self):
return self._radius
def Draw(self):
# 画个六芒星
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, 48, 48)
ctx = cairo.Context(surface)
# 填充紫色底色
ctx.set_source_rgb(0.27, 0.1, 0.47)
ctx.paint()
ctx.save()
ctx.set_source_rgb(1, 1, 1)
for i in range(2):
if i == 1:
# 对称翻转
ctx.scale(1, -1) # (x * dx, y * dy)
ctx.translate(0, -48) # 设置坐标轴原点
# 画直角三角形
ctx.move_to(0, 12)
ctx.line_to(48, 12)
ctx.line_to(24, 48)
ctx.close_path()
ctx.stroke()
ctx.restore()
# 紫色底不太明显,画四个角,标识下边缘
ctx.set_source_rgb(0.6, 0.6, 0.6)
for i in range(2):
if i == 1:
# 关于 x 轴对称
ctx.scale(1, -1)
ctx.translate(0, -48)
for j in range(2):
if j == 1:
# 关于 y 轴对称
ctx.scale(-1, 1)
ctx.translate(-48, 0)
ctx.move_to(0, 6)
ctx.line_to(0, 0)
ctx.line_to(6, 0)
ctx.close_path()
ctx.fill()
return surface
def Act(self, tank=None):
# 离开范围,等待时间归零
tx, ty, tw, th = tank.GetRect()
x, y, w, h = self.GetRect()
if tx < x - tw or tx > x + w or ty < y - th or ty > y + h:
tank.SetTransTime(0)
def SecondAct(self, tank=None):
# 进入法阵范围
# 新进入,设置进入时间
if tank.GetTransTime() == 0:
tank.SetTransTime(cv.times * cv.SPEED)
# 符合时间,开始传送
if tank.GetTransTime() + self._waitTime <= cv.times * cv.SPEED:
tank.SetX(self._dest.GetX())
tank.SetY(self._dest.GetY())
tank.SetTransTime(0)
def Bind(self, transmission):
# 法阵双向绑定
self._dest = transmission
transmission.SetDest(self)
def GetDest(self):
return self._dest
def SetDest(self, dest):
self._dest = dest
# -*- coding: utf-8 -*-
# example3.py
import random
import math
import wx
import wx.lib.wxcairo
import cairo
import board
from display import MovieClip
from tank import Player, Enemy
from weapon import Weapon, Bullet
from obstacle import *
class cv(board.cv):
"""
常量,用类属性避免使用全局变量
"""
# 面板尺寸
BOARD_WIDTH = 600
BOARD_HEIGHT = 400
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.InitPlayer()
self.InitObstacles()
def InitPlayer(self):
self.player = Player(60, 50) # 实例化一个玩家坦克
self.sceneObjects.append(self.player)
# 初始武器系统,并装弹
self.bullets = []
weapon = Weapon(capacity=100, speed=2)
for i in range(50):
bullet = Bullet(type=3) # 装载第4行子弹
weapon.AddBullet(bullet)
self.bullets.append(bullet)
self.sceneObjects.append(bullet)
bullet = Bullet(type=6) # 装载第7行子弹
weapon.AddBullet(bullet)
self.bullets.append(bullet)
self.sceneObjects.append(bullet)
self.player.LoadWeapon(weapon) # 玩家坦克加载武器系统
def InitObstacles(self):
self.obstacles = []
# 生成4个砖墙
brick1 = Brick(10, 50)
brick2 = Brick(58, 98)
brick3 = Brick(106, 98)
brick4 = Brick(230, 220)
# 生成1个草地
grass = Grass(300, 50)
# 生成1个水面
water = Water(360, 50)
# 生成1个冰面
ice = Ice(230, 50)
# 生成3个小铁块
iron1 = Iron(230, 120)
iron2 = Iron(254, 120)
iron3 = Iron(254, 144)
# 生成碉堡
blockhouse = Blockhouse(10, 220)
# 需要把碉堡子弹也加入对象列表进行绘制和碰撞检测
self.bullets += blockhouse.GetWeapon().GetClip()
self.sceneObjects += blockhouse.GetWeapon().GetClip()
# 生成两个泥坑
mud1 = Mud(290, 220)
mud2 = Mud(338, 220)
# 生成两个传送阵
transmission1 = Transmission(390, 220)
transmission2 = Transmission(500, 120)
transmission1.Bind(transmission2)
self.obstacles += [brick1, brick2, brick3, brick4, grass, water,
ice, iron1, iron2, iron3, blockhouse, mud1, mud2, transmission1, transmission2]
self.sceneObjects += [brick1, brick2, brick3, brick4, grass, water,
ice, iron1, iron2, iron3, blockhouse, mud1, mud2, transmission1, transmission2]
def DrawSceneObjects(self, ctx):
# print("绘制", self.player.GetRect())
# 后绘制的对象在上层显示
for obstacle in self.obstacles:
if obstacle.GetLayer() == -1:
self.DrawSprite(ctx, obstacle)
self.DrawSprite(ctx, self.player)
for bullet in self.bullets:
self.DrawSprite(ctx, bullet)
for obstacle in self.obstacles:
if obstacle.GetLayer() >= 0:
self.DrawSprite(ctx, obstacle)
def DrawSprite(self, ctx, sprite):
ctx.set_source_surface(sprite.GetSurface(), sprite.GetX(), sprite.GetY())
ctx.paint()
def OnKeyDown(self, e):
self.player.OnKeyDown(e)
self.Refresh()
def OnKeyUp(self, e):
self.player.OnKeyUp(e)
def CheckStrategies(self):
self.CheckCollisions()
def CheckCollisions(self):
# 检测子弹砖墙碰撞
# print("碰撞检测", self.player.GetRect())
for obstacle in self.obstacles[:]:
# 子弹可穿行,则跳过检测
if obstacle.CanBulletCross():
continue
rect = obstacle.GetRect()
for bullet in self.bullets[:]:
if rect.Intersects(bullet.GetRect()):
# 如果可摧毁,则碰撞后子弹和障碍都销毁
if obstacle.CanDestroy():
obstacle.Destroy()
self.obstacles.remove(obstacle)
self.sceneObjects.remove(obstacle)
bullet.Destroy()
self.bullets.remove(bullet)
self.sceneObjects.remove(bullet)
break
# 检测坦克和砖墙碰撞,碰撞后坦克位置重置
for obstacle in self.obstacles[:]:
# 坦克可穿行并且没有特殊效果,则跳过检测
if obstacle.CanCross() and not obstacle.HasBuff():
continue
rect = obstacle.GetRect()
if obstacle.IsRemote():
x, y, w, h = rect
radius = obstacle.GetRadius()
# 根据作用半径求出扩大范围后的矩形参数,因为是四方向,所以半径为一半边长
x = x - (radius - w / 2)
y = y - (radius - h / 2)
width = height = radius * 2
rect = wx.Rect(x, y, width, height)
if rect.Intersects(self.player.GetRect()):
# print("检测到碰撞", self.player.GetRect())
# 如果有特殊效果则只不执行后续位置重置
if obstacle.HasBuff():
obstacle.Act(self.player)
# 无远程作用障碍跳过
if not obstacle.IsRemote():
continue
# 远程作用障碍判断障碍本身与坦克碰撞
rect = obstacle.GetRect()
if not rect.Intersects(self.player.GetRect()):
continue
# buff + 远程作用 + 能通行 + 本身碰撞,二次作用,泥坑定制
if obstacle.CanCross():
obstacle.SecondAct(self.player)
continue
direction = self.player.GetDirection()
x, y, w, h = rect
_, _, pw, ph = self.player.GetRect()
if direction == wx.WXK_UP:
self.player.SetY(y + h)
elif direction == wx.WXK_DOWN:
self.player.SetY(y - ph)
elif direction == wx.WXK_LEFT:
self.player.SetX(x + w)
elif direction == wx.WXK_RIGHT:
self.player.SetX(x - pw)
# print("碰撞重置为", self.player.GetRect())
# -*- coding: utf-8 -*-
# weapon.py
import wx
import wx.lib.wxcairo
import cairo
from display import MovieClip
import board
class cv(board.cv):
"""
常量,用类属性避免使用全局变量
"""
# 面板尺寸
BOARD_WIDTH = 600
BOARD_HEIGHT = 400
class Weapon:
def __init__(self, capacity=10, speed=20, interval=100):
self._clip = [] # 弹夹
self._capacity = capacity # 弹夹容量
self._speed = speed # 发射出的子弹速度
self._isFire = False # 开火状态
self._tank = None # 被装载对象
self._currentBullet = None # 当前要发射的子弹
self._fireTime = -interval - 1 # 上次开火时间,首次开火时间必然满足
self._fireInterval = interval # 默认开火间隔时间0.1秒
def GetCapacity(self):
return self._capacity
def GetSpeed(self):
return self._speed
def GetBulletNum(self):
return len(self._clip)
def GetClip(self):
return self._clip
def isFire(self):
return self._isFire
def Loaded(self, tank):
# 被装载
self._tank = tank
def AddBullet(self, bullet):
# 装弹并返回装弹结果
if len(self._clip) < self._capacity:
self._clip.append(bullet)
return True
else:
return False
def Fire(self, state=True):
# 开火或停火
self._isFire = state
# 判断状态及开火间隔时间是否大于武器限定间隔时间
if state and (self._fireTime + self._fireInterval) < cv.times * cv.SPEED and len(self._clip) > 0:
# 记录开火时间
self._fireTime = cv.times * cv.SPEED
self._currentBullet = self._clip.pop()
# 根据坦克坐标和尺寸计算子弹起始位置
direction = self._tank.GetDirection()
x, y, w, h = self._tank.GetRect()
_, _, bw, bh = self._currentBullet.GetRect()
if direction == wx.WXK_UP:
bx = x + w / 2 - bw / 2
by = y - bh
elif direction == wx.WXK_DOWN:
bx = x + w / 2 - bw / 2
by = y + h
elif direction == wx.WXK_LEFT:
bx = x - bw
by = y + h / 2 - bh / 2
elif direction == wx.WXK_RIGHT:
bx = x + w
by = y + h / 2 - bh / 2
# 设置子弹方向
self._currentBullet.SetDirection(direction)
# 设置发射初始坐标
self._currentBullet.SetX(bx)
self._currentBullet.SetY(by)
# 设置子弹速度
self._currentBullet.SetSpeed(self._speed)
# 发射
self._currentBullet.Fired()
class Bullet(MovieClip):
def __init__(self, x=-100, y=0, path="bullets.png", rect=(0, 0, 16, 16), fps=100, type=0):
super(Bullet, self).__init__(x, y, path, rect, fps)
self._speed = 0 # 子弹初始速度
self._type = type
self._direction = None
def SetDirection(self, direction):
self._direction = direction
def SetSpeed(self, speed):
self._speed = speed
def Fired(self):
# 尽量只用中心对称的子弹,避免换方向发射还要做旋转处理
self.GotoAndPlay(0, self._type)
def Fly(self):
if not self._destroyed and self._speed > 0:
if self._direction == wx.WXK_UP:
self._y -= self._speed
elif self._direction == wx.WXK_DOWN:
self._y += self._speed
elif self._direction == wx.WXK_LEFT:
self._x -= self._speed
elif self._direction == wx.WXK_RIGHT:
self._x += self._speed
def Update(self, times, speed):
self.Fly()
super().Update(times, speed)
# -*- coding: utf-8 -*-
# tank.py
import random
import wx
import wx.lib.wxcairo
import cairo
from display import MovieClip
import board
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 轴方向
self._weapon = None # 武器
self._originSpeed = 10 # 原始速度,用于恢复
self._transTime = 0 # 等待法阵传送时间
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)
def LoadWeapon(self, weapon):
weapon.Loaded(self)
self._weapon = weapon
def GetWeapon(self):
return self._weapon
def GetSpeed(self):
return self._speed
def SetSpeed(self, speed):
self._speed = speed
def ResetSpeed(self):
self._speed = self._originSpeed
def GetTransTime(self):
return self._transTime
def SetTransTime(self, time):
self._transTime = time
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)
self._direction = wx.WXK_UP
def OnKeyDown(self, e):
key = e.GetKeyCode()
if key == wx.WXK_UP:
self.Up()
self._direction = wx.WXK_UP
if key == wx.WXK_DOWN:
self.Down()
self._direction = wx.WXK_DOWN
if key == wx.WXK_LEFT:
self.Left()
self._direction = wx.WXK_LEFT
if key == wx.WXK_RIGHT:
self.Right()
self._direction = wx.WXK_RIGHT
self._x += self._dx * self._speed
self._y += self._dy * self._speed
# 开火
if key == wx.WXK_SPACE and self._weapon:
self._weapon.Fire(True)
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()
# 停火
if key == wx.WXK_SPACE and self._weapon:
self._weapon.Fire(False)
def GetDirection(self):
return self._direction
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._originSpeed = 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 SetDirection(self, direction):
self._direction = 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)