Free Cam for Debugging

Introduction

This article outlines the process of creating a "Free Cam" system using GDScript, primarily for debugging purposes. It's designed to be a part of my portfolio, demonstrating my skills to potential employers and colleagues. The key features include controlling camera speed via the scroll wheel, customizable key mappings for camera movement, and intuitive mouse controls.

Section 1: Setup and Input Configuration

To start, let's go through setting up the input variables. This allows for an intuitive experience when controlling the camera. The input system:

  • Key Mappings: Use the WASD or arrow keys to move the camera. These are mapped to actions in Godot's Input Map (e.g., move up, move down).
  • Input Map Configuration:
    1. Navigate to Project Settings > Input Map.
    2. Add new actions and assign keys or mouse inputs to these actions.
    3. This configuration allows you to bind multiple keys to a single movement action.
A low-poly 3D terrain showcasing coastal cliffs, grassy hills, and polygonal water—an artistic representation of the mathematical principles behind mesh generation.

Section 2: Implementing Camera Movement

Next, we convert these inputs into actual camera movements within the game:

  • Camera Translation: By mapping the defined inputs to camera movement actions, the camera can move in any direction.
  • Speed Control: Adjust speed through the scroll wheel, allowing fine control over how fast you navigate the environment.
  • func _process(delta: float) -> void:
        # Up and down
        if Input.is_action_pressed("move_down"):
            global_position += -transform.basis.y * Global.MovementSpeed * delta
        elif Input.is_action_pressed("move_up"):
            global_position += transform.basis.y * Global.MovementSpeed * delta
        # Forward and backward
        if Input.is_action_pressed("move_forward"):
            global_position += -transform.basis.z * Global.MovementSpeed * delta
        elif Input.is_action_pressed("move_backward"):
            global_position += transform.basis.z * Global.MovementSpeed * delta
        # Left and right
        if Input.is_action_pressed("move_left"):
            global_position += -transform.basis.x * Global.MovementSpeed * delta
        elif Input.is_action_pressed("move_right"):
            global_position += transform.basis.x * Global.MovementSpeed * delta
    
    func handle_movement_speed(event): 
        # Scrollwheel used to increase and decrease movement speed
        if event.is_action_pressed("increase_speed"):
            Global.MovementSpeed += 0.5
        elif event.is_action_pressed("decrease_speed"):
            Global.MovementSpeed -= 0.5
        Global.MovementSpeed = clamp(Global.MovementSpeed, 0.5, 250)

Section 3: Saving and Loading System

To enhance the debugging experience, the system can save and load camera positions:

  • Saving State: Save camera rotation and position using JSON formatting.
  • Loading State: When the game starts, load the last saved state to continue debugging seamlessly without relocating the camera position.
  • func _notification(what):
        # Save position and rotation when program closes
        if what == NOTIFICATION_WM_CLOSE_REQUEST:
            var save_file = FileAccess.open("user://savegame.save", FileAccess.WRITE)
            var save_data: Dictionary = {
                "position" : position,
                "rotation" : rotation,
                "camera_rot" : camera.rotation
            }
            var json_string = JSON.stringify(save_data)
            save_file.store_line(json_string)
            get_tree().quit() # default behavior
    
    func _ready():
        # Load position and rotation
        Global = get_tree().root.get_node("/root/Global")
      
        if not FileAccess.file_exists("user://savegame.save"):
            return # Error! We don't have a save to load.
          
        var save_file = FileAccess.open("user://savegame.save", FileAccess.READ)
        while save_file.get_position() < save_file.get_length():
            if not save_file.eof_reached():
                var current_line = JSON.parse_string(save_file.get_line())
                if current_line:
                  
                    position = Vector3(string_to_vector3(current_line["position"]))
                    rotation = Vector3(string_to_vector3(current_line["rotation"]))
                    camera.rotation = Vector3(string_to_vector3(current_line["camera_rot"]))
              
    static func string_to_vector3(string := "") -> Vector3:
        if string:
            var new_string: String = string
            new_string = new_string.erase(0, 1)
            new_string = new_string.erase(new_string.length() - 1, 1)
            var array: Array = new_string.split(", ")
    
            return Vector3(float(array[0]), float(array[1]), float(array[2]))
    
        return Vector3.ZERO

Section 4: Full-Screen and Mouse Control

Lastly, we cover toggling full-screen mode and managing mouse visibility:

  • Toggle Mouse Visibility: Use the Escape key to toggle the mouse cursor's visibility. This helps when needing to switch between camera control and interacting with UI elements.
  • Full-Screen Mode: Enable or disable full screen via input controls to enhance the debugging view.
  • func handle_mouse_state(event): # Freeing the mouse
        if event.is_action_pressed("esc") and !mouse_free:
            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
            mouse_free = true
        elif event.is_action_pressed("esc") and mouse_free:
            Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
            mouse_free = false
    
    func handle_fullscreen(event):
        if event.is_action_pressed("f11") and fullscreen:
            DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
            fullscreen = false
        elif event.is_action_pressed("f11") and !fullscreen:
            DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
            fullscreen = true
    

Conclusion

Setting up a free camera system in GDScript doesn't require complex techniques but rather a clear understanding of input mapping and basic scripting skills. This project is a testament to achieving functionality with simplicity, making it a valuable addition to any developer's toolkit.