游戏手柄(Gamepad)使用指南¶
约 3284 个字 248 行代码 预计阅读时间 14 分钟
Gamepad 常用方法¶
| 方法 | 作用 | 备注与注意事项 |
|---|---|---|
| rumble(double rumble1, double rumble2, int durationMs) | 使手柄震动 | 参数:左马达强度(0-1)、右马达强度(0-1)、持续时间(毫秒) |
| rumble(int durationMs) | 以 100%功率震动手柄 | 简化版本的震动方法 |
| rumbleBlips(int count) | 震动指定次数 | 用于简单的反馈信号 |
| stopRumble() | 停止当前震动 | 可用于中断长时间震动 |
| isRumbling() | 检查手柄是否正在震动 | 返回布尔值 |
| setLedColor(double r, double g, double b, int durationMs) | 设置手柄 LED 灯颜色 | 仅 PS4 和部分第三方手柄支持,颜色值范围 0-1 |
| getButtonState(Button button) | 获取指定按钮的状态 | 返回布尔值,表示按钮是否被按下 |
| setCourse(double course, double power) | 设定摇杆方向和力度 | 用于创建虚拟摇杆输入,course 为角度(0-360 度) |
| atRest() | 检查手柄是否处于静止状态 | 返回布尔值,所有按钮未按下且摇杆在中央位置时为 true |
| copy(Gamepad gamepad) | 复制另一个手柄的状态 | 用于保存手柄状态的快照 |
| toString() | 返回手柄状态的字符串表示 | 用于调试和记录 |
| type() | 返回手柄类型 | 区分不同型号的手柄 |
| refreshTimestamp() | 刷新时间戳 | 记录最后一次数据更新的时间 |
什么是游戏手柄 (Gamepad)?¶
游戏手柄是 FTC 机器人比赛中用来控制机器人的主要输入设备。它就像是电视游戏机的手柄,让操作员可以通过按钮、摇杆和扳机来控制机器人的各种动作。
在 FTC 比赛中:
-
最多可以同时使用 2 个游戏手柄
-
每个手柄都需要通过 USB 线连接到驾驶站 (Driver Station) 设备上
-
官方允许的手柄型号包括 Logitech F310(震动:没有、LED 控制:无)、Sony DualShock 4、Sony DualSense 和 Xbox 360 等
连接游戏手柄¶
在比赛中正确连接和设置游戏手柄非常重要:
- 检查游戏手柄模式开关:确保 Logitech F310 手柄底部的模式开关处于 "X" 位置(即 Xbox 模式)
检查游戏手柄模式开关:确保 Logitech F310 手柄底部的模式开关处于 "X" 位置(即 Xbox 模式)
- 连接到驾驶站:使用 USB 线将游戏手柄连接到驾驶站设备(通常是一台 Android 手机或平板)
连接到驾驶站:使用 USB 线将游戏手柄连接到驾驶站设备(通常是一台 Android 手机或平板)
- 指定手柄编号:同时按下 Start 按钮和 A 按钮,将手柄指定为 1 号手柄(用于主驾驶)同时按下 Start 按钮和 B 按钮,将手柄指定为 2 号手柄(用于副驾驶)
指定手柄编号:
-
同时按下 Start 按钮和 A 按钮,将手柄指定为 1 号手柄(用于主驾驶)
-
同时按下 Start 按钮和 B 按钮,将手柄指定为 2 号手柄(用于副驾驶)
-
确认连接成功:正确连接后,驾驶站界面右上角会显示一个手柄图标,标有 "User 1" 或 "User 2"。当您操作手柄时,对应的图标会显示绿色高亮
确认连接成功:正确连接后,驾驶站界面右上角会显示一个手柄图标,标有 "User 1" 或 "User 2"。当您操作手柄时,对应的图标会显示绿色高亮
游戏手柄的按钮和摇杆¶
主要控制元件¶
游戏手柄上的控制元件可以分为以下几类:
-
摇杆(Joysticks):左右两个可以上下左右移动的小杆
-
按钮(Buttons):A、B、X、Y、Back、Start、左右摇杆按钮等
-
方向键(Dpad):上、下、左、右四个方向按键
-
扳机(Triggers):左右两个类似扳机的控制器(可变压力)
-
肩部按钮(Bumpers):左右两个位于手柄上部的按钮
数值范围¶
不同控制元件的读数范围:
-
摇杆:-1.0 到 +1.0左:-1.0,右:+1.0上:-1.0,下:+1.0(注意:Y 轴是反向的!也就是 Y 轴前推,但是得到的是负数值。)
-
左:-1.0,右:+1.0
-
上:-1.0,下:+1.0(注意:Y 轴是反向的!也就是 Y 轴前推,但是得到的是负数值。)
-
扳机:0.0 到 1.0
-
按钮和肩部按钮:按下为 true,松开为 false
-
方向键:按下为 true,松开为 false
在代码中使用游戏手柄¶
在 FTC 编程中,游戏手柄是通过gamepad1和gamepad2两个对象来访问的:
-
gamepad1对应 1 号手柄(主驾驶)
-
gamepad2对应 2 号手柄(副驾驶)
基本用法示例¶
以下是一些常见的游戏手柄控制示例:
控制电机(使用摇杆)¶
// 使用左摇杆的垂直方向控制电机
// 注意:通常需要取负值,因为摇杆向上是-1,向下是+1
double motorPower = -gamepad1.left_stick_y;
motor.setPower(motorPower);
// 使用右摇杆的水平方向控制转向
double steeringPower = gamepad1.right_stick_x;
leftMotor.setPower(motorPower + steeringPower);
rightMotor.setPower(motorPower - steeringPower);
控制舵机(使用按钮)¶
// 使用A和B按钮控制舵机位置
if (gamepad1.a) {
servo.setPosition(0.0); // 移动到最小位置
} else if (gamepad1.b) {
servo.setPosition(1.0); // 移动到最大位置
}
使用扳机控制吸取装置¶
// 使用左右扳机控制吸取装置
double intakePower = gamepad1.right_trigger - gamepad1.left_trigger;
intakeMotor.setPower(intakePower);
使用方向键选择自动程序¶
// 使用方向键选择不同的自动程序
if (gamepad1.dpad_up) {
selectedAuto = "路径A";
} else if (gamepad1.dpad_right) {
selectedAuto = "路径B";
} else if (gamepad1.dpad_down) {
selectedAuto = "路径C";
} else if (gamepad1.dpad_left) {
selectedAuto = "路径D";
}
常见编程模式¶
基于状态的控制¶
这种模式适合控制需要在不同位置之间切换的组件,如手爪、机械臂等:
// 翻转状态变量的值
if (gamepad1.x && !xWasPressed) {
clawOpen = !clawOpen;
if (clawOpen) {
claw.setPosition(CLAW_OPEN_POSITION);
} else {
claw.setPosition(CLAW_CLOSED_POSITION);
}
}
xWasPressed = gamepad1.x;
组合按钮¶
有时需要同时按下多个按钮来触发特殊功能:
精确控制(缩放输入)¶
有时需要降低摇杆的灵敏度,以便进行精确操作:
// 将摇杆输入缩小到50%,提供更精确的控制
double preciseDrivePower = -gamepad1.left_stick_y * 0.5;
motor.setPower(preciseDrivePower);
完整示例:坦克驱动控制¶
下面是一个完整的例子,展示如何使用游戏手柄控制机器人的坦克式驱动系统:
@TeleOp(name="基础坦克驱动", group="示例")
public class BasicTankDrive extends LinearOpMode {
// 声明电机
private DcMotor leftMotor = null;
private DcMotor rightMotor = null;
@Override
public void runOpMode() {
// 初始化电机
leftMotor = hardwareMap.get(DcMotor.class, "left_drive");
rightMotor = hardwareMap.get(DcMotor.class, "right_drive");
// 设置电机方向
leftMotor.setDirection(DcMotor.Direction.FORWARD);
rightMotor.setDirection(DcMotor.Direction.REVERSE);
// 等待开始
telemetry.addData("状态", "初始化完成");
telemetry.update();
waitForStart();
// 主循环
while (opModeIsActive()) {
// 读取游戏手柄输入
double leftPower = -gamepad1.left_stick_y;
double rightPower = -gamepad1.right_stick_y;
// 设置电机功率
leftMotor.setPower(leftPower);
rightMotor.setPower(rightPower);
// 显示当前电机功率
telemetry.addData("左电机", "%.2f", leftPower);
telemetry.addData("右电机", "%.2f", rightPower);
telemetry.update();
}
}
}
游戏手柄高级功能¶
震动反馈¶
在 FTC SDK 中,您可以让游戏手柄震动,为驾驶员提供触觉反馈:
检测游戏手柄事件¶
有时您可能需要检测按钮的按下和释放事件,而不仅仅是当前状态:
boolean aWasPressed = false;
// 在循环中使用
if (gamepad1.a && !aWasPressed) {
// 仅在A按钮刚被按下时执行
toggleSomething();
}
aWasPressed = gamepad1.a;
游戏手柄属性列表¶
以下是完整的游戏手柄属性列表,您可以在代码中访问这些属性:
摇杆¶
-
gamepad1.left_stick_x- 左摇杆水平位置 (-1.0 到 1.0)
-
gamepad1.left_stick_y- 左摇杆垂直位置 (-1.0 到 1.0)
-
gamepad1.right_stick_x- 右摇杆水平位置 (-1.0 到 1.0)
-
gamepad1.right_stick_y- 右摇杆垂直位置 (-1.0 到 1.0)
摇杆按钮¶
-
gamepad1.left_stick_button- 左摇杆按钮 (布尔值)
-
gamepad1.right_stick_button- 右摇杆按钮 (布尔值)
主要按钮¶
-
gamepad1.a- A 按钮 (布尔值)
-
gamepad1.b- B 按钮 (布尔值)
-
gamepad1.x- X 按钮 (布尔值)
-
gamepad1.y- Y 按钮 (布尔值)
肩部按钮和扳机¶
-
gamepad1.left_bumper- 左肩部按钮 (布尔值)
-
gamepad1.right_bumper- 右肩部按钮 (布尔值)
-
gamepad1.left_trigger- 左扳机 (0.0 到 1.0)
-
gamepad1.right_trigger- 右扳机 (0.0 到 1.0)
方向键¶
-
gamepad1.dpad_up- 方向键上 (布尔值)
-
gamepad1.dpad_down- 方向键下 (布尔值)
-
gamepad1.dpad_left- 方向键左 (布尔值)
-
gamepad1.dpad_right- 方向键右 (布尔值)
其他按钮¶
-
gamepad1.back- 返回按钮 (布尔值)
-
gamepad1.start- 开始按钮 (布尔值)
-
gamepad1.guide- 指南按钮 (布尔值)
官方示例代码解析¶
下面我们来分析 FTC SDK 中的官方示例代码,看看如何在实际程序中使用游戏手柄。
示例 1:基础操作模式(BasicOpMode_Linear)¶
这个示例展示了最基本的游戏手柄用法,用于控制一个简单的双轮机器人:
// POV模式控制:使用左摇杆前进/后退,右摇杆左右转向
double drive = -gamepad1.left_stick_y; // 获取左摇杆的Y轴值
// 注意:摇杆向前推是负值,所以要取反
double turn = gamepad1.right_stick_x; // 获取右摇杆的X轴值(左右转向)
// 计算左右电机功率
leftPower = Range.clip(drive + turn, -1.0, 1.0); // 将值限制在-1到1之间
rightPower = Range.clip(drive - turn, -1.0, 1.0);
// 坦克模式控制:左摇杆控制左电机,右摇杆控制右电机
// leftPower = -gamepad1.left_stick_y; // 左摇杆Y轴直接控制左电机
// rightPower = -gamepad1.right_stick_y; // 右摇杆Y轴直接控制右电机
// 将计算的功率发送到电机
leftDrive.setPower(leftPower);
rightDrive.setPower(rightPower);
代码解析:
-
首先获取游戏手柄的摇杆值:gamepad1.left_stick_y:左摇杆上下移动的值(-1 到 1)gamepad1.right_stick_x:右摇杆左右移动的值(-1 到 1)
-
gamepad1.left_stick_y:左摇杆上下移动的值(-1 到 1)
-
gamepad1.right_stick_x:右摇杆左右移动的值(-1 到 1)
-
由于摇杆向前是负值(-1),所以需要取反使其变为正值
-
通过数学计算,将前进动作和转向动作结合起来:左电机 = 前进值 + 转向值右电机 = 前进值 - 转向值
-
左电机 = 前进值 + 转向值
-
右电机 = 前进值 - 转向值
-
使用Range.clip()方法确保功率值不超出-1 到 1 的范围
-
最后将计算出的功率值设置给电机
示例 2:POV 控制模式(RobotTeleopPOV_Linear)¶
这个示例更加复杂,展示了如何用游戏手柄控制机器人的驱动系统、机械臂和机械爪:
// 驱动控制(与示例1类似)
drive = -gamepad1.left_stick_y; // 前后移动
turn = gamepad1.right_stick_x; // 左右转向
left = drive + turn; // 左侧电机功率
right = drive - turn; // 右侧电机功率
// 规范化值,确保不超过±1.0
max = Math.max(Math.abs(left), Math.abs(right));
if (max > 1.0) {
left /= max;
right /= max;
}
// 控制机械爪开合
if (gamepad1.right_bumper)
clawOffset += CLAW_SPEED; // 按下右肩键,增加爪子偏移量(开爪)
else if (gamepad1.left_bumper)
clawOffset -= CLAW_SPEED; // 按下左肩键,减小爪子偏移量(闭爪)
// 将爪子偏移量限制在合理范围内
clawOffset = Range.clip(clawOffset, -0.5, 0.5);
// 设置左右爪舵机位置(注意它们是镜像的)
leftClaw.setPosition(MID_SERVO + clawOffset);
rightClaw.setPosition(MID_SERVO - clawOffset);
// 控制机械臂上下移动
if (gamepad1.y)
leftArm.setPower(ARM_UP_POWER); // 按Y键,机械臂向上
else if (gamepad1.a)
leftArm.setPower(ARM_DOWN_POWER); // 按A键,机械臂向下
else
leftArm.setPower(0.0); // 不按键,机械臂停止
代码解析:
-
驱动系统控制与基础示例类似,但增加了规范化处理:当计算出的功率值超过 1 时,按比例缩小左右电机功率,保持转向比例不变
-
当计算出的功率值超过 1 时,按比例缩小左右电机功率,保持转向比例不变
-
机械爪控制使用左右肩部按钮(bumpers):右肩按钮增加爪子偏移量,使爪子打开左肩按钮减小爪子偏移量,使爪子关闭每次按下只调整一点点(CLAW_SPEED=0.02),实现缓慢平滑的控制
-
右肩按钮增加爪子偏移量,使爪子打开
-
左肩按钮减小爪子偏移量,使爪子关闭
-
每次按下只调整一点点(CLAW_SPEED=0.02),实现缓慢平滑的控制
-
机械臂控制使用 Y 和 A 按钮:Y 按钮使机械臂向上移动A 按钮使机械臂向下移动不按任何按钮时停止机械臂
-
Y 按钮使机械臂向上移动
-
A 按钮使机械臂向下移动
-
不按任何按钮时停止机械臂
示例 3:检测按钮状态变化(防止重复触发)¶
在很多情况下,我们需要检测按钮的按下事件,而不是持续检测按钮的状态。下面的代码展示了如何做到这一点:
// 在类中定义变量来记住上一次的按钮状态
boolean aButtonPrevState = false;
boolean bButtonPrevState = false;
// 在循环中检测按钮状态变化
// 当前按钮状态
boolean aButtonCurrentState = gamepad1.a;
boolean bButtonCurrentState = gamepad1.b;
// 检测A按钮的按下事件(从未按下到按下的变化)
if (aButtonCurrentState && !aButtonPrevState) {
// A按钮刚刚被按下,执行一次性动作
// 例如:切换LED灯的开关状态
ledEnabled = !ledEnabled;
}
// 检测B按钮的释放事件(从按下到未按下的变化)
if (!bButtonCurrentState && bButtonPrevState) {
// B按钮刚刚被释放,执行一次性动作
// 例如:切换到下一个自动程序
selectedAutoMode = (selectedAutoMode + 1) % totalAutoModes;
}
// 更新上一次的按钮状态,为下一次循环做准备
aButtonPrevState = aButtonCurrentState;
bButtonPrevState = bButtonCurrentState;
代码解析:
-
定义变量记录上一次循环中按钮的状态
-
在每次循环中,先获取当前按钮状态
-
通过比较当前状态和上一次状态,检测状态变化:当前为true且上一次为false:表示按钮刚刚被按下当前为false且上一次为true:表示按钮刚刚被释放
-
当前为true且上一次为false:表示按钮刚刚被按下
-
当前为false且上一次为true:表示按钮刚刚被释放
-
在循环结束时,更新"上一次"状态变量
这种方法可以防止按钮被按住时重复触发动作,确保每次按下只执行一次操作。
示例 4:使用手柄震动提供反馈¶
某些游戏手柄(如 PS4 手柄)支持震动功能,可以提供触觉反馈:
// 让手柄震动1秒钟
gamepad1.rumble(1.0, 1.0, 1000); // 参数:左马达强度,右马达强度,持续时间(毫秒)
// 创建自定义震动序列
Gamepad.RumbleEffect customRumble = new Gamepad.RumbleEffect.Builder()
.addStep(0.0, 1.0, 300) // 右马达震动300毫秒
.addStep(0.0, 0.0, 100) // 暂停100毫秒
.addStep(1.0, 0.0, 300) // 左马达震动300毫秒
.build();
// 运行自定义震动序列
gamepad1.runRumbleEffect(customRumble);
// 简单地震动3次
gamepad1.rumbleBlips(3);
代码解析:
-
基本震动使用rumble()方法,指定左右马达强度和持续时间
-
自定义震动序列允许创建复杂的震动模式,如摩尔斯电码或特定信号
-
rumbleBlips()提供简单的多次震动功能
注意:并非所有手柄都支持震动功能。罗技 F310 不支持震动,而 PS4 和 Xbox 手柄支持。
实用技巧与进阶方法¶
实现精确控制的几种方法¶
在实际竞赛中,有时需要更精确的控制。以下是几种常用方法:
1. 非线性控制曲线¶
// 应用平方函数,保留原始符号
// 这使得小输入有更精细的控制,大输入仍能达到最大速度
double rawInput = -gamepad1.left_stick_y; // 获取原始输入
double sign = Math.signum(rawInput); // 保存符号(1或-1)
double squared = Math.pow(rawInput, 2); // 计算平方值
double scaledInput = sign * squared; // 应用原始符号
motor.setPower(scaledInput);
2. 可调节的敏感度控制¶
// 使用扳机调节敏感度
double sensitivity = 0.5 + (gamepad1.right_trigger * 0.5); // 0.5到1.0的范围
double drive = -gamepad1.left_stick_y * sensitivity;
// 或者使用按钮切换不同的速度模式
if (gamepad1.b) {
speedMode = "高速";
speedMultiplier = 1.0;
} else if (gamepad1.x) {
speedMode = "中速";
speedMultiplier = 0.5;
} else if (gamepad1.a) {
speedMode = "低速";
speedMultiplier = 0.25;
}
// 应用速度乘数
leftPower = drive * speedMultiplier;
多驾驶员协作控制¶
在 FTC 比赛中,可以有两个驾驶员,通常使用两个手柄协作控制机器人:
// 驾驶员1控制移动
double drive = -gamepad1.left_stick_y;
double turn = gamepad1.right_stick_x;
leftDrive.setPower(drive + turn);
rightDrive.setPower(drive - turn);
// 驾驶员2控制机械装置
if (gamepad2.a) {
// 控制收集机构
intakeMotor.setPower(1.0);
} else if (gamepad2.b) {
// 反向运行收集机构
intakeMotor.setPower(-1.0);
} else {
// 停止收集机构
intakeMotor.setPower(0);
}
// 驾驶员2控制发射机构
if (gamepad2.right_trigger > 0.5) {
// 启动发射器
launcherMotor.setPower(1.0);
// 等待发射器达到全速
sleep(500);
// 触发推块机构
pusherServo.setPosition(PUSH_POSITION);
sleep(250);
// 收回推块机构
pusherServo.setPosition(RETRACT_POSITION);
}
这种分工可以让一名驾驶员专注于机器人的移动,另一名驾驶员专注于操作机械装置,从而提高整体效率。
常见问题与经验总结¶
1. 调试游戏手柄输入¶
在开发过程中,通常需要查看游戏手柄的输入值以便调试:
// 在遥测中显示所有重要的游戏手柄输入
telemetry.addData("左摇杆", "X: %.2f Y: %.2f", gamepad1.left_stick_x, gamepad1.left_stick_y);
telemetry.addData("右摇杆", "X: %.2f Y: %.2f", gamepad1.right_stick_x, gamepad1.right_stick_y);
telemetry.addData("扳机", "左: %.2f 右: %.2f", gamepad1.left_trigger, gamepad1.right_trigger);
telemetry.addData("肩部按钮", "左: %b 右: %b", gamepad1.left_bumper, gamepad1.right_bumper);
telemetry.addData("按钮", "A: %b B: %b X: %b Y: %b", gamepad1.a, gamepad1.b, gamepad1.x, gamepad1.y);
telemetry.update();
2. 避免常见陷阱¶
-
忘记取反 Y 轴:摇杆向前推是负值(-1),如果忘记取反,机器人会向后移动
-
死区处理不当:小输入可能导致电机随机抖动,应该添加死区逻辑
-
按钮触发重复:如果在每次循环中检测按钮状态,会导致持续按住按钮时重复触发
-
驾驶站不显示手柄:确保正确指定手柄编号(按 Start+A 或 Start+B)
3. 高级控制系统设计模式¶
随着机器人复杂性增加,可以考虑更先进的控制系统设计:
-
状态机:将机器人操作分解为不同状态,使用游戏手柄在状态之间切换
-
命令队列:允许驾驶员通过游戏手柄输入预设一系列动作
-
辅助功能:开发自动辅助功能,如自动对准目标或自动平衡
总结¶
游戏手柄是 FTC 机器人比赛中的核心输入设备,掌握其用法对于开发高效的遥控程序至关重要。从基本的移动控制到复杂的机械操作,游戏手柄提供了丰富的输入方式。通过学习本指南介绍的各种技术和模式,您可以开发出更加精确、可靠和用户友好的机器人控制系统。
记住:
-
理解摇杆和按钮的数值范围和行为
-
适当处理输入值,如取反、缩放和应用死区
-
检测按钮状态变化而不是简单检测当前状态
-
根据任务的复杂性设计合适的控制模式和驾驶员分工
参考资源¶
-
FTC 官方游戏手柄文档
-
FTC SDK 官方游戏手柄 API 文档
-
游戏手柄使用 - Game Manual 0