Flutter动作识别插件leap的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter 动作识别插件 Leap 的使用

概述

Leap 是一个用于在 Flame 引擎上创建 2D 平台游戏的工具包。它利用了 Tiled 地图来创建关卡,并通过相应的 Flame 组件自动赋予行为和地形。

警告

请注意,此库仍在开发中,可能会频繁更改,每个版本都可能引入破坏性更改,直到发布 v1.0.0 版本(这可能永远不会发生,因为这是一个个人项目)。

特点

使用 Tiled 创建关卡

Leap 使用 Tiled 瓷砖地图不仅用于视觉渲染,还用于通过创建对应的 Flame 组件来赋予关卡行为和地形。

物理引擎

Leap 的物理引擎基于以下指南: 实现 2D 平台游戏的指南

Leap 使用一种更高效且专门针对基于瓷砖的平台游戏的碰撞检测系统,其中每个碰撞盒都是轴对齐的边界框。

支持的功能

  • 地形
  • 单向平台
  • 斜坡
  • 移动平台
  • 梯子

将来的功能

  • 地面瓷砖的摩擦控制(如冰)
  • 交互式地面瓷砖
  • 可移除的地面瓷砖
  • 单向墙壁

简单的物理设计适合 2D 平台游戏

Leap 中的物理实体具有 velocity 属性,用于存储当前的 xy 速度,并将自动更新实体的位置。当移动的实体与关卡地形碰撞时,其速度将自动设置为 0,位置也将更新以防止重叠。此外,还有一个全局的 gravity 率应用于 y 速度的每次游戏循环。静态实体不会被速度或重力移动。

开始使用

在使用 Leap 之前,你应该熟悉以下 Flame 组件:

  • FlameGame
  • CameraComponent
  • PositionComponent
  • TiledComponent

使用方法

LeapGame

要使用 Leap,你的游戏实例必须扩展 LeapGame(该类继承自 FlameGame)。建议使用 game.loadWorldAndMap 来初始化游戏的 worldmap

LeapWorld

可通过 LeapGame.world 访问,该组件管理物理引擎所需的任何全局逻辑。

LeapMap

