Unless you want your ship to attack enemies by ramming into it with a plasma drill, the projectile that got emitted should probably move forwards over time until it hits something. We use motion resources that combine to dictate how the projectile moves.
We start with a base class just like we did with projectiles and emitters. It’s a Resource, like a texture or audio file, with data that relates to the motion but can also call a function. All it needs is a reference to the projectile it’s affecting and a function that affects it. We call the function _update_movement
and pass in the current direction of travel and delta time. It returns a Vector2 - the actual movement it should effect based on this motion.
We can add all motions’ movements together to get a final intended movement.
src/ProjectileMotion/ProjectileMotion.gd
class_name ProjectileMotion extends Resource # The projectile this motion should affect var projectile: Projectile func _update_movement(_direction: Vector2, _delta: float) -> Vector2: return Vector2.ZERO
The first implementation is going to be a straight motion. It has an exposed travel_speed
property, and its _update_movement
function returns the product of speed, delta and direction.
src/ProjectileMotion/Motions/StraightMotion.gd
class_name StraightMotion extends ProjectileMotion export var travel_speed := 300.0 func _update_movement(direction: Vector2, delta: float) -> Vector2: return direction * travel_speed * delta
Now you can create a straight motion as a Resource in the base of your modular system directory. Right-click on the ProjectileMotions folder, select New Resource and find StraightMotion
to create it.
ProjectileMotions/StraightMotion.tres
You can add the motion to the weapons system via the inspector right away. However, if you intend to pick up motion modifiers and add them during gameplay, it’d be nice to have a helper function that prevents duplicates, because two straight motions mean double the speed!
In the ModularWeapon.gd
script, we create the add_motion
function. It compares script names with current motions and adds those that don’t already exist. An optional boolean checks if it should allow duplicates instead.
func add_motion(new_motion: ProjectileMotion, allows_duplicates := false) -> void: if not allows_duplicates: var has_motion := false for motion in projectile_motions: has_motion = new_motion.get_script() == motion.get_script() if has_motion: return projectile_motions.append(new_motion)
All that’s left now is to have a way to use those motions to affect projectiles. In the base projectile Projectile.gd
, the _update_movement
name is re-used from the motions. Its job is to go through each motion it has access to and return a vector that is the total of all its motions.
func _update_movement(delta: float) -> Vector2: var movement_vector := Vector2.ZERO if motions.empty(): return movement_vector for motion in motions: movement_vector += motion._update_movement(direction, delta) return movement_vector
In BasicProjectile.gd
, we use this new function to get the expected movement and use move_and_collide
to slide the projectile along. If we hit something, then we disappear. We’ll react with collisions next.
func _physics_process(delta: float) -> void: var movement := _update_movement(delta) var collision := move_and_collide(movement) if collision: queue_free()