0 votes

So, I am working on my first 3D game with the Godot Engine and I've set up a First Person Controller similar to that of the tutorial "Godot 101: Intro to 3D (part 7): First-person Character" by the KidsCanCode YouTube Channel. Albeit with a few modifications to enable some basic game pad support.

One of the features I would love to add to my game is a head bob/view bob whenever the player walks or runs (I haven't added running functionality yet). I was searching with my default search engine for any resources to help me understand how the mechanic would work and I found this forum question on the gamedev stackexchange here:
https://gamedev.stackexchange.com/questions/109056/how-to-implement-view-bobbing

Although the answer is in Unity C#, and I'm don't think I'm converting it correctly. It keeps giving me errors with the t var and when I try to += the bobOscillate var it also throws errors on that. Are there some resources to help me learn how to properly do this stuff? Also, one thing that isn't explained in the stackexchange post is what and where I assign to the time variable.

I'm kind of new to Godot so bear with me on this please.

Here is my scripts so far:

extends KinematicBody

onready var camera = $Pivot/Camera

var gravity = -ProjectSettings.get_setting("physics/3d/default_gravity")
var jump_speed = 4.9
var max_speed = 8
var mouse_sensitivity = 0.002 # Radians/Pixel
var gpDelta = 0

const JOY_DEADZONE = 0.2
const JOY_AXIS_RESCALE = 1.0/(1.0-JOY_DEADZONE)
const JOY_ROTATION_MULTIPLIER = 200.0 * PI / 180.0

var velocity = Vector3()

func get_input():
    var input_dir = Vector3()
    if Input.is_action_pressed("move_forward"):
        input_dir += -camera.global_transform.basis.z
    if Input.is_action_pressed("move_backward"):
        input_dir += camera.global_transform.basis.z
    if Input.is_action_pressed("move_left"):
        input_dir += -camera.global_transform.basis.x
    if Input.is_action_pressed("move_right"):
        input_dir += camera.global_transform.basis.x
    input_dir = input_dir.normalized()
    return input_dir

func _unhandled_input(event):
    if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
        rotate_y(-event.relative.x * mouse_sensitivity)
        $Pivot.rotate_x(-event.relative.y * mouse_sensitivity)
        $Pivot.rotation.x = clamp($Pivot.rotation.x, -1.2, 1.2) 

func _physics_process(delta):
    velocity.y += gravity * delta
    var desired_velocity = get_input() * max_speed

    velocity.x = desired_velocity.x
    velocity.z = desired_velocity.z
    velocity = move_and_slide(velocity, Vector3.UP, true)

    #ApplyBob(delta)

    if Input.get_connected_joypads().size() > 0:
        var xAxis = Input.get_joy_axis(0, JOY_AXIS_2)
        if abs(xAxis) > JOY_DEADZONE:
            if xAxis > 0:
                xAxis = (xAxis - JOY_DEADZONE) * JOY_AXIS_RESCALE
            else:
                xAxis = (xAxis + JOY_DEADZONE) * JOY_AXIS_RESCALE
            rotate_object_local(Vector3.UP, -xAxis * delta * JOY_ROTATION_MULTIPLIER)

        var yAxis = Input.get_joy_axis(0, JOY_AXIS_3)
        if abs(yAxis) > JOY_DEADZONE:
            if yAxis > 0:
                yAxis = (yAxis - JOY_DEADZONE) * JOY_AXIS_RESCALE
            else:
                yAxis = (yAxis + JOY_DEADZONE) * JOY_AXIS_RESCALE
            $Pivot.rotate_object_local(Vector3.RIGHT, -yAxis * delta * JOY_ROTATION_MULTIPLIER/2)
            $Pivot.rotation.x = clamp($Pivot.rotation.x, -1.2, 1.2)
    else:
        return

func ApplyBob(time):
    var t = $Pivot.translate
    var vel = self.velocity
    vel.y = 0 # 0 out y (up) component for xz velocity only
    var velocityBob = vel.normalized()

    if abs(velocityBob.z) <= 0.0001:
        return;

    var bobOscillate = sin(time * velocityBob.z * (2 * PI))

    t.y += bobOscillate # Oscillate the position in y (up) axis
    t.x += bobOscillate * 5 # Oscillate 5 degrees in each direction around x axis, presented this way for clarity.

I messed with the code for the oscillating part earlier and now I don't know what I had that didn't throw errors, but also didn't really work either.

Godot version 3.3.stable.official
in Engine by (23 points)

1 Answer

+1 vote

For my character camera motion, I made an AnimationPlayer as a child of my First-Person Camera. The Camera node is the child of a plain-old Spatial node I call "Head"; this is the node I use to figure out which way the player is facing.
In that AnimationPlayer, I made a simple side-to-side rocking animation that plays whenever I walk; I use the following code snippet to either play the animation, or reset the Camera's Transform:

if input_movement_vector != Vector2.ZERO and is_on_floor():
    if not cam_bob_anim.is_playing():
        cam_bob_anim.play("Camera_Bob")
elif input_movement_vector == Vector2.ZERO or not is_on_floor():
    if cam_bob_anim.is_playing():
        cam_bob_anim.stop()
    camera.transform.basis = Basis.IDENTITY
    camera.transform.origin = Vector3()

In the above code, input_movement_vector is a Vector2 with my character's lateral motion (forwards/backwards and left/right). cam_bob_anim is an variable linking to the AnimationPlayer child of the Camera; camera is the Camera node itself.

I check to see if the player is on the floor and the lateral motion is anything but Vector2.ZERO (indicating I am holding a directional button). If so, I then check to see if the animation is playing; if not, I just play the animation. This prevents the sort of "jittering" you might get if you constantly play an animation over and over without letting it run.

If the player is not on the floor, or they are no longer holding a direction button, I stop the animation (if it is running) and reset the Basis of the Camera's Transform to Basis.IDENTITY removing all rotations, and I set the origin to Vector3.ZERO. Because the Camera is the child of my "Head" Spatial node, I don't have to worry about the game character being forced to face one direction. Just make sure to use transform and not global_transform.

(Writing this out now, I realize I can simply put camera.transform = Transform.IDENTITY.)

by (333 points)

So, there isn't a way to make a head bob effect with just mathematics and code? I decided to do an animation thing for now (shortly after posting this question the other day), but I was really hoping to have the amount of bobbing depend on player velocity. It seems so simple in Unity's C#, but GDScript's basis system is so confusing to me. All I really need to find out is how to modify the y position and (I think) x rotation. The math has already been done with that sin function, but regardless thanks for the answer.

I figured it out with a bit of research based on an answer I got from another question I asked (later than this one).

So, basically what I was doing is this (in my code):

object.basis.y += variableName

When I should have been doing this:

object.basis.y.y += variableName

Albeit that is only one difference in a single line of my code here is my new (current) bobbing function, it's not complete, but it's a start:

# Bob the player HEAD/Pivot
func ApplyBob(time : float):
    var vel : Vector3 = velocity

    vel.y = 0
    var velMag = vel.length()

    if (abs(velMag) <= 0.0001):
        return;

    var bobOscillate : float = sin(time * velMag * (2 * PI))

    $Pivot.transform.basis.y.y += bobOscillate
    var rotAmount = bobOscillate * 5 
    $Pivot.rotation_degrees.z = clamp(rotAmount, -4, 4)

Thanks again!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.