App Inventor 2 台球游戏制作教程

« 返回首页

概述

台球游戏的核心是2D物理模拟:碰撞检测、反弹计算、摩擦力。App Inventor的Canvas + ImageSprite可以实现。

已有文档Canvas游戏基础。本文聚焦台球物理。

核心物理概念

概念 说明 实现方式
碰撞检测 球与球/球与边界 距离计算
弹性碰撞 球碰撞后速度交换 向量计算
反弹 球碰到边界反弹 速度分量取反
摩擦力 球逐渐减速 速度衰减
入袋检测 球进入袋口 距离判断

界面设计

Screen_Pool
├── Canvas_球桌(球台画布)
│   ├── ImageSprite_白球
│   ├── ImageSprite_球1 ~ 球15
│   └── 球杆线(Canvas画线)
├── 按钮_击球
├── 标签_分数
├── 标签_状态
├── 滑块_力度(控制击球力量)
├── Clock_物理引擎(驱动物理模拟)
└── Clock_绘图(绘制辅助线)

球桌布局

摩擦系数

global 球台宽度 = 480
global 球台高度 = 800
global 球半径 = 15
global 摩擦系数 = 0.995
global 袋口半径 = 25
global 袋口X = [0, 240, 480, 0, 240, 480]
global 袋口Y = [0, 0, 0, 800, 800, 800]

物理引擎

球的数据结构

global 球X = []
global 球Y = []
global 球VX = []
global 球VY = []
global 球编号 = []
global 球激活 = []

procedure 创建球(x, y, 编号) {
  listadd(球X, x)
  listadd(球Y, y)
  listadd(球VX, 0)
  listadd(球VY, 0)
  listadd(球编号, 编号)
  listadd(球激活, true)
}

物理更新(每帧)

global 球X = []
global 球Y = []
global 球VX = []
global 球VY = []
global 球激活 = []
global 球半径 = 15
global 球台宽度 = 480
global 球台高度 = 800
global 摩擦系数 = 0.995

when 计时器_物理引擎.Timer() {
  var i = 1
  while i <= length(球X) {
    if select(球激活, i) {
      var x = select(球X, i) + select(球VX, i)
      var y = select(球Y, i) + select(球VY, i)
      var vx = select(球VX, i) * 摩擦系数
      var vy = select(球VY, i) * 摩擦系数
      if abs(vx) < 0.05 { vx = 0 }
      if abs(vy) < 0.05 { vy = 0 }
      if x - 球半径 < 0 { x = 球半径; vx = -vx * 0.8 }
      if x + 球半径 > 球台宽度 { x = 球台宽度 - 球半径; vx = -vx * 0.8 }
      if y - 球半径 < 0 { y = 球半径; vy = -vy * 0.8 }
      if y + 球半径 > 球台高度 { y = 球台高度 - 球半径; vy = -vy * 0.8 }
      listreplace(球X, i, x)
      listreplace(球Y, i, y)
      listreplace(球VX, i, vx)
      listreplace(球VY, i, vy)
    }
    i = i + 1
  }
  碰撞检测()
  绘制球桌()
}

球与球碰撞

global 球X = []
global 球Y = []
global 球VX = []
global 球VY = []
global 球半径 = 15

procedure 碰撞检测() {
  for i = 1 to length(球X) {
    for j = i + 1 to length(球X) {
      var dx = select(球X, j) - select(球X, i)
      var dy = select(球Y, j) - select(球Y, i)
      var 距离 = sqrt(dx ^ 2 + dy ^ 2)
      if 距离 < 2 * 球半径 and 距离 > 0 {
        listreplace(球VX, i, select(球VX, j))
        listreplace(球VX, j, select(球VX, i))
        listreplace(球VY, i, select(球VY, j))
        listreplace(球VY, j, select(球VY, i))
      }
    }
  }
}

入袋检测

global 球X = []
global 球Y = []
global 球激活 = []
global 袋口X = [0, 240, 480, 0, 240, 480]
global 袋口Y = [0, 0, 0, 800, 800, 800]
global 袋口半径 = 25
global 分数 = 0

procedure 检查入袋(索引) {
  for i = 1 to length(袋口X) {
    var dx = select(球X, 索引) - select(袋口X, i)
    var dy = select(球Y, 索引) - select(袋口Y, i)
    if sqrt(dx ^ 2 + dy ^ 2) < 袋口半径 {
      listreplace(球激活, 索引, false)
      分数 = 分数 + 10
      标签_分数.Text = join("分数:", 分数)
    }
  }
}

击球控制

拖动瞄准

global 球X = [100]
global 球Y = [400]
global 球VX = [0]
global 球VY = [0]
global 等待击球 = true

when 画布_球桌.Dragged(startX, startY, prevX, prevY, currentX, currentY, draggedAnySprite) {
  if 等待击球 and draggedAnySprite {
    画布_球桌.DrawLine(select(球X, 1), select(球Y, 1), currentX, currentY)
  }
}

when 画布_球桌.TouchUp(x, y) {
  var dx = select(球X, 1) - x
  var dy = select(球Y, 1) - y
  var 距离 = sqrt(dx ^ 2 + dy ^ 2)
  if 距离 > 0 {
    listreplace(球VX, 1, dx / 距离 * 20)
    listreplace(球VY, 1, dy / 距离 * 20)
    等待击球 = false
  }
}

绘制

global 球X = []
global 球Y = []
global 球激活 = []
global 球半径 = 15

procedure 绘制球桌() {
  画布_球桌.Clear()
  for i = 1 to length(球X) {
    if select(球激活, i) {
      画布_球桌.DrawCircle(select(球X, i), select(球Y, i), 球半径, true)
    }
  }
}

常见问题

Q1: 碰撞后球会卡住?

确保碰撞后有分离步骤(将重叠的球推开)。

Q2: 性能卡顿?

  • 减少Clock频率(16ms → 33ms)
  • 球数量多时碰撞检测慢(O(n²)),可优化空间分区

Q3: 球运动不自然?

调整摩擦系数和反弹衰减系数,多测试找到合适参数。

总结

核心技术 关键
碰撞检测 距离 < 两球半径之和
弹性碰撞 交换法向速度分量
摩擦力 每帧速度乘以衰减系数
入袋 球心到袋口距离 < 袋口半径
击球 反方向拖动,力度控制速度

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

文档反馈