Maker.io main logo

Desktop Pet

120

2026-03-18 | By Odd_Jayy (Jorvon Moss)

License: Attribution Non-commercial

gobot_1

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.

create_2

Step 3: The setup

In the top left corner, under create root node select 2D scene.

setup_3

In the Scene panel (top left), click + to add a new node.

  1. Right-click Node2D and select “Add child node” and type animatedSprite 2D into the search bar.

  2. Select AnimatedSprite2D as a child Node, and select Create.

  3. Right click again Node2D selected again (NOT AnimatedSprite2D!), search Area2D and select Area2D as another child Node, select create.

  4. Now right-click Area2D and add child Node, search CollisionShape2D as a child of Area2D, select create.

panel_4

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

close_5

close_6

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.

image7

  1. Click on AnimatedSprite2D in the top left corner of the Scene tree.

  2. In the Inspector panel on the top right, find Sprite Frames. Click [empty] next to it, and under New, click SpriteFrames.

  3. 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.)

click_8

click_9

You can click and drag your folder over to this area to find your png, but make sure the sprite frames.

drag_10

Click the + button in the Animations list.

button_11

  1. Name it exactly: walk_left (spelling matters!)

  2. In the FileSystem panel, open your sprites folder and find all the walk_left frames.

  3. Select all the frames (click the first, then Shift+click the last).

  4. Drag them all into the big empty frame area on the right side of the editor.

  5. Set the FPS to 8 — this controls how fast the animation plays.

  6. Check the Loop checkbox so it keeps repeating!

image12

image13

checkbox_14

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.

repeat_15

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.)

look_16

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.

frames_17

frames_18

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.

set_19

set_20

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.

verify_21

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.

save_22

⚠️ 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:

Copy Code
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.

common_23

Step 8 — Export as a Real App! 📦

One-time setup: Go to Editor → Export Templates and click Download and Install.

Export for Linux:

  1. Go to Project → Export

  2. Click Add → Linux / X11

  3. Check Embed PCK in Options

  4. Click Export Project, name it DesktopPet

  5. Make it executable: right-click → Properties → Permissions → "Allow executing as program" (or in terminal: chmod +x DesktopPet)

  6. Double-click to run!

Export for Windows:

  1. Same process, but choose Windows Desktop

  2. Name it DesktopPet.exe

  3. Drop it on any Windows PC and double-click it, no install needed!

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.