Navigate

Sunday, October 22, 2023

Ready, Set, Godot! #1: First Person Controller

 It's been a while since I've dedicated time to game development. I've missed it. Now I'm back...I'm also back on this blog because Tumblr has created obstacles to non-users to find content.

I've been really excited about getting started on Godot. I pursued Unity over Unreal because none of my computers at the time could run Unreal: hahaha! I liked that Unity was more simple if not bare bones, allowing you to build up to near Unreal graphics if you desired. But Unreal, it seemed like you were stuck at max settings. I liked how Unity reminded me of the graphics from my childhood, while Unreal always chased more effects. I also liked  coding in C# and the amount of support the community provided each other.

But then Unity went public. I had a strong suspicion that they would cease prioritizing making a good game engine and instead prioritizing milking money from their product. I mean, what else could you expect from for-profit organization, right? I looked into less greed-inspired game engines and found Godot. Given that it's open source, I imagine it won't abandon its users for big dollars, and it looks like the folks putting this together have done amazing work. For the style of games I'd like to make I think Godot 4 is exactly what I need.

Still...Godot is deceptively different from Unity. So much looked the same, but I've spent hours relearning basic principles I had memorized for Unity. But it's good for me, and I'm excited to be making headway.

I've had the project Candles in development for years. I want to finalize this project. But also have several other FPS projects I've wanted to make. So why not do them all? My goal is to push forward on many projects near simultaneously and use the lessons learned on each project to make the others better.

But first, I need a First Person Controller. 

Since it had been nearly a year since I took the Godot 3 tutorials, I wasn't really sure where to start. I remember each Node being limited to one script. That's a little confusing cause I'm used to loading several scripts onto a single object. But in a way, each node could be like a component in Unity...maybe. So I followed a tutorial. And I think it was this one:

While I used this as a starting point I made some changes for my needs. I included sprinting as well as decreased movement speed when jumping. I'm proud of that actually. It felt really weird to jump and fly through the air. I thought it would make sense to lose momentum in the air, so I added some code to achieve that and I think it feels pretty good.

extends CharacterBody3D

var SPEED = 0.0 #how fast does the player move
var running = false
@export var run_speed = 0.0
@export var walk_speed = 0.0
@export var air_speed = 0.0
@export var air_speed_reduction = 0.0

@export var JUMP_VELOCITY = 0.0 #how high can the player jump
@export var spin = 0.0 #turn speed for the player's mouse
@export var mouse_visible = false
@export var look_up = 0 #clamp angle for looking up
@export var look_down = 0 #clamp angle for looking down

var count_up = 0.0
var countdown = 10

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var camera_player:= $Camera_Player

func _ready():
    Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
    SPEED = walk_speed

func _unhandled_input(event)-> void:
    if event is InputEventMouseMotion:
        rotate_y(-event.relative.x * spin)
        camera_player.rotate_x(-event.relative.y * spin)
        camera_player.rotation.x = clamp(camera_player.rotation.x, deg_to_rad(look_down),deg_to_rad(look_up) )
       
    if Input.is_action_just_pressed("shift"):
        if running == true:
            running = false
        else:
            running = true
       
    if Input.is_action_just_pressed("ui_cancel"):
        if mouse_visible == false:
            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
            mouse_visible = true
        else:
            Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
            mouse_visible = false

func _physics_process(delta):
    # Add the gravity.
    if not is_on_floor():
        velocity.y -= gravity * delta
        if running == true:
            Reduce_Air_Speed()
    else:
        if running == true:
            SPEED = run_speed
        else:
            SPEED = walk_speed

    # Handle Jump.
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Get the input direction and handle the movement/deceleration.
    # As good practice, you should replace UI actions with custom gameplay actions.
    var input_dir = Input.get_vector("left", "right", "forward", "back")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)
    

    move_and_slide()
    
func Reduce_Air_Speed():
    SPEED = SPEED - air_speed_reduction * get_physics_process_delta_time()
    if SPEED < air_speed:
        SPEED = air_speed

There are probably some obvious ways to improve this (feel free to advise) but I'm coming back from a coding hiatus and making a switch to an engine that doesn't work the same.