Desktop Pet
2026-03-18 | By Odd_Jayy (Jorvon Moss)
License: Attribution Non-commercial
A lightweight desktop companion app built in Godot 4, featuring an animated character that crawls along the edges of your screen. It uses a transparent, borderless, always-on-top window to blend seamlessly into your desktop — exported for both Linux and Windows.
Step 1: Download Gadot.
https://godotengine.org/ It's open-source and free.
Step 2: Open the program and create a new Project. Name it whatever you like.
Click the big button that says Create. Leave the render set to Forward+ and version control to Git.
Step 3: The setup
In the top left corner, under create root node select 2D scene.
In the Scene panel (top left), click + to add a new node.
Right-click Node2D and select “Add child node” and type animatedSprite 2D into the search bar.
Select AnimatedSprite2D as a child Node, and select Create.
Right click again Node2D selected again (NOT AnimatedSprite2D!), search Area2D and select Area2D as another child Node, select create.
Now right-click Area2D and add child Node, search CollisionShape2D as a child of Area2D, select create.
Step 4: project settings
By default, Godot makes a normal game window with a background. But we want our sprite to float on the desktop with NO background — like a sticker on your screen!
From the menus at the top, select Project → Project Settings, then set the following:
In the search bar, type Display and select Window:
Size > Viewport Width: 200
Size > Viewport Height: 200 (just big enough for your sprite!)
Borderless: ON — no window border or title bar
Transparent: ON — the background becomes see-through
Always on Top: ON — stays on top of all other windows
In the search bar, type Rendering and select Viewport:
Transparent Background: ON — THIS is what makes the green/grey go away!
In the search bar, type per Pixel and select Allowed:
Display > Window > Per Pixel Transparency > Allowed ✅
Click the Close button to exit the settings window
Step 5: Setting up animations
I recommend you have all your PNG images for your animations in one folder, easy to find. You can click and drag that folder over to the bottom left filesystem. You can download a set of example images here.
Click on AnimatedSprite2D in the top left corner of the Scene tree.
In the Inspector panel on the top right, find Sprite Frames. Click [empty] next to it, and under New, click SpriteFrames.
Click on SpriteFrames in the selection box — this opens the Animation Frames editor at the bottom. (See the image below of what to click on.)
You can click and drag your folder over to this area to find your png, but make sure the sprite frames.
Click the + button in the Animations list.
Name it exactly: walk_left (spelling matters!)
In the FileSystem panel, open your sprites folder and find all the walk_left frames.
Select all the frames (click the first, then Shift+click the last).
Drag them all into the big empty frame area on the right side of the editor.
Set the FPS to 8 — this controls how fast the animation plays.
Check the Loop checkbox so it keeps repeating!
Make sure to resize your Sprite into the 200x200 box that you should see on your screen; it's a thin purple line.
Repeat for ALL animations: walk_right, crawl_left_up, crawl_left_down, crawl_right_up, crawl_right_down, crawl_top_left, crawl_top_right, idle, pick_up, splat.
By the end, it should look similar to this.
(It's okay for your image naming to be different than your animation naming. Your animation name is what is most important to the script.)
Also, for the idle and the pickup animations, make sure you select the loop off. Also, for these two animation frames, change the FPS to 1.0.
Set the Collision Shape: Click on CollisionShape2D in the top left corner of the Scene tree. In the Inspector on the top right corner, click the Shape field and choose New.
RectangleShape2D. Resize the rectangle to roughly match your sprite. This is the invisible hit area for clicking.
To verify that each animation is correctly set up, click on AnimatedSprite2D on the top right.thist, select each animation and make sure the sprite is completely surrounded by the collision box.
Step:6-script
How to attach the script: Click on the root node (the Node2D at the very top). Right click node2d and scroll down to attach script Attach Script button. Name the script whatever.gd and click Create. Copy and paste your script into the code and press Ctrl +S to save.
⚠️ Script Must Go on the ROOT Node! Make sure you attach the script to (the Node2D root), NOT on AnimatedSprite2D or Area2D. This is the other common mistake!
Clear out everything and paste in this full script:
extends Node2D
var speed = 100
var direction = Vector2(1, 0)
var screen_size = Vector2()
var window_size = Vector2(200, 200)
var is_dragging = false
var drag_offset = Vector2()
var idle_timer = 0.0
var is_idling = false
@onready var sprite = $AnimatedSprite2D
@onready var area = $Area2D
func _ready():
screen_size = Vector2(DisplayServer.screen_get_size())
area.input_event.connect(_on_area_input)
_pick_new_direction()
func _physics_process(delta):
if is_dragging:
var mouse_pos = Vector2(DisplayServer.mouse_get_position())
var new_win_pos = mouse_pos - drag_offset
DisplayServer.window_set_position(Vector2i(new_win_pos))
sprite.play("pick_up")
return
if is_idling:
idle_timer -= delta
if idle_timer <= 0:
is_idling = false
_pick_new_direction()
return
var win_pos = Vector2(DisplayServer.window_get_position())
win_pos += direction * speed * delta
win_pos.x = clamp(win_pos.x, 0, screen_size.x - window_size.x)
win_pos.y = clamp(win_pos.y, 0, screen_size.y - window_size.y)
DisplayServer.window_set_position(Vector2i(win_pos))
_update_animation(win_pos)
if win_pos.x <= 0 or win_pos.x >= screen_size.x - window_size.x:
direction.x *= -1
_maybe_idle()
if win_pos.y <= 0 or win_pos.y >= screen_size.y - window_size.y:
direction.y *= -1
_maybe_idle()
func _update_animation(win_pos):
var on_bottom = win_pos.y >= screen_size.y - window_size.y - 2
var on_top = win_pos.y <= 2
var on_left = win_pos.x <= 2
var on_right = win_pos.x >= screen_size.x - window_size.x - 2
if on_bottom:
if direction.x > 0:
sprite.play("walk_right")
else:
sprite.play("walk_left")
elif on_top:
if direction.x > 0:
sprite.play("crawl_top_right")
else:
sprite.play("crawl_top_left")
elif on_left:
if direction.y > 0:
sprite.play("crawl_left_down")
else:
sprite.play("crawl_left_up")
elif on_right:
if direction.y > 0:
sprite.play("crawl_right_down")
else:
sprite.play("crawl_right_up")
func _maybe_idle():
if randf() < 0.3:
is_idling = true
idle_timer = randf_range(1.0, 3.0)
var r = randi() % 3
if r == 0:
sprite.play("idle")
elif r == 1:
sprite.play("crouch")
else:
sprite.play("splat")
func _pick_new_direction():
var angle = randf() * TAU
direction = Vector2(cos(angle), sin(angle)).normalized()
func _on_area_input(_viewport, event, _shape_idx):
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
is_dragging = true
var mouse_pos = Vector2(DisplayServer.mouse_get_position())
var win_pos = Vector2(DisplayServer.window_get_position())
drag_offset = mouse_pos - win_pos
else:
is_dragging = false
_pick_new_direction()Step 7: Test and Error,
Press the ▶ Play button and check off this list:
Your sprite appears on the screen and starts moving
It walks at the bottom and crawls on the sides and top
Clicking and dragging makes it follow your mouse with the pick_up animation
Letting go snaps it back to the nearest edge
It occasionally stops to idle
It rarely does a crouch or splat
Common problems & fixes:
Pet disappears when I add code
Script is on the wrong node, or there’s a bug.
Check the Output panel for red error messages.
Background isn’t transparent
Ensure Transparent (Window) is ON.
Ensure Transparent Background (Rendering) is ON.
Wrong animations play
Verify that animation names in SpriteFrames exactly match the names in your code.
Spelling and capitalization must be identical.
Can’t click/drag the pet
Area2D must be a direct child of Node2D.
CollisionShape2D must have a shape assigned.
Step 8 — Export as a Real App! 📦
One-time setup: Go to Editor → Export Templates and click Download and Install.
Export for Linux:
Go to Project → Export
Click Add → Linux / X11
Check Embed PCK in Options
Click Export Project, name it DesktopPet
Make it executable: right-click → Properties → Permissions → "Allow executing as program" (or in terminal: chmod +x DesktopPet)
Double-click to run!
Export for Windows:
Same process, but choose Windows Desktop
Name it DesktopPet.exe
Drop it on any Windows PC and double-click it, no install needed!

