How to Build a Godot Weapon System With Mouse Aiming

Want a Godot weapon system that lets your player and enemies share the same weapons? Drawing on Zenva’s experience teaching coding and game development through over 400 courses, this tutorial walks you through reusable item classes, a sword attack animation, and a controller for equipping, aiming, and attacking.

This tutorial assumes you know basic GDScript and have the project’s character setup and sword asset available.

Download Project Files

You can keep reading the free tutorial below, or follow the full guided project with the Intermediate Godot Mini-Degree, which includes the course, project files, and full project.

Make a Complete Card Battler in Godot e1788415191322 - How to Build a Godot Weapon System With Mouse Aiming
FREE GODOT COURSE
LEARN GODOT, UNITY, UNREAL & MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Build the Foundation of Your Godot Weapon System

Start with a modular design: a sword should belong to a generic Character, not specifically to a player or an enemy. That lets both character types use the same weapon behavior without separate weapon code.

The Weapon System’s Inheritance Hierarchy

Use inheritance to separate shared item behavior from specialized weapon behavior:

Class inheritance diagram showing EquipItem at the top branching into Weapon and Shield, with Weapon further branching into MeleeWeapon and RangedWeapon.

  • EquipItem tracks the use rate, aim angle, owning character, and functions for equipping and using an item.
  • Weapon inherits from EquipItem and holds weapon-specific data such as attack range.
  • MeleeWeapon inherits from Weapon and is intended for direct damage to overlapping characters.
  • RangedWeapon also inherits from Weapon, with attacks that spawn projectiles.
  • Shield inherits directly from EquipItem for defensive behavior.

Here, you’ll build the shared item foundation and the sword’s equipping, aiming, and attack animation. Damage handling, ranged weapons, and shields are outside this tutorial.

Creating the EquipItem Script

In the FileSystem dock, create a Weapons folder inside Scripts. Create equip_item.gd in that folder.

Extend Node2D so the item can sit inside a character scene. Give it a global class_name so other scripts can reference its type:

class_name EquipItem
extends Node2D

Defining the Item Properties

Add the properties shared by every equippable item:

  • use_rate sets the interval between uses, in seconds.
  • last_use_time stores the timestamp of the last successful use.
  • aim_angle stores the target rotation in radians.
  • owner_character references the Character holding the item.
  • can_use lets you enable or disable item use.
@export var use_rate : float = 0.5
var last_use_time : float
var aim_angle : float
var owner_character : Character
var can_use : bool = true

Laying Out the Core Functions

Next, lay out the functions that control the item’s behavior. You’ll fill in the shared logic first and leave item-specific actions for subclasses.

  • _process(delta) smoothly rotates the item every frame.
  • set_aim_direction(aim_dir) receives an aiming direction from the owning character.
  • _equip() and _unequip() provide hooks for subclasses to customize equipping and unequipping.
  • _try_use() checks whether the item can be used and returns a bool.
  • _use() performs the item’s action, such as an attack animation.
func _process (delta : float):
	pass

func set_aim_direction (aim_dir : Vector2):
	pass

func _equip ():
	pass

func _unequip ():
	pass

func _try_use () -> bool:
	pass

func _use ():
	pass

Implementing _try_use

The character calls _try_use() when it wants to use an item. This function checks the can_use flag and cooldown before allowing the action:

  1. Return false if can_use is false.
  2. Return false if the time since the last use is less than use_rate.
  3. Otherwise, record the current Unix time, call _use(), and return true.
func _try_use () -> bool:
	if not can_use:
		return false

	if Time.get_unix_time_from_system() - last_use_time < use_rate:
		return false

	last_use_time = Time.get_unix_time_from_system()
	_use()

	return true

Leave _equip(), _unequip(), and _use() empty in this base class. Subclasses override them with their own behavior, such as a sword playing an attack animation or a bow spawning a projectile.

Rotating Toward the Aim Angle in _process

Use lerp_angle in _process to turn smoothly toward aim_angle instead of snapping to it:

func _process (delta : float):
	global_rotation = lerp_angle(global_rotation, aim_angle, 40 * delta)

Converting the Aim Vector Into an Angle

The owning character supplies a direction: toward the mouse for the player, or toward the player for an enemy. Convert that Vector2 into an angle in radians with angle():

func set_aim_direction (aim_dir : Vector2):
	aim_angle = aim_dir.angle()

An Abstract Base Class

You won’t attach EquipItem directly to a specific object. Treat it as a base class that more specialized item classes inherit and extend. Next, you’ll add the weapon and melee-weapon layers.

Create the Sword Scene and Attack Animation

With the item foundation in place, you can create the weapon scripts, build the sword scene, and animate its attack.

