在方法中转换不同的子类

时间:2018-04-16 16:27:19

标签: c# unity3d unet

我想发送,接收和转换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子类的类型。

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

例如, GrenadeEvent 可能需要一个目标点,爆炸的大小和损坏。

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 个答案:

答案 0 :(得分:1)

如果您使用C#7而GrenadeSkillEventOtherSkillEvent派生自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中创建一个抽象方法,并在所有具体类中覆盖它,但是你需要有共同的返回类型和参数。

答案 1 :(得分:1)

执行此操作的最佳方法是在顶级类SkillEvent上编写泛型getter方法,然后在类中重写方法是特殊的。既然你似乎需要不同的类型我会认为你可以使用一个方法来执行你在类中引用的方法

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

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();
}

答案 2 :(得分:1)

简短的回答是,你可以通过制作自己的类型来做你所要求的,但你真的不想这样做。这种代码正是多态性旨在防止的。为了理解原因,假设您在代码中的5个或10个不同位置使用了此模式 - 每个模式都有一个基于Type的switch语句,以及在每种情况下运行的不同代码。现在让我们假设您有一种新类型的SkillEvent要介绍:您必须搜索代码中您打开Type并添加更多代码的每个地方。所有已经过质量检查的代码突然打开了,所有这一切都必须再次进行质量检查。另外,如果你忘了其中一个斑点怎么办?多么痛苦,只需添加一个新的具体实例,就可以编辑所有这些不同的地方。

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

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

abstract Map<String, Object> getParameters();

或者

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

现在具体类创建了一个地图并用自己的参数填充它,但是你的调用方法并不需要知道有关该方法实现的任何信息。无论如何,你想要知道GrenadeSkillEvent在那个班级里面的地方。您不希望其他课程了解GrenadeSkillEvent的详细信息。毕竟,您可能决定在将来更改其实现,以便它具有新参数。此时,您不想记住要查找包含此开关的callXYZServiceCall代码。 (更糟糕的是,你没有添加新的参数,而是项目中的另一位工程师,他甚至不知道担心这个转换语句。)

答案 3 :(得分:0)

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

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

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

您可以将技能ID和参数设置为字符串,发送rpc,接收命令,解析字符串,使用para技能执行id技能代码。

  1. 将技能参数和技能ID添加到字符串
  2. 使用此字符串发送RPC
  3. 接收RPC
  4. 将字符串解析为您的格式(在此exlample中为jsonObject)
  5. id_skill查找已使用的技能,并使用参数执行代码
  6. 的利润!
  7. 解析或创建字符串的有用工具将是一些JSON解析器(serializator .. 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
        }
    }
    

    我希望你理解我的想法。