You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 02:35:55