|
Component理解为Data其实本身没什么问题,但是”只是把Component里的逻辑移动到System里“这就是一个很大的误区。之所以Component不能带逻辑,首先ECS本身起源是一种Data Driven的概念,其次Component描述的是”你有的“,而不是”你就是“,这点逻辑概念决定了一个程序员是否合适使用ECS。OOP的思路是“我是什么”;ECS的思路是“我有什么”。
OOP:”我“是一个戴眼镜的人,所以我有一副眼镜(Property),我会擦眼镜(”我“的Function之一)。我.眼镜=true。我.擦眼镜()。
ECS:“我”有一副眼镜(Component)。所以我可以是一个戴眼镜的人(检查是否是戴眼镜的人的System),我也会擦眼镜(擦眼镜System)。因为这两个System都关心所有有眼镜的entity。
看起来貌似一样的结果,却有着截然不同的思路。如果顺着”只是把Component里的逻辑移动到System里“,那么当我还能脱眼镜,能戴眼镜的时候……分歧就会越来越大,直到变成完全不同的两个思路。
System相关Component其实更像是一个过滤器:”我“(这个System)只关心拥有那些“标志”(Component)的entity,但并不代表“我”不关心的那些Component在逻辑中就不能被使用。
ECS的System中也的确不该有状态,举一个例子:
我们假设有一个简单的打牌游戏,你当然可以想象成争上游那样,但可能会比争上游更加简单,当然这里的焦点不在打牌游戏的规则是什么,而是在这个状态机如何工作。
状态1:等待玩家出牌;
状态2:玩家出了牌的动画;
状态3:敌人出牌的动画;
状态4:一方胜利了。
通常这些状态的联系是:状态1->如果玩家获胜->状态4->否则->状态2->状态3->如果敌人获胜->状态4->否则->状态1。
这个时候我们最常见的思路就是,在Util中有个变量叫GameState,他有s1, s2, s3, s4这些状态,然后Switch GameState……
但是ECS的思路不是这样的。我们需要一些System来完成这些状态,而观察世界的视角也从状态变成了牌。这世界上的牌(entity)有:
1,已经打出的牌:牌面+位置+渲染,这些component。
2,我手里的牌:牌面+位置+渲染+我手里,这些component。
3,正在打出的牌:牌面+位置+渲染+移动,这些component。
4,敌人的牌:牌背+位置+渲染+敌人手里,这些component。
我们还需要一个component叫做“打出去”,它的功能室标示这个牌即将被打出去。
我们依然需要在util里面记录游戏的状态,但是不同的是,状态需要“玩家出牌”(Bool),“可否出牌”(Bool),“比赛结果”(enum,还未结束、玩家胜、敌人胜)3个数据。“比赛结果”如果不是“还未结束”,那么以下的System都应该在第一时间return了。“玩家出牌”和“可否出牌”则决定了InputSystem是否可以通过点击等一系列(包括UI在内,比如选择了好几张牌,然后点一下出牌按钮)的操作,给“我手里”的牌,添加“打出去”,最后把Util.玩家出牌变成false。
除了上述的InputSystem,我们还需要一些system来关心他们的运动,并且组织出状态1-3(状态4就不说了,因为对这个问题没有意义):
1,玩家出牌System:关心所有的带有“我手里”“打出去”的牌。把这些牌的“我手里”和“打出去”去掉,并且添加“移动”,这样他就变成了“正在打出的牌”。
2,牌移动System:工作非常简单,关心所有带有“位置”和“移动”的component,如果“位置”符合“移动”,那么丢掉“移动”这个component,让它变成一张“已经打出的牌”。Util.可否出牌=(这里捕获的entity个数<=0)。
3,敌人AISystem:如果Util.玩家出牌==true或者Util.可否出牌==false,就会直接return。否则捕获所有“敌人手里”的牌,这个System其实和之前的状态1-4都无关,只是敌人总得出牌啊。因此,敌人出牌的行为就是如果捕获的entity里没有一个拥有“打出去”Component,就根据【AI算法】(这里就省略了)把“敌人手里”的牌标记上“打出去”,并且把玩家出牌变成true。当然如果这个时候没有捕获到任何一个“敌人手里”的牌,那么说明敌人打完了,敌人获胜。
4,怎么飞System:这个System关心的是“打出去”和“位置”,给这些entity添加“移动”Component,不然牌移动System将永远在睡觉。
所以你可以看到,所谓的“状态”其实都是发生在Util下的,System本身没有状态,System可以看做是不同的看这个世界的视角——比如牌移动的System里看来,这个游戏就只有牌会飞行一个事情,并且在他眼里的牌,并不是我们肉眼中看到的这么多。诚然,写这样的逻辑的确远不如switch case轻松就对了,因为他对抽象能力要求非常非常之高,很多时候,你需要换一个思路来思考,用逆转裁判的话说就是——把你的思维逆转过来。
ECS的一个特性是——生来就是为了更方便修改的;而不是类似某些所谓“引擎”——生来就是为了搭个牢笼约束别人的(事实上我的buff机制和很多人设计的技能框架的本质区别也是如此)。所以你从一开始,就应该抱有一个开放的心态去思考,才能更好的理解ECS的本质——ECS并不是一个写法这么简单的问题。
|
|