找回密码
 点一下
查看: 1926|回复: 9

在.NET框架下初步模拟星际2的Abil-Behavior-Effect框架

[复制链接]
发表于 2018-1-6 08:35:22 | 显示全部楼层 |阅读模式
本帖最后由 zt0616 于 2018-1-16 10:36 编辑

本文适合人群:.NET入门者,
需要:有银河编辑器基础,有C#语言基础(代码主要以C#示例)。
纯粹编辑器爱好者,可以直接跳至第二部分开始,或许能在那里获得启发。启发。

第一部分 模拟武器的目标筛选器
第二部分 模拟效果树

第一部分 模拟武器的目标筛选器
1. 设置单位条目
我们从实现一个单位条目开始。每一单位条目都是CUnit类的一个实例。但在申明CUnit之前,我们需要先声明一个抽象类CatalogScope,并让CUnit类继承自CatalogScope。
  1. using System.Collections.Generic;

  2. public abstract class CatalogScope { }

  3. public class CUnit : CatalogScope { }
复制代码

CatalogScope(数据模板域)将作为所有类的基类,这些继承自CatalogScope的“子代”可以是条目类,如CUnit;可以是抽象类,如CAbil;也可以是SC2里的结构,如SCardLayout(命令面板)。类的名称以C开头,结构以S开头。
CatalogScope的存在有一定好处,后面可以用作泛型条件约束。

“抽象类”这个名词将在后文大量出现。抽象类是什么?简单的来说,抽象类是一个不完整的概念,比如“现充”这个概念就是一个抽象类,我们似乎觉得身边就有现充,但非要抓一个出来是不可能的,因为谁都无法准确定义现充。所以抽象类没有实际的例子,即无法实例化。
但继承于抽象类的类可以实例化,比如我们定义一个“现充”的子类叫“二十岁就结婚的人”,那就可以从身边的人里抓出来了。。

2. 设置武器条目
单位要攻击,需要一把武器,武器要能筛选单位是空军还是地面,单位也需有平面字段以供武器筛选。
  1. public class CUnit : CatalogScope
  2. {
  3.     private List<Plane> planeArray;
  4.     public List<Plane> PlaneArray { get { return planeArray; } }

  5.     private List<CWeapon> weaponArray;
  6.     public List<CWeapon> WeaponArray { get { return weaponArray; } }
  7. }

  8. public enum Plane { Ground, Air };


  9. public class CWeapon : CatalogScope
  10. {
  11.     private CTargetFilters targetFilters;
  12.     public CTargetFilters TargetFilters => targetFilters;
  13. }
复制代码

因为数据模板条目在运行时不会轻易更改,因此这里将字段的访问权限设为私有,其属性具有公共可读权限。使用get return还是Lambda表达式取决于我们的习惯和语言版本。
这里或许有疑问,星际原本使用CFlagArray类描述平面数组(平面阵列)、单位属性数组、标旗数组,这里为什么使用List<枚举>?考虑到代码和文章的简洁性我们就不声明这个类了,后面在细节方面也不会完全遵循星际2的格式。

下面来完成CTargetFilters(目标筛选器)类。这是个厉害的类,无论数据编辑器还是触发器都大量用到,可见其重要性。目标筛选器在星际2里为每个字段提供了“允许、需要、不包括”三个选项,就像这样:
  1. enum State { Allowed, Required, Excluded };

  2. class CTargetFilters {
  3.     Self: State;
  4.     Massive: State;
  5.     Visible: State;
  6.     constructor(Self: State, Massive: State, Visible: State) {
  7.         this.Self = Self;
  8.         this.Massive = Massive;
  9.         this.Visible = Visible;
  10.     }
  11. }
复制代码

  1. var f1 = new CTargetFilters(State.Allowed, State.Allowed, State.Required);
复制代码

“需要:可见的;允许:自身、大型”。

或者这样:
  1. var f2 = new CTargetFilters(State.Excluded, State.Allowed, State.Allowed);
复制代码

“允许:大型、可见的;不包括:自身”。

这里我们用可扩展的方式实现目标筛选器。首先申明类CTargetFilters,字典值为bool类型,为true时代表“需要”,为false时“不包括”,原本“允许的”这个选项本来也可有可无。以后的单位属性筛选、标旗筛选也会添加进这个类。
  1. public class CTargetFilters
  2. {
  3.     private Dictionary<Plane, bool> planeArray;
  4.     public Dictionary<Plane, bool> PlaneArray => planeArray;
  5. }
复制代码


到这里类已经定义好了。以自己的方式实例化这些类,将条目序列化成XML、Json等文本格式,或者游戏引擎所提供持久化数据格式进行保存。下面我们来定义一个单位类。因为单位、效果、动作者这些都是可以通过触发器创建的类,在此之前得定义一个名为Creatable的抽象类,具有entry字段,并提前申明OnCreated()抽象方法,让子类来实现,回应创建这个动作。
  1. public abstract class Creatable
  2. {
  3.     protected CatalogScope entry;
  4.     public CatalogScope Entry { get { return entry; } }

  5.     public abstract void OnCreated();
  6. }
