Interface and Application Programming

Task:-

Group assignment:

Individual assignment


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.

1

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.

Concept Behind the Game

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

Main Player

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
The other characters — Amma, Ammu, and Ashtami — remain idle and raise their hands when Achan approaches them.

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.

Apple Types

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.



Progression System

  • 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
At the end of the game, all characters appear together and the player wins.

Randomized Apple Requirements

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
Life System

The game starts with three lives. Life Reduces When:

  • hitting obstacles
  • collecting rotten apples
Life Restoration: Lives can be restored gradually when Achan successfully reaches the next character.

NeoPixel Feedback System

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
First Spiral - compeleted this week

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.

2

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


3

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)
Godot_v4.6.2-stable_win64_console.exe
  • 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);
              }