English document <— Attention English-speaking international coder:here is the document you wanted
本项目是在 RN-Game-Joystick 项目基础上进行简单的修改,
原项目是一个摇杆控制一个小球,我在此基础上实现两个摇杆控制两个小球,摇杆之间互不影响,可以同时操控,
可用于双摇杆控制的场景,如游戏、无人机和赛车遥控,
最近在制作 flix-drone 四旋翼无人机项目,有这个控制需求。
下面是 APP 运行效果 (gif图片不动了,就刷新一下网页):
默认情况下,React Native Gesture Handler(RNGH) 在同一个 GestureHandlerRootView 内只允许一个手势处于活跃状态(active),即使它们区域不重叠。
要支持多个同时激活的手势(如双摇杆控制),每个独立的手势组件应包裹在自己的 GestureHandlerRootView 中。
实现双摇杆的关键就是每个 Joystick 必须使用独立的 GestureHandlerRootView (查看 app\index.tsx)。
下面的文档内容基本上是原项目文档的翻译内容,只是在一些描述上做了修改,详情参考原项目--> RN-Game-Joystick
一款极简、性能高的React Native游戏双摇杆控制器实现,采用Expo、React Native Reanimated v4和Gesture Handler构建。
- 🎮虚拟双摇杆,手势作流畅
- ⚡ 基于帧的动画在UI线程上实现60fps性能
- 🎯 边界约束字符移动
- 📱 适用于iOS、Android和网页版
- 🔧 支持TypeScript及完整类型定义
- 📚 全面的组件文档
该项目展示了:
- 两个可拖曳的摇杆控制器,可以报告方向输入
- 两个角色(红、蓝色小球),根据摇杆位置持续移动
- UI 线程上的动画流畅运行
- 正确的边界检测以保持角色在屏幕上
- Node.js 18+
- npm 或者 yarn
- Expo CLI (可选,包含在依赖中)
# 克隆此项目
cd React-Native-Game-Dual-Joysticks
# 安装依赖
npm install
# 启动
npx expo start然后:
按下 i 键启动 iOS 模拟器
按下 a 键启动 Android 模拟器
用 Expo Go 应用扫描二维码
React-Native-Game-Dual-Joysticks/
├── app/
│ ├── _layout.tsx # 根布局
│ └── index.tsx # 主游戏画面与操纵杆演示
├── components/
│ └── game/
│ ├── Joystick.tsx # 虚拟摇杆组件
│ ├── Character.tsx # 可移动小球组件
│ └── README.md # 组件文档
├── assets/
│ └── images/ # App 图标
│ └── display.gif # APP 演示效果
│ └── double_joysticks.png # 双摇杆实现代码
├── .gitignore # Git ignore file
├── app.json # Expo 配置
├── package.json # 项目依赖
├── tsconfig.json # TypeScript 配置
└── README.md # 中文文档
└── README_en.md # 英文文档
|__SETUP.md # 设置说明
import { useCallback } from "react";
import { useSharedValue, useFrameCallback } from "react-native-reanimated";
import { Joystick, Character } from "@/components/game";
export default function GameScreen() {
// 小球位置
const characterX = useSharedValue(100);
const characterY = useSharedValue(100);
// 摇杆输入
const joystickX = useSharedValue(0);
const joystickY = useSharedValue(0);
// 更新每一帧
useFrameCallback(() => {
characterX.value += joystickX.value * 5;
characterY.value += joystickY.value * 5;
});
const handleJoystickMove = useCallback((x: number, y: number) => {
joystickX.value = x;
joystickY.value = y;
}, []);
return (
<>
<Character x={characterX} y={characterY} />
<Joystick onMove={handleJoystickMove} />
</>
);
}调整小球移动速度:
const moveSpeed = 8; // 小球的移动速度
useFrameCallback(() => {
characterX.value += joystickX.value * moveSpeed;
});自定义摇杆外观:
<Joystick
size={200}
stickSize={80}
baseColor="rgba(33, 150, 243, 0.2)"
stickColor="rgba(33, 150, 243, 0.8)"
/>改变小球参数:
<Character x={x} y={y} size={80} color="#4CAF50" />Joystick.tsx- 带有手势处理的虚拟摇杆Character.tsx- 可移动小球- 完整的TypeScript支持
- 详细文档(you're reading it)
- Expo ~54.0.23
- React Native Reanimated ~4.1.1 (with scheduleOnRN)
- React Native Gesture Handler ~2.28.0
- React Native Worklets 0.5.1
1.摇杆组件:检测手势并报告归一化后的x/y值(-1到1)
2.共享值:存储摇杆输入和角色位置
3.帧回调:在UI线程上每秒更新字符位置60次
4.角色组件:在当前位置使用 Reanimated 进行渲染
- 手势检测:UI线程(平滑、即时响应)
- 回调桥接:使用
scheduleOnRN与 JS 线程通信 - 位置更新:UI 线程通过
useFrameCallback(60fps 性能)
这种混合方法在保持代码简洁且易于维护的同时,提供了卓越的性能。
- expo: ~54.0.23 - React Native 框架
- react-native-reanimated: ~4.1.1 - 60fps animations on UI thread
- react-native-gesture-handler: ~2.28.0 - Gesture detection
- react-native-worklets: 0.5.1 - Thread communication (scheduleOnRN)
- expo-router: ~6.0.14 - File-based routing
有关详细的组件文档、实现细节及高级使用,请参见:
- Component Documentation - 性能分析的深入指南
- ✅ 设备上流畅实现60fps
- ✅ 针对双角色优化(本次演示)
- ✅ 可以无性能地处理1到10个字符
⚠️ 对于100+实体,可以考虑纯UI线程优化(参见组件README)
问题: 在 useFrameCallback 中使用 console.log 会导致崩溃。
解决方案:使用:scheduleOnRN
import { scheduleOnRN } from "react-native-worklets";
useFrameCallback(() => {
scheduleOnRN(console.log, "Position:", x.value);
});问题:当摇杆保持稳定时,角色会停止。
解决方案:使用 useFrameCallback 在每一帧轮询摇杆数值(此演示中已实现)
问题:摇杆对触碰没有响应。
解决方案:确保你的应用被包裹在 GestureHandlerRootView 中(已在 app/index.tsx中完成)。
下面是一些可以拓展的方向:
- 添加多个小球
- 实现碰撞检测
- 添加敌人或障碍物
- 用额外的按键实现射击/动作
- 添加加速度/动量物理
- 创建一个有边界的游戏场地
- 添加分数追踪
- RN-Game-Joystick
- React Native Reanimated Docs
- React Native Worklets Docs
- React Native Gesture Handler Docs
- Expo Documentation
MIT
注意:这是一个简约演示,专注于纵杆控制器的功能。它有意排除主题系统、导航复杂性及其他模板,以保持实现清晰易懂。 它最初是基于开箱即用的 Expo 应用模板(using npx create-expo-app@latest with Expo SDK 54),然后进行了简化以消除不必要的复杂性。我对这个项目不做任何保证。