复制代码


相同类型的所有单位实例引用同一个CUnit条目。这里用了显式类型转换,因为我们不知道“手动”引用条目的什么类型。所以引用的时候要特别注意。实现的OnCreated()暂时先放着。
  1. public class Unit : Creatable
  2. {
  3.     public new CUnit Entry => (CUnit)entry;

  4.     public override void OnCreated()
  5.     {
  6.         throw new NotImplementedException();
  7.     }
  8. }
复制代码


3.模拟武器的目标筛选器
下面我们来模拟一下武器筛选过程。假设我们已经取得了若干个目标单位,我们要根据武器来筛选掉这些不能攻击的目标。声明一个静态类UnitExtension和FilterTargets方法,并取得单位实例的第一把武器。
  1. using System.Linq;

  2. public static class UnitExtension
  3. {
  4.     public static void FilterTargets(this Unit unit, ref List<Unit> targets)
  5.     {
  6.         CWeapon weapon = unit.Entry.WeaponArray[0];
  7.     }
  8. }
复制代码


然后倒序遍历参数里引用进来的目标单位List,如果其中一个目标不满足筛选条件,就移除掉。
  1. public static class UnitExtension
  2. {
  3.     public static void FilterTargets(this Unit unit, ref List<Unit> targets)
  4.     {
  5.         CWeapon weapon = unit.Entry.WeaponArray[0];

  6.         for (int i = targets.Count - 1; i >= 0; i--)
  7.         {
  8.             Unit target = targets[i];
  9.             
  10.             foreach (var filter in weapon.TargetFilters.PlaneArray)
  11.             {
  12.                 if (filter.Value != target.Entry.PlaneArray.Contains(filter.Key))
  13.                 {
  14.                     targets.Remove(target);
  15.                     break;
  16.                 }
  17.             }
  18.         }
  19.     }
  20. }
复制代码


同理,我们再给CUnit添加单位标旗字段,并让其可以被目标筛选器去匹配。
  1. public class CUnit : CatalogScope
  2. {
  3.     private List<UnitFlag> flagArray;
  4.     public List<UnitFlag> FlagArray { get { return flagArray; } }

  5.     private List<Plane> planeArray;
  6.     public List<Plane> PlaneArray { get { return planeArray; } }

  7.     private List<CWeapon> weaponArray;
  8.     public List<CWeapon> WeaponArray { get { return weaponArray; } }
  9. }

  10. public enum UnitFlag { Worker, Missile };
复制代码

  1. public class CTargetFilters
  2. {
  3.     private Dictionary<UnitFlag, bool> flagArray;
  4.     public Dictionary<UnitFlag, bool> FlagArray => flagArray;

  5.     private Dictionary<Plane, bool> planeArray;
  6.     public Dictionary<Plane, bool> PlaneArray => planeArray;
  7. }
复制代码


现在可以将CUnit条目的单位标旗设置成工人、发射物或者无,保存。相应地,FilterTargets()方法目前还只能检测空军或者地面,因此也需要做一些改变。由于检测“是不是发射物”优先级比“是地面还是空军”高,我们把检测单位标旗放在前面。
  1. public static class UnitExtension
  2. {
  3.     public static void FilterTargets(this Unit unit, ref List<Unit> targets)
  4.     {
  5.         CWeapon weapon = unit.Entry.WeaponArray[0];

  6.         for (int i = targets.Count - 1; i >= 0; i--)
  7.         {
  8.             Unit target = targets[i];
  9.             
  10.             bool removed = false;
  11.             foreach (var filter in weapon.TargetFilters.FlagArray)
  12.             {
  13.                 if (filter.Value != target.Entry.FlagArray.Contains(filter.Key))
  14.                 {
  15.                     targets.Remove(target);
  16.                     removed = true;
  17.                     break;
  18.                 }
  19.             }
  20.             if (removed) continue;
  21.             foreach (var filter in weapon.TargetFilters.PlaneArray)
  22.             {
  23.                 if (filter.Value != target.Entry.PlaneArray.Contains(filter.Key))
  24.                 {
  25.                     targets.Remove(target);
  26.                     break;
  27.                 }
  28.             }
  29.         }
  30.     }
  31. }
复制代码


至此,可扩展的目标筛选器定制完毕~
发表于 2018-1-6 11:06:50 | 显示全部楼层
66666,大佬求QQ~
回复

使用道具 举报

发表于 2018-1-6 11:21:16 | 显示全部楼层
也就是说:我们可以自己给单位定义筛选值,然后在触发器的单位组中像目标筛选器那样筛选出来?
回复

使用道具 举报

 楼主| 发表于 2018-1-6 11:44:21 来自手机 | 显示全部楼层
