Flutter技能树管理插件skill_tree的使用

Flutter技能树管理插件skill_tree的使用

技能树

       

一个用于构建技能树的包。与graphview不同的是,它只为用户提供了一个接口来创建技能树,而不是通用的查看器。

简单使用

class Home extends StatelessWidget {
  const Home({ Key? key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SkillTree<void, void, String>.layered(
      delegate: LayeredTreeDelegate(
        mainAxisSpacing: 32.0,
        crossAxisSpacing: 48.0,
      ),
      graph: LayeredGraph(
        layout: [
          ['0', '1', '2', null],
          ['3', '4', '5', null],
          ['6', '7', '8', null],
          [null, '9', '10', null],
          ['11', '12', null, '13'],
          [null, null, '14', null],
          [null, '15', '16', null],
        ],
        edges: [
          Edge(from: '7', to: '9', data: null),
          Edge(from: '10', to: '14', data: null),
          Edge(from: '12', to: '15', data: null),
        ],
        nodes: [
          Node(id: '0', data: null),
          Node(id: '1', data: null),
          Node(id: '2', data: null),
          Node(id: '3', data: null),
          Node(id: '4', data: null),
          Node(id: '5', data: null),
          Node(id: '6', data: null),
          Node(id: '7', data: null),
          Node(id: '8', data: null),
          Node(id: '9', data: null),
          Node(id: '10', data: null),
          Node(id: '11', data: null),
          Node(id: '12', data: null),
          Node(id: '13', data: null),
          Node(id: '14', data: null),
          Node(id: '15', data: null),
          Node(id: '16', data: null),
        ],
      ),
    );
  }
}

这将创建一个自上而下的技能树。EdgeNode 都不是小部件,而是简单的密封类。数据字段可以在应用程序中存储特定信息。不作任何关于解锁性、路径或限制的假设,以便完全可定制。

用户可以完全访问他们在 nodeBuilderedgeBuilder 中定义的数据。这个构建器的唯一工作就是返回一个具体的 SkillNodeSkillEdge 小部件实例。如果你更喜欢直接在图中定义它们,则可以自由这样做。注意在这种情况下你必须提供更多字段。

LayeredGraph(
  // ...
  edges: [
    SkillEdge(from: '7', to: '9', data: null, /*...*/),
  ],
  nodes: [
    SkillNode(id: '7', data: null, /*...*/),
    Node(id: '9', data: null), // 你可以混合使用类型
    // ...
  ],
  // ...
),

类型严格性

我们传递 datanull 的原因之一是因为默认情况下,图是严格类型的。上面的类型是 <EdgeType, NodeType, IdType>EdgeType 对应于边上的数据。我们没有关心它,所以它隐式地被类型化为 voidNodeType 是节点上的数据,这一点同样适用。IdType 指的是用于匹配边的末端到节点的类型。上面,类型被类型化为 String。尝试在边或节点中使用 int 将会出错。

完整功能的技能树

要查看类似于《魔兽世界》或《边境之地》中的完整功能技能图,请参阅示例。以下是对各个任务的粗略信息:

解锁性

要定义一个可解锁的节点,我们需要在节点上存储信息。即它的当前级别和最大级别:

class MyNodeData {
  const NodeInfo({
    required this.value,
    required this.maxValue,
  });

  bool get isMaxedOut => value == maxValue;

  final int value;

