15、游戏开发:用加速度传感器控制小球移动
说实话,加速度传感器最酷的应用场景,就是做游戏控制。
你想想看,手机就是游戏手柄,倾斜手机就能控制角色移动——这种体验比点按屏幕爽多了。我最早接触这个玩法,是在一个打砖块游戏里,用倾斜控制挡板接球。当时我就想,这玩意儿要是用在自家项目里该多好。
今天我们就来实现一个经典案例:用加速度传感器控制一个小球在屏幕上移动。小球会随着手机倾斜的方向滚动,倾斜角度越大,滚得越快。
核心思路
说白了,就是把传感器的数值映射到小球的位置变化上。
- X轴加速度 → 控制小球左右移动
- Y轴加速度 → 控制小球上下移动
- Z轴 → 暂时忽略,或者用来做其他效果
但这里有个坑:传感器返回的是加速度值,不是位移值。你不能直接把加速度当位置用,否则小球会飞出去。正确的做法是:用加速度值作为速度的增量,然后速度再影响位置。
核心公式:
速度 += 加速度 × 时间步长
位置 += 速度 × 时间步长
再加上阻尼系数,让小球停下来。
代码实现
我们先写一个简单的游戏循环。我习惯用 SurfaceView 来做实时渲染,因为它可以在子线程里画图,不会卡住主线程。
public class BallGameView extends SurfaceView implements SurfaceHolder.Callback, SensorEventListener {
private Paint ballPaint;
private float ballX, ballY;
private float ballRadius = 40f;
private float vx = 0f, vy = 0f;
private float damping = 0.95f; // 阻尼系数
private float scaleFactor = 2.0f; // 加速度映射到速度的系数
private SensorManager sensorManager;
private Sensor accelerometer;
public BallGameView(Context context) {
super(context);
getHolder().addCallback(this);
ballPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
ballPaint.setColor(0xFF4CAF50);
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
@Override
public void onSensorChanged(SensorEvent event) {
float ax = event.values[0]; // X轴加速度
float ay = event.values[1]; // Y轴加速度
// 注意:手机平放时,X和Y接近0,Z接近9.8
// 我们只关心水平方向的倾斜
vx += ax * scaleFactor;
vy += ay * scaleFactor;
// 应用阻尼,让小球慢慢停下来
vx *= damping;
vy *= damping;
}
// 在游戏循环中更新位置
public void update() {
ballX += vx;
ballY += vy;
// 边界碰撞检测
if (ballX - ballRadius < 0) {
ballX = ballRadius;
vx = -vx * 0.5f;
} else if (ballX + ballRadius > getWidth()) {
ballX = getWidth() - ballRadius;
vx = -vx * 0.5f;
}
if (ballY - ballRadius < 0) {
ballY = ballRadius;
vy = -vy * 0.5f;
} else if (ballY + ballRadius > getHeight()) {
ballY = getHeight() - ballRadius;
vy = -vy * 0.5f;
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawColor(0xFFF5F5F5);
canvas.drawCircle(ballX, ballY, ballRadius, ballPaint);
}
// 省略 SurfaceHolder.Callback 和 SensorEventListener 的其他方法
}
小技巧:阻尼系数 damping 我一般设在 0.9~0.98 之间。数值越大,小球滑行越久,手感越「飘」。数值越小,小球停得越快,手感越「硬」。你可以根据游戏类型调整。
坐标系问题
这里有个容易搞混的地方:传感器的坐标系和屏幕坐标系是反的。
手机平放时,传感器 X 轴向右为正,Y 轴向上为正。但屏幕的 Y 轴是向下为正。所以如果你直接把 Y 轴加速度传给小球,小球会往反方向跑。
解决办法很简单:把 Y 轴加速度取反。
vy += -ay * scaleFactor; // 注意负号
嗯,这个坑我踩过。第一次写的时候,手机往左倾斜,小球往右跑,我还以为是传感器坏了……
知识体系图
下面这张图帮你理清整个控制链路:
调参经验
写代码容易,调参数才是真功夫。我分享几个经验值:
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| scaleFactor | 1.0 ~ 5.0 | 控制灵敏度。数值越大,小球反应越灵敏 |
| damping | 0.90 ~ 0.98 | 控制滑行感。数值越大越滑,越小越硬 |
| 碰撞恢复系数 | 0.3 ~ 0.7 | 控制反弹力度。0.5 左右手感比较自然 |
| 更新频率 | 30 ~ 60 FPS | 游戏循环的帧率,建议用固定时间步长 |
注意:千万不要在 onSensorChanged() 里直接更新 UI 或 Canvas。这个回调频率很高(可能几百赫兹),直接画图会导致界面卡死。正确的做法是:在回调里只更新数据,然后在游戏循环的 update() 和 draw() 里使用这些数据。
扩展思路
基础功能做完了,你可以加点花样:
- 多球模式:创建多个小球,各自独立运动
- 重力场效果:用 Z 轴加速度改变重力方向
- 迷宫游戏:把小球限制在迷宫通道里,倾斜手机引导小球走出迷宫
- 粒子系统:小球撞墙时产生粒子特效
我个人觉得,迷宫游戏是最适合练手的。你只需要在 Canvas 上画一些障碍物,然后检测小球和障碍物的碰撞即可。我曾经用这个思路做过一个「滚珠迷宫」的小游戏,用户反馈特别好。
好了,今天的核心内容就这些。记住:传感器控制游戏的关键在于「加速度→速度→位置」的映射,再加上阻尼和碰撞,手感就能调出来。多试几次参数,找到最适合你游戏的感觉。