星际2目标筛选器的实现应该被写死了,无法自定义逻辑,头目的war 3mod也用的“映射”(对应)的方法来自定义。帖子中的那些是用在其他游戏引擎里的,只是借用了SC 2优雅、先进的思想

点评

原来是这样啊。  发表于 2018-1-6 19:36
回复

使用道具 举报

 楼主| 发表于 2018-1-7 12:40:56 | 显示全部楼层
本帖最后由 zt0616 于 2018-1-16 10:38 编辑

第二部分 模拟效果树
1. 观察效果历史
下面来实现一下效果树。在开始之前,我们需要先找个参照。

星际2里有EffectHistory(效果历史),创建后的效果实例会被注册进施法者,可以通过索引查询各种相关信息,返回条目数也可以自定义。
索引从1开始,索引越小时间上越近。
若要给一个施法者实例注册效果,得先增加该单位条目的字段EffectHistoryLimit(效果历史限制)每个分类的值。要被注册的效果条目下CasterHistory(施法者历史)字段也需要做相应设置。
这里有演示

效果历史条目信息可以通过触发器查询。下面是触发器中有关效果历史的(包括虚空之遗新增的),我们先来观察一下。

首先是枚举类型:
  1. enum e_effectHistory { Damage, Death, Modifier, Healing };
  2. enum e_effectUnit { Caster, Origin, Outer, Source, Target };
  3. enum e_effectHistoryAmount { Absorbed, Damaged, Dodged, EnergyChanged, EnergyLeeched, Found, Healed, Killed, LifeChanged, LifeLeeched, ShieldsChanged, ShieldsLeeched, Splashed };
  4. enum e_effectHistoryEffect { Current, Root };
复制代码


下面是所有相关的方法(函数):
  1. interface Trigger {
  2.     /**
  3.      * Returns an Effect History object that can be used to inspect the history of effects for a unit.
  4.      * 返回一个可以用来检索一个单位的效果历史的的效果历史对象。
  5.      * In order for a unit to register effects to its history, the unit must specify an Effect History Limit value,
  6.      * 一个单位若想要在它的历史中注册效果,就必须指定一个“效果历史限制”值,
  7.      * and any effects you want to track must specify a Caster History value.
  8.      * 此外,所有想要追踪的效果都必须指定一个“施法者历史”值。
  9.      * The Caster History value specifies the category under which you want to track the effect.
  10.      * 施法者历史值决定你想要将该效果追踪于哪个分类中。
  11.      * You can use Effect History Entry Type to query the category for an Effect History entry.
  12.      * 你可以使用“效果历史条目类型”来查询一个效果历史条目的类型。
  13.      * A max count of 0 (the default) will get all available entries.
  14.      * 将最大计数设为0(默认)则会获得所有可用的条目。
  15.      */
  16.     UnitEffectHistory(unit: Unit, maxCount: int): EffectHistory

  17.     /**
  18.      * Returns the number of entries in the specified Effect History.
  19.      * 返回指定效果历史中的条目数量。
  20.      * Use Effect History Of Unit to get an Effect History.
  21.      * 使用“单位的效果历史”来获取效果历史。
  22.      */
  23.     EffectHistoryCount(history: EffectHistory): int

  24.     /**
  25.      * Returns the type of effect that was logged in the Effect History at the specified Index.
  26.      * 返回记录在效果历史中指定索引的效果类型。
  27.      * The type for the effect is set in the effect data in the Caster History field.
  28.      * 这一效果类型由效果数据的“施法者历史”字段决定。
  29.      */
  30.     EffectHistoryGetType(history: EffectHistory, index: int): e_effectHistory

  31.     /**
  32.      * Returns the game time (in seconds) when the effect that was logged in the Effect History at the specified Index occurred.
  33.      * 返回记录在效果历史中指定索引的效果发生时的游戏时间(以秒为单位)。
  34.      */
  35.     EffectHistoryGetTime(history: EffectHistory, index: int): fixed

  36.     EffectHistoryGetAmountInt(history: EffectHistory, index: int, amount: e_effectHistoryAmount, total: boolean): int

  37.     EffectHistoryGetAmountFixed(history: EffectHistory, index: int, amount: e_effectHistoryAmount, total: boolean): fixed

  38.     EffectHistoryGetUnitByLocation(history: EffectHistory, index: int, unit: e_effectUnit): Unit

  39.     /**
  40.      * If the Effect parameter is set to Root, this will return the effect that was at the root of the effect tree for the effect that was logged in the Effect History at the specified Index.
  41.      * 假如效果参数设为根,会返回记录在效果历史中指定索引处的效果的效果树的根效果。
  42.      * If the Effect parameter is set to Current, then this will return the effect that was logged in the Effect History at the specified Index.
  43.      * 假如效果参数设为当前,则会返回记录在效果历史中指定索引处的效果。
  44.      */
  45.     EffectHistoryGetEffect(history: EffectHistory, index: int, effect: e_effectHistoryEffect): CEffect

  46.     /**
  47.      * Returns the weapon that originated the effect that was logged in the Effect History at the specified Index.
  48.      * 返回引发记录在效果历史中指定索引的效果的武器。
  49.      * If the effect came from an ability rather than a weapon, this will return No Game Link.
  50.      * 假如效果由技能而非武器引发,则会返回“无游戏链接”。
  51.      */
  52.     EffectHistoryGetWeapon(history: EffectHistory, index: int): CWeapon

  53.     /**
  54.      * Returns the ability that originated the effect that was logged in the Effect History at the specified Index.
  55.      * 返回引发记录在效果历史中指定索引的效果的技能。
  56.      * If the effect came from a weapon rather than an ability, this will return No Game Link.
  57.      * 假如效果由武器而非技能引发,则会返回“无游戏链接”。
  58.      */
  59.     EffectHistoryGetAbil(history: EffectHistory, index: int): CAbil
  60. }
