Resume

The Memory Book

https://jacob-aberdevine.itch.io/the-memory-book

About

The game features three distinct levels, two enemy types, and multiple ways to handle spawning enemies. I programmed the game myself and was involved in designing all the mechanics.

The final version of the game is quite different from its initial form during KomiksJam 2024, as its mechanics were not engaging enough. We realized the game needed major reworks, so we revisited it, hoping to compete in the ZTGK 2024 computer development team competition soon after. Our booth there attracted enough interest to earn us an award!

2D Mouse Input to 3D World Coordinates

The primary way to defend against enemies is by shooting a projectile that follows a parabolic trajectory toward a target position determined by the mouse. Fine-tuning this mechanic took some time to get right.

Below is a section of the player’s script responsible for detecting right mouse button clicks, determining the target position on the ground, and passing the coordinates to the projectile. In summary, the process:

  1. Reads the 2D mouse position on the screen.
  2. Casts a ray based on the camera’s position and orientation.
  3. Detects a collision with the floor and stores the exact impact point in 3D space.
  4. Spawns a projectile at the player’s position with the ray intersection as its target.
if Input.is_action_just_pressed("right_mouse"):
	if not $Cooldown.get_time_left() > 0 and power > 0:
		var mousePos = get_viewport().get_mouse_position()
		var rayLength = 1000
		var from = camera.project_ray_origin(mousePos)
		var to = from + camera.project_ray_normal(mousePos) * rayLength
		var space = get_world_3d().direct_space_state
		var rayQuery = PhysicsRayQueryParameters3D.new()
		rayQuery.from = from
		rayQuery.to = to
		rayQuery.collide_with_areas = true
		rayQuery.collision_mask = 1
		var result = space.intersect_ray(rayQuery)
		
		if result.get("position"):
			power -= 1
			stop_move = true
			$Cooldown.start()
			var init = projectile.instantiate()
			get_parent().get_node("Projectiles").add_child(init)
			init.global_position = self.global_position
			init.end_pos = result.position

To achieve parabolic movement, the projectile updates its position every frame using linear interpolation between two points moving along the parabola, completing the smooth motion in one second.

func _physics_process(delta):
	if finished:
		return
	if count < 1.0:
		count += 1.0 * delta
		var m1 = lerp(start_pos, height_pos, count)
		var m2 = lerp(height_pos, end_pos, count)
		global_position = lerp(m1, m2, count)
	else:
		explode()
		finished = true
		return