DEV Community

Manasa Jayasri
Manasa Jayasri

Posted on

Behind the Scenes: Designing a Beat Saber-Style Game with Godot

Playing Beat Saber on the Meta Quest 2 is a total thrill ride, but honestly, more than 20 minutes gets me jittery with a headache that sticks around for a while. Still, that didn't stop me from diving into crafting my own version in Godot. I aimed to capture that same intense slice-and-dice rhythm. The challenge was to mimic the gameplay and to imbue it with my own technical interpretations.

The Build:

Laser Swords Setup

I started by crafting the laser swords for the VR controllers. Utilizing Godot's LineRenderer, I drew the swords extending along the negative z-axis of the controllers. Each sword's visibility toggled with the respective controller buttons—'A' for right and 'X' for left—making sure they interacted with game elements only when I wanted them to.

Line Renderer

Dynamic Cube Targets

Then, the cubes. These guys start off in the distance and zoom towards you, putting your reflexes and rhythm to the test. I programmed them to poof into thin air when hit by a sword—just as satisfying as the real Beat Saber.

Recentering the Player’s View

A cool feature to add was recentering the player's perspective. Hooked into OpenXR's "On Pose Recentered" signal, pressing and holding the Oculus button now snaps the player’s view directly to an incoming cube, centering them perfectly for the action.

# Handle OpenXR pose recentered signal
func _on_right_controller_button_pressed(name):
    if name == "primary_click":
        XRServer.center_on_hmd(XRServer.RESET_BUT_KEEP_TILT, true)
Enter fullscreen mode Exit fullscreen mode

Adding Audio Feedback

Sound effects were crucial. I went ahead and integrated funny boing sounds to accompany each cube destruction, because, why not? I'm just having fun. Digging through free sound databases provided me with the perfect clips that I then synced with the cube hits.

Adding Audio Feedback - Godot environment

Cube Spawning Mechanism

To keep the game lively, I implemented a CubeSpawner script. This little piece of code works to randomly generating cubes at intervals between 0.5 and 2.0 seconds, ensuring the gameplay stayed unpredictable and engaging.

extends WorldEnvironment

var cube = preload("res://SceneInstances/cube.tscn")
var second_cube = preload("res://SceneInstances/second_cube.tscn")
var timer = Timer.new()

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
    timer.wait_time = randf_range(0.5, 2.0)
    timer.autostart = true
    timer.one_shot = false
    add_child(timer)
    timer.timeout.connect(_on_Timer_timeout)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
    pass

func _on_Timer_timeout():
    var red_cube = cube.instantiate()
    red_cube.set_script(load("res://Scripts/CubeMovement.gd"))
    red_cube.get_child(0).set_collision_layer_value(10, true)
    add_child(red_cube)

    var blue_cube = second_cube.instantiate()
    blue_cube.set_script(load("res://Scripts/CubeOnMove.gd"))
    blue_cube.get_child(0).set_collision_layer_value(9, true)
    add_child(blue_cube)

Enter fullscreen mode Exit fullscreen mode

Efficient Game Object Management

I then tackled potential performance issues by ensuring cubes that weren't hit didn't linger—they auto-destruct to keep everything running smooth. And just like the original, only a sword of the right color can smash its corresponding cube.

The Result:

Building this Beat Saber-inspired game in Godot was fun. It pushed my understanding of VR capabilities in Godot. For now, that's where I'm pausing, but I'm excited to enhance the game further. Next steps include integrating a music track for players to interact with, and adding directional textures to each cube to indicate the strike direction—arrows will point up, down, left, or right. Additionally, I want the cubes to break into smaller pieces that scatter and disappear when hit.

Got any killer ideas to make this better? Drop them in the comments!

Top comments (0)