You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:12:09