Interface and Application Programming
Task:-
Group assignment:
- Compare as many tool options as possible.
- Make and compare test casts with each of them
Individual assignment
- Write an application for the embedded board that you made. that interfaces a user with an input and/or output device(s)
This week i focused on learning the basics of game development using Godot Engine- open source game engine and understanding how to interface a digital game with a my input and output device. For that i used pcb which was made in electronic design and input device week,
This project was also a continuation of an older attempt. Before joining Fab Academy, I had tried making a similar game in Unity but could not complete it successfully.
This week's assignment gave me a chance to rebuild and improve the idea.
A 2D pixel game developed on Godot engine using GDScript for gameplay programming and logic development.
To interface the game with custom hardware, the Seeed Studio XIAO RP2040 was programmed using
the Keyboard.h library, allowing the PCB to function as a USB Human Interface Device (HID).
The push button on the PCB acts like a keyboard key(space bar) input and controls the jump action inside the game.
Concept Ideation
The plan is a 2D pixel-art game connected with my sitar shaped pcb — Seeed Studio XIAO RP2040 using 3 touch pads, a push button, and NeoPixel feedback.
The initial idea was to create a 2D pixel-art game set in an apple orchard in Himachal Pradesh.
The concept idea for this game came from a personal memory. My father often says that one day he wants
to visit Himachal Pradesh's apple farm in northern India, walk through apple orchards, pluck apples directly from the farm, and eat them there.
The final goal of the game is that after completing all the levels and meeting every family member, a final message appears saying: "We will go to Himachal soon."
The player “Achan”, who continuously runs through the orchard.
Apples and obstacles appear randomly while the background keeps moving.
Achan has three actions:
- Idle
- Run
- Jump
Touch Pad Interactions
The player interacts using three touch pads:
- Touch Pad 1: collecting red apples
- Touch Pad 2: collecting green apples
- Touch Pad 3: jumping obstacles
- The push button is used to start or restart the game.
There are three types of apples in the game:Red apple, Green apple, Rotten apple.
Only the red and green apples should be collected.
If the player presses Pad 1 or Pad 2 when a rotten apple appears, the player loses life.
- Achan first collects apples for Amma After reaching the required number of apples, he meets Amma
- Then he continues collecting apples for Ammu Later for Ashtami
The number of apples required for each character is randomized.
Example:
- 2 green apples and 3 red apples
- 1 green apple and 4 red apples
The game starts with three lives. Life Reduces When:
- hitting obstacles
- collecting rotten apples
The NeoPixel provides visual feedback during gameplay.
| Event | NeoPixel Color |
|---|---|
| Game running | Blue |
| Apple collected | Pink |
| Life increased | Green |
| Life decreased | Red |
| Game over | Red blinking |
This week, I accomplished the first spiral of the game as a realistic and achievable development goal.
The project started with a simpler first spiral inspired by the Chrome Dino game and
the focus was on creating the basic endless runner mechanic where the player jumps over obstacles.
The prototype includes a parallax scrolling background, two types of obstacle spawning, player movement, and jump mechanics.
The player has to jump over the obstacles, and if the character collides with an obstacle, the game ends.
Assest
Chatgpt
AI Prompt Used:
For creating the characters, I used ChatGPT to generate pixel-art references through prompts and multiple iterations.
For Achan's(father) character, I used prompts describing:
- a South Indian father
- wearing a mundu and shirt
- 2D pixel-art style
- sprite sheet animations for idle, run, and jump
For the other family characters — Amma(mother), Ammu(sister), and Ashtami(me) — I used their photo as references
and generated pixelated character sprites with simple animations such as idle standing and hand-raising gestures.
For the environment and assets:
- orchard and landscape elements were collected from Pinterest references
- apple graphics and layered background compositions were combined using Canva
- obstacles were sourced from pixel-art references found through online image searches
Game Development using GODOT
Godot Engine is a free, open-source, cross-platform game engine ideal for creating 2D, 3D, and XR (VR/AR/MR)
applications and games.
Godot supports a variety of programming languages, including its own scripting language - GDScript (which is Python-like), C++ and C#
Click here to download godot
Godot_v4.6.2-stable_win64.exe
- Normal Godot editor
- Used for making and running games
- Opens with graphical interface (GUI)
- Adds console window
- Shows debug logs and print statements
- Useful for programming/debugging
Godot Engine uses a scene + node system, as i'm making 2d game: Node2D is the backbone of everything in 2D.
Node2D
Transform (Position + Rotation + Scale) + Parent-Child System
So any node that needs to exist in 2D space must inherit from Node2D.
Source: Godot 101 - Part 1: Introduction to Godot
godot docs to learn the basics
extends ParallaxBackground
# This script is attached to a ParallaxBackground node
# It creates a moving scrolling background
var scroll_speed = 150 # Speed of background movement
var game_started = false # Checks whether the game has started
# Initially false
# _process() runs every frame
# delta = time between frames
func _process(delta):
# Start the game when the jump button is pressed
if not game_started and Input.is_action_just_pressed("jump"):
# "jump" action is set in Input Map
game_started = true
# Move background only after game starts
if game_started:
# Move the background toward left side
scroll_offset.x -= scroll_speed * delta
# This script is attached to the Player CharacterBody2D node
extends CharacterBody2D
# Jump force value
# Negative value moves the player upward
const JUMP_VELOCITY = -550.0
# Gravity value used to pull the player back down
var gravity = 2100
# Variable to check whether the game has started or not
var game_started = false
# Accessing the AnimatedSprite2D node from the scene
@onready var sprite = $AnimatedSprite2D
# Accessing the jump sound node
@onready var jump_sound = $jumpsound
# This function runs every physics frame
func _physics_process(delta: float) -> void:
# If the "jump" action is pressed
# (mapped to Space Bar or HID button input)
if Input.is_action_just_pressed("jump"):
# If the game has not started yet
if not game_started:
# Print message in debug console
print("Game Starting via Serial/HID!")
game_started = true # Start the game
# Allow jump only if player is touching the ground
if is_on_floor():
velocity.y = JUMP_VELOCITY # Apply jump force upward
if jump_sound: # Play jump sound
jump_sound.play()
# If the game has NOT started yet
if not game_started:
velocity.y = 0 # Keep player fixed in place
sprite.play("idle") # Play idle animation
# If the game has started
else:
if not is_on_floor(): # If player is in the air
velocity.y += gravity * delta # Apply gravity continuously
sprite.play("jump")# Play jump animation
# If player is on the ground
else:
if velocity.y >= 0: # Prevent extra downward movement
velocity.y = 0 # Reset vertical movement
sprite.play("run") # Play running animation
# Built-in Godot function for movement and collision
move_and_slide()
# This script is attached to the obstacle object
# The obstacle is an Area2D node
extends Area2D
# Speed at which the obstacle moves
var speed = 150
# This function runs every frame
func _process(delta):
# Reduce X position continuously
# This creates the illusion that the player is running
position.x -= speed * delta
# If obstacle goes outside the screen
if position.x < -550:
# Remove obstacle from the scene
# Helps save memory and performance
queue_free()
# This function runs when another body touches the obstacle
func _on_body_entered(body: Node2D) -> void:
# Check whether the colliding body is the player
if body.name == "achan":
# Print debug message in console
print("HIT: Achan crashed into an obstacle!")
# Reload the current scene safely
# This restarts the game after collision
get_tree().call_deferred("reload_current_scene")
# This script controls obstacle spawning in the game
extends Marker2D # Marker2D is used as the spawn position reference
# Preload the small obstacle scene
@onready var obs_small = preload("res://ObstacleSmall.tscn")
# Preload the large obstacle scene
@onready var obs_large = preload("res://ObstacleLarge.tscn")
# Variable to check whether the game has started
var game_started = false
# Runs every frame
func _process(_delta):
# Wait until the player presses the jump button
# (Space Bar or PCB button mapped as "jump")
if not game_started and Input.is_action_just_pressed("jump"):
# Start the game
game_started = true
# Start the timer immediately
# Small delay added so obstacle does not appear instantly
$Timer.start(0.1)
# This function runs every time the timer finishes
func _on_timer_timeout():
# If game has not started,
# stop the timer and prevent spawning
if not game_started:
$Timer.stop()
return
# Randomly choose how many obstacles spawn together
# Can spawn between 1 and 3 obstacles
var group_size = randi_range(1, 3)
# Loop based on group size
for i in range(group_size):
# Randomly choose between small and large obstacle
var choice = randi() % 2
# If random choice is 0 -create small obstacle; Otherwise create large obstacle
var obstacle = obs_small.instantiate() if choice == 0 else obs_large.instantiate()
# Add obstacle into the main scene
get_parent().add_child(obstacle)
# Add spacing between spawned obstacles
var internal_gap = i * 150
# Set obstacle position
obstacle.global_position = global_position + Vector2(internal_gap, 0)
# Randomize next spawn timing
# Prevents obstacles from spawning too close
$Timer.wait_time = randf_range(2.0, 4.0)
# Restart timer
$Timer.start()
Simple Jump Button using XIAO RP2040 This code turns the XIAO RP2040 into a USB keyboard. When the button connected to pin D9 is pressed: The board sends a SPACEBAR key press to the computer
// Include the Keyboard library
// This allows the board to act like a USB keyboard
#include "Keyboard.h"
// Store the pin number where the button is connected
const int buttonPin = D9;
// Variable to remember the previous button state
// INPUT_PULLUP is used:
- Button NOT pressed = HIGH
- Button pressed = LOW
bool lastButtonState = HIGH;
void setup() { // Runs only once when the board starts
// Set button pin as INPUT with internal pull-up resistor
pinMode(buttonPin, INPUT_PULLUP);
// Set the onboard LED as OUTPUT
pinMode(LED_BUILTIN, OUTPUT);
// Start Serial Monitor communication
// Used for debugging and seeing messages
Serial.begin(115200);
// Start keyboard functionality
Keyboard.begin();
// Wait 2 seconds
// Gives the computer time to recognize the board as a keyboard
delay(2000);
// Turn OFF the onboard LED
// LOW = OFF on most XIAO boards
digitalWrite(LED_BUILTIN, LOW);
// Print message in Serial Monitor
Serial.println("Ready to Jump!");
}
void loop() {// Runs again and again forever
// Read the current state of the button
bool currentButtonState = digitalRead(buttonPin);
/*
Detect button press:
currentButtonState == LOW
button is currently pressed
lastButtonState == HIGH
button was NOT pressed before
This means:
The button has JUST been pressed
*/
if (currentButtonState == LOW && lastButtonState == HIGH) {
// Turn ON the onboard LED
digitalWrite(LED_BUILTIN, HIGH);
// Press SPACEBAR key
Keyboard.press(' ');
// Small delay so computer can detect key press properly
delay(20);
// Release all keyboard keys
Keyboard.releaseAll();
// Print message in Serial Monitor
Serial.println("JUMP!");
// Turn OFF the LED after key press
digitalWrite(LED_BUILTIN, LOW);
// Small delay to prevent accidental double press
delay(50);
}
// Save current state as last state
// Needed for next loop comparison
lastButtonState = currentButtonState;
// Tiny delay for stable reading
delay(5);
}