How do I make a mouse moving parrallax start screen?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Lapiz (Quarstudz)

Basically it’s a background which moves with your mouse.

I would like to know how can I make it so I can enhance my game.

:bust_in_silhouette: Reply From: joschi

I just wanted to make the same effect, but couldn’t find something on the internet, so I am writing a little tutorial here, it is not that hard to make.

Lets set up the nodes first, you need a ParallaxBackgroundand two ore more ParallaxLayer as child nodes and each ParallaxLayer needs a Sprite as child. Give the first Spritethe background texture, the second Spritethe foreground texture.

Next you need to get the x and y coordinates of your mouse, that is possible with

func _input(event):
    if event is InputEventMouseMotion:
        var mouse_x = event.position.x
        var mouse_y = event.position.y

Next you want to map the mouse coordinates on the screen between the intervall
[-1 1].
For this you need the size of your screen, you can get that with get_viewport.size(), there are many other ways to get the size of your screen, you may use what suits you best.
Then you can do

var relative_x = (mouse_x - (viewport_size.x/2)) / (viewport_size.x/2)
var relative_y = (mouse_y - (viewport_size.y/2)) / (viewport_size.y/2)

Now you have the positon of your mouse relative to the middle of the screen, which is 0 in the middle 1 at the far right/down and -1 at the far left/up, perfect!
Whats left to do is to manipulate the position of the ParallaxLayers, just get the node for each ParallaxLayer and set the motion_offset property like this:

ParallayLayer.motion_offset.x = multiplier * relative_x
ParallayLayer.motion_offset.y = multiplier * relative_y

multiplier is a value that amplifies the parallax effect, use smaller values for background layers and greater values for foreground layers, but it should be at least 2, to have a visible effect.
So thats it!

The ParallaxLayers need to update every time the mouse moves so all that code goes into the func _input(event):
To avoid that part of your screen goes blank, make the textures for the background larger than the screen.
Here is how it can look like with 7 layers:

Props to LuminousDragonGames for the parallax images

Oh my God I can’t thank you enough for this!!
I’ve been trying to achieve this effect since so long, but couldn’t find anything on the internet as well. I’m not good at math programming so I couldn’t do it myself…
You’re a lifesaver!

Lulullia | 2020-04-09 17:43

Hello. Would you please be able to show me the script that you have for this? I’m not quite able to figure it out

Amateur.game.dev. | 2020-09-25 13:36

No problem, here is the code:

extends ParallaxBackground

var viewport_size
var relative_x
var relative_y

func _ready():
	get_tree().get_root().connect("size_changed", self, "viewport_changed") # register event if viewport changes
	viewport_changed()
	relative_x = 0
	relative_y = 0

func _input(event):
	if event is InputEventMouseMotion:
		var mouse_x = event.position.x
		var mouse_y = event.position.y
		relative_x = -1 * (mouse_x - (viewport_size.x/2)) / (viewport_size.x/2)
		relative_y = -1 * (mouse_y - (viewport_size.y/2)) / (viewport_size.y/2)
		# print("relative_y: " + str(relative_y))
		# print("relative_x: " + str(relative_x))
		var count = 4
		for child in self.get_children(): # for each parallaxlayer do...
			child.motion_offset.x = count * relative_x
			child.motion_offset.y = count * relative_y
			count = count * 1.6

# gets called on the start of the application once and every time the viewport changes
# centers the images
func viewport_changed():
	viewport_size = get_viewport().size
	for child in self.get_children():
		child.get_node("Sprite").offset.x = viewport_size.x / 2
		child.get_node("Sprite").offset.y = viewport_size.y / 2

Don’t forget you need the tree to look like this

-AnyNode
  -ParallaxLayer Background [script]
      -ParallaxLayer0
          -Sprite
      -ParallaxLayer1
          -Sprite
      -ParallaxLayer2
          -Sprite
      -ParallaxLayer3
          -Sprite
etc...

Hope it helps!
If you have any questions, let me know :slight_smile:

joschi | 2020-09-25 16:06

It says Invalid get index ‘offset’ (on base: ‘null instance’)

Amateur.game.dev. | 2020-09-26 17:02

Seems like one of your parallaxlayer has not a sprite as a child, or you have other nodes as child of parallaxBackground.
Make sure that all childs of parallaxBackground are parallaxLayers and each parallaxLayer has a Sprite as a child.
Because I used child.get_node("Sprite") each Sprite of a parallaxLayer has to have the name Spriteotherwise it will not find the right node.

joschi | 2020-09-26 17:56

Can it do it on 3d?

Glasses | 2021-07-15 15:41

Can you change it for Godot 4.0?