Creating the Base Weapon Script

Create weapon.gd in the Weapons folder. This class inherits the properties and functions of EquipItem and adds shared weapon data rather than handling damage itself:

class_name Weapon
extends EquipItem

@export var range : float = 10.0

The range value is intended to help enemies decide how close to get before attacking. You won’t use it directly yet.

Creating the Melee Weapon Script

In the same folder, create melee_weapon.gd. This class extends Weapon with damage and hit-force values, plus placeholders for the animation player and hitbox references you’ll add later:

class_name MeleeWeapon
extends Weapon

@export var damage : int
@export var hit_force : float

# animation
# hitbox

func _use():
	pass

func detect_hits():
	pass

_use() will perform the attack, while detect_hits() is reserved for checking characters inside the hitbox. The hit_force value represents the force applied to a hit character: a light sword might push gently, while a heavier axe could apply more force.

Overriding Functions and the super Keyword

Declaring _use() in MeleeWeapon overrides the version inherited from EquipItem. Calling it on a MeleeWeapon instance runs the melee weapon’s version.

If you need the parent’s implementation too, call it with super. This example runs the parent function before the child-specific code:

func _use():
	super._use()
	# code specific to MeleeWeapon goes here

For now, keep _use() as pass. You’ll replace it with animation playback after connecting the player controller.

Building the Sword Scene

Now create the sword that your characters can use:

  1. Create a new scene with a Node2D root named Weapon_Sword.
  2. Attach melee_weapon.gd to the root.
  3. In the Inspector, set Damage to 1 and Hit Force to 150.

The Weapon_Sword Node2D is selected in the scene tree with the melee_weapon.gd script attached, and the Inspector shows its exported Damage, Hit Force, and Range values.

Adding the Sword Sprite

Add a Sprite2D child named Sprite. Drag the sword texture, such as Item_Sword from the items folder, into its Texture property.

The weapon’s forward direction is right. Rotate the sprite to point right, then set its Position X value to 11 as a starting offset so it doesn’t sit directly on top of the character.

The sword sprite is rotated to point to the right and offset on the x-axis so it sits out from the center of the player character.

Adding the Hitbox

Use an Area2D to detect overlapping bodies and areas. Set up the sword’s hitbox as follows:

  1. Add an Area2D child to the weapon root and name it HitBox.
  2. Add a CollisionShape2D child to HitBox.
  3. Set its Shape to a new RectangleShape2D.
  4. Move and resize the rectangle around the sword’s tip, covering the area the blade will sweep through during its attack.

A cyan rectangular collision shape is positioned around the tip of the sword sprite, marking the area that will detect enemy hits.

This rectangle defines the area for hit detection. You’ll prepare the hitbox here, but the damage logic is not part of this tutorial.

Animating the Attack

Add an AnimationPlayer child to the weapon root. With it selected, open the Animation panel, choose Animation > New, and name the animation attack. Set its duration to 0.5 seconds.

Select Sprite and click the key icon beside its Position property. When prompted, click Create without enabling the reset track. This creates the position track for the attack.

The Animation panel shows the attack animation with a track for the sprite position and keyframes on the timeline, while the Inspector displays values for the currently selected keyframe.

Build a short jab: pull back, hold briefly, thrust forward, then return to the starting pose.

  1. The starting-position keyframe is inserted automatically at 0.0.
  2. Move the timeline cursor forward, for example to around 0.1 seconds. Right-click the track, choose Insert Key, and set that keyframe’s X value to a small negative number.
  3. Copy the pull-back keyframe and paste it slightly later to create a brief hold.
  4. Insert another keyframe further along with a larger positive X value, moving the sword beyond its starting position.
  5. Copy the original keyframe and paste it at the end so the sword returns to its idle pose.

Preview the animation in the editor. To make the forward motion faster, move the pull-back and thrust keyframes closer together.

The finished attack animation shows four keyframes on the sprite position track, laying out the pull-back, hold, jab, and return poses across half a second.

Connect Weapon Equipping, Aiming, and Attacking

The sword now has a scene and an animation. Next, add a controller that equips it, aims it toward the mouse, and requests an attack when you click.

Godot game running with the player holding the sword but not aiming it toward the mouse cursor.

Saving the Weapon Sword as a Scene

The controller will instantiate the sword from code rather than keep it as a fixed child of the player. Create a weapons folder inside scenes and save the sword as the reusable WeaponSword.tscn scene.

If the sword is still inside the player scene, drag its node into scenes/weapons to save it as a separate scene, then remove that fixed sword node from the player.

Godot Scene dock showing the WeaponSword node with its Sprite, HitBox, and AnimationPlayer children.

Adding the PlayerWeapons Node

