RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1591131
Accepted
Данил
Данил
Asked:2024-08-18 22:14:22 +0000 UTC2024-08-18 22:14:22 +0000 UTC 2024-08-18 22:14:22 +0000 UTC

请建议选择项目的正确架构。统一

  • 772

我已经绞尽脑汁想了两天了。简而言之,我正在制作一款游戏,并且针对武器和生命值实现了 2 个独立的逻辑。 (我试图使它们通用)这个概念是不存在这样的设备。该武器有一定数量的弹药,仅此而已。使用后,它会被扔掉,你会寻找另一件。我遵循这个逻辑。有一把武器,只有武器知道里面有多少子弹;玩家不应该知道任何事情;玩家的任务是在拿起武器并且有子弹的情况下重现射击。因此,任务是通过选择将哪些弹药筒添加到所使用的武器中来增加奖金。问题听起来是这样的。我不知道如何向武器添加弹药,以便拾取的弹药事先不知道武器(其他一切都一样)。我不想违反 SRP。请给我一些建议,让我朝哪个方向前进。我尝试应用访客模式并意识到它不属于这里。我也考虑过事件总线,但在我看来,它会导致同样的 SRP 违规,或者我在错误的方向上思考。

c#
  • 2 2 个回答
  • 68 Views

2 个回答

  • Voted
  1. Vladimir
    2024-08-18T23:06:59Z2024-08-18T23:06:59Z

    像这样的东西:

    public enum WeaponType { Pistol, Rifle, GrenadeLauncher }
    
    public class Ammo {
        public int Amount { get; set; }
        public WeaponType WeaponType { get; set; }
    }
    
    public class Weapon {
        int _ammo;
    
        public WeaponType WeaponType {get; }
    
        public Weapon(WeaponType weaponType, int initialAmmo) {
            WeaponType = weaponType;
            _ammo = initialAmmo;
        }
        
        public void AddAmmo(int ammo) => _amount = ammo;
        
        public void Shoot() {
            if (_ammo == 0)
                return;
                
            _ammo--;
        }
    }
    
    public class Player {
        Weapon _currentWeapon;
        
        public void OnWeaponPickedUp(Weapon newWeapon) => newWeapon = _currentWeapon;
        
        public void OnAmmoPickedUp(Ammo ammo) {
            if (_currentWeapon.WeaponType != ammo.WeaponType)
                return;
                
            _currentWeapon.AddAmmo(ammo.Amount);
        }
    }
    

    Weapon 类对 Ammo 类一无所知。虽然他有这样的权利,但既然谁用了墨盒呢?所以说是public void AddAmmo(Ammo ammo)完全有可能做到的。

    Ammo 类对 Weapon 类一无所知,也不应该知道。如果 WeaponType 令人困惑,那么问题是,WeaponType 与 OOP 有什么关系? WeaponType 是data。

    Player 类知道两者。既然他也有这个权利——谁拿起武器?谁来捡弹药?如果在你的情况下,玩家负责其他事情,那么创建几个类,例如 PlayerMovement 和 PlayerArsenal,甚至 Player、WeaponPicker 和 AmmoPicker,这样就不会违反 SRP。

    SRP 并不是说​​一个类中的代码不应该接触另一个类。这是关于我们在一堂课上留下了什么以及我们在另一堂课上带了什么。也就是说,关于逻辑和数据应该或不应该在一起。


    访问者模式与问题根本无关,因为它是一种主要用于处理遗留代码的模式,当您需要绕过(例如,在循环中)彼此不正式相关的对象时,深入探讨其实施细节。


    事件、命令、反应性及其各种实现是为对象之间相互通信而创建的,是的。但在大多数情况下,它们的任务是当对象 A 知道对象 B 时断开双向连接,但反之则不然,并且通信必须在两个方向上进行。然后,一个对象调用另一个对象的方法,并以相反的方向进行通信,订阅更改。是的,有两个方向的通信实现,但为什么在这个特殊情况下会这样呢?这要解决什么实际问题?


    是的,如果我们正在谈论减少代码一致性,那么我们可以使用接口来代替具体类型:

    public class Ammo : IAmmo { ... }
    
    public class Weapon : IWeapon {
        public void AddAmmo(IAmmo ammo) { ... }
    }
    
    public class Player : IPlayer {
        public void OnWeaponPickedUp(IWeapon newWeapon)
        public void OnAmmoPickedUp(IAmmo ammo) {
    }
    

    在这种情况下,从 OOP 的角度来看,Weapon 类不知道 Ammo 类,并且 Player 类不知道 Ammo 类或 Weapon 类。这样的抽象有必要吗?对于小项目 - 通常不是。对于一个大的 - 好吧,如果使用依赖注入,那么也许。嗯,Unity的可序列化字段对接口不友好。因此,如果您有 MonoBehaviours 并且通过 Inspector 连接依赖项,那么绝对不会。

    • 3
  2. Best Answer
    Yaroslav
    2024-08-18T23:52:22Z2024-08-18T23:52:22Z

    可用的:

    • 作为受控角色的单位
    • 角色可以拿起并给予攻击动力的拾取武器
    • 拾取弹药筒,如果所拿武器类型与其对应,则会拾取弹药筒

    正确的?不是真的!还有很多持续的责任!有人拿起一些东西,有人把它全部放在某个地方,有人以某种方式使用它,有人进行可视化。物流也是演员,往往由几个环节组成,不仅在代码架构上,在生活中也是如此。


    尽管武器和弹药筒不是库存物品,而是你拿起、使用和扔掉的消耗品,但实际上什么都不应该改变。抽象的要点是编写具有单一职责的小类来执行功能,并且不涉及整体实现的细节。

    该单位有能力装备某些东西,并不关心它是什么。最主要的是,这个废话处于某种抽象之下IHandsTakeable,它有一组用于输入和命令的方法。我不在乎它是武器还是你拖着一个盒子,我不在乎它来自哪里,从库存还是从地板上。

    public interface IHandsTakeable
    {
        event Action DropRequired;
        void OnTake (object master);
        void Drop ();
        void StartUse ();
        void StopUse ();
    }
    
    public class Unit : MonoBehaviour
    {
        public IHandsTakeable HandsItem { get; private set; };
    
        public void TakeHandsItem (IHandsTakeable item)
        {
            DropHandsItem();
            HandsItem = item;
            HandsItem.OnTake(this);
        }
    
        public void DropHandsItem ()
        {
            HandsItem?.Drop();
            HandsItem = null;
        }
    
        public void StartAttack ()
            => HandsItem?.StartUse();
    
        public void StopAttack ()
            => HandsItem?.StopUse();
    }
    

    我写了一篇关于物品选择的大文章。写信就足够了IDropCollector,他的唯一责任就是弄清楚如何处理这种特定类型的战利品。

    public interface IAmmoUser 
    {
        string RequiredType { get; }
    
        void AddAmmo (int amount);
    }
    
    public class AmmoCollector : IDropCollector
    {
        private readonly Unit _unit;
    
        public AmmoCollector (Unit unit)
        {
            _unit = unit;
        }
    
        public bool TryCollect (ICollectobleDrop target)
        {
            if (target is CollectableAmmo ammoTarget &&
                _unit.HandsItem != null &&
                _unit.HandsItem is IAmmoUser au &&
                au.RequiredType == ammoTarget.Type)
            {
                au.AddAmmo(au.Amount);
                return true;
            }
            return false;
        }
    }
    

    附注RequiredType这里的string键可能是"pistol"or "9x18mm",但在检查器中最好使用enum,这总是可能的.ToString(),以免手动编写字符串文字。


    但如果它是完全面向对象的,那么武器不应该有任何弹药筒总数,它应该只根据命令开火。它必须有一个委托(Func<bool> shotCondition)来检查是否可以射击或将射击文件(播放空弹匣的声音)。如果shotCondition == null,那么武器将随心所欲地射击,例如在小怪手中。在其他情况下,这是一种检查库存中的物品或其他地方的数量的方法。武器的最大容量是弹匣中的子弹数量,这决定了何时需要重新装弹。也就是说,IHandsTakeable这个的本质不是Weapon实现这个接口的,而是它里面的包装器Weapon是它可以使用的墨盒数量。

    public class TakeableWeapon : IHandsTakeable, IAmmoUser 
    {
        private int _ammo;
        
        public TakeableWeapon (Weapon weapon) 
        {
            Weapon = weapon;
        }
    
        public Weapon { get; }
    
        public string RequiredType => Weapon.AmmoType;
    
    
        public void OnTake (object master)
        {
            // только у мастера умеющего подбирать лут, будут конечные патроны
            if (master is Component cm)
                SetInfinityAmmo(cm.GetComponent<DropCollector>() == null);
            else
                SetInfinityAmmo(true);
        }
    
    
        public void StartUse () => Weapon.StartShoot();
    
        public void StopUse () => Weapon.StopShoot();
    
        public void AddAmmo (int amount) => _ammo += amount;
    
        private bool HaveAmmo () => _ammo > 0;
    
        private void OnShot () => _ammo--;
    
        private void SetInfinityAmmo (bool infinity)
        {
            if (infinity)
            {
                Weapon.ShotCondition = null;
                Weapon.Fired -= OnShoot;
            }
            else
            {
                Weapon.ShotCondition = HaveAmmo;
                Weapon.Fired -= OnShoot;
            }
        }
    }
    

    也就是说,该类Weapon应该是这样的:如果它是从库存中发射弹药筒的库存物品,则它不会以任何方式改变,因为它具有相同的责任。


    每个人都通过接口相互沟通,没有人依赖任何人。

    • 作为一个IHandsTakeable,Unit它可以与任何东西互动,甚至捡起一块鹅卵石并扔掉它。顺便说一句,它负责可视化UnitView,并且IHandsTakeable可能包含使用它时所需的动画的名称。
    • DropCollector你甚至可以把它交给一个吸尘器,它有自己的吸尘器,IDropCollector它会像垃圾一样不加区别地销毁所有物体。
    • TakeableWeapon您甚至可以将其安装在炮塔或装饰品上,与所有者没有任何联系,并且在破坏后,该塔可以由角色拾取。
    • 1

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5