Is it possible to have uniform ParallaxLayer motion_scale for rotation?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diet Estus

I have a Camera2D with a screen_shake() method which alters the camera’s rotation_degrees as well as its offset.

I notice that this causes my ParallaxLayersto rotate different amounts. I imagine this is because they all have different motion_scales. Ideally, I’d like them all to rotate the same amount.

Is this possible?

:bust_in_silhouette: Reply From: markopolo

Also see Parallax Background messes up when Camera rotates.

The short answer is no. The engine’s built-in parallax layers do not respect camera rotation, and there is no magic checkbox that I know of to fix the issue.

However, you can write your own parallax functionality that does respect rotation. See below for one I put together for a project last spring (and haven’t touched since - fair warning!).

Rotating parallax layers or backgrounds is not sufficient: they have to be rotated around the camera, which is extra weird because the motion scales muck with the camera’s “position” anyways. I don’t know how parallax backgrounds work internally: it’s a bit odd to me since they’re separate canvas layers by definition, and afaik canvas layers usually ignore cameras that aren’t their own descendants.

extends Node2D

# scene tree:
#   parallax_layer (Node2D)
#     camera_follower(Node2D)

# mirroring should be at least as big as screen dimensions.
# if cameras are allowed to rotate, each mirroring dimension
# has to be at least as big as the biggest resolution dimension.
# i.e. when the camera is turned 90 degrees, the wrapping needs
# to happen beyond the width of the viewport that (in this situation)
# wraps with *y* motion.

export(NodePath) var camera_path
var camera setget set_camera

export(float) var parallax_scale = 1.0

export(Vector2) var mirroring = Vector2()
export(int) var mirror_margin = 64

var original_position
var motion = Vector2()

func _ready():
  if camera_path:
    camera = get_node(camera_path)
  
  original_position = get_position()

func _process(delta):
  if camera != null:
    update_motion()
    wrap_children()
  else:
    # grab the first thing in the 'camera' group
    var camera_group = get_tree().get_nodes_in_group("camera")
    if camera_group.size() > 0:
      set_camera(camera_group[0])


func update_motion():
  var to_camera = camera.get_global_position() - $camera_follower.get_global_position()
  motion += to_camera
  
  position = original_position + (motion * (1.0 - parallax_scale))
  
  $camera_follower.set_global_position(camera.get_global_position())

func wrap_children():
  if mirroring.x == 0 and mirroring.y == 0:
    return
  
  var mirror_bounds = Rect2()
  
  mirror_bounds.size = Vector2(mirroring)
  
  mirror_bounds.position = camera.get_global_position()
  # position is camera top left, not center
  # not actually camera top left, but bounds top left :\
  # avoids viewport dimension non-squareness causing wrap problems when rotated
  mirror_bounds.position -= mirror_bounds.size / 2.0
  
  for child in get_children():
    if mirror_bounds.size.x != 0:
      if child.get_global_position().x < (mirror_bounds.position.x - mirror_margin):
        child.position.x += mirror_bounds.size.x + (2 * mirror_margin)
      elif child.get_global_position().x > (mirror_bounds.position.x + mirror_bounds.size.x + mirror_margin):
        child.position.x -= mirror_bounds.size.x + (2 * mirror_margin)
    
    if mirror_bounds.size.y != 0:
      if child.get_global_position().y < (mirror_bounds.position.y - mirror_margin):
        child.position.y += mirror_bounds.size.y + (2 * mirror_margin)
      elif child.get_global_position().y > (mirror_bounds.position.y + mirror_bounds.size.y + mirror_margin):
        child.position.y -= mirror_bounds.size.y + (2 * mirror_margin)

func set_camera(new_cam):
  camera = new_cam
  # initial offset so things are where they are
  motion += get_global_position() - camera.get_global_position()