复制代码


根据上面的信息可以看出,效果历史由多个条目构成,各个条目至少记录了如下这些信息:

1. 效果链接及其根效果
2. 起源武器
3. 起源技能
4. 各个位置的单位(e_effectUnit:目标单位、施法者、源单位等)
5. 效果伤害类型(e_effectHistoryAmount,吸收、击杀等)
6. 效果发生时间
7. 分类(e_effectHistory:伤害、死亡、治疗、修改四个类)

看到这里相信大家可以想象,效果条目里平时制作技能时非常重要的两个字段:轰击位置/发射位置,大概是怎么运作的。
但是真正的效果树并不是被记录在施法单位上的,而且效果历史条目信息还缺乏“目标点、施法者点”这个重要信息。可效果历史无疑为我们提供了一个重要参考。

2. 设置条目
在第一部分,我们成功地让武器能筛选出可攻击单位来,还得让武器能对单位施放效果,才是一把真正的武器。下面将模拟武器对目标发射飞弹并对其造成伤害。
首先申明抽象类CEffect,并让CEffectLaunchMissile和CEffectDamage成为其子类。
  1. public abstract class CEffect : CatalogScope { }

  2. public class CEffectLaunchMissile : CEffect { }

  3. public class CEffectDamage : CEffect { }
复制代码


给这些类及先前的武器新增字段。
  1. public class CWeapon : CatalogScope
  2. {
  3.     private CTargetFilters targetFilters;
  4.     public CTargetFilters TargetFilters => targetFilters;

  5.     private CEffect effect;
  6.     public CEffect Effect => effect;
  7. }
复制代码
  1. public class CEffectLaunchMissile : CEffect
  2. {
  3.     private CUnit ammoUnit;
  4.     public CUnit AmmoUnit => ammoUnit;

  5.     private CEffect impactEffect;
  6.     public CEffect ImpactEffect => impactEffect;

  7.     private SEffectWhichLocation launchLocaction;
  8.     public SEffectWhichLocation LaunchLocaction { get { return launchLocaction; } }

  9.     private SEffectWhichLocation impactLocation;
  10.     public SEffectWhichLocation ImpactLocation { get { return impactLocation; } }
  11. }

  12. public class CEffectDamage : CEffect
  13. {
  14.     private float amount;
  15.     public float Amount => amount;

  16.     private SEffectWhichLocation launchLocaction;
  17.     public SEffectWhichLocation LaunchLocaction { get { return launchLocaction; } }

  18.     private SEffectWhichLocation impactLocation;
  19.     public SEffectWhichLocation ImpactLocation { get { return impactLocation; } }
  20. }
复制代码

注:这里使用float作为伤害量的类型,只是为了文章简洁性。考虑到ABE框架适合用确定性(deterministic)同步方式,浮点数运算在不同中央处理器架构下不具有确定性,因此这里应该用FixedPoint描述amount。具体实现这里不做叙述,有兴趣的话可以了解下.NET下的FixedMath库。

下面申明位置字段。CEffectDamage和CEffectLaunchMissile都具有发射位置和轰击位置字段。这种表明位置字段有4个类:
SEffectWhichUnit——哪个单位,用于CEffectApplyBehavior、CEffectIssueOrder、CEffectModifyUnit等;
枚举类型:
  1. enum EffectUnit { Caster, Source, Target, Outer, Origin, CasterOuter, TargetOuter };
复制代码


SEffectWhichLocation——哪个单位和点,用于CEffectCreatePersistent、EffectDamage、CEffectEnumArea、CEffectLaunchMissile等;
枚举类型:
  1. enum EffectLocation {
  2.     CasterUnit, SourceUnit, TargetUnit, OuterUnit, OriginUnit, CasterOuterUnit, TargetOuterUnit,
  3.     CasterPoint, SourcePoint, TargetPoint, OuterPoint, OriginPoint, CasterOuterPoint, TargetOuterPoint
  4. };
复制代码


