需要C#面向对象编程建议:这种情况下合适的数据结构是什么?

3
好的,我需要一些关于面向对象编程的可靠建议。这是设置:我有一个玩家对象,其中代码处理移动、寻路逻辑等内容。现在我需要为每个玩家分配一个大型变量和设置表。即,每个玩家都可以执行约40种不同的武器动作,每种武器都有6-8个相关变量。我的问题是,哪种数据结构最合适。请注意,我不想为每个武器创建一个类,因为在我的游戏中武器从未被实例化。
以下是具体细节:每个玩家都有40种不同的武器需要按名称检索。例如:
Weapon #1: Pistol
Weapon #2: Shotgun
Weapon #3: Rifle
Weapon #4: Bow
Weapon #5: RocketLauncher

每个武器都有相同的变量分配给它。例如:
Pistol.HasBeenUnlocked (true/false)
Pistol.Priority (1-40)
Pistol.AmmoQuantityMax (0-10)
Pistol.AmmoQuantityCurrent (0-10)
Pistol.UIButtonState ("Hidden", "Selected", "Deselected", "CoolOff")
Pistol.CoolOffTimerMax (0.0-5.0)
Pistol.CoolOffOn (true/false)

现在我需要一种数据结构,可以组织所有这些变量并使它们易于访问。例如,我想能够循环遍历所有40个武器,并根据特定玩家是否解锁它们来生成按钮。这是我想做的:

for (int i = 0; i < Player.Weapons.NumberOfWeaponsTotal(); i++) {
     if (Player.Weapons.Weapon[i].UIButtonState == "Hidden"){
          // don't create a UI button to fire weapon
     }
     else {
          // create a UI button to fire weapon
     }
}

变量名称在不同武器之间将是相同的。所以 Weapons.Pistol.UIButtonState 将存在,Weapons.Rifle.UIButtonState 也是如此。
那么最好的方法是什么?2D数组并不美观。结构似乎不够强大。类看起来有些奇怪。我从未将类用于此类事情。我想确保这些“对象”与玩家联系在一起,因此当玩家被摧毁时,它们也会被销毁。

5
你的意思是什么,他们永远不会在我的游戏中被实例化?这句话对我来说毫无意义。 - Jouke van der Maas
想象一下基于文本的游戏和《光环》之间的区别。如果我实际上不会创建一个具有网格等内容的霰弹枪,则可以考虑使用类。我只是想要一个表格,可以查询并查看哦,玩家#3已解锁了这支霰弹枪,并且该霰弹枪具有以下属性。 - liquidgraph
1
我认为你应该将玩家拥有的武器集合与武器本身分开。为什么武器对象要知道UIButtonState?这似乎属于表现而不是游戏领域。将武器添加到玩家的武器列表中以模拟他所拥有的武器似乎很自然。再见! - nick2083
3个回答

4
我认为类对于这种情况是理想的。创建一个 IWeapon 接口,其中包含常见属性和方法,如 NameGetAmmoCount()IsEnabled 等。然后为每种类型的武器创建类,并设置所有特定属性。 (但如果您有8个具有相同方法和属性的不同手枪,则可以创建通用的 Pistol 类,并相应地设置变量)。
Player 对象中,有一个 List<IWeapon> 来保存玩家拥有的所有武器。
当销毁 Player 时,只需调用 List 上的 .Empty() 方法来删除它们全部。
被握持的武器将成为 Player 类的附加成员: IWeapon ActiveWeapon
您可以执行以下操作:
if(ActiveWeapon.GetAmmoCount() > 0) ActiveWeapon.Fire());

或者:

this.ActiveWeapon = new Pistol("LaserPistol");
this.Weapons.Add(this.ActiveWeapon);

this.ActiveWeapon = new AssaultRifle();

这里的优点是,您的Player在运行时不必知道他们可能拥有哪些类型的武器,您可以轻松地将它们换成其他武器。 编辑:我找到了一个页面,提供了使用策略设计模式的示例:http://hnaser.blogspot.com/2009/07/abc-of-design-patterns.html

