App Inventor 2 Canvas绘图教程 - 游戏精灵与动画实现

« 返回首页

概述

Canvas组件是App Inventor 2中实现2D绘图和游戏的核心组件。它支持:

  • 基本图形绘制(线、圆、矩形)
  • 图片精灵(ImageSprite)动画
  • 触摸/拖拽交互
  • 碰撞检测
  • 定时动画

Canvas组件核心属性

属性 说明
Width/Height 画布尺寸
BackgroundColor 背景颜色
BackgroundImage 背景图片
PaintColor 画笔颜色
LineWidth 线条宽度
FontSize 文字大小

核心事件

事件 参数 说明
Touched(x, y) 触摸坐标 点击画布
Dragged(x, y, prevX, prevY) 拖拽坐标 拖拽画布
Flung(x, y, speed, heading) 速度方向 快速滑动

基本图形绘制

绘制线条

when 画布1.Dragged(startX, startY, prevX, prevY, currentX, currentY, draggedAnySprite) {
  画布1.DrawLine(prevX, prevY, currentX, currentY)
}

事件参数名必须与组件定义逐字一致startXprevX…)。写成自己起的名字, 块里读到的就是没赋值的变量。

绘制圆形

when 画布1.Touched(x, y, touchedAnySprite) {
  画布1.DrawCircle(x, y, 20, true)
}

绘制矩形

Canvas 没有画矩形的方法,用四条线拼,或者用 DrawShape 传四个角点:

procedure 画矩形(左, 上, 右, 下) {
  画布1.DrawLine(左, 上, 右, 上)
  画布1.DrawLine(右, 上, 右, 下)
  画布1.DrawLine(右, 下, 左, 下)
  画布1.DrawLine(左, 下, 左, 上)
}

when 画布1.Touched(x, y, touchedAnySprite) {
  画矩形(x - 25, y - 25, x + 25, y + 25)
}

实心矩形就用 DrawShape(点列表,第二个参数是是否填充):

when 画布1.Touched(x, y, touchedAnySprite) {
  画布1.DrawShape([[x - 25, y - 25], [x + 25, y - 25], [x + 25, y + 25], [x - 25, y + 25]], true)
}

绘制文字

when 画布1.Touched(x, y, touchedAnySprite) {
  画布1.DrawText("Hello!", x, y)
}

文字颜色跟随画布的 PaintColor 属性,不是 DrawText 的参数:

when 按钮_红字.Click() {
  画布1.PaintColor = "&HFFFF0000"
  画布1.DrawText("红色的字", 50, 50)
}

ImageSprite(图片精灵)

ImageSprite是Canvas上的可移动图片对象,是实现游戏角色的基础。

核心属性

属性 说明
Picture 精灵图片
X, Y 位置坐标
Heading 移动方向(0-360°,0=向上)
Speed 移动速度
Interval 移动间隔(毫秒)
Rotates 是否跟随方向旋转
Visible 是否可见
Z 层级(越大越前面)

基本动画

when Screen1.Initialize() {
  图像精灵1.Picture = "character.png"
  图像精灵1.X = 100
  图像精灵1.Y = 100
  图像精灵1.Heading = 90  // 向右
  图像精灵1.Speed = 5
  图像精灵1.Interval = 50  // 50ms移动一次
  图像精灵1.Enabled = true
}

方向控制(键盘/按钮)

when 按钮_上.Click() {
  图像精灵1.Heading = 0  // 向上
}
when 按钮_下.Click() {
  图像精灵1.Heading = 180  // 向下
}
when 按钮_左.Click() {
  图像精灵1.Heading = 270  // 向左
}
when 按钮_右.Click() {
  图像精灵1.Heading = 90  // 向右
}

碰撞检测

when 图像精灵1.CollidedWith(other) {
  图像精灵1.Enabled = false
  对话框1.ShowAlert("碰撞!游戏结束")
}

参数 other撞上来的那个组件。要区分撞到了谁,在编辑器里把「组件块」(调色板里 每个组件都有一个同名的值块)拖进比较块与 other 比较即可 —— 组件块是拖出来的, 伪代码写不出来。另外 ShowAlert 只有一个参数,两个参数的是 ShowMessageDialog

到达边缘

when 图像精灵1.EdgeReached(edge) {
  图像精灵1.Bounce(edge)
}

Bounce 会按撞上的那条边自动算出反弹方向,比自己加 180 度准确(斜着撞角落时尤其明显)。

实战案例:地球围绕太阳旋转

global 角度 = 0
global 太阳X = 200
global 太阳Y = 200
global 轨道半径 = 100

when Screen1.Initialize() {
  画布1.DrawCircle(太阳X, 太阳Y, 30, true)
  计时器1.TimerInterval = 50
  计时器1.TimerEnabled = true
}

