Godot + Arduino series: companion project code
Self-contained project code for the "Godot Isn't Just for Games" series: Arduino sketches, the Python serial<->UDP bridge, and the Godot 4 projects, one folder per article.
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Godot editor cache (regenerated locally)
|
||||||
|
.godot/
|
||||||
|
*.import
|
||||||
|
export_presets.cfg
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# OS / editor
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Codebycandle
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Godot + Arduino — blog project code
|
||||||
|
|
||||||
|
Companion code for the *"Godot Isn't Just for Games"* series on the
|
||||||
|
[Codebycandle blog](https://codebycandle.com/blog) — using the Godot 4 game
|
||||||
|
engine as a hardware control panel for an Arduino Uno.
|
||||||
|
|
||||||
|
Each folder is a self-contained project for one article: the Arduino sketch, the
|
||||||
|
Python serial↔UDP bridge, and the Godot project.
|
||||||
|
|
||||||
|
| Part | Article | Folder | What it does |
|
||||||
|
|------|---------|--------|--------------|
|
||||||
|
| 1 | [Your First Arduino Round-Trip](https://codebycandle.com/blog/godot-arduino-led-first-light) | [`godot-arduino-1-led-first-light/`](./godot-arduino-1-led-first-light) | A Godot button lights a real LED on D13, over a Python-to-UDP bridge. |
|
||||||
|
| 2 | A Live Joystick Dashboard *(coming soon)* | [`godot-arduino-2-joystick-dashboard/`](./godot-arduino-2-joystick-dashboard) | A KY-023 joystick streams `x,y,btn` telemetry into a glowing Godot XY pad. |
|
||||||
|
|
||||||
|
## The shape of it
|
||||||
|
|
||||||
|
```
|
||||||
|
Arduino <--USB serial--> bridge.py <--UDP 127.0.0.1--> Godot
|
||||||
|
```
|
||||||
|
|
||||||
|
Godot has no serial support, so a ~40-line Python bridge owns the COM port and
|
||||||
|
relays each line over UDP on localhost, in both directions. See each part's
|
||||||
|
README for wiring and run steps.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Arduino Uno (or compatible) + the Arduino IDE
|
||||||
|
- Python 3 with `pyserial` (`pip install pyserial`)
|
||||||
|
- [Godot 4](https://godotengine.org/) (standard build — no GDExtension needed)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Released under the [MIT License](./LICENSE) — use it freely.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Part 1 — Your First Arduino Round-Trip
|
||||||
|
|
||||||
|
Code for [*"Godot Isn't Just for Games: Your First Arduino Round-Trip"*](https://codebycandle.com/blog/godot-arduino-led-first-light).
|
||||||
|
|
||||||
|
A button in a Godot 4 scene reaches down a USB cable — through a Python-to-UDP
|
||||||
|
bridge — and lights a real LED on an Arduino Uno.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
```
|
||||||
|
arduino/
|
||||||
|
LedBasics/LedBasics.ino # reads L1/L0 off serial, drives the LED on D13
|
||||||
|
bridge/bridge.py # serial <-> UDP relay (run this, leave it running)
|
||||||
|
bridge/ledtest.py # quick standalone LED blink test (no Godot)
|
||||||
|
godot/
|
||||||
|
ArduinoGodot/ # Godot 4 project: one styled toggle button
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
|
||||||
|
D13 → 220 Ω resistor → LED long leg (anode); LED short leg (cathode) → GND.
|
||||||
|
No breadboard? The Uno's onboard LED is already on D13.
|
||||||
|
|
||||||
|
## Run it
|
||||||
|
|
||||||
|
1. **Flash** `arduino/LedBasics/LedBasics.ino` to the Uno (Arduino IDE, 115200 baud).
|
||||||
|
2. **Bridge:** `pip install pyserial`, then `python arduino/bridge/bridge.py`. Leave it running.
|
||||||
|
3. **Godot:** open `godot/ArduinoGodot/` in Godot 4, press Play, click the button.
|
||||||
|
|
||||||
|
The bridge auto-detects the board and reconnects if the cable pops out.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* LedBasics.ino — Part 1: the minimal round trip.
|
||||||
|
*
|
||||||
|
* Godot sends "L1" / "L0" over USB serial (via the Python bridge) and this
|
||||||
|
* sketch toggles an LED. No sensors yet — Part 2 (JoystickBridge.ino) adds the
|
||||||
|
* joystick telemetry going the other way.
|
||||||
|
*
|
||||||
|
* PROTOCOL
|
||||||
|
* IN (host -> Uno): "L1" = LED on, "L0" = LED off (newline-terminated)
|
||||||
|
*
|
||||||
|
* WIRING
|
||||||
|
* D13 -> 220 ohm resistor -> LED anode (+, long leg)
|
||||||
|
* LED cathode (-, short leg) -> GND
|
||||||
|
* (D13 is also the Uno's onboard LED, so it flashes during every upload —
|
||||||
|
* a free "is this pin alive" check before you've wired anything.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uint8_t PIN_LED = 13; // LED via 220 ohm resistor (also the onboard LED)
|
||||||
|
|
||||||
|
char cmdBuf[8];
|
||||||
|
uint8_t cmdLen = 0;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
pinMode(PIN_LED, OUTPUT);
|
||||||
|
digitalWrite(PIN_LED, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Commands in from Godot: "L1" / "L0", one per line.
|
||||||
|
while (Serial.available() > 0) {
|
||||||
|
char c = Serial.read();
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
if (cmdLen >= 2 && cmdBuf[0] == 'L') {
|
||||||
|
digitalWrite(PIN_LED, cmdBuf[1] == '1' ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
cmdLen = 0;
|
||||||
|
} else if (cmdLen < sizeof(cmdBuf) - 1) {
|
||||||
|
cmdBuf[cmdLen++] = c;
|
||||||
|
} else {
|
||||||
|
cmdLen = 0; // overflow guard: drop the runt line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
bridge.py — serial <-> UDP relay between the Arduino Uno and Godot (Main.gd)
|
||||||
|
|
||||||
|
Godot never touches the serial port; it only speaks UDP:
|
||||||
|
* it LISTENS on 127.0.0.1:4242 for telemetry ("x,y,btn")
|
||||||
|
* it SENDS to 127.0.0.1:4243 for LED commands ("L1" / "L0")
|
||||||
|
|
||||||
|
This bridge sits in the middle:
|
||||||
|
Arduino --serial line "x,y,btn\\n"--> bridge --UDP--> Godot :4242
|
||||||
|
Godot :4243 --UDP "L1"/"L0"--> bridge --serial--> Arduino
|
||||||
|
|
||||||
|
Double-click friendly: with no arguments it auto-detects the Arduino port and
|
||||||
|
keeps the window open on exit so you can read any error. Override if needed:
|
||||||
|
python bridge.py --port COM3
|
||||||
|
python bridge.py --port COM3 --baud 115200 # baud must match the .ino
|
||||||
|
|
||||||
|
Requires pyserial: pip install pyserial
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
import serial # pyserial
|
||||||
|
from serial.tools import list_ports
|
||||||
|
except ImportError:
|
||||||
|
print("pyserial not installed. Run: pip install pyserial")
|
||||||
|
input("\nPress Enter to close...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
TELEMETRY_ADDR = ("127.0.0.1", 4242) # Godot listens here
|
||||||
|
COMMAND_PORT = 4243 # Godot sends LED commands here
|
||||||
|
|
||||||
|
# Substrings that identify a typical Uno / clone in the port description.
|
||||||
|
PORT_HINTS = ("arduino", "ch340", "usb-serial", "usb serial", "wchusb", "cp210")
|
||||||
|
|
||||||
|
|
||||||
|
def pick_port() -> str:
|
||||||
|
"""Return a serial port: the obvious Arduino one, else the only one, else ask."""
|
||||||
|
ports = list(list_ports.comports())
|
||||||
|
if not ports:
|
||||||
|
raise SystemExit("No serial ports found. Is the Uno plugged in?")
|
||||||
|
|
||||||
|
for p in ports:
|
||||||
|
blob = f"{p.description} {p.manufacturer or ''}".lower()
|
||||||
|
if any(h in blob for h in PORT_HINTS):
|
||||||
|
print(f"auto-detected {p.device} ({p.description})")
|
||||||
|
return p.device
|
||||||
|
|
||||||
|
if len(ports) == 1:
|
||||||
|
print(f"using only port {ports[0].device} ({ports[0].description})")
|
||||||
|
return ports[0].device
|
||||||
|
|
||||||
|
print("Multiple serial ports found:")
|
||||||
|
for i, p in enumerate(ports):
|
||||||
|
print(f" [{i}] {p.device} {p.description}")
|
||||||
|
choice = input("Pick a number: ").strip()
|
||||||
|
return ports[int(choice)].device
|
||||||
|
|
||||||
|
|
||||||
|
def run(port: str, baud: int) -> None:
|
||||||
|
# UDP socket: sends telemetry to Godot AND receives LED commands from it.
|
||||||
|
# Bind to all interfaces ("") not just 127.0.0.1 — on some Windows setups a
|
||||||
|
# loopback-only bind won't see packets whose source the sender picked freely.
|
||||||
|
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
udp.bind(("", COMMAND_PORT))
|
||||||
|
udp.setblocking(False)
|
||||||
|
|
||||||
|
# Open the serial port. A short timeout keeps readline() from blocking forever.
|
||||||
|
ser = serial.Serial(port, baud, timeout=0.05)
|
||||||
|
time.sleep(2.0) # Uno auto-resets when the port opens; wait for the sketch to boot
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
print(f"bridge up: {port}@{baud} -> UDP {TELEMETRY_ADDR}, <- UDP :{COMMAND_PORT}")
|
||||||
|
print("leave this window open. Ctrl+C to quit.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# 1) Arduino -> Godot: forward each complete telemetry line.
|
||||||
|
line = ser.readline() # b"x,y,btn\r\n" or b"" on timeout
|
||||||
|
if line:
|
||||||
|
text = line.strip()
|
||||||
|
if text:
|
||||||
|
udp.sendto(text, TELEMETRY_ADDR)
|
||||||
|
|
||||||
|
# 2) Godot -> Arduino: drain any pending LED commands, newest applied last.
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data, src = udp.recvfrom(64)
|
||||||
|
except BlockingIOError:
|
||||||
|
break
|
||||||
|
except ConnectionResetError:
|
||||||
|
# Windows: a prior sendto hit a closed port; ICMP resets recv. Ignore.
|
||||||
|
break
|
||||||
|
cmd = data.strip()
|
||||||
|
if cmd in (b"L1", b"L0"):
|
||||||
|
ser.write(cmd + b"\n")
|
||||||
|
ser.flush()
|
||||||
|
print(f"cmd {cmd.decode()} from {src} -> serial")
|
||||||
|
else:
|
||||||
|
print(f"ignored UDP {data!r} from {src}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
udp.close()
|
||||||
|
print("\nbridge closed.")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser(description="Arduino <-> Godot serial/UDP bridge")
|
||||||
|
ap.add_argument("--port", help="serial port, e.g. COM3 (auto-detected if omitted)")
|
||||||
|
ap.add_argument("--baud", type=int, default=115200, help="must match JoystickBridge.ino")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
port = args.port or pick_port()
|
||||||
|
run(port, args.baud)
|
||||||
|
except Exception as e:
|
||||||
|
# Keep the window open so double-click users can actually read the error.
|
||||||
|
print(f"\nERROR: {e}")
|
||||||
|
input("\nPress Enter to close...")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
ledtest.py — isolate the bridge's receive path from Godot.
|
||||||
|
|
||||||
|
Sends a single LED command straight to the bridge's command port, exactly like
|
||||||
|
Godot does. If the running bridge prints "cmd L1 ... -> serial" (and the LED
|
||||||
|
lights), the bridge's receive side is fine and any failure is on Godot's end.
|
||||||
|
If the bridge stays silent even for this, the problem is the bridge/binding.
|
||||||
|
|
||||||
|
Usage (with bridge.py already running):
|
||||||
|
python ledtest.py L1 # turn LED on
|
||||||
|
python ledtest.py L0 # turn LED off
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
|
msg = (sys.argv[1] if len(sys.argv) > 1 else "L1").encode()
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.sendto(msg, ("127.0.0.1", 4243))
|
||||||
|
print(f"sent {msg!r} to 127.0.0.1:4243")
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
# Part 1 — the minimal round trip: a Godot button that toggles an LED on the
|
||||||
|
# Arduino. Only the OUTBOUND (command) half of the link is used here; Part 2
|
||||||
|
# adds inbound telemetry from the joystick.
|
||||||
|
|
||||||
|
const COMMAND_ADDR := "127.0.0.1"
|
||||||
|
const COMMAND_PORT := 4243
|
||||||
|
|
||||||
|
@onready var led_button: Button = $Panel/LedButton
|
||||||
|
|
||||||
|
var _udp_out := PacketPeerUDP.new()
|
||||||
|
var _led_on := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# To SEND with PacketPeerUDP in Godot 4, use set_dest_address — NOT
|
||||||
|
# connect_to_host, which is for receiving and silently drops outbound packets.
|
||||||
|
_udp_out.set_dest_address(COMMAND_ADDR, COMMAND_PORT)
|
||||||
|
led_button.pressed.connect(_on_led_pressed)
|
||||||
|
|
||||||
|
func _on_led_pressed() -> void:
|
||||||
|
_led_on = not _led_on
|
||||||
|
_udp_out.put_packet(("L1" if _led_on else "L0").to_utf8_buffer())
|
||||||
|
led_button.text = "LED: ON" if _led_on else "LED: OFF"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ctgyyk0it7dpd
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
[gd_scene load_steps=6 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://LedBasics.gd" id="1_led"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="bg"]
|
||||||
|
bg_color = Color(0.043, 0.055, 0.078, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_normal"]
|
||||||
|
bg_color = Color(0.11, 0.145, 0.204, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.2, 0.6, 1, 0.5)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_hover"]
|
||||||
|
bg_color = Color(0.145, 0.196, 0.278, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.3, 0.7, 1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_pressed"]
|
||||||
|
bg_color = Color(0.2, 0.6, 1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[node name="Main" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("1_led")
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
theme_override_styles/panel = SubResource("bg")
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Panel"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 40.0
|
||||||
|
offset_top = 96.0
|
||||||
|
offset_right = 480.0
|
||||||
|
offset_bottom = 130.0
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.93, 1, 1)
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "GODOT + ARDUINO: LED"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Subtitle" type="Label" parent="Panel"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 40.0
|
||||||
|
offset_top = 132.0
|
||||||
|
offset_right = 480.0
|
||||||
|
offset_bottom = 152.0
|
||||||
|
theme_override_colors/font_color = Color(0.4, 0.55, 0.7, 1)
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "· click to reach down the wire ·"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="LedButton" type="Button" parent="Panel"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 150.0
|
||||||
|
offset_top = 220.0
|
||||||
|
offset_right = 370.0
|
||||||
|
offset_bottom = 284.0
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.93, 1, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(0.043, 0.055, 0.078, 1)
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
theme_override_styles/normal = SubResource("btn_normal")
|
||||||
|
theme_override_styles/hover = SubResource("btn_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("btn_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("btn_normal")
|
||||||
|
text = "LED: OFF"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="ArduinoGodot"
|
||||||
|
run/main_scene="res://LedBasics.tscn"
|
||||||
|
config/features=PackedStringArray("4.7")
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/viewport_width=520
|
||||||
|
window/size/viewport_height=610
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
|
||||||
|
project/assembly_name="ArduinoGodot"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Part 2 — A Live Joystick Dashboard
|
||||||
|
|
||||||
|
Code for *"Godot Isn't Just for Games: A Live Joystick Dashboard"* **(article coming soon).**
|
||||||
|
|
||||||
|
Builds on [Part 1](../godot-arduino-1-led-first-light): a 2-axis KY-023 joystick streams
|
||||||
|
its position up the same bridge into a Godot console — a glowing dot tracks the
|
||||||
|
stick across an XY pad in real time, with the LED button still working.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
```
|
||||||
|
arduino/
|
||||||
|
JoystickBridge/JoystickBridge.ino # streams "x,y,btn" + still drives the LED
|
||||||
|
bridge/bridge.py # same serial <-> UDP relay as Part 1
|
||||||
|
godot/
|
||||||
|
ArduinoGodot/ # Godot 4 project: XY pad + dot + LED button
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wiring (adds to Part 1)
|
||||||
|
|
||||||
|
KY-023 joystick: `+5V`→5V, `GND`→GND, `VRx`→A0, `VRy`→A1, `SW`→D2. Keep the
|
||||||
|
LED from Part 1 on D13.
|
||||||
|
|
||||||
|
## Run it
|
||||||
|
|
||||||
|
1. **Flash** `arduino/JoystickBridge/JoystickBridge.ino` to the Uno.
|
||||||
|
2. **Bridge:** `python arduino/bridge/bridge.py` (leave running).
|
||||||
|
3. **Godot:** open `godot/ArduinoGodot/` in Godot 4 and press Play — move the stick.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* JoystickBridge.ino — Arduino Uno <-> Godot (ArduinoGodot/Main.gd)
|
||||||
|
*
|
||||||
|
* Talks plain serial lines to the UDP<->serial bridge process, which forwards
|
||||||
|
* telemetry to Godot on UDP 4242 and relays LED commands from UDP 4243.
|
||||||
|
*
|
||||||
|
* PROTOCOL (must match Main.gd)
|
||||||
|
* OUT (Uno -> host): "x,y,btn\n" raw 10-bit ADC, e.g. "512,498,1"
|
||||||
|
* btn = 1 when the stick's push-button is pressed.
|
||||||
|
* IN (host -> Uno): "L1" = LED on, "L0" = LED off (newline optional)
|
||||||
|
*
|
||||||
|
* WIRING (KY-023 2-axis analog joystick + external LED)
|
||||||
|
* Joystick +5V / VCC -> 5V
|
||||||
|
* Joystick GND -> GND
|
||||||
|
* Joystick VRx -> A0
|
||||||
|
* Joystick VRy -> A1
|
||||||
|
* Joystick SW -> D2 (uses internal pull-up; pressed = LOW)
|
||||||
|
*
|
||||||
|
* LED: D13 -> 220 ohm resistor -> LED anode (+, long leg)
|
||||||
|
* LED cathode (-, short leg) -> GND
|
||||||
|
* (D13 is also the Uno's onboard LED, so it flashes during every upload —
|
||||||
|
* a free "is this pin alive" check. 220-330 ohm keeps it near ~15 mA.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uint8_t PIN_VRX = A0; // joystick X axis
|
||||||
|
const uint8_t PIN_VRY = A1; // joystick Y axis
|
||||||
|
const uint8_t PIN_SW = 2; // joystick push-button (active LOW)
|
||||||
|
const uint8_t PIN_LED = 13; // external LED through a 220 ohm resistor (also onboard LED)
|
||||||
|
|
||||||
|
const unsigned long BAUD = 115200; // must match the bridge process
|
||||||
|
const unsigned long SEND_MS = 16; // ~60 Hz telemetry
|
||||||
|
|
||||||
|
unsigned long lastSend = 0;
|
||||||
|
char cmdBuf[8];
|
||||||
|
uint8_t cmdLen = 0;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(BAUD);
|
||||||
|
pinMode(PIN_SW, INPUT_PULLUP);
|
||||||
|
pinMode(PIN_LED, OUTPUT);
|
||||||
|
digitalWrite(PIN_LED, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
handleCommands();
|
||||||
|
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now - lastSend >= SEND_MS) {
|
||||||
|
lastSend = now;
|
||||||
|
int x = analogRead(PIN_VRX); // 0..1023
|
||||||
|
int y = analogRead(PIN_VRY); // 0..1023
|
||||||
|
int btn = (digitalRead(PIN_SW) == LOW) // pull-up: LOW == pressed
|
||||||
|
? 1 : 0; // Godot wants 1 = pressed
|
||||||
|
|
||||||
|
// "x,y,btn\n"
|
||||||
|
Serial.print(x);
|
||||||
|
Serial.print(',');
|
||||||
|
Serial.print(y);
|
||||||
|
Serial.print(',');
|
||||||
|
Serial.println(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse newline-terminated commands. Recognizes "L1" / "L0".
|
||||||
|
void handleCommands() {
|
||||||
|
while (Serial.available() > 0) {
|
||||||
|
char c = Serial.read();
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
if (cmdLen >= 2 && cmdBuf[0] == 'L') {
|
||||||
|
digitalWrite(PIN_LED, cmdBuf[1] == '1' ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
cmdLen = 0;
|
||||||
|
} else if (cmdLen < sizeof(cmdBuf) - 1) {
|
||||||
|
cmdBuf[cmdLen++] = c;
|
||||||
|
} else {
|
||||||
|
cmdLen = 0; // overflow guard: drop the runt line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
bridge.py — serial <-> UDP relay between the Arduino Uno and Godot (Main.gd)
|
||||||
|
|
||||||
|
Godot never touches the serial port; it only speaks UDP:
|
||||||
|
* it LISTENS on 127.0.0.1:4242 for telemetry ("x,y,btn")
|
||||||
|
* it SENDS to 127.0.0.1:4243 for LED commands ("L1" / "L0")
|
||||||
|
|
||||||
|
This bridge sits in the middle:
|
||||||
|
Arduino --serial line "x,y,btn\\n"--> bridge --UDP--> Godot :4242
|
||||||
|
Godot :4243 --UDP "L1"/"L0"--> bridge --serial--> Arduino
|
||||||
|
|
||||||
|
Double-click friendly: with no arguments it auto-detects the Arduino port and
|
||||||
|
keeps the window open on exit so you can read any error. Override if needed:
|
||||||
|
python bridge.py --port COM3
|
||||||
|
python bridge.py --port COM3 --baud 115200 # baud must match the .ino
|
||||||
|
|
||||||
|
Requires pyserial: pip install pyserial
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
import serial # pyserial
|
||||||
|
from serial.tools import list_ports
|
||||||
|
except ImportError:
|
||||||
|
print("pyserial not installed. Run: pip install pyserial")
|
||||||
|
input("\nPress Enter to close...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
TELEMETRY_ADDR = ("127.0.0.1", 4242) # Godot listens here
|
||||||
|
COMMAND_PORT = 4243 # Godot sends LED commands here
|
||||||
|
|
||||||
|
# Substrings that identify a typical Uno / clone in the port description.
|
||||||
|
PORT_HINTS = ("arduino", "ch340", "usb-serial", "usb serial", "wchusb", "cp210")
|
||||||
|
|
||||||
|
|
||||||
|
def pick_port() -> str:
|
||||||
|
"""Return a serial port: the obvious Arduino one, else the only one, else ask."""
|
||||||
|
ports = list(list_ports.comports())
|
||||||
|
if not ports:
|
||||||
|
raise SystemExit("No serial ports found. Is the Uno plugged in?")
|
||||||
|
|
||||||
|
for p in ports:
|
||||||
|
blob = f"{p.description} {p.manufacturer or ''}".lower()
|
||||||
|
if any(h in blob for h in PORT_HINTS):
|
||||||
|
print(f"auto-detected {p.device} ({p.description})")
|
||||||
|
return p.device
|
||||||
|
|
||||||
|
if len(ports) == 1:
|
||||||
|
print(f"using only port {ports[0].device} ({ports[0].description})")
|
||||||
|
return ports[0].device
|
||||||
|
|
||||||
|
print("Multiple serial ports found:")
|
||||||
|
for i, p in enumerate(ports):
|
||||||
|
print(f" [{i}] {p.device} {p.description}")
|
||||||
|
choice = input("Pick a number: ").strip()
|
||||||
|
return ports[int(choice)].device
|
||||||
|
|
||||||
|
|
||||||
|
def run(port: str, baud: int) -> None:
|
||||||
|
# UDP socket: sends telemetry to Godot AND receives LED commands from it.
|
||||||
|
# Bind to all interfaces ("") not just 127.0.0.1 — on some Windows setups a
|
||||||
|
# loopback-only bind won't see packets whose source the sender picked freely.
|
||||||
|
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
udp.bind(("", COMMAND_PORT))
|
||||||
|
udp.setblocking(False)
|
||||||
|
|
||||||
|
# Open the serial port. A short timeout keeps readline() from blocking forever.
|
||||||
|
ser = serial.Serial(port, baud, timeout=0.05)
|
||||||
|
time.sleep(2.0) # Uno auto-resets when the port opens; wait for the sketch to boot
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
print(f"bridge up: {port}@{baud} -> UDP {TELEMETRY_ADDR}, <- UDP :{COMMAND_PORT}")
|
||||||
|
print("leave this window open. Ctrl+C to quit.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# 1) Arduino -> Godot: forward each complete telemetry line.
|
||||||
|
line = ser.readline() # b"x,y,btn\r\n" or b"" on timeout
|
||||||
|
if line:
|
||||||
|
text = line.strip()
|
||||||
|
if text:
|
||||||
|
udp.sendto(text, TELEMETRY_ADDR)
|
||||||
|
|
||||||
|
# 2) Godot -> Arduino: drain any pending LED commands, newest applied last.
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data, src = udp.recvfrom(64)
|
||||||
|
except BlockingIOError:
|
||||||
|
break
|
||||||
|
except ConnectionResetError:
|
||||||
|
# Windows: a prior sendto hit a closed port; ICMP resets recv. Ignore.
|
||||||
|
break
|
||||||
|
cmd = data.strip()
|
||||||
|
if cmd in (b"L1", b"L0"):
|
||||||
|
ser.write(cmd + b"\n")
|
||||||
|
ser.flush()
|
||||||
|
print(f"cmd {cmd.decode()} from {src} -> serial")
|
||||||
|
else:
|
||||||
|
print(f"ignored UDP {data!r} from {src}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
udp.close()
|
||||||
|
print("\nbridge closed.")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser(description="Arduino <-> Godot serial/UDP bridge")
|
||||||
|
ap.add_argument("--port", help="serial port, e.g. COM3 (auto-detected if omitted)")
|
||||||
|
ap.add_argument("--baud", type=int, default=115200, help="must match JoystickBridge.ino")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
port = args.port or pick_port()
|
||||||
|
run(port, args.baud)
|
||||||
|
except Exception as e:
|
||||||
|
# Keep the window open so double-click users can actually read the error.
|
||||||
|
print(f"\nERROR: {e}")
|
||||||
|
input("\nPress Enter to close...")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
# Part 1 — the minimal round trip: a Godot button that toggles an LED on the
|
||||||
|
# Arduino. Only the OUTBOUND (command) half of the link is used here; Part 2
|
||||||
|
# adds inbound telemetry from the joystick.
|
||||||
|
|
||||||
|
const COMMAND_ADDR := "127.0.0.1"
|
||||||
|
const COMMAND_PORT := 4243
|
||||||
|
|
||||||
|
@onready var led_button: Button = $Panel/LedButton
|
||||||
|
|
||||||
|
var _udp_out := PacketPeerUDP.new()
|
||||||
|
var _led_on := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# To SEND with PacketPeerUDP in Godot 4, use set_dest_address — NOT
|
||||||
|
# connect_to_host, which is for receiving and silently drops outbound packets.
|
||||||
|
_udp_out.set_dest_address(COMMAND_ADDR, COMMAND_PORT)
|
||||||
|
led_button.pressed.connect(_on_led_pressed)
|
||||||
|
|
||||||
|
func _on_led_pressed() -> void:
|
||||||
|
_led_on = not _led_on
|
||||||
|
_udp_out.put_packet(("L1" if _led_on else "L0").to_utf8_buffer())
|
||||||
|
led_button.text = "LED: ON" if _led_on else "LED: OFF"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ctgyyk0it7dpd
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
[gd_scene load_steps=6 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://LedBasics.gd" id="1_led"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="bg"]
|
||||||
|
bg_color = Color(0.043, 0.055, 0.078, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_normal"]
|
||||||
|
bg_color = Color(0.11, 0.145, 0.204, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.2, 0.6, 1, 0.5)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_hover"]
|
||||||
|
bg_color = Color(0.145, 0.196, 0.278, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.3, 0.7, 1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_pressed"]
|
||||||
|
bg_color = Color(0.2, 0.6, 1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[node name="Main" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("1_led")
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
theme_override_styles/panel = SubResource("bg")
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Panel"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 40.0
|
||||||
|
offset_top = 96.0
|
||||||
|
offset_right = 480.0
|
||||||
|
offset_bottom = 130.0
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.93, 1, 1)
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "GODOT + ARDUINO: LED"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Subtitle" type="Label" parent="Panel"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 40.0
|
||||||
|
offset_top = 132.0
|
||||||
|
offset_right = 480.0
|
||||||
|
offset_bottom = 152.0
|
||||||
|
theme_override_colors/font_color = Color(0.4, 0.55, 0.7, 1)
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "· click to reach down the wire ·"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="LedButton" type="Button" parent="Panel"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 150.0
|
||||||
|
offset_top = 220.0
|
||||||
|
offset_right = 370.0
|
||||||
|
offset_bottom = 284.0
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.93, 1, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(0.043, 0.055, 0.078, 1)
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
theme_override_styles/normal = SubResource("btn_normal")
|
||||||
|
theme_override_styles/hover = SubResource("btn_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("btn_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("btn_normal")
|
||||||
|
text = "LED: OFF"
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
const TELEMETRY_PORT := 4242
|
||||||
|
const COMMAND_ADDR := "127.0.0.1"
|
||||||
|
const COMMAND_PORT := 4243
|
||||||
|
|
||||||
|
const ADC_MAX := 1023.0
|
||||||
|
const SMOOTH_SPEED := 12.0 # higher = snappier, lower = floatier
|
||||||
|
|
||||||
|
@onready var led_button: Button = $Panel/LedButton
|
||||||
|
@onready var pad: Panel = $Panel/Pad
|
||||||
|
@onready var dot: ColorRect = $Panel/Pad/Dot
|
||||||
|
|
||||||
|
var _udp_in := PacketPeerUDP.new()
|
||||||
|
var _udp_out := PacketPeerUDP.new()
|
||||||
|
|
||||||
|
var _target_x := 512.0 # rest near center so the dot starts in the middle
|
||||||
|
var _target_y := 512.0
|
||||||
|
var _smoothed_x := 512.0
|
||||||
|
var _smoothed_y := 512.0
|
||||||
|
var _btn_pressed := false
|
||||||
|
var _led_on := false
|
||||||
|
|
||||||
|
const COLOR_DOT_IDLE := Color(0.3, 0.7, 1.0) # cyan
|
||||||
|
const COLOR_DOT_PRESSED := Color(0.2, 1.0, 0.4) # green
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_udp_in.bind(TELEMETRY_PORT, "127.0.0.1")
|
||||||
|
# set_dest_address is the reliable way to *send* with PacketPeerUDP in Godot 4;
|
||||||
|
# connect_to_host is aimed at receiving and can silently drop outbound packets.
|
||||||
|
_udp_out.set_dest_address(COMMAND_ADDR, COMMAND_PORT)
|
||||||
|
led_button.pressed.connect(_on_led_pressed)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
# Drain the queue; the newest packet wins. Format: "x,y,btn" e.g. "512,498,0"
|
||||||
|
while _udp_in.get_available_packet_count() > 0:
|
||||||
|
var parts := _udp_in.get_packet().get_string_from_utf8().split(",")
|
||||||
|
if parts.size() >= 3 and parts[0].is_valid_int() and parts[1].is_valid_int():
|
||||||
|
_target_x = float(parts[0])
|
||||||
|
_target_y = float(parts[1])
|
||||||
|
_btn_pressed = parts[2].strip_edges() == "1"
|
||||||
|
|
||||||
|
# Frame-rate-independent smoothing so the dot glides instead of twitching.
|
||||||
|
var t := 1.0 - exp(-SMOOTH_SPEED * delta)
|
||||||
|
_smoothed_x = lerpf(_smoothed_x, _target_x, t)
|
||||||
|
_smoothed_y = lerpf(_smoothed_y, _target_y, t)
|
||||||
|
|
||||||
|
# Normalize raw ADC (0..1023) to 0..1.
|
||||||
|
var nx := clampf(_smoothed_x / ADC_MAX, 0.0, 1.0)
|
||||||
|
var ny := clampf(_smoothed_y / ADC_MAX, 0.0, 1.0)
|
||||||
|
|
||||||
|
# Move the dot within the pad. Invert Y so pushing the stick UP moves the dot UP.
|
||||||
|
# (If it feels backwards, change "1.0 - ny" to "ny", or swap for X below.)
|
||||||
|
var travel := pad.size - dot.size
|
||||||
|
dot.position = Vector2(nx * travel.x, (1.0 - ny) * travel.y)
|
||||||
|
|
||||||
|
# Reflect the stick's push-button by recoloring the dot.
|
||||||
|
dot.color = COLOR_DOT_PRESSED if _btn_pressed else COLOR_DOT_IDLE
|
||||||
|
|
||||||
|
func _on_led_pressed() -> void:
|
||||||
|
_led_on = not _led_on
|
||||||
|
_udp_out.put_packet(("L1" if _led_on else "L0").to_utf8_buffer())
|
||||||
|
led_button.text = "LED: ON" if _led_on else "LED: OFF"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d103i6ghi3ydl
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b81ornfxnvxxo"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://d103i6ghi3ydl" path="res://Main.gd" id="1_main"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="bg"]
|
||||||
|
bg_color = Color(0.043, 0.055, 0.078, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="pad"]
|
||||||
|
bg_color = Color(0.071, 0.09, 0.125, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.2, 0.6, 1, 0.55)
|
||||||
|
corner_radius_top_left = 10
|
||||||
|
corner_radius_top_right = 10
|
||||||
|
corner_radius_bottom_right = 10
|
||||||
|
corner_radius_bottom_left = 10
|
||||||
|
shadow_color = Color(0.2, 0.6, 1, 0.15)
|
||||||
|
shadow_size = 12
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_normal"]
|
||||||
|
bg_color = Color(0.11, 0.145, 0.204, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.2, 0.6, 1, 0.5)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_pressed"]
|
||||||
|
bg_color = Color(0.2, 0.6, 1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="btn_hover"]
|
||||||
|
bg_color = Color(0.145, 0.196, 0.278, 1)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.3, 0.7, 1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
corner_radius_bottom_right = 8
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
|
||||||
|
[node name="Main" type="Control" unique_id=2088197079]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_main")
|
||||||
|
|
||||||
|
[node name="Panel" type="Panel" parent="." unique_id=2059422118]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_styles/panel = SubResource("bg")
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Panel" unique_id=1798584764]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 40.0
|
||||||
|
offset_top = 28.0
|
||||||
|
offset_right = 480.0
|
||||||
|
offset_bottom = 58.0
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.93, 1, 1)
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
text = "GODOT + ARDUINO DEMO"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Subtitle" type="Label" parent="Panel" unique_id=462472763]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 40.0
|
||||||
|
offset_top = 60.0
|
||||||
|
offset_right = 480.0
|
||||||
|
offset_bottom = 78.0
|
||||||
|
theme_override_colors/font_color = Color(0.4, 0.55, 0.7, 1)
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "· real-time serial link ·"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="Pad" type="Panel" parent="Panel" unique_id=1034082861]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 70.0
|
||||||
|
offset_top = 104.0
|
||||||
|
offset_right = 450.0
|
||||||
|
offset_bottom = 484.0
|
||||||
|
theme_override_styles/panel = SubResource("pad")
|
||||||
|
|
||||||
|
[node name="CrosshairV" type="ColorRect" parent="Panel/Pad" unique_id=1237114789]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 189.0
|
||||||
|
offset_right = 191.0
|
||||||
|
offset_bottom = 380.0
|
||||||
|
color = Color(0.2, 0.6, 1, 0.12)
|
||||||
|
|
||||||
|
[node name="CrosshairH" type="ColorRect" parent="Panel/Pad" unique_id=1265685762]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_top = 189.0
|
||||||
|
offset_right = 380.0
|
||||||
|
offset_bottom = 191.0
|
||||||
|
color = Color(0.2, 0.6, 1, 0.12)
|
||||||
|
|
||||||
|
[node name="Dot" type="ColorRect" parent="Panel/Pad" unique_id=2003621157]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_right = 28.0
|
||||||
|
offset_bottom = 28.0
|
||||||
|
color = Color(0.3, 0.7, 1, 1)
|
||||||
|
|
||||||
|
[node name="LedButton" type="Button" parent="Panel" unique_id=1325564653]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 160.0
|
||||||
|
offset_top = 516.0
|
||||||
|
offset_right = 360.0
|
||||||
|
offset_bottom = 566.0
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.93, 1, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(0.043, 0.055, 0.078, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
theme_override_styles/normal = SubResource("btn_normal")
|
||||||
|
theme_override_styles/pressed = SubResource("btn_pressed")
|
||||||
|
theme_override_styles/hover = SubResource("btn_hover")
|
||||||
|
theme_override_styles/focus = SubResource("btn_normal")
|
||||||
|
text = "LED: OFF"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="ArduinoGodot"
|
||||||
|
run/main_scene="res://Main.tscn"
|
||||||
|
config/features=PackedStringArray("4.7")
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/size/viewport_width=520
|
||||||
|
window/size/viewport_height=610
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
|
||||||
|
project/assembly_name="ArduinoGodot"
|
||||||
Reference in New Issue
Block a user