09kingarthur I just made a quick test project. It uses two simple 2D curves for the horizontal and vertical sway and then offsets the weapon.

weapon-bob.zip
4kB

extends Node2D

@export var horizontal_sway: Curve
@export var vertical_sway: Curve

@onready var initial_position := position


func _process(_delta: float) -> void:
    var t := wrapf(Time.get_unix_time_from_system(), 0.0, 1.0)
    position = initial_position + Vector2(horizontal_sway.sample(t) * 50.0, -vertical_sway.sample(t) * 50.0)

    Toxe Easier to do it without curves by just calling sin()

    • Toxe replied to this.

      xyz Yes and no. Curves are just nice because you can quickly change and customize them completely in a very visual way whereas changing a formula, well, it can be quicker if you know what you are doing math-wise or the formula is easy like a simple sine. But once it gets more complex curves are just neat because you actually see what you get.

      • xyz replied to this.

        Toxe In this case I doubt you'd want anything else other than pure sine so using a general purpose bezier curve is a bit excessive... and a bit slower.

        • Toxe replied to this.

          xyz Oh in this case you are right, yes. It's just that I wanted to explicitly use curves in this case because it makes it a bit easier to understand. Or maybe "self-documenting" would be the better word?

          Toxe Wait, you can make curves?! This is amazing! Using this method, I was able to recreate the weapon bob!
          What it looked like before:

          $cam/viewmodel.position.x += ((weapon_offset[equipped_weap].x)-(sin(weapon_bob*1)*0.03*(int(sprint)+1))-(($cam/viewmodel.position.x)))*0.1
          $cam/viewmodel.position.y += ((weapon_offset[equipped_weap].y)-(sin(weapon_bob*2)*0.015*(int(sprint)+1))-(($cam/viewmodel.position.y)))*0.1


          What it looks like now:

          $cam/viewmodel.position.x += ((weapon_offset[equipped_weap].x)-(weaponbob_x.sample(weapon_bob))*0.04*(int(sprint)+1)-(($cam/viewmodel.position.x)))*0.1
          $cam/viewmodel.position.y += ((weapon_offset[equipped_weap].y)+(weaponbob_y.sample(weapon_bob))*0.02*(int(sprint)+1)-(($cam/viewmodel.position.y)))*0.1


          Thank you so much for your help!!

            @09kingarthur @xyz For completeness here is the same but using sine. Although they are not exactly the same because the curves aren't real sine waves but Bézier curves.

            func _process(_delta: float) -> void:
                var t := Time.get_unix_time_from_system()
                var t_hori := wrapf(t, 0.0, 1.0)
                var t_vert := wrapf(t + 0.25, 0.0, 1.0) * 2.0
            
                var r := t_vert * PI
                var v := 1.0 - sin(r if r < PI else r - PI)
                var h := sin(t_hori * 2.0 * PI)
            
                position = initial_position + Vector2(h * 50.0, -v * 50.0)

            Curves

            Sine

            Megalomaniak I may add an option for figure 8 or half-circle weapon bob later, for now I'm gonna keep the half-circle because I like the way it feels when you move around.

              Megalomaniak Absolutely! I think some of it also comes down to fine-tuning how the viewbob and weaponbob actually sync and are timed in general; I did a bit of tweaking to both and I think this iteration has the best feel so far.

              The gif's framerate doesn't really do it justice but it feels amazing when you're playing it.

              Looks pretty good.