Flutter开发石头剪刀布碰撞游戏需求咨询:寻适配开发方案
Flutter 石头剪刀布碰撞游戏实现方案
不需要额外开发包,用Flutter原生API就能实现,以下是分步实现思路和代码示例:
1. 定义游戏核心数据模型
先定义游戏对象的类型和实体类,存储位置、移动方向等关键属性:
enum EntityType { rock, paper, scissors } class GameEntity { final EntityType type; Offset position; Offset velocity; final double size; GameEntity({ required this.type, required this.position, required this.velocity, this.size = 40.0, }); // 获取对象的碰撞检测矩形 Rect get rect => Rect.fromCenter( center: position, width: size, height: size, ); }
2. 初始化游戏对象
在页面State中生成15个随机位置、随机移动方向的游戏对象:
late List<GameEntity> entities; late Size screenSize; @override void initState() { super.initState(); entities = List.generate(15, (index) { // 分配5个石头、5个布、5个剪刀 EntityType type; if (index < 5) { type = EntityType.rock; } else if (index < 10) { type = EntityType.paper; } else { type = EntityType.scissors; } // 生成屏幕内的随机初始位置 final posX = Random().nextDouble() * (screenSize.width - 40); final posY = Random().nextDouble() * (screenSize.height - 40); // 生成随机移动方向(固定速率) final speed = 2.0; final angle = Random().nextDouble() * 2 * pi; final velocity = Offset(cos(angle) * speed, sin(angle) * speed); return GameEntity( type: type, position: Offset(posX + 20, posY + 20), // 偏移确保对象完全在屏幕内 velocity: velocity, ); }); // 启动16ms间隔的游戏循环(约60帧) Timer.periodic(const Duration(milliseconds: 16), _updateGame); }
3. 游戏循环:位置更新与碰撞处理
实现_updateGame方法,处理对象移动、边界反弹和对象间碰撞逻辑:
void _updateGame(Timer timer) { // 更新所有对象位置 for (var entity in entities) { entity.position += entity.velocity; } // 处理屏幕边界反弹 _handleWallCollisions(); // 处理对象间碰撞 _handleEntityCollisions(); // 触发页面重绘 setState(() {}); } void _handleWallCollisions() { for (var entity in entities) { final rect = entity.rect; // 左右边界碰撞:反转X方向速度,修正位置避免卡墙 if (rect.left <= 0 || rect.right >= screenSize.width) { entity.velocity = Offset(-entity.velocity.dx, entity.velocity.dy); entity.position = Offset( rect.left <= 0 ? rect.width / 2 : screenSize.width - rect.width / 2, entity.position.dy, ); } // 上下边界碰撞:反转Y方向速度,修正位置避免卡墙 if (rect.top <= 0 || rect.bottom >= screenSize.height) { entity.velocity = Offset(entity.velocity.dx, -entity.velocity.dy); entity.position = Offset( entity.position.dx, rect.top <= 0 ? rect.height / 2 : screenSize.height - rect.height / 2, ); } } } void _handleEntityCollisions() { // 遍历所有对象对,避免重复检测 for (int i = 0; i < entities.length; i++) { for (int j = i + 1; j < entities.length; j++) { final a = entities[i]; final b = entities[j]; if (a.rect.overlaps(b.rect)) { if (a.type == b.type) { // 同类型碰撞:互相反弹(交换速度向量) final tempVelocity = a.velocity; a.velocity = b.velocity; b.velocity = tempVelocity; } else { // 不同类型碰撞:判断胜负 bool aWins = false; if ((a.type == EntityType.rock && b.type == EntityType.scissors) || (a.type == EntityType.scissors && b.type == EntityType.paper) || (a.type == EntityType.paper && b.type == EntityType.rock)) { aWins = true; } // 移除败方,胜方反向移动 if (aWins) { entities.removeAt(j); a.velocity = -a.velocity; } else { entities.removeAt(i); b.velocity = -b.velocity; } // 移除后跳出循环,避免索引错乱 return; } } } } }
4. 自定义绘制游戏对象
用CustomPainter绘制不同类型的游戏对象,区分视觉样式:
class GamePainter extends CustomPainter { final List<GameEntity> entities; GamePainter(this.entities); @override void paint(Canvas canvas, Size size) { final rockPaint = Paint()..color = Colors.grey.shade700; final paperPaint = Paint()..color = Colors.white..style = PaintingStyle.stroke..strokeWidth = 2; final scissorsPaint = Paint()..color = Colors.green.shade600; for (var entity in entities) { switch (entity.type) { case EntityType.rock: canvas.drawCircle(entity.position, entity.size / 2, rockPaint); break; case EntityType.paper: canvas.drawRect(entity.rect, paperPaint); break; case EntityType.scissors: // 绘制简易三角形代表剪刀 final path = Path() ..moveTo(entity.position.dx, entity.position.dy - entity.size / 2) ..lineTo(entity.position.dx + entity.size / 2, entity.position.dy + entity.size / 2) ..lineTo(entity.position.dx - entity.size / 2, entity.position.dy + entity.size / 2) ..close(); canvas.drawPath(path, scissorsPaint); break; } } } @override bool shouldRepaint(covariant GamePainter oldDelegate) { return oldDelegate.entities != entities; } }
5. 构建游戏页面
在build方法中获取屏幕尺寸并渲染游戏画布:
@override Widget build(BuildContext context) { screenSize = MediaQuery.of(context).size; return Scaffold( body: CustomPaint( painter: GamePainter(entities), size: screenSize, ), ); }
优化提示
- 若需要更精确的碰撞检测(比如圆形碰撞),可以改用圆心距离判断:
(a.position - b.position).distance <= a.size / 2 + b.size / 2 - 游戏循环也可以用
AnimationController配合addListener实现,兼容性更好 - 移除对象时倒序遍历列表,可以避免索引错乱问题
内容的提问来源于stack exchange,提问作者Golnar sheikh bahaie
相关产品推荐
相关产品推荐