可通过 LeapGame.map 访问,该组件管理 Tiled 地图并自动构建带有适当碰撞检测的瓷砖。参见 [Tiled 地图集成](#Tiled map integration) 部分。

游戏代码片段

完整的游戏代码可查看 标准平台游戏示例

void main() {
  runApp(GameWidget(game: MyLeapGame()));
}

class MyLeapGame extends LeapGame with HasTappables, HasKeyboardHandlerComponents {
  late final Player player;

  [@override](/user/override)
  Future<void> onLoad() async {
    await super.onLoad();

    // "map.tmx" 应该是一个符合 Leap 要求的 Tiled 地图
    await loadWorldAndMap('map.tmx', 16);
    setFixedViewportInTiles(32, 16);

    player = Player();
    add(player);
    camera.followComponent(player);
  }
}

物理实体的物理系统

Leap 的物理系统要求每个与游戏物理世界交互的 Component 都必须扩展 PhysicalEntity 并添加到 LeapWorld 组件中,该组件可通过 LeapGame.world 访问。

角色

Character 是一个 PhysicalEntity,适用于玩家、敌人等角色。它有 health 概念和一个 onDeath 函数,可以在健康值达到零(或负数)时覆盖。还可以设置 removeOnDeath = true 以在角色死亡时自动从游戏中删除。

实体动画和行为

实体通常用 SpriteAnimation 渲染。通常需要不同的状态有不同的动画。这就是 AnchoredAnimationGroup 的作用。

例如:

class Player extends JumperCharacter with HasAnimationGroup {
    Player() {
        // 行为在物理计算之前运行
        ...
        // 物理
        add(GravityAccelerationBehavior());
        add(CollisionDetectionBehavior());
        // 行为在物理计算之后运行
        ...
        add(ApplyVelocityBehavior());
        // 渲染相关的操作
        add(AnimationVelocityFlipBehavior());

        animationGroup = PlayerAnimation();
    }
}

enum _AnimationState { walk, jump }

class PlayerAnimation extends AnchoredAnimationGroup<_AnimationState, Player> {
    [@override](/user/override)
    Future<void> onLoad() async {
        animations = {
            _AnimationState.walk: SpriteAnimation(...),
            _AnimationState.jump: SpriteAnimation(...),
        };
        return super.onLoad();
    }

    [@override](/user/override)
    void update(double dt) {
       if (character.isWalking) {
           current = _AnimationState.walking;
       } else if (character.isJumping) {
           current = _AnimationState.jumping;
       }
       super.update(dt);
    }
}

AnchoredAnimationGroup 自动处理将动画定位在其父组件碰撞盒的中心。可以通过 hitboxAnchor 属性改变定位。

AnchoredAnimationGroup 必须通过 HasAnimationGroup 混入在 PhysicalEntity 组件中,并通常设置为 animationGroup 属性。

死亡动画

推荐的方法是添加 RemoveOnDeathBehaviorRemoveAfterDeathAnimationBehavior。如果依赖于死亡动画的完成,还需要:

  1. 在角色上设置 AnchoredAnimationGroup,确保将其 current 动画设置为所需的死亡动画。
  2. 确保死亡动画的 loop = false 以便不会永远播放。
  3. 确保其他游戏组件不会像它仍然活着一样与之互动。
状态效果系统

PhysicalEntity 组件可以有状态(EntityStatus),这些状态会修改其行为。状态影响它们所附加的组件。例如,可以实现一个 StarPowerStatus,当添加到玩家组件时,使其闪烁颜色并变得无敌。

重置地图上的玩家死亡

通常希望在玩家死亡时重置地图。推荐的做法是在 Game 类中重新加载地图并添加:

class MyGame {
  ...

  Future<void> reloadLevel() async {
    await loadWorldAndMap(
      tiledMapPath: 'map.tmx',
      tiledObjectHandlers: tiledObjectHandlers,
    );

    // 不让摄像机移动到地图之外,将视口大小的一半作为边缘的内边距
    final inset = camera.viewport.virtualSize;
    camera.setBounds(
      Rectangle.fromLTWH(
        inset.x / 2,
        inset.y / 2,
        leapMap.width - inset.x,
        leapMap.height - inset.y,
      ),
    );
  }

  [@override](/user/override)
  void onMapUnload(LeapMap map) {
    player?.removeFromParent();
  }

  [@override](/user/override)
  void onMapLoaded(LeapMap map) {
    if (player != null) {
      world.add(player!);
      player!.resetPosition();
    }
  }
}

确保在 Player 中调用 game.reloadLevel() 当玩家死亡时。

Tiled 地图集成

Leap 会自动解析特定 Tiled 图层中的特定功能。

地形层

必须是名为 “Ground” 的瓦片层,默认情况下,放置在此层的所有瓦片都被视为游戏中的地形。这意味着这些瓦片将静止不动,并具有与瓦片宽度和高度相匹配的碰撞盒。

特殊地形
  • 斜坡:允许物理实体上下坡。这些瓦片必须有两个自定义的 int 属性 LeftTopRightTop。例如,一个 16x16 像素的瓦片,LeftTop = 0RightTop = 8 表示从左到右上升的斜坡。

  • 单向平台:允许物理实体从所有方向通过,除了一个方向。这些是通过 GroundTileHandler 类实现的,因此可以使用任何你想要的类。

自定义地形处理

要完全控制地形层中的个别瓦片,可以使用 Tiled 编辑器中的 class 属性来钩入 groundTileHandlers

例如:

class MyCustomTileHandler implements GroundTileHandler {
  [@override](/user/override)
  LeapMapGroundTile handleGroundTile(LeapMapGroundTile groundTile, LeapMap map) {
    tile.tags.add('PowerUpTile');

    // 在特殊瓦片上添加一些额外的渲染
    map.add(PowerUpTileAnimationComponent(x: groundTile.x, y: groundTile.y));

    // 使用提供的瓦片实例
    return tile;
  }
}

元数据层

必须是名为 “Metadata” 的对象组,用于放置任何你想要的游戏对象,如关卡开始/结束点、敌人的生成点等。

对象组图层

任何对象组图层(包括元数据层)都可以包含任意对象。如果你希望自动创建某些对象的 Flame 组件,可以通过 Tiled 中的 Class 字符串来实现。

例如:

class CoinFactory implements TiledObjectFactory<Coin> {
  late final SpriteAnimation spriteAnimation;

  CoinFactory(this.spriteAnimation);

  [@override](/user/override)
  void handleObject(TiledObject object, Layer layer, LeapMap map) {
    final coin = Coin(object, spriteAnimation);
    map.add(coin);
  }

  static Future<CoinFactory> createFactory() async {
    final tileset = await Flame.images.load('my_animated_coin.png');
    final spriteAnimation = SpriteAnimation.fromFrameData(
      tileset,
      SpriteAnimationData.sequenced(...),
    );
    return CoinFactory(spriteAnimation);
  }
}

class Coin extends PhysicalEntity {
  Coin(TiledObject object, this.animation)
      : super(static: true) {
    anchor = Anchor.center;

    // 使用 Tiled 地图中的位置
    position = Vector2(object.x, object.y);

    // 使用 Tiled 对象中的自定义属性
    value = tiledObject.properties.getValue<int>('CoinValue');
  }

  ...
}

其他图层

其他图层将仅用于视觉渲染,但不会对游戏产生影响。你可以通过 LeapGame.map.tiledMap 访问这些图层,并根据需要添加自己的特殊行为。

移动平台

要创建移动平台,你需要实现一个继承自 MovingPlatform 的组件,并提供一个 Sprite(或其他渲染方式)。

例如:

class MovingPlatform extends SpriteComponent with TiledObjectLoader {
  [@override](/user/override)
  void onLoad() {
    // 读取对象的自定义属性
    moveSpeedX = double.parse(tiledObject.properties['MoveSpeedX']);
    moveSpeedY = double.parse(tiledObject.properties['MoveSpeedY']);
    loopMode = tiledObject.properties['LoopMode'];
    tilePath = tiledObject.properties['TilePath'].split(';').map((path) => path.split(',').map(int.parse).toList()).toList();
  }

  [@override](/user/override)
  void update(double dt) {
    // 更新平台的位置
    ...
  }
}

梯子

要创建梯子,你需要实现一个继承自 Ladder 的组件,并提供一个 Sprite(或其他渲染方式)。

例如:

class Player extends PhysicalEntity {
    void update(double dt) {
        // 这些布尔变量是虚构的,根据你自己的系统实现有意义的内容
        if (isNearLadder && actionButton.isPressed) {
            add(OnLadderStatus(ladder));
        } else if (hasStatus<OnLadderStatus>() && jumpButton.isPressed) {
            remove(getStatus<OnLadderStatus>());
        }
    }
}

自定义图层名称和类

虽然上面的结构应该始终遵循,但开发者可以让 Leap 使用不同的类、类型和名称。

例如:

class MyLeapGame extends LeapGame {
  MyLeapGame() : super(
    configuration: LeapConfiguration(
      tiled: const TiledOptions(
        groundLayerName: 'Ground',
        metadataLayerName: 'Metadata',
        playerSpawnClass: 'PlayerSpawn',
        damageProperty: 'Damage',
        platformClass: 'Platform',
        slopeType: 'Slope',
        slopeRightTopProperty: 'RightTop',
        slopeLeftTopProperty: 'LeftTop',
      ),
    ),
  );
}

调试

慢动作

LeapWorld 包含 HasTimeScale 混入,因此你可以设置 world.timeScale = 0.5 使整个游戏减慢到 50% 的速度,便于测试细微的错误。你也可以将此用于游戏慢动作。

渲染碰撞盒

PhysicalEntity 包含 debugHitbox 属性,你可以设置它以自动绘制一个框,指示碰撞检测系统正在使用的精确碰撞盒。

class MyPlayer extends PhysicalEntity {
  [@override](/user/override)
  void update(double dt) {
    // 绘制实体的碰撞盒
    debugHitbox = true;
  }
}
渲染碰撞

PhysicalEntity 包含 debugCollisions 属性,你可以设置它以自动绘制一个框,指示当前与其他实体碰撞的碰撞盒。

class MyPlayer extends PhysicalEntity {
  [@override](/user/override)
  void update(double dt) {
    // 绘制实体的碰撞
    debugCollisions = true;
  }
}

更多关于Flutter动作识别插件leap的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter动作识别插件leap的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用Leap Motion插件的一个基本示例。Leap Motion是一种用于手势识别的设备,通过它你可以捕捉手部动作并将其集成到你的Flutter应用中。

首先,确保你已经安装了Leap Motion设备和Leap Motion SDK。然后,你可以使用leap_flutter插件(假设有这样一个插件,实际上Flutter社区插件可能有所不同,但原理相似,以下代码将基于假设的插件结构进行说明)。

1. 添加依赖

在你的pubspec.yaml文件中添加Leap Motion插件的依赖(注意:以下插件名称是假设的,实际使用时请查找并使用官方或社区提供的正确插件名称):

dependencies:
  flutter:
    sdk: flutter
  leap_flutter: ^x.y.z  # 替换为实际版本号

2. 导入插件

在你的Dart文件中导入Leap Motion插件:

import 'package:leap_flutter/leap_flutter.dart';

3. 初始化Leap Motion

在你的Flutter应用的入口文件(通常是main.dart)中初始化Leap Motion连接:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  LeapController.instance.initialize(); // 假设LeapController是插件提供的类
  runApp(MyApp());
}