SEffectWhichPlayer——哪个玩家,用于CEffectModifyPlayer、CEffectCreateUnit、CEffectIssueOrder等;
枚举类型:
  1. enum EffectPlayer { Caster, Source, Target, Outer, Origin, CasterOuter, TargetOuter, Creator, Hostile, Neutral };
复制代码


SEffectWhichTimeScale——哪个时间刻度,用于CEffectCreatePersistent、CEffectApplyForce。
枚举类型:
  1. enum EffectTimeScale { Caster, Target, Global };
复制代码


这些结构共有effect字段,所以将他们继承于抽象类CEffectWhich。
  1. public abstract class CEffectWhich
  2. {
  3.     private CEffect effect;
  4.     public CEffect Effect { get { return effect; } }
  5. }

  6. public class SEffectWhichLocation : CEffectWhich
  7. {
  8.     private CEffectLocation value;
  9.     public CEffectLocation Value { get { return value; } }
  10. }

  11. public class SEffectWhichUnit : CEffectWhich
  12. {
  13.     private CEffectLocationUnit value;
  14.     public CEffectLocationUnit Value { get { return value; } }
  15. }
复制代码


然后是位置类型的类,用于手动引用到效果条目。这里并没直接套用星际2的格式EffectLocation,而是将EffectLocation分为EffectLocationUnit和EffectLocationPoint,这样代码会清晰得多。
  1. public abstract class CEffectLocation : CatalogScope { }

  2. public enum EffectUnit
  3. {
  4.     Caster, Source, Target, Outer, Origin, CasterOuter, TargetOuter
  5. };

  6. public class CEffectLocationUnit : CEffectLocation
  7. {
  8.     private EffectUnit type;
  9.     public EffectUnit Type { get { return type; } }
  10. }

  11. public enum EffectPoint
  12. {
  13.     Caster, Source, Target, Outer, Origin, CasterOuter, TargetOuter
  14. };

  15. public class CEffectLocationPoint : CEffectLocation
  16. {
  17.     private EffectPoint type;
  18.     public EffectPoint Type { get { return type; } }
  19. }
复制代码
回复

使用道具 举报

发表于 2018-1-10 09:23:50 | 显示全部楼层
楼主很有想法哪。

点评

头目潜台词:“瞎猜也要有个分寸哪,这种奇思妙想就不要误人子弟了:)”  详情 回复 发表于 2018-1-10 09:35
回复

使用道具 举报

 楼主| 发表于 2018-1-10 09:35:43 | 显示全部楼层

头目潜台词:“瞎猜也要有个分寸哪,这种奇思妙想就不要误人子弟了:)”
回复

使用道具 举报

 楼主| 发表于 2018-1-12 14:09:32 | 显示全部楼层
本帖最后由 zt0616 于 2018-1-12 20:32 编辑

3. 设置点、效果类和扩展类
至此条目设置完成,下面设置点类。首先得引用命名空间System.Numerics。System.Numerics库提供了无限整数、复数、矩阵等结构,我们在这里只用到四元数和三维向量。
四元数描述旋转,三维向量描述位置。四元数、三维向量的换算、计算(运算符重载)将不在这里叙述。
  1. using System.Numerics;

  2. public class Point
  3. {
  4.     private Vector3 position;
  5.     public Vector3 Position { get { return position; } }

  6.     private Quaternion rotaion;
  7.     public Quaternion Rotation { get { return rotaion; } }

  8.     public Point(Vector3 position, Quaternion rotaion)
  9.     {
  10.         this.position = position;
  11.         this.rotaion = rotaion;
  12.     }
  13. }
复制代码


下面设置可创建的效果类。每个效果具有“位置”和“历史位置”字段,用来查询上游效果。
  1. public class Effect : Creatable
  2. {
  3.     public new CEffect Entry { get { return (CEffect)entry; } }

  4.     public SEffectLocations locations;
  5.     public Dictionary<CEffect, SEffectLocations> historyLocations;

  6.     public override void OnCreated()
  7.     {

  8.     }
  9. }

  10. public class EffectLaunchMissile : Effect
  11. {
  12.     public new CEffectLaunchMissile Entry { get { return (CEffectLaunchMissile)entry; } }

  13.     public void Launch() { }
  14. }

  15. public class EffectDamage : Effect
  16. {
  17.     public new CEffectDamage Entry { get { return (CEffectDamage)entry; } }

  18.     public void Damage() { }
  19. }