在这种情况下创建40个类会过度杀伤。但还是谢谢回复。 - liquidgraph
嗨,liquidgraph。你为什么认为需要40个类?你可以只有一个通用的Weapon类来模拟你的武器,并在该类中添加一个静态方法来创建40个不同的实例。例如:Weapon.Shotgun - nick2083
有趣...我得更深入地探索一下。 - liquidgraph

1

免责声明

您可以使用此模型来实现您所需的功能。请注意,与任何设计一样,它可能存在缺陷。它旨在帮助您解决问题,而不是作为解决方案。

武器

这个类代表了您应用程序中的武器。 请注意重写方法以正确实现比较、搜索和索引。

public class Weapon
{
    public Weapon(WeaponName name)
    {
        Name = name;
    }

    public WeaponName Name { get; set; }
    public int Priority { get; set; }
    public int MaxAmmoQuantity { get; set; }
    public int CurrentAmmoQuantity { get; set; }

    public override string ToString()
    {
        return Enum.GetName(typeof(WeaponName), Name);
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;

        var other = obj as Weapon;
        if (other == null) return false;

        return Name == other.Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

武器名称

针对武器名称的固定选项(假设,如您所说,它们的选项是有限的)

public enum WeaponName
{
    Pistol,
    Shotgun,
    Rifle,
    Bow,
    RocketLauncher
}

玩家

请注意:

  • 在内部,您可以按照自己喜欢的结构存储武器,但对于外部世界,只发布IEnumerable实例
  • 它代表了玩家可以抓取武器或更改活动武器的事实(在此,您可以添加任何所需的操作)。通过事件,玩家向外界传达他的行动。
  • 您正在为对象提供具有语义的方法。而不是player.Weapons.Add,您正在明确玩家的行为。

    public class Player { private readonly IList _ownedWeapons = new List(); protected Weapon ActiveWeapon { get; private set; }

    public IEnumerable<WeaponName> OwnedWeapons { get; set; }
    
    public event EventHandler<WeaponEventArgs> WeaponGrabbed;
    public event EventHandler<WeaponEventArgs> ActiveWeaponChanged;
    
    public void InvokeActiveWeaponChanged()
    {
        var handler = ActiveWeaponChanged;
        if (handler != null) handler(this, new WeaponEventArgs(ActiveWeapon));
    }
    public void InvokeWeaponGrabbed(Weapon weapon)
    {
        var handler = WeaponGrabbed;
        if (handler != null) handler(this, new WeaponEventArgs(weapon));
    }
    
    public void SwitchWeapon(WeaponName weaponName)
    {
        ActiveWeapon = _ownedWeapons.Where(weapon => weapon.Name == weaponName).First();
        InvokeActiveWeaponChanged();
    }
    public void Grab(Weapon weapon)
    {
        _ownedWeapons.Add(weapon);
        InvokeWeaponGrabbed(weapon);
    }
    

    }

    public class WeaponEventArgs : EventArgs { public WeaponEventArgs(Weapon weapon) { Weapon = weapon; }

        public Weapon Weapon { get; set; }
    }
    

武器库

  • 在您的应用程序中集中所有关于玩家可以使用的武器的知识,注册可用武器名称及其在构造函数中的创建。
  • 此外,它提供了创建和查询可用武器的方法。
  • 您可以使用WeaponsNotOwnedBy方法告诉玩家尚未拥有哪些武器。

    public class WeaponRepository { readonly Dictionary> _availableWeapons = new Dictionary>();

    public WeaponRepository()
    {
        _availableWeapons.Add(WeaponName.Pistol, () => new Weapon(WeaponName.Pistol) );
        _availableWeapons.Add(WeaponName.Shotgun, () => new Weapon(WeaponName.Shotgun) );
    }
    
    public Weapon Create(WeaponName name)
    {
        return _availableWeapons[name]();
    }
    
    public IEnumerable<WeaponName> AvailableWeapons()
    {
        return Enum.GetValues(typeof (WeaponName)).Cast<WeaponName>();
    }
    
    public IEnumerable<WeaponName> WeaponsNotOwnedBy(Player player)
    {
        return AvailableWeapons().Where(weaponName => !player.OwnedWeapons.Contains(weaponName));
    }
    

    }

使用方法

  • 您可以将类分配为构建和更新玩家工具栏的责任。
  • 在BuildToolbar方法中,使用WeaponRepository中的AvailableWeapons方法为每个武器创建一个按钮。
  • 在此类中,订阅玩家事件。这样,您可以根据需要启用/禁用/突出显示活动武器。

注意:为简洁起见,我省略了WeaponButton。

public class WeaponToolbarBuilder
{
    private readonly Player _player;
    private readonly WeaponRepository _weaponRepository;
    private List<WeaponButton> _buttons = new List<WeaponButton>();

    public WeaponToolbarBuilder(Player player, WeaponRepository weaponRepository)
    {
        _player = player;
        _weaponRepository = weaponRepository;

        _player.ActiveWeaponChanged += _player_ActiveWeaponChanged;
        _player.WeaponGrabbed += _player_WeaponGrabbed;
    }

    void _player_WeaponGrabbed(object sender, WeaponEventArgs e)
    {
        var weaponButton = _buttons.Where(button => button.WeaponName == e.Weapon.Name).First();
        weaponButton.Enable();
    }

    void _player_ActiveWeaponChanged(object sender, WeaponEventArgs e)
    {
        var currentActiveButton = _buttons.Where(button => button.Highlighted).First();
        currentActiveButton.Highlight(false);

        var newActiveButton = _buttons.Where(button => button.WeaponName == e.Weapon.Name);
        newActiveButton.Highlight(true);
    }

    public void BuildToolbar() { ... }
}

1

定义一个结构体或类类型,其中包含所有不同武器的属性。创建一个长度为40的武器类型数组,并将其存储为玩家类型的属性。定义一个int枚举,用作命名索引到武器数组中。手枪=0,霰弹枪=1等等。在代码中使用这些枚举名称来索引武器数组。

当你需要按字符串名称查找武器时,你可以通过扫描武器数组来查找与你要查找的名称匹配的武器。捕获该武器的索引并放弃字符串名称,以便所有进一步的操作都是通过索引数组进行的。线性扫描40个项目的代价不会明显。

如果您确实绝对需要避免扫描列表以查找匹配名称,则可以构造一个字典以提供快速按名称查找的功能。您希望字典条目返回特定玩家的武器,因此您需要为每个玩家存储不同的字典实例,并将其存储在玩家对象的字段中。如果简单的扫描足够(几乎肯定是),则不要费心使用字典方法。

无论您将武器类型定义为结构体还是类,它都是一种结构化类型,并且每次使用它时都会创建此类型的实例。每个玩家有40个实例。类和结构体之间的主要区别在于内存分配的位置/方式以及赋值是否复制值。如果您小心地从数组中不将武器分配到本地武器变量中,并且不使用上面的字典或执行任何需要共享对同一武器数据的引用的操作,则结构体应该可以胜任。如有疑问,请使用类。
当玩家离开时,您不必担心武器被处理 - 只要武器数组是保持长期引用武器的唯一事物,那么当玩家被处理时,它们都将被处理。适当的封装和最小化不同对象之间的交叉引用将有所帮助。
如果您确定您选择的武器始终具有相同的变量和相同的操作(方法),则可以通过单个武器类型来获取所有武器,该类型可以是结构体或类。
如果您开始涉及特殊情况,例如此操作对该武器的行为不同,或者此武器具有其他武器不需要的附加数据,则应考虑定义基础武器类并根据需要进行专业化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接