4. 创建Leap Motion监听器

创建一个类来处理Leap Motion数据:

class LeapMotionListener {
  LeapMotionListener() {
    LeapController.instance.addListener(_onFrame);
  }

  void _onFrame(LeapFrame frame) {
    // 处理Leap Frame数据
    print('Number of hands: ${frame.hands.length}');
    for (var hand in frame.hands) {
      print('Hand ID: ${hand.id}');
      // 可以访问更多手部和手指数据
    }
  }
}

5. 在你的应用中使用Leap Motion数据

在你的主应用组件中使用Leap Motion数据。例如,你可以在一个StatefulWidget中更新UI来显示手部数据:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String leapData = 'No Leap Data';

  @override
  void initState() {
    super.initState();
    LeapMotionListener(); // 初始化监听器
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Leap Motion Flutter Demo'),
        ),
        body: Center(
          child: Text(leapData),
        ),
      ),
    );
  }
}

注意:上述代码中的LeapController, LeapFrame, 和相关方法(如initialize, addListener等)都是假设的API。实际使用时,你需要查阅Leap Motion Flutter插件的文档来了解正确的API调用方式和数据结构。

6. 运行应用

确保Leap Motion设备已连接并运行,然后运行你的Flutter应用。你应该能够在控制台中看到Leap Motion捕获的手部数据,并在UI中显示(如果你实现了相应的UI更新逻辑)。

由于Leap Motion Flutter插件的具体实现可能会有所不同,以上代码仅作为示例,实际使用时请查阅相关插件的官方文档。如果Leap Motion没有官方的Flutter插件,你可能需要通过平台通道(Platform Channels)与原生Leap Motion SDK进行交互。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!