如何创建接收相同参数的不同Bullet子类实例?(东方风格游戏)
解决方案:基于工厂模式实现动态子弹生成与冷却管理
核心思路是用抽象工厂+字典注册的方式,把子弹类型、创建逻辑、冷却时间绑定在一起,让角色只需要通过子弹标识(比如"A""B")就能获取冷却时间并触发子弹创建,彻底解耦角色与具体子弹类。
1. 重构抽象基类,统一子弹接口
先把所有子弹的共性抽象出来,强制子类实现自身的冷却时间:
// 所有子弹的抽象基类 public abstract class Attack { public Texture2D Texture { get; protected set; } public Vector2 Position { get; protected set; } public float Rotation { get; protected set; } public float Speed { get; protected set; } // 每个子弹必须自己定义冷却时间 public abstract float Cooldown { get; } protected Attack(Texture2D texture, Vector2 position, float rotation, float speed) { Texture = texture; Position = position; Rotation = rotation; Speed = speed; } public abstract void Update(GameTime gameTime); }
修改你的BulletA子类,实现抽象属性:
public class BulletA : Attack { // 子类自身的冷却时间(这里用毫秒单位,你可以根据需求调整) public override float Cooldown => 500f; public BulletA(Texture2D texture, Vector2 position, float rotation, float speed) : base(texture, position, rotation, speed) { } public override void Update(GameTime gameTime) { // 子弹移动逻辑示例 Position += new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation)) * Speed * (float)gameTime.ElapsedGameTime.TotalSeconds; } }
2. 实现子弹工厂类,统一管理创建与冷却
创建一个静态工厂类,负责注册所有子弹类型,提供获取冷却、创建实例的方法:
public static class BulletFactory { // 存储子弹标识对应的创建逻辑和冷却时间 private static readonly Dictionary<string, (Func<Texture2D, Vector2, float, float, Attack> Creator, float Cooldown)> _bulletRegistry = new(); // 注册子弹类型,游戏初始化时调用 public static void RegisterBullet<T>(string bulletId) where T : Attack { // 用反射创建子弹实例的委托 Func<Texture2D, Vector2, float, float, Attack> creator = (tex, pos, rot, spd) => (T)Activator.CreateInstance(typeof(T), tex, pos, rot, spd); // 临时创建实例获取冷却时间(如果是常量也可以直接读取,这种方式更通用) using var tempInstance = (T)Activator.CreateInstance(typeof(T), null, Vector2.Zero, 0f, 0f); float cooldown = tempInstance.Cooldown; _bulletRegistry[bulletId] = (creator, cooldown); } // 根据标识获取冷却时间 public static float GetCooldown(string bulletId) { if (_bulletRegistry.TryGetValue(bulletId, out var data)) { return data.Cooldown; } throw new KeyNotFoundException($"找不到标识为{bulletId}的子弹类型"); } // 根据标识创建子弹实例 public static Attack CreateBullet(string bulletId, Texture2D texture, Vector2 position, float rotation, float speed) { if (_bulletRegistry.TryGetValue(bulletId, out var data)) { return data.Creator(texture, position, rotation, speed); } throw new KeyNotFoundException($"找不到标识为{bulletId}的子弹类型"); } }
3. 初始化工厂,注册所有子弹类型
在游戏启动阶段(比如Game.LoadContent())注册你需要的子弹:
// 注册BulletA,标识为"A" BulletFactory.RegisterBullet<BulletA>("A"); // 后续新增BulletB时,直接注册即可 // BulletFactory.RegisterBullet<BulletB>("B");
4. 修改Player类,实现动态切换与冷却控制
Player不再依赖具体子弹类,只需要持有子弹标识列表,通过工厂获取冷却时间:
public class Player : Character { // 存储可切换的子弹标识,比如可以从配置文件读取 private List<string> _bulletTypeIds = new List<string> { "A" }; private float _shotCoolDown = 0f; // 角色配置的子弹速度 public float BulletSpeed { get; set; } = 200f; protected override void Update(GameTime gameTime) { var kstate = Keyboard.GetState(); if(kstate.IsKeyDown(Keys.F)) { _shotCoolDown -= (float)gameTime.ElapsedGameTime.TotalSeconds; if (_shotCoolDown <= 0) { // 用当前选中的子弹标识射击 Shoot(_bulletTypeIds[0]); } } base.Update(gameTime); } public void Shoot(string bulletTypeId) { // 从工厂获取冷却时间,转成秒(如果Cooldown是毫秒的话) _shotCoolDown = BulletFactory.GetCooldown(bulletTypeId) / 1000f; // 触发事件,传递子弹标识和创建所需的参数 OnSpriteEvent(new CharacterEventArgs { BulletTypeId = bulletTypeId, SpawnPosition = this.Position, Rotation = this.Rotation, Speed = this.BulletSpeed }); } } // 修改事件参数类,传递创建子弹的必要信息 public class CharacterEventArgs : SpriteEventArgs { public string BulletTypeId { get; set; } public Vector2 SpawnPosition { get; set; } public float Rotation { get; set; } public float Speed { get; set; } }
5. 修改GameEngine的事件处理器,创建子弹实例
在事件处理器中通过工厂创建子弹,交给BulletManager管理:
private void PlayerEventHandler(object sender, SpriteEventArgs args) { var player = sender as Player; var characterArgs = args as CharacterEventArgs; // 加载对应子弹的纹理(这里假设你有统一的资源管理) Texture2D bulletTexture = Globals.Content.Load<Texture2D>($"Bullets/Bullet{characterArgs.BulletTypeId}"); // 用工厂创建子弹实例 Attack bullet = BulletFactory.CreateBullet( characterArgs.BulletTypeId, bulletTexture, characterArgs.SpawnPosition, characterArgs.Rotation, characterArgs.Speed ); // 添加到BulletManager BulletManager.AddBullet(bullet); }
核心优势
- 彻底解耦:Player不需要知道任何具体子弹类的存在,只需要子弹标识
- 易于扩展:新增子弹类型时,只需要写子类并在工厂注册,无需修改原有逻辑
- 冷却统一管理:冷却时间由子弹自身定义,工厂负责统一读取,避免硬编码
内容的提问来源于stack exchange,提问作者CrimsonVengeance
相关产品推荐
相关产品推荐