  final int maxValue;
}

有了这些信息,在 nodeBuilder 中我们可以决定该节点是否被锁定。

nodeBuilder: (node, graph) {
  final canBeUnlocked = node.isMaxedOut;

  return SkillNode.fromNode(
    node: node,
    child: Item(
      canBeUnlocked: canBeUnlocked,
      node: node,
    ),
  );
},

当然,这并不涵盖连接到此节点的节点尚未解锁的情况——在这种情况下,我们需要首先检查该节点是否已解锁,依此类推。为此,我们需要查询所有具有当前节点的 to 的边并检索 from —— 如果新的节点满足相同的标准,则连续执行。

为此,graph 上定义了辅助函数。

可达节点

可达性由图类型定义。分层图有一个 pointsPerLayer 系统的概念。即用户必须获得一定数量的分数才能进入树的下一层。可达性逻辑由以下代码处理。这只是其中一个用例,您可以根据您的应用自由调整逻辑:

nodeBuilder: (node, graph) {
  final ancestorLayers = graph.ancestorLayersForNode(node);
  final layerOfNode = graph.layerForNode(node);
  final pointsToUnlock = pointsPerLayer * layerOfNode;
  final pointsInAncestorLayers = ancestorLayers.fold&lt;int&gt;(
    0,
    (acc, layer) {
      acc += layer.fold&lt;int&gt;(0, (acc, id) {
        if (id != null) {
          final node = graph.getNodeFromIdType(id);
          acc += node.data.value;
        }

        return acc;
      });
      return acc;
    },
  );

  /// 用户能够达到此节点如果他们有足够的分数。
  final isReachable = previousNodesAreMaxed &amp;&amp;
      pointsToUnlock &lt;= pointsInAncestorLayers;

  return SkillNode.fromNode(
    node: node,
    child: Item(
      isReachable: isReachable,
      node: node,
    ),
  );
},

额外数据

您可以在节点或边的数据中存储任何东西。例如,施放技能所需的魔法值(MP)。

nodeBuilder: (node) {
  return SkillNode.fromNode(
    node: node,
    child: Column(
      children: [
        Text(node.name),
        Text('MP cost: ${node.data.cost}'),
      ],
    ),
  );
},

边绘制和路由

EdgePathPainter

如果没有提供画家,将使用默认画家,只是简单的 canvas.drawPath。目前,边缘画家的签名是:

typedef EdgePathPainter = void Function({
  required Path path,
  required Canvas canvas,
});

其中提供的路径是由 EdgePathBuilder 构造的。

EdgePathBuilder

通过向 SkillEdge 提供 edgePathBuilder,甚至可以绘制自定义边。绘制是一个复杂的领域,但您可以自由使用默认的 edgePainters 或提供自己的。

当前的签名如下所示:

typedef EdgePathBuilder = Path Function({
  required Offset toNodeCenter,
  required Offset fromNodeCenter,
  required List&lt;Rect&gt; allNodeRects,
  required List&lt;Offset&gt; controlPointCenters,
});

更多关于Flutter技能树管理插件skill_tree的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter技能树管理插件skill_tree的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


skill_tree 是一个用于管理技能树的 Flutter 插件,它可以帮助开发者以树状结构展示和管理技能、知识点或任何层次化的数据。虽然截至我所知的信息(2023年10月),skill_tree 并不是一个广泛使用的官方插件,但假设它是一个社区开发的插件,以下是一个基本的使用指南。


1. 添加依赖

首先,在 pubspec.yaml 文件中添加 skill_tree 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  skill_tree: ^1.0.0 # 请根据实际版本号填写

然后运行 flutter pub get 以安装依赖。


2. 导入插件

在你的 Dart 文件中导入 skill_tree

import 'package:skill_tree/skill_tree.dart';

3. 创建技能树

假设 skill_tree 插件提供了一个 SkillTree 组件,你可以通过以下方式创建和展示技能树:

class MySkillTree extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('技能树示例'),
      ),
      body: SkillTree(
        rootNode: SkillTreeNode(
          title: 'Flutter开发',
          children: [
            SkillTreeNode(
              title: '基础组件',
              children: [
                SkillTreeNode(title: 'Text'),
                SkillTreeNode(title: 'Button'),
                SkillTreeNode(title: 'Image'),
              ],
            ),
            SkillTreeNode(
              title: '布局组件',
              children: [
                SkillTreeNode(title: 'Row'),
                SkillTreeNode(title: 'Column'),
                SkillTreeNode(title: 'Stack'),
              ],
            ),
            SkillTreeNode(
              title: '状态管理',
              children: [
                SkillTreeNode(title: 'Provider'),
                SkillTreeNode(title: 'Riverpod'),
                SkillTreeNode(title: 'Bloc'),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

4. 自定义样式和交互

如果 skill_tree 插件支持自定义样式和交互,你可以通过以下方式进行调整:

SkillTree(
  rootNode: rootNode, // 根节点
  nodeStyle: SkillTreeNodeStyle(
    titleTextStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
    icon: Icon(Icons.star),
    backgroundColor: Colors.blue[100],
  ),
  onNodeTap: (node) {
    print('点击了节点: ${node.title}');
  },
)

5. 动态更新技能树

如果需要动态更新技能树,可以将 SkillTree 包装在 StatefulWidget 中,并在状态更新时重新构建:

class DynamicSkillTree extends StatefulWidget {
  [@override](/user/override)
  _DynamicSkillTreeState createState() => _DynamicSkillTreeState();
}

class _DynamicSkillTreeState extends State<DynamicSkillTree> {
  SkillTreeNode rootNode = SkillTreeNode(
    title: 'Flutter开发',
    children: [
      SkillTreeNode(title: '基础组件'),
      SkillTreeNode(title: '布局组件'),
    ],
  );

  void addNode() {
    setState(() {
      rootNode.children.add(SkillTreeNode(title: '新增节点'));
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('动态技能树'),
      ),
      body: SkillTree(rootNode: rootNode),
      floatingActionButton: FloatingActionButton(
        onPressed: addNode,
        child: Icon(Icons.add),
      ),
    );
  }
}
回到顶部