Skip to content

Instantly share code, notes, and snippets.

@elementbound
Created July 8, 2026 11:20
Show Gist options
  • Select an option

  • Save elementbound/3d147c6bce74cc254fc5fbfd07b9f7c7 to your computer and use it in GitHub Desktop.

Select an option

Save elementbound/3d147c6bce74cc254fc5fbfd07b9f7c7 to your computer and use it in GitHub Desktop.
netfox spawning alternative
# Input Node
var input: PlayerInput
# Record-only property in RBS
var projectile: Node3D
# Manual display tracking
var has_fired: bool
# Convenience class for display tracking
var has_fired_observer := EventObserver.new()
func _rollback_tick(dt: float, t: int, _is_fresh: bool) -> void:
# Retrieve the projectile we've recorded for the next tick; `null` is the default value
var recorded_projectile := NetworkHistoryServer.get_value_at(self, "projectile", t + 1, null)
if input.is_firing and can_fire():
if not is_instance_valid(recorded_projectile):
# We are firing, but haven't spawned a projectile yet
# This projectile will be recorded for the next tick
projectile = spawn_projectile()
# Keep a reference in `recorded_projectile` so we can set it up
recorded_projectile = projectile
# We've fired a brand new projectile, play the animation and such
has_fired = true
# Same as above but with convenience class - mark the event as happened
has_fired_observer.mark()
# Whether the projectile existed already or if it's brand new, align it to our current aim
# This is important in case the server corrects our position or orientation
recorded_projectile.position = position
recorded_projectile.quaternion = quaternion
# Do additional bookkeeping here, e.g. deducing ammo, hitscanning, etc.
else:
if is_instance_valid(recorded_projectile):
# We are not firing, yet we have a projectile
# Probably we've predicted we can fire, then the server told us no
recorded_projectile.queue_free()
projectile = null
func _after_tick_loop() -> void:
if has_fired:
# We've fired our gun, play animation
animation_player.play("fire")
# Reset flag for next tick loop
has_fired = false
func _process(_dt: float) -> void:
# Same display check as above, but with convenience class
# You could put this piece of code in `_process()`, `_physics_process()`,
# or anywhere your animation code resides
if has_fired_observer.check():
animation_player.play("fire")
# Resetting is not needed, it's handled by the class
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment