0

我想发送、接收和转换 SkillEvent 类的子类,并能够在里面获取自定义参数,具体取决于它的类型。

可能有许多具有不同参数的不同子类。

有没有办法避免为每种技能编写不同的方法?

class SkillEvent { float timestamp, EventType type }
class GrenadeSkillEvent : SkillEvent { Vector3 target_point}

[ClientRpc]
RpcSendSkillEvent(SkillEvent event){
    eventManager.ExecuteEvent(event);
}

ExecuteEvent(SkillEvent event){
    switch(event.type){
        case GrenadeSkillEvent : 
            GrenadeSkillEvent grenadeEvent = (GrenadeSkillEvent) event;
            float grenadeParam = event.grenadeParam;
            break;

        case OtherSkillEvent : 
            OtherSkillEvent otherEvent = (OtherSkillEvent ) event;
            string stringParam = event.stringParam;
            break;
    }
}

我以为可以那样做,但显然不行。

有没有办法避免为每种技能编写不同的方法?

编辑 :

GrenadeSkillEvent 和 OtherSkillEvent 是 SkillEvent 的子项。

它们都有一个时间戳和一个类型,我认为我可能需要帮助将事件变量转换为正确的 SkillEvent 子类。

我的问题是它们具有相同的方法,比如说执行,但是每种子类都需要不同类型的参数。

例如,手榴弹事件可能需要一个目标点、爆炸的大小和伤害。

BonusEvent可以为玩家增加一些 HP

TerrainEvent可以在靠近玩家的重要对象上激活一些自定义动画等

这些技能背后的所有逻辑都是一样的。

他们都有两种方法:

Simulate(SkillEvent Event) and Render(SkillEvent Event)

但是例如在 GrenadeSkill 中,我需要它

Simulate(GrenadeSkillEvent Event) and Render(GrenadeSkillEvent Event)

编辑 2:

networkedPlayer.RpcOnSkillEvent(
    new SkillEvent[] {
        new GrenadeSkillEvent() {
            timestamp = state.timestamp,
            id = 0,
            point = target
        } 
    }
);

编辑3:

var dictionary = new Dictionary<Type, Action<SkillEvent>> {
    {
        typeof(GrenadeSkillEvent), (SkillEvent e) => {
            Debug.Log("GrenadeSkill OnConfirm point"+ ((GrenadeSkillEvent)e).point);
        }
    }
};

public override void OnConfirm(SkillEvent skillEvent) {

    if (skillEvent == null) return;

    Debug.Log(skillEvent.GetType());
    dictionary[skillEvent.GetType()](skillEvent);
}
4

4 回答 4

2

简短的回答是,您可以通过制作自己的类型来做您所要求的事情,但您真的不想这样做。这种代码正是多态性旨在防止的。为了理解原因,假设您在代码中的 5 或 10 个不同的地方使用了这种模式——每个地方都有一个基于 Type 的 switch 语句,并且在每种情况下运行不同的代码。现在假设您要引入一种新的 SkillEvent 类型:您必须在代码中找到您打开 Type 的每个位置并添加更多代码。所有已经被 QA'ed 的代码你突然打开了,这一切都必须再次进行 QA'ed。另外,如果你忘记了其中一个地方怎么办?多么痛苦,不得不去编辑所有这些不同的地方只是为了添加一个新的类的具体实例。

现在考虑正确的方法:对于这 10 个位置中的每一个,您在基类中创建一个抽象方法,然后在每个具体类中覆盖它。抽象方法创建了一个契约,每个具体类都可以以自己的方式实现它。

在您的具体示例中,您说您想从不同的具体类中提取不同的参数。你打算用这些参数做什么?是创建一个描述对象的字符串吗?是将这些参数传递给您即将进行的服务调用吗?想想你想要实现的目标。我假设是后一种情况,因为那是您实际需要单独参数的地方。因此,使您在基类中定义的抽象方法成为

abstract Map<String, Object> getParameters();

也许

abstract void addParameters(Map<String, Object>);

现在具体类创建一个映射并用自己的参数填充它,但是您的调用方法不必知道有关此方法的实现的任何信息。GrenadeSkillEvent无论如何,这就是您希望在该课程中的知识。您不希望其他班级知道有关GrenadeSkillEvent. 毕竟,您将来可能会决定更改其实现,使其具有新参数。那时,您不必记住去寻找callXYZServiceCall其中包含此开关的代码。(更糟糕的是,添加新参数的不是你,而是项目中的另一位工程师,他甚至都不知道要担心这个 switch 语句。)

于 2018-04-16T22:21:30.187 回答
1

最好的方法是在顶级类上编写一个通用的 getter 方法,SkillEvent然后在特殊的类中覆盖该方法。由于您似乎需要不同的类型,我认为您可以使用一种方法来执行您在类内部参数化的方法

这是您发布当前代码的示例:

class SkillEvent { 

  float timestamp; 

  EventType type ;

  public virtual void executeEvent(){ //handles the general case for the execute funciton
    execute(timestamp);
  }
}

class GrenadeSkillEvent : SkillEvent { //overriden to pass target point into event execution

  Vector3 target_point;

  public override void executeEvent(){
    execute(target_point);
  }
}

class BonusEvent : SkillEvent { 

  int bonus_health;

  public override void executeEvent(){/overriden to pass bonus health into event
    execute(bonus_health);
  }
}

class TerrainEvent : SkillEvent { 

  GameObject obj;

  public override void executeEvent(){//overriden to play animation before event is executed
    animation(obj);
    execute();
  }
}

[ClientRpc]
RpcSendSkillEvent(SkillEvent event){
    eventManager.ExecuteEvent(event);
}

ExecuteEvent(SkillEvent event){
    event.executeEvent();
}
于 2018-04-16T17:13:45.243 回答
0

如果您使用 C# 7 并GrenadeSkillEvent从中OtherSkillEvent派生,SkillEvent则可以使用模式匹配:

switch (@event) //@event is of SkillEvent type
{
    case GrenadeSkillEvent gse:
        float grenadeParam = gse.grenadeParam;
        break;
    case OtherSkillEvent ose:
        string stringParam = ose.stringParam;
        break;
    case null:
        //null handling
        break;
    default:
        //other types
        break;
}

如果没有,您可以使用字典:

var dictionary = new Dictionary<Type, Action<SkillEvent>>
    {
        {
            typeof(GrenadeSkillEvent), e => 
            {
                float grenadeParam = ((GrenadeSkillEvent)e).grenadeParam;
                //some logic
            }
        },
        {
            typeof(OtherSkillEvent), e => 
            {
                string stringParam = ((OtherSkillEvent)e).stringParam;
                //some logic
            }
        }
    }

if (@event == null)
{
    //null handling
}
else
{
    dictionary[@event.GetType()](@event);
}

或者,一种更面向对象的方法是在内部创建一个抽象方法SkillEvent并在所有具体类中覆盖它,但您需要有共同的返回类型和参数。

于 2018-04-16T16:40:39.457 回答
0

Idk,这只是想法。(不知道,我使用相同的方法)

也许好的解决方案是使用技能数据库,并使用字符串参数发送 RPC 而不是可序列化的类(您的SkillEvent

因此,使用此技能数据库,您可以根据需要执行带有字符串参数的代码。

您可以将技能 id 和参数放入字符串,发送 rpc,接收命令,解析字符串,通过带有参数的 id 技能执行代码。

  1. 将技能参数和技能 ID 放到字符串中
  2. 使用此字符串发送 RPC
  3. 接收 RPC
  4. 将字符串解析为您的格式(在此示例中为 jsonObject)
  5. 通过参数查找使用过的技能id_skill并执行代码
  6. 利润!

解析或创建字符串的有用工具将是一些 JSON 解析器(序列化器.. idk)

也许你会问我:“为什么是字符串和 json?”

我回答你:“因为它很灵活!”

例子:

void UseGrenade(){ // when some player using grenade skill.
    JsonObject json = new JsonObject();
    json.addField("id_skill", id); // id of grenade skill
    json.addField("timestamp", 1.5f);
    json.addField("position", targetPosition);
    SendRpcSkill(json.Print()); // converting json to string and sending this string
}

例如执行手榴弹的参数:

 // it is pseudo json for example
{
    id_skill: 1, // id of grenade
    timestamp: 1.5,
    position: {1,21,3}
}

或者其他一些技能:

// it is pseudo json for example
{
    id_skill: 2, // id of some skill
    timestamp: 1.8,     
    msg:"rain of fishes for cats!",
    // ... and other params
}

并执行此技能:

[ClientRpc]
RpcSendSkill(string skillData){
    JsonObject json = new JsonObject(skillData); //converting string to json
    int idskill = json["id_skill"]; // get id of used skill
    mySkillsDataBase[idskill].Execute(json); // find skill and execute code
}

在你的游戏中的某个地方:

public class GrenadeSkill: BaseSkill{

    public override void Execute(JsonObject json){
       float timeStamp = (int)json["timestamp"];        
       Vector3 targetPosition = (Vector3)json["position"];
       // ...
       // and you grenade logic
    }
}

我希望你能理解我的想法。

于 2018-04-17T06:23:38.387 回答