复制代码

  1. public struct SEffectLocations
  2. {
  3.     public Dictionary<EffectUnit, Unit> units;
  4.     public Dictionary<EffectPoint, Point> points;

  5.     public SEffectLocations(
  6.         Unit casterUnit, Point casterPoint,
  7.         Unit sourceUnit, Point sourcePoint,
  8.         Unit targetUnit, Point targetPoint,
  9.         Unit outerUnit, Point outerPoint,
  10.         Unit originUnit, Point originPoint)
  11.     {
  12.         units = new Dictionary<EffectUnit, Unit>
  13.             {
  14.                 { EffectUnit.Caster, casterUnit },
  15.                 { EffectUnit.Source, sourceUnit },
  16.                 { EffectUnit.Target, targetUnit },
  17.                 { EffectUnit.Outer, outerUnit },
  18.                 { EffectUnit.Origin, originUnit }
  19.             };

  20.         points = new Dictionary<EffectPoint, Point> {
  21.                 { EffectPoint.Caster, casterPoint },
  22.                 { EffectPoint.Source, sourcePoint},
  23.                 { EffectPoint.Target, targetPoint},
  24.                 { EffectPoint.Outer, outerPoint},
  25.                 { EffectPoint.Origin, originPoint}
  26.             };
  27.     }
  28. }
复制代码


另外,给单位类添加一些字段字段,除了transport用于引用载物(外层),其他的unitImpact、pointImpact和launcher用于发射物。这3个字段普通单位用不上,或许申明一个CUnitMissile类更好。
  1. public class Unit : Creatable
  2. {
  3.     public new CUnit Entry => (CUnit)entry;

  4.     private Unit transport;
  5.     public Unit Transport { get { return transport ?? this; } }

  6.     private Point location;
  7.     public Point Location { get { return location; } }

  8.     public Unit unitImpact;
  9.     public Point pointImpact;
  10.     public EffectLaunchMissile launcher;

  11.     public override void OnCreated()
  12.     {
  13.         throw new NotImplementedException();
  14.     }
  15. }
复制代码


给Creatable添加SetEntry方法。
  1. public Creatable SetEntry(CatalogScope entry) { this.entry = entry; return this; }
复制代码


最后,是所有这些类的扩展方法。

实现触发器“在单位处创建”和“在点处创建”。这里需要以自己的方式实例化,注意Create<T>泛型约束需要在最后出现new()。之前申明的OnCreated()抽象方法在这里被调用。
  1. public static class CatalogScopeExtension
  2. {
  3.     public static T Create<T>(this CatalogScope entry, Unit unit) where T : Creatable, new()
  4.     {
  5.         T created = (T)(new T()).SetEntry(entry);
  6.         created.OnCreated();
  7.         return created;
  8.     }

  9.     public static T Create<T>(this CatalogScope entry, Point point) where T : Creatable, new()
  10.     {
  11.         T created = (T)(new T()).SetEntry(entry);
  12.         created.OnCreated();
  13.         return created;
  14.     }
  15. }
复制代码


CreateEffectLaunchMissile、CreateEffectDamage则是根据条目实例化。在实例化后,紧跟着便是对位置信息及上游位置的赋值,非常关键。
  1. public static class CEffectLaunchMissileExtension
  2. {
  3.     public static void CreateEffectLaunchMissile(this CEffectLaunchMissile entry, SEffectLocations locations, Dictionary<CEffect, SEffectLocations> historyLocations)
  4.     {
  5.         EffectLaunchMissile effect = entry.Create<EffectLaunchMissile>(locations.units[EffectUnit.Source]);
  6.         effect.locations = locations;
  7.         effect.historyLocations = historyLocations ?? new Dictionary<CEffect, SEffectLocations>();

  8.         effect.Launch();
  9.     }
  10. }

  11. public static class CEffectDamageExtension
  12. {
  13.     public static void CreateEffectDamage(this CEffectDamage entry, SEffectLocations locations, Dictionary<CEffect, SEffectLocations> historyLocations)
  14.     {
  15.         EffectDamage effect = entry.Create<EffectDamage>(locations.units[EffectUnit.Caster]);
  16.         effect.locations = locations;
  17.         effect.historyLocations = historyLocations ?? new Dictionary<CEffect, SEffectLocations>();

  18.         effect.Damage();
  19.     }
  20. }
复制代码


CreateEffect扩展方法,用以判断手动引用的条目是什么类型,如果是CEffectDamage就实例化伤害效果,如果是CEffectLaunchMissile则实例化发射效果。
至于ForEachType扩展方法,利用了Action<T>进行委托,让“if (entry.GetType() == typeof(CEffect))”这样的语句变得清晰、干净。CreateEffect方法便用到了,结合Lambda表达式是不是看起来整洁许多呢。
  1. public static class CEffectExtension
  2. {
  3.     public static void ForEachType(this CEffect entry, Action<CEffectDamage> damageAction, Action<CEffectLaunchMissile> launchMissileAction)
  4.     {
  5.         if (entry.GetType() == typeof(CEffect)) { }
  6.         else if (entry.GetType() == typeof(CEffectDamage))
  7.         {
  8.             damageAction((CEffectDamage)entry);
  9.         }
  10.         else if (entry.GetType() == typeof(CEffectLaunchMissile))
  11.         {
  12.             launchMissileAction((CEffectLaunchMissile)entry);
  13.         }
  14.     }

  15.     public static void CreateEffect(this CEffect entry, SEffectLocations locations, Dictionary<CEffect, SEffectLocations> historyLocations)
  16.     {
  17.         entry.ForEachType((d) =>
  18.         {
  19.             d.CreateEffectDamage(locations, historyLocations);
  20.         }, (lm) =>
  21.         {
  22.             lm.CreateEffectLaunchMissile(locations, historyLocations);
  23.         });
  24.     }
  25. }