when 计时器1.Timer() {
  角度 = 角度 + 3
  if 角度 > 360 {
    角度 = 0
  }
  var 地球X = 太阳X + 轨道半径 * cos(角度 * 3.1415926 / 180)
  var 地球Y = 太阳Y + 轨道半径 * sin(角度 * 3.1415926 / 180)
  画布1.Clear()
  画布1.DrawCircle(太阳X, 太阳Y, 30, true)
  画布1.DrawCircle(地球X, 地球Y, 12, true)
}

实战案例:翻翻乐(记忆配对游戏)

功能需求

  • 4x4的卡片网格(8对)
  • 点击翻开卡片
  • 配对成功保持翻开,失败翻回
  • 计分和计时

界面设计

  • Canvas(填满屏幕)
  • Clock(计时用)
  • 标签_分数、标签_时间

积木块代码

global 卡片数据 = ["苹果", "香蕉", "橙子", "葡萄", "苹果", "香蕉", "橙子", "葡萄"]
global 卡片状态 = []
global 第一次选择 = 0
global 第二次选择 = 0
global 分数 = 0

when Screen1.Initialize() {
  for i = 1 to 8 {
    listadd(卡片状态, false)
  }
  绘制卡片()
}

procedure 绘制卡片() {
  画布1.Clear()
  for i = 1 to length(卡片数据) {
    if select(卡片状态, i) {
      画布1.DrawText(select(卡片数据, i), 20, i * 30)
    } else {
      画布1.DrawText("未翻开", 20, i * 30)
    }
  }
}

when 画布1.Touched(x, y, touchedAnySprite) {
  var 索引 = max(1, min(length(卡片数据), floor(y / 30)))
  if select(卡片状态, 索引) == false {
    listreplace(卡片状态, 索引, true)
    if 第一次选择 = 0 {
      第一次选择 = 索引
    } else {
      第二次选择 = 索引
      if select(卡片数据, 第一次选择) == select(卡片数据, 第二次选择) {
        分数 = 分数 + 10
        标签_分数.Text = join("分数:", 分数)
      }
      第一次选择 = 0
      第二次选择 = 0
    }
    绘制卡片()
  }
}

Canvas绘图方法汇总

参数名与顺序以此为准(写错顺序块能生成,跑起来是错的):

方法 参数 说明
DrawLine x1, y1, x2, y2 画线
DrawPoint x, y 画点
DrawCircle centerX, centerY, radius, fill 画圆
DrawArc left, top, right, bottom, startAngle, sweepAngle, useCenter, fill 画弧/扇形
DrawShape pointList, fill 按点列表画多边形(矩形用它或四条线)
DrawText text, x, y 画文字(文字在前
DrawTextAtAngle text, x, y, angle 旋转文字
Clear 清除画布
Save 保存为图片,返回路径
SaveAs fileName 另存为指定文件名
GetPixelColor x, y 取该点颜色(含精灵)
GetBackgroundPixelColor x, y 取背景该点颜色
SetBackgroundPixelColor x, y, color 设背景该点颜色

没有 DrawRect / DrawRectangle / DrawImage 这些方法 —— 矩形用 DrawShape 或四条 DrawLine;要显示图片就把 BackgroundImage 属性设成图片,或用 ImageSpritePicture。 画笔颜色、粗细是画布的 PaintColor / LineWidth 属性,不是绘图方法的参数。

常见问题

Q1: 闪烁问题怎么解决?

Canvas频繁重绘会闪烁。解决方案:

  • 使用双缓冲(先画完再显示)
  • 减少重绘频率
  • 使用Sprite而不是Canvas绘图

Q2: 精灵图片背景透明?

使用PNG格式的图片,去掉背景色。App Inventor的ImageSprite支持PNG透明通道。

Q3: 如何实现虚拟摇杆?

Canvas + Dragged事件实现虚拟摇杆:

global 摇杆中心X = 100
global 摇杆中心Y = 300
global 摇杆半径 = 50

when 画布1.Dragged(startX, startY, prevX, prevY, currentX, currentY, draggedAnySprite) {
  var dx = currentX - 摇杆中心X
  var dy = currentY - 摇杆中心Y
  var 距离 = sqrt(dx ^ 2 + dy ^ 2)
  var 摇杆X = 摇杆中心X
  var 摇杆Y = 摇杆中心Y
  if 距离 <= 摇杆半径 {
    摇杆X = currentX
    摇杆Y = currentY
  } else if 距离 > 0 {
    摇杆X = 摇杆中心X + 摇杆半径 * dx / 距离
    摇杆Y = 摇杆中心Y + 摇杆半径 * dy / 距离
  }
  图像精灵1.Heading = atan2(-(摇杆Y - 摇杆中心Y), 摇杆X - 摇杆中心X) * 180 / 3.1415926
}

总结

功能 方案
自由绘画 Canvas + Dragged
游戏角色 ImageSprite
碰撞检测 ImageSprite.Collided
圆周运动 三角函数计算坐标
卡片游戏 Canvas重绘
虚拟摇杆 Canvas + 拖拽

版权声明:MIT App Inventor 官方文档采用 CC BY-SA 4.0 授权,本文档由 ai2claw 🐝 整理。

文档反馈