Godot 4模块化武器系统架构优化问询:求改进建议
Godot 4模块化武器系统架构优化方案
当前核心问题
- 武器数据(Resource)与输入逻辑耦合:武器子类的
actions()直接处理输入判断,导致数据与行为绑定,难以复用和扩展 - 武器Resource直接操作场景树/强依赖Weapon_System:违背单一职责原则,Resource应仅存储数据,不应负责场景实例化、节点操作等逻辑
- 模块化不足,武器直接依赖外部节点:枪械直接调用Weapon_System的raycaster、action_timer,甚至操作场景根节点,后续扩展易出现兼容性问题(如持刀状态下弹药道具失效)
针对性改进方案
1. 分离武器数据与输入逻辑:事件驱动+输入统一管理
将输入判断从武器Resource中完全抽离,由Weapon_System统一监听输入并向当前武器转发事件指令,武器仅负责处理触发后的具体行为。
修改武器基类(Handheld_Weapon)
去掉actions(),定义抽象行为方法,让子类专注于实现武器功能:
class_name Handheld_Weapon extends Resource @export_group("Default") @export var weapon_name : String @export var weapon_scene : PackedScene @export var min_damage : int @export var max_damage : int @export_range(0,100) var weight_modifier : float # 修正原拼写错误 # 武器行为抽象方法,由子类实现 func on_primary_fire() -> bool: # 返回是否成功触发(如是否有弹药) return false func on_reload(): pass func on_enter(weapon_view: Node3D): # 传入武器场景实例,替代直接持有animator pass func on_exit(weapon_view: Node3D): pass
修改主武器系统(Weapon_System)
负责输入监听、事件转发、场景管理,统一处理射速等通用逻辑:
class_name Weapon_System extends Node @export var action_timer : Timer @export var raycaster : RayCast3D @export var hand_node : Node3D @export var weapon_inventory : Array[Handheld_Weapon] var inventory_index = 0 var current_weapon : Handheld_Weapon var current_weapon_view : Node3D # 持有当前武器的场景实例 func _ready(): if weapon_inventory.size() > 0: switch_to_weapon(weapon_inventory[inventory_index]) func _input(event): # 武器切换逻辑保留 if event.is_action_pressed("Scroll_up"): inventory_index = min(inventory_index+1, weapon_inventory.size()-1) switch_to_weapon(weapon_inventory[inventory_index]) elif event.is_action_pressed("Scroll_down"): inventory_index = max(inventory_index-1, 0) switch_to_weapon(weapon_inventory[inventory_index]) func _physics_process(_delta): if current_weapon == null: return # 统一处理输入事件 if Input.is_action_pressed("Left_Click"): handle_primary_fire() if Input.is_action_just_pressed("R_key"): current_weapon.on_reload() func handle_primary_fire(): # 统一管理射速逻辑 if action_timer.is_stopped(): var fire_success = current_weapon.on_primary_fire() if fire_success: action_timer.start() func switch_to_weapon(weapon): if current_weapon == weapon: return # 退出当前武器 if current_weapon != null: current_weapon.on_exit(current_weapon_view) await current_weapon_view.get_node("Animations").animation_finished current_weapon_view.queue_free() current_weapon = weapon # 由Weapon_System负责场景实例化 current_weapon_view = current_weapon.weapon_scene.instantiate() hand_node.add_child(current_weapon_view) # 通知武器进入状态,传入视图节点 current_weapon.on_enter(current_weapon_view) await current_weapon_view.get_node("Animations").animation_finished # 提供武器需要的公共服务,避免武器直接依赖节点 func perform_raycast() -> Dictionary: return { "is_colliding": raycaster.is_colliding(), "point": raycaster.get_collision_point(), "normal": raycaster.get_collision_normal() } func spawn_decal(decal_scene: PackedScene, position: Vector3, normal: Vector3): var decal = decal_scene.instantiate() get_tree().get_root().add_child(decal) decal.global_transform.origin = position if normal != Vector3.UP and normal != Vector3.DOWN: decal.look_at(position + normal, Vector3.UP) decal.transform = decal.transform.rotated_local(Vector3.RIGHT, PI/2.0) elif normal == Vector3.UP: decal.transform = decal.transform.rotated_local(Vector3.RIGHT, PI) decal.rotate(normal, randf_range(0, 2*PI)) get_tree().create_timer(5).timeout.connect(func(): decal.queue_free())
修改枪械子类(Handheld_Gun)
仅处理武器核心行为,不再涉及输入判断:
class_name Handheld_Gun extends Handheld_Weapon @export_group("Gun") @export_flags ("Automatic", "Semi-automatic") var action_type = 0 @export var magazine_max : int @export var current_magazine : int @export var rpm : int @export var bullet_decal : PackedScene var weapon_system : Weapon_System var animator : AnimationPlayer var rate_of_fire : float func _init(weapon_sys: Weapon_System): weapon_system = weapon_sys func on_enter(weapon_view: Node3D): animator = weapon_view.get_node("Animations") rate_of_fire = 1.0 / (rpm / 60.0) weapon_system.action_timer.wait_time = rate_of_fire animator.play("BringIn") print("Bring " + weapon_name + " in!") func on_exit(weapon_view: Node3D): animator = weapon_view.get_node("Animations") print("Putting " + weapon_name + " away...") animator.play("PutAway") func on_primary_fire() -> bool: # 半自动模式下限制连续触发 if action_type == 2 and not weapon_system.action_timer.is_stopped(): return false if current_magazine <= 0: print("Out of ammo!") return false current_magazine -= 1 print("you have " + str(current_magazine) + " rounds left") # 调用Weapon_System提供的服务 var ray_result = weapon_system.perform_raycast() if ray_result.is_colliding: weapon_system.spawn_decal(bullet_decal, ray_result.point, ray_result.normal) animator.seek(0) animator.play("Shoot") print("Bang!") return true func on_reload(): current_magazine = magazine_max print("Reloading!")
2. 解耦Resource与场景树:让Weapon_System成为服务提供者
- 武器Resource仅存储纯数据(伤害、射速、场景引用等),所有场景实例化、节点操作、定时器管理均由Weapon_System负责
- 武器需要的外部功能(射线检测、Decal生成)通过Weapon_System提供的接口调用,而非直接持有Weapon_System的节点引用
3. 提升模块化:引入全局状态管理
新增PlayerAmmoState模块统一管理玩家弹药数据,解决不同武器状态下道具兼容性问题:
class_name PlayerAmmoState extends Node @export var ammo_pools : Dictionary = {} # key: 弹药类型ID, value: 当前数量 func get_ammo(ammo_type: String) -> int: return ammo_pools.get(ammo_type, 0) func add_ammo(ammo_type: String, amount: int): ammo_pools[ammo_type] = ammo_pools.get(ammo_type, 0) + amount func consume_ammo(ammo_type: String, amount: int) -> bool: if get_ammo(ammo_type) >= amount: ammo_pools[ammo_type] -= amount return true return false
修改Handheld_Gun依赖该状态模块:
# 在Handheld_Gun中新增 @export var ammo_type : String = "9mm" var ammo_state : PlayerAmmoState func _init(weapon_sys: Weapon_System, ammo_state_ref: PlayerAmmoState): weapon_system = weapon_sys ammo_state = ammo_state_ref func on_primary_fire() -> bool: if action_type == 2 and not weapon_system.action_timer.is_stopped(): return false # 从全局状态扣减弹药 if not ammo_state.consume_ammo(ammo_type, 1): print("Out of ammo!") return false # ... 其余射击逻辑不变
此后道具系统只需修改PlayerAmmoState的数据,即可对所有武器生效,彻底解决扩展兼容性问题。
内容的提问来源于stack exchange,提问作者Dragon20C
相关产品推荐
相关产品推荐

