Modular weapon system roadblock

Godot Version

4.4

Question

`So i’m attempting to make a modular weapon system, where you start with 2 simple weapons, you can swap between those weapons and then at the end of a round you can buy/replace those 2 weapons, so think similar to helldivers 2 system as the most recent reference i can think of. I want a combination of melee and shooting weapons and as such I created a weapon resource that houses all of the weapon stats including the .TSCN for each weapon.

My current predicament: With the resource holding the stats of each gun, but also the gun scene, how would i pass on those stats onto that gun scene? The biggest issue for me is the guns shoot objects instead of a raycast, since they’re medieval style weapons like bow and arrows so they instantiate bolts`

weapon controller script, it’s worth noting the input event in this doesn’t do anything yet, that’s the crossbow script:

extends Node3D

@export var WEAPON_TYPE: WeaponsResource
@export var current_weapon_model: Node3D


@onready var timer: Timer = $Timer

var current_weapon: Node3D


func _ready():
	load_weapon()

func _input(event):
	var weapon = current_weapon.get_children()
	if Input.is_action_just_pressed("hit"):
		if WEAPON_TYPE.melee == false:
			get_node("shoot")
		
	if event.is_action_pressed("WeaponSlot1"):
		current_weapon.queue_free()
		WEAPON_TYPE = load("res://Scenes/Weapons/Axe/axe_resource.tres")
		load_weapon()
	if event.is_action_pressed("WeaponSlot2"):
		current_weapon.queue_free()
		WEAPON_TYPE = load("res://Scenes/Weapons/Crossbow/crossbow_resource.tres")
		load_weapon()

func load_weapon():
	if WEAPON_TYPE != null:
		if current_weapon_model and WEAPON_TYPE.WeaponModel:
			current_weapon = WEAPON_TYPE.WeaponModel.instantiate()
			current_weapon_model.add_child(current_weapon)
			
			#if WEAPON_TYPE.melee == true:
				#current_weapon.hitbox_component.damage = WEAPON_TYPE.damage
			
			current_weapon.position = WEAPON_TYPE.position
			current_weapon.rotation_degrees = WEAPON_TYPE.rotation
			current_weapon.scale = WEAPON_TYPE.scale

weapon resource script:

extends Resource
class_name WeaponsResource

@export var name: StringName
@export var melee: bool
@export_category("Weapon Orientation")
@export var position: Vector3
@export var rotation: Vector3
@export var scale:= Vector3(1,1,1)

@export_category("Visual Settings")
@export var WeaponModel: PackedScene
@export_category("Shooting Stats")
@export var damage: float
@export var spread: float
@export var reload_speed: float
#this will be how many mags are left that hold the reserve
@export var mag_amt: int
#this will be how many the current mag has: 4/10 shots
@export var current_mag_ammo: int #would be 4
@export var max_mag_ammo: int #would be 10
#in a shotgun system how many get shot at once, most will be 1
@export var bullet_amt: int = 1
@export var bullet_range: int = 40
@export var automatic: bool = false

crossbow script:

extends Node3D


@onready var gun_barrel: RayCast3D = $RayCast3D

var bullet = load("res://Scenes/Weapons/Crossbow/bolt.tscn")
var instance


func _input(event):
	if event.is_action_pressed("hit"):
		shoot()
	
func shoot():
	instance = bullet.instantiate()
	instance.global_transform = gun_barrel.global_transform  # Set full world transform

	# Add to the main scene tree (prevents it from moving with the player)
	get_tree().get_root().add_child(instance)

	# Apply initial force/velocity
	instance.linear_velocity = -gun_barrel.global_transform.basis.z * instance.speed

The crossbow has the projectile it shoots on the crossbow script. If you place the projectile on the Resource script instead, then the weapon will be able to get the projectile from its resource to instantiate.

1 Like

Very interesting, i never thought of doing that but I’ll try that out to see how that works but it sounds promising thank you for your input! I guess i never thought of that because i’m utilizing the crossbow to tell the bolt where to go with the crossbow’s raycast

1 Like

One alternative method I’m thinking about also is initializing the weapon stats in the load weapon script and then keep the crossbow almost the same but manage the logic there, and just have each weapon have an initialize_wepaon function, but i’m not sure if that’s just overly cumbersome.

func load_weapon():
	if WEAPON_TYPE != null:
		blah blah blah...
			
			if current_weapon.has_method("initialize_weapon"):
				print("initializes")
				current_weapon.initialize_weapon(WEAPON_TYPE)

then in the crossbow:

extends Node3D
var spread: float

func initialize_weapon(weapon_data: WeaponsResource):
	spread = weapon_data.spread
	and so on...
1 Like

I got something like this setup for a boomer shooter prototype. I used a similar style of having the weapon scene/script holds the functions and then anytime a weapon swap happens, it reloads all the stats to update damage, rate of fire, and any other variables to match the weapon resource.

It seemed like a great modular approach so I don’t think you can go wrong with it. It also seemed pretty easy to add in different weapon gimmicks and traits. One of those composition over inheritance situations.

Have fun!

1 Like