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),
],
),
);
}
}
这将创建一个自上而下的技能树。Edge
和 Node
都不是小部件,而是简单的密封类。数据字段可以在应用程序中存储特定信息。不作任何关于解锁性、路径或限制的假设,以便完全可定制。
用户可以完全访问他们在 nodeBuilder
和 edgeBuilder
中定义的数据。这个构建器的唯一工作就是返回一个具体的 SkillNode
或 SkillEdge
小部件实例。如果你更喜欢直接在图中定义它们,则可以自由这样做。注意在这种情况下你必须提供更多字段。
LayeredGraph(
// ...
edges: [
SkillEdge(from: '7', to: '9', data: null, /*...*/),
],
nodes: [
SkillNode(id: '7', data: null, /*...*/),
Node(id: '9', data: null), // 你可以混合使用类型
// ...
],
// ...
),
类型严格性
我们传递 data
为 null
的原因之一是因为默认情况下,图是严格类型的。上面的类型是 <EdgeType, NodeType, IdType>
。EdgeType
对应于边上的数据。我们没有关心它,所以它隐式地被类型化为 void
。NodeType
是节点上的数据,这一点同样适用。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<int>(
0,
(acc, layer) {
acc += layer.fold<int>(0, (acc, id) {
if (id != null) {
final node = graph.getNodeFromIdType(id);
acc += node.data.value;
}
return acc;
});
return acc;
},
);
/// 用户能够达到此节点如果他们有足够的分数。
final isReachable = previousNodesAreMaxed &&
pointsToUnlock <= 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<Rect> allNodeRects,
required List<Offset> controlPointCenters,
});
更多关于Flutter技能树管理插件skill_tree的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于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),
),
);
}
}