Add a Node2D child to Player and name it PlayerWeapons. This is the attachment point for equipped weapons. Move it down slightly to sit around the center of the player’s torso.

Godot 2D viewport showing the PlayerWeapons Node2D positioned around the player character's torso.

Planning the Script Structure

Use a shared CharacterWeapons base class for equipping and unequipping, with specialized PlayerWeapons and EnemyWeapons subclasses.

Diagram showing the CharacterWeapons base with PlayerWeapons and EnemyWeapons subclasses branching from it.

The separation lets the player attack through mouse and keyboard input, while an enemy can attack based on distance to the player. You’ll implement the player-specific controller here.

The CharacterWeapons Script

Create CharacterWeapons.gd inside scripts/character. Extend Node2D and declare the starting weapon scene, current weapon, and owning character:

class_name CharacterWeapons
extends Node2D

@export var weapon_to_equip : PackedScene

var current_weapon : Weapon

@onready var character : Character = $".."

The controller node is a direct child of the character, so $".." gives you the parent character reference.

Add the functions below. _ready() equips the starting weapon if one is assigned. equip_weapon() removes an existing weapon, instantiates the new scene, adds it as a child, aligns its position, assigns its owner, and calls _equip().

unequip_weapon() returns immediately when there is no current weapon. Otherwise, it calls _unequip() and queues the weapon node for deletion.

func _ready ():
	if weapon_to_equip:
		equip_weapon(weapon_to_equip)

func equip_weapon (weapon_scene : PackedScene):
	if current_weapon:
		unequip_weapon()

	current_weapon = weapon_scene.instantiate()
	add_child(current_weapon)
	current_weapon.global_position = global_position

	current_weapon.owner_character = character
	current_weapon._equip()

func unequip_weapon ():
	if not current_weapon:
		return

	current_weapon._unequip()
	current_weapon.queue_free()

Don’t attach this base script directly to a node. The specialized controller scripts inherit from it.

The PlayerWeapons Script

Create PlayerWeapons.gd in scripts/character. It extends CharacterWeapons and handles player input:

class_name PlayerWeapons
extends CharacterWeapons

func _process (delta : float):
	var mouse_pos : Vector2 = get_global_mouse_position()
	var mouse_dir : Vector2 = global_position.direction_to(mouse_pos)

	if current_weapon:
		current_weapon.set_aim_direction(mouse_dir)

		if Input.is_action_just_pressed("attack"):
			current_weapon._try_use()

Each frame, the script finds the direction from the controller’s global position to the mouse. If a weapon is equipped, it updates that weapon’s aim direction. Pressing the attack action calls _try_use(), which checks the cooldown before allowing an attack.

Hooking Up the PlayerWeapons Node

Connect the controller in the editor:

  1. Select the player’s PlayerWeapons node.
  2. Attach PlayerWeapons.gd.
  3. Drag WeaponSword.tscn into the Inspector’s Weapon To Equip slot.

Run the game. The sword is instantiated at startup and rotates to follow the mouse.

Godot game running with the sword instantiated on the player and rotating to follow the mouse cursor.

Implementing the Melee Attack

Aiming is connected, but the melee weapon’s _use() still needs an action. Open the sword scene and its attached melee weapon script. Add references to AnimationPlayer and HitBox, then replace the empty _use() with animation playback:

class_name MeleeWeapon
extends Weapon

@export var damage : int
@export var hit_force : float

@onready var anim : AnimationPlayer = $AnimationPlayer
@onready var hit_box : Area2D = $HitBox

func _use ():
	anim.play("attack")

The inherited _try_use() calls this function only after the cooldown has passed. Rapid clicks therefore won’t restart the attack every frame.

Testing the Attack

Run the game again. The sword should equip automatically, follow the mouse, and play its attack animation when you left-click. Repeated clicks trigger the animation only once every 0.5 seconds, matching use_rate.

Godot game running with the player character swinging the sword in its attack animation near an enemy.

The sword can aim and animate, but it doesn’t deal damage yet. Hit detection and applying damage are the next stage of the project.

Recap and Next Steps

You now have a reusable weapon foundation and a sword that the player can equip, aim, and animate. In this tutorial, you covered:

  • Shared item properties, smooth aiming, and a use-rate cooldown in EquipItem.
  • Weapon inheritance, a sword sprite and hitbox, and a jab animation.
  • Equipping and unequipping through CharacterWeapons.
  • Mouse aiming and attack input through PlayerWeapons.

With these pieces connected, you’re ready to continue with hit detection and damage handling.

To follow the full project and build your Godot skills through a guided path, explore the Intermediate Godot Mini-Degree.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image - How to Build a Godot Weapon System With Mouse Aiming

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.