复制代码


同上,为CEffectLocation扩展一个委托方法。
  1. public static class CEffectLocationExtension
  2. {
  3.     public static void ForEachType(this CEffectLocation location, Action<CEffectLocationUnit> unitAction, Action<CEffectLocationPoint> pointAction)
  4.     {
  5.         if (location.GetType() == typeof(CEffectLocation)) { }
  6.         else if (location.GetType() == typeof(CEffectLocationUnit))
  7.         {
  8.             unitAction((CEffectLocationUnit)location);
  9.         }
  10.         else if (location.GetType() == typeof(CEffectLocationPoint))
  11.         {
  12.             pointAction((CEffectLocationPoint)location);
  13.         }
  14.     }
  15. }
复制代码



至此,需要的类被设置完成~
回复

使用道具 举报

 楼主| 发表于 2018-1-12 20:33:54 | 显示全部楼层
4. 模拟武器的发射、轰击与伤害
这里作为ABE框架的核心,将是最重要的环节。
中心思想是:每一个效果都要能在其生命周期内查询上游效果,比如根据上游效果实例,判断当前应该轰击施法者还是目标。

首先为EffectLaunchMissile实现一个扩展方法,用以取得历史位置中的效果条目(根据效果类型)。在将来扩展到其他效果类后,也要相应扩充这类ForEachType扩展方法。
  1. public static class EffectLaunchMissileExtension
  2. {
  3.     public static CEffect GetEntryInHistory(this SEffectWhichLocation whichLocation, Dictionary<CEffect, SEffectLocations> historyLocations)
  4.     {
  5.         CEffect entry = null;
  6.         if (whichLocation.Effect != null)
  7.         {
  8.             whichLocation.Effect.ForEachType((d) =>
  9.             {
  10.                 if (historyLocations.ContainsKey(d)) entry = d;
  11.             }, (lm) =>
  12.             {
  13.                 if (historyLocations.ContainsKey(lm)) entry = lm;
  14.             });
  15.         }
  16.         return entry;
  17.     }
  18. }
复制代码


为EffectLaunchMissile添加一些描述位置的字段。下面将根据不同的位置类型(点?单位?)以及不同的效果条目(当前效果?历史效果?)赋值。
Launch()方法在Create时调用;
OnAmmoUnitImpacted()方法在子弹命中后,委托子弹单位调用。复写OnCreated()方法,创建后注册AmmoUnitImpact事件。
  1. public class EffectLaunchMissile : Effect
  2. {
  3.     public new CEffectLaunchMissile Entry { get { return (CEffectLaunchMissile)entry; } }

  4.     public Action AmmoUnitImpact;

  5.     private Unit ammoUnit;

  6.     private Unit unitLaunch;
  7.     private Point pointLaunch;
  8.     private Unit unitImpact;
  9.     private Point pointImpact;

  10.     public override void OnCreated()
  11.     {
  12.         base.OnCreated();
  13.         AmmoUnitImpact += OnAmmoUnitImpacted;
  14.     }

  15.     public void Launch() { }

  16.     private void OnAmmoUnitImpacted() { }
  17. }
复制代码


下面是“让子弹飞出去”的具体实现。Launch()分为两部分,上面部分是实例化子弹,下面部分是设置子弹实例的目标。
这个方法的目的是:在期待的位置类型(点?单位?)和期待的位置值(施法者?目标?)通过效果条目下的AmmoUnit单位类型创建子弹实例;然后给子弹实例获取期待的目标。
  1. public class EffectLaunchMissile : Effect
  2. {
  3.     public new CEffectLaunchMissile Entry { get { return (CEffectLaunchMissile)entry; } }

  4.     public Action AmmoUnitImpact;

  5.     private Unit ammoUnit;

  6.     private Unit unitLaunch;
  7.     private Point pointLaunch;
  8.     private Unit unitImpact;
  9.     private Point pointImpact;

  10.     public override void OnCreated()
  11.     {
  12.         base.OnCreated();
  13.         AmmoUnitImpact += OnAmmoUnitImpacted;
  14.     }

  15.     public void Launch()
  16.     {
  17.         CEffect entryLaunch = Entry.LaunchLocaction.GetEntryInHistory(historyLocations) ?? Entry;
  18.         SEffectLocations launchLocations = (historyLocations.ContainsKey(entryLaunch)) ? historyLocations[entryLaunch] : locations;
  19.         Entry.LaunchLocaction.Value.ForEachType((u) =>
  20.         {
  21.             unitLaunch = launchLocations.units[u.Type];
  22.             ammoUnit = Entry.AmmoUnit.Create<Unit>(unitLaunch.Location);
  23.         }, (p) =>
  24.         {
  25.             pointLaunch = launchLocations.points[p.Type];
  26.             ammoUnit = Entry.AmmoUnit.Create<Unit>(unitLaunch.Location);
  27.         });

  28.         CEffect entryImpact = Entry.ImpactLocation.GetEntryInHistory(historyLocations) ?? Entry;
  29.         SEffectLocations impactLocations = (historyLocations.ContainsKey(entryImpact)) ? historyLocations[entryImpact] : locations;
  30.         Entry.ImpactLocation.Value.ForEachType((u) =>
  31.         {
  32.             unitImpact = impactLocations.units[u.Type];
  33.             ammoUnit.unitImpact = unitImpact;
  34.         }, (p) =>
  35.         {
  36.             pointImpact = impactLocations.points[p.Type];
  37.             ammoUnit.pointImpact = pointImpact;
  38.         });

  39.         ammoUnit.launcher = this;
  40.     }
  41. }
复制代码


最后,实现子弹命中事件调用的OnAmmoUnitImpacted()方法。目标是实例化一个期待类型的效果(EffectLM?EffectDamage?),并传递给他位置信息。
在这里,新效果的施法者为当前效果的施法者,源单位为子弹,目标单位为当前轰击的单位,外层为子弹单位的载具,起始单位为当前效果的起始单位。而对应的点则为这个瞬间所有单位的Location字段值,是一个Point类型。
  1. public class EffectLaunchMissile : Effect
  2. {
  3.     public new CEffectLaunchMissile Entry { get { return (CEffectLaunchMissile)entry; } }

  4.     public Action AmmoUnitImpact;

  5.     private Unit ammoUnit;

  6.     private Unit unitLaunch;
  7.     private Point pointLaunch;
  8.     private Unit unitImpact;
  9.     private Point pointImpact;

  10.     public override void OnCreated()
  11.     {
  12.         base.OnCreated();
  13.         AmmoUnitImpact += OnAmmoUnitImpacted;
  14.     }

  15.     public void Launch()
  16.     {
  17.         CEffect entryLaunch = Entry.LaunchLocaction.GetEntryInHistory(historyLocations) ?? Entry;
  18.         SEffectLocations launchLocations = (historyLocations.ContainsKey(entryLaunch)) ? historyLocations[entryLaunch] : locations;
  19.         Entry.LaunchLocaction.Value.ForEachType((u) =>
  20.         {
  21.             unitLaunch = launchLocations.units[u.Type];
  22.             ammoUnit = Entry.AmmoUnit.Create<Unit>(unitLaunch.Location);
  23.         }, (p) =>
  24.         {
  25.             pointLaunch = launchLocations.points[p.Type];
  26.             ammoUnit = Entry.AmmoUnit.Create<Unit>(unitLaunch.Location);
  27.         });

  28.         CEffect entryImpact = Entry.ImpactLocation.GetEntryInHistory(historyLocations) ?? Entry;
  29.         SEffectLocations impactLocations = (historyLocations.ContainsKey(entryImpact)) ? historyLocations[entryImpact] : locations;
  30.         Entry.ImpactLocation.Value.ForEachType((u) =>
  31.         {
  32.             unitImpact = impactLocations.units[u.Type];
  33.             ammoUnit.unitImpact = unitImpact;
  34.         }, (p) =>
  35.         {
  36.             pointImpact = impactLocations.points[p.Type];
  37.             ammoUnit.pointImpact = pointImpact;
  38.         });

  39.         ammoUnit.launcher = this;
  40.     }

  41.     private void OnAmmoUnitImpacted()
  42.     {
  43.         CEffect impactEffect = Entry.ImpactEffect;
  44.         if (impactEffect != null)
  45.         {
  46.             historyLocations[Entry] = locations;
  47.             SEffectLocations impactLocations = new SEffectLocations(
  48.                 locations.units[EffectUnit.Caster],
  49.                 locations.points[EffectPoint.Caster],
  50.                 ammoUnit,
  51.                 ammoUnit.Location,
  52.                 unitImpact,
  53.                 unitImpact.Location,
  54.                 ammoUnit.Transport,
  55.                 ammoUnit.Transport.Location,
  56.                 locations.units[EffectUnit.Origin],
  57.                 locations.points[EffectPoint.Origin]
  58.                 );
  59.             impactEffect.CreateEffect(impactLocations, historyLocations);
  60.         }
  61.     }
  62. }
复制代码


至此,ABE框架中,最基本、最重要的效果树框架完成~后面能按需扩展其他的效果类型,而在这个框架上扩展,就灰常简单了。同时,位置的传递也可以自定义。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-11-22 15:41 , Processed in 0.224882 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表