【问题标题】:Unity inheritance creating copies of member variablesUnity继承创建成员变量的副本
【发布时间】:2019-09-10 09:36:52
【问题描述】:

我正在尝试通过使用继承来最小化我的项目中的重复代码量。

例如:

我游戏中的每个角色,无论是玩家、敌人还是 NPC,都有一个成员字段 HP。

它们都具有影响HP的TakeDamage功能。

在我的游戏中,这个功能非常精细,可以比较攻击者的属性、攻击武器的属性与被攻击者的属性和他们的装备。

这个功能经常调整以获得它,所以感觉不错,我不希望它在 npc、敌人和玩家控制器上重复,让我们想象一下有一天“networkPlayer”,因为嘿,一个人可以梦想!

还有更多类似的功能也是相同的,我真的很想将它们全部放在一个 CharacterController 类中,PlayerController、EnemyController 等都从该类继承。

好的,那么,这是继承的正确用法吗?

这里还有第二层。

我的玩家、敌人和 npc 都是从在线数据库构建的,该数据库会随着新敌人和玩家的添加而变化。

所以我有一个 CharacterBase 类,它在 HP、Stats、Name 等字符之间构建共享变量...

还有像 EnemyBase 这样的派生类(从 Character base 继承 name、hp、stats),但在这个类上扩展了它自己的变量,如 Rarety、MaxGoldHeMayCarry、XPGainedIfYouKillMe 等......

现在的问题是:

扩展 CharacterBase 的 EnemyBase 有两份HP! 在检查器中,它显示为 CharacterBase->HP 和 EnemyBase->HP

当我打电话时 CharacterController.HP 减一 它从 CharcterBase HP 中减去 1 但是有这个未动过的 EnemyBase HP!

这真的很罗嗦。对不起。下面是一些测试代码来演示

public class TestingInheritance : MonoBehaviour
{
    // set this to an empty prefab in the inspector
    public GameObject TestObjectPrefab;

    private void Start()
    {
        GameObject TestPlayer = Instantiate(TestObjectPrefab);

        PlayerController pController = TestPlayer.AddComponent<PlayerController>();
        pController.TakeDamage();   //calls the overridden version of TakeDamage() (which is good)


        GameObject TestEnemy = Instantiate(TestObjectPrefab);

        EnemyController eController = TestEnemy.AddComponent<EnemyController>();
        eController.TakeDamage();   //calls the overrideden version in EnemyController (which is good)

    }
}

[System.Serializable]   //comes from db
public class CharacterBase
{
    public int HP=100;
}

[System.Serializable]   //comes from db
public class Enemy : CharacterBase
{
    public string Name = "Enemy";
}

[System.Serializable]   //comes from db
public class Player : CharacterBase
{
    public string Name = "Player";
}

public class CharacterController : MonoBehaviour
{

    //I want to be able to access the variables that all characters share in common, such as HP, Name, Stats, Inventory
    public CharacterBase characterBase = new CharacterBase();

    /// <summary>
    /// Reduces HP by 1
    /// TakeDamage() in my real game is actually much more complicated and I dont want to duplicate the code in Enemy, Player, and NPC 
    /// there are many more functions that I want to be able to call for any kind of character such as UseItem(potion), ChangeState(poisoned), Die()
    /// All these functions share a ton of the same code and I dont want to duplicate it, especially because it changes often during my development
    /// </summary>
    public virtual void TakeDamage()
    {
        //here we just loose one hp
        characterBase.HP -= 1;
    }

}

public class PlayerController : CharacterController
{
    public Player playerBase = new Player();

    public override void TakeDamage()
    {
        //do the code that is shared for all Characters
        base.TakeDamage();

        Debug.Log(playerBase.Name + " was hit!--- HP now: " + playerBase.HP);
        //Prints Player was hit! --- HP now: 100 and the inspector shows CharacterBase >> HP 99 AND PlayerBase >> HP: 100, Name: Player

        //IF WE CHANGE THE ABOVE LINE TO SAY
        Debug.Log(playerBase.Name + " was hit!--- HP now: " + characterBase.HP); 
        //Now we print 99, which is good. but we still have both CharacterBase >> HP 99
        //AND the Wrong, ugly, bad, confusing PlayerBase >> HP: 100

    }
}


public class EnemyController : CharacterController
{
    public Enemy enemyBase = new Enemy();

    public override void TakeDamage()
    {
        base.TakeDamage();
        Debug.Log(enemyBase.Name + " was hit! --- HP now: " + enemyBase.HP);
        //Prints Enemy was hit! --- HP now: 100 and the inspector shows CharacterBase >> HP 99 AND EnemyBase >> HP: 100, Name: Enemy
    }
}

那么,我该如何重构它,让它只有一个变量 HP,CharacterController 和 PlayerController 都可以访问?

抱歉,写了这么久,还有人在看吗?我不知道如何使这个更简洁......

【问题讨论】:

  • 我很确定您没有发布实际代码 - 您的实际代码可能在 Enemy 类中也有 public int HP=100;...(此处显示的示例确实无法表现你描述的)
  • @AlexeiLevenkov 感谢您的阅读!它没有public int HP=100; 这是复制和粘贴的。我认为public Enemy enemyBase = new Enemy(); 行创建了重复的 HP 变量。 ...我也觉得很奇怪
  • 忽略之前的评论 - 我以为你只是在谈论 characterBase.HP -= 1; 行为错误(这显然不会在显示的代码中发生)......但相反,你期待基本控制器中的一些魔法来发现新的派生类的属性...而不是使用相同的属性...确实,您显示的代码完全展示了错误,您只是有很多关于序列化的不相关人员。
  • 正确,序列化是必要的,因为这些类(Enemy、Player 等)是通过读取由 DB 创建的 json 生成的。是序列化导致我出现问题吗?
  • 另外,我不希望基类神奇地发现新属性。我根本不想要新房产!!!我希望派生类使用基类属性

标签: c# unity3d inheritance


【解决方案1】:

为什么会在这里?

public CharacterBase characterBase = new CharacterBase();

从它继承的所有类都将与其自己的代码分开调用它,(例如public Enemy enemyBase = new Enemy();)。

这就是为什么你会看到两者。结果,每个控制器都有两个单独的统计数据。如果你想能够访问它,你需要在角色控制器中创建一个get 方法:

public CharacterBase stats { get; protected set; }

然后在它的子类中用一个值填充该字段。

【讨论】:

    【解决方案2】:

    感谢 Draco18s 你的回答使它工作。

    为了避免重复字段,我们肯定需要去掉:

    public CharacterBase characterBase = new CharacterBase();

    但我们仍然需要对 characterBase 的引用,所以我添加了:

        protected CharacterBase characterBase;
        public void Init(CharacterBase _characterBase)
        {
            characterBase = _characterBase;
        }
    

    现在 Start() 看起来像这样

    private void Start()
        {
            GameObject TestPlayer = Instantiate(TestObjectPrefab);
            TestPlayer.name = "p";
    
            PlayerController pController = TestPlayer.AddComponent<PlayerController>();
            pController.Init(pController.playerBase);
            pController.TakeDamage();  
        }
    
    

    就是这样!!不再有重复的字段!并在 Debug.Log 中正确输出 - 谢谢各位!

    【讨论】:

    • @Menyus 这是一篇关于继承的好文章。当使用继承的控制器类使事情变得混乱时,我的问题就出现了。我今天早上想知道这是否是一个正常的用例?其他人不是在“对象”上而是在“控制器”上使用继承吗?
    • 我向您发送链接的原因是因为您使用的是对基类的直接引用,但我想向您展示您可以使用 base 关键字访问基类方法和变量
    猜你喜欢
    • 1970-01-01
    • 2020-08-18
    • 1970-01-01
    • 2016-12-03
    • 1970-01-01
    • 1970-01-01
    • 2012-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多