Processing for Education

Processing is an open-source programming language and environment designed for artists, designers, and educators to create visual and interactive projects. It uses Java-based syntax and is commonly used for generative art, data visualization, and creative coding.

Processing for Education cover image

Creative Coding

I started working with Processing back in 2013. I started using it as a tool for my design thesis, but it quickly turned into something I genuinely enjoyed exploring. Below is a screengrab from an application interface I designed using Processing.

Processing Concept Image
[↑] An instrument cluster designed in Processing with UDP communication setup with the driving simulator at The Swedish National Road and Transport Research Institute

As I continued experimenting with Processing, I began teaching creative coding in an engaging way, helping kids learn by building interactive games. Below is a p5.js playground showcasing the code for a bouncing ball. Go ahead and click on the play button.

The Workshop

The goal was to create an engaging way for students to explore key concepts in physics, coding, and interactive design through the ping pong game.

Gameplay

This version of the game featured different mediums for the ball to travel through, along with various material choices for the ball itself. The idea was to encourage players to strategize their choice of medium and material based on their understanding of the properties involved and compete against each other. The following were the expected learning outcomes.

Medium Resistance ⛓️‍πŸ’₯

The game simulates how different mediums (air, water, oil) affect the speed of the ball. This helps students understand how resistance and viscosity impact motion.

Acceleration and Deceleration πŸš€

The ball's speed changes based on the medium, demonstrating how external factors affect velocity.

Game Logic 🎰

Students can learn how to structure a game loop, manage player inputs, and handle scoring.

Welcome screen for Fluid Pong

A welcome screen was added to introduce players to the game's core features and controls. It highlights the available mediums, material choices, and the corresponding keys for gameplay, ensuring players can get started with ease.

Code + Demo

The code below provides a reference for understanding the game's core structure, including how medium and material selections were implemented to enhance gameplay and learning.

// Fluid Pong with Dynamic Elements

                    // Define Medium class first
                    class Medium {
                      float factor;
                      color col;
                      float gravityFactor;
                      
                      Medium(float f, color c, float g) {
                        factor = f;
                        col = c;
                        gravityFactor = g;
                      }
                    }

                    // Game states
                    final int START_SCREEN = 0;
                    final int GAME_PLAY = 1;
                    int gameState = START_SCREEN;
                    boolean buttonHovered = false;

                    // Game variables
                    float ballX, ballY;
                    float ballSpeedX, ballSpeedY;
                    float paddle1Y, paddle2Y;
                    int score1, score2;
                    float paddleHeight = 80;
                    float paddleWidth = 10;
                    String currentMedium = "air";
                    float mediumFactor = 1;
                    HashMap<String, Medium> mediums;
                    float paddleSpeed = 5;
                    float originalPaddleHeight;
                    int powerUpTimer = 0;
                    boolean powerUpActive = false;
                    float powerUpX, powerUpY;
                    int consecutiveHits = 0;
                    color ballColor;
                    float ballSize = 20;
                    boolean fireballActive = false;
                    int comboMultiplier = 1;

                    void setup() {
                      size(800, 400);
                      initializeGame();
                    }

                    void initializeGame() {
                      resetBall();
                      paddle1Y = height/2 - paddleHeight/2;
                      paddle2Y = height/2 - paddleHeight/2;
                      score1 = 0;
                      score2 = 0;
                      originalPaddleHeight = paddleHeight;
                      ballColor = color(255);
                      
                      // Initialize mediums
                      mediums = new HashMap<String, Medium>();
                      mediums.put("air", new Medium(1, color(200, 200, 255, 100), 1.0));
                      mediums.put("water", new Medium(0.6, color(100, 100, 255, 150), 0.4));
                      mediums.put("oil", new Medium(0.4, color(255, 255, 100, 150), 0.2));
                      
                      spawnPowerUp();
                    }

                    void draw() {
                      if (gameState == START_SCREEN) {
                        drawStartScreen();
                      } else {
                        drawGame();
                      }
                    }

                    void drawStartScreen() {
                      background(0);
                      
                      // Title
                      textAlign(CENTER);
                      textSize(48);
                      fill(255);
                      text("Fluid Pong", width/2, height/4);
                      
                      // Instructions
                      textSize(20);
                      float instructY = height/2 - 60;
                      text("Player 1: W/S to move", width/2, instructY);
                      text("Player 2: ↑/↓ to move", width/2, instructY + 30);
                      text("Press 1/2/3 to change medium (Air/Water/Oil)", width/2, instructY + 60);
                      text("Collect power-ups to gain special abilities!", width/2, instructY + 90);
                      
                      // Start button
                      float buttonX = width/2 - 100;
                      float buttonY = height - 100;
                      float buttonW = 200;
                      float buttonH = 50;
                      
                      // Check if mouse is over button
                      buttonHovered = mouseX >= buttonX && mouseX <= buttonX + buttonW &&
                                      mouseY >= buttonY && mouseY <= buttonY + buttonH;
                      
                      // Draw button with hover effect
                      fill(buttonHovered ? color(100, 200, 100) : color(50, 150, 50));
                      rect(buttonX, buttonY, buttonW, buttonH, 10);
                      
                      // Button text
                      fill(255);
                      textSize(24);
                      text("START GAME", width/2, buttonY + 32);
                    }

                    void mousePressed() {
                      if (gameState == START_SCREEN) {
                        float buttonX = width/2 - 100;
                        float buttonY = height - 100;
                        float buttonW = 200;
                        float buttonH = 50;
                        
                        if (mouseX >= buttonX && mouseX <= buttonX + buttonW &&
                            mouseY >= buttonY && mouseY <= buttonY + buttonH) {
                          gameState = GAME_PLAY;
                          initializeGame();
                        }
                      }
                    }

                    void drawGame() {
                      background(0);
                      
                      // Draw medium
                      fill(mediums.get(currentMedium).col);
                      rect(0, 0, width, height);
                      
                      // Draw and update power-up
                      if (!powerUpActive) {
                        fill(255, 255, 0);
                        ellipse(powerUpX, powerUpY, 15, 15);
                        
                        if (dist(ballX, ballY, powerUpX, powerUpY) < 20) {
                          powerUpActive = true;
                          powerUpTimer = frameCount + 300;
                          activateRandomPowerUp();
                        }
                      }
                      
                      // Draw player labels and combo multiplier
                      textSize(16);
                      fill(255);
                      textAlign(LEFT);
                      text("Player 1 (W/S)", 10, 30);
                      text("Combo: x" + comboMultiplier, 10, 50);
                      textAlign(RIGHT);
                      text("Player 2 (↑/↓)", width - 10, 30);
                      
                      // Draw scores
                      textSize(32);
                      textAlign(LEFT);
                      text(str(score1), width/4, 50);
                      textAlign(RIGHT);
                      text(str(score2), 3*width/4, 50);
                      
                      // Game mechanics
                      updateGameLogic();
                    }

                    void updateGameLogic() {
                      // Draw paddles
                      fill(255);
                      rect(10, paddle1Y, paddleWidth, paddleHeight);
                      rect(width - 20, paddle2Y, paddleWidth, paddleHeight);
                      
                      // Draw ball with effects
                      fill(ballColor);
                      if (fireballActive) {
                        fill(255, 128, 0);
                        for (int i = 0; i < 5; i++) {
                          ellipse(ballX - (ballSpeedX * i/2), ballY - (ballSpeedY * i/2), ballSize - i*2, ballSize - i*2);
                        }
                      }
                      ellipse(ballX, ballY, ballSize, ballSize);
                      
                      // Ball physics and movement
                      ballSpeedY += 0.2 * mediums.get(currentMedium).gravityFactor;
                      float speedMultiplier = mediumFactor * (1 + (comboMultiplier - 1) * 0.2);
                      ballX += ballSpeedX * speedMultiplier;
                      ballY += ballSpeedY * speedMultiplier;
                      
                      // Collisions and scoring
                      handleCollisions();
                      handleInput();
                      
                      // Power-up timer
                      if (powerUpActive && frameCount > powerUpTimer) {
                        resetPowerUps();
                      }
                      
                      // Display current medium and effects
                      textSize(20);
                      textAlign(LEFT);
                      text("Medium: " + currentMedium, 10, height - 10);
                      if (fireballActive) {
                        text("FIREBALL ACTIVE!", width/2 - 50, height - 10);
                      }
                    }

                    void handleCollisions() {
                      // Ball collision with top and bottom
                      if (ballY > height || ballY < 0) {
                        ballSpeedY *= -0.9;
                        ballY = constrain(ballY, 0, height);
                        consecutiveHits = 0;
                        comboMultiplier = 1;
                      }
                      
                      // Ball collision with paddles
                      if ((ballX < 30 && ballY > paddle1Y && ballY < paddle1Y + paddleHeight) ||
                          (ballX > width - 30 && ballY > paddle2Y && ballY < paddle2Y + paddleHeight)) {
                        ballSpeedX *= -1.1;
                        consecutiveHits++;
                        updateComboMultiplier();
                        ballSpeedY += random(-1, 1);
                        
                        if (fireballActive) {
                          paddleSpeed = 8;
                        }
                      }
                      
                      // Scoring
                      if (ballX < 0 || ballX > width) {
                        if (ballX < 0) score2++;
                        else score1++;
                        resetBall();
                        resetPowerUps();
                      }
                    }

                    void handleInput() {
                      if (keyPressed) {
                        if (key == 'w' && paddle1Y > 0) paddle1Y -= paddleSpeed;
                        if (key == 's' && paddle1Y < height - paddleHeight) paddle1Y += paddleSpeed;
                        if (keyCode == UP && paddle2Y > 0) paddle2Y -= paddleSpeed;
                        if (keyCode == DOWN && paddle2Y < height - paddleHeight) paddle2Y += paddleSpeed;
                      }
                    }

                    void resetBall() {
                      ballX = width/2;
                      ballY = height/2;
                      ballSpeedX = random(3, 5) * (random(1) > 0.5 ? 1 : -1);
                      ballSpeedY = random(-2, 2);
                      consecutiveHits = 0;
                      comboMultiplier = 1;
                    }

                    void spawnPowerUp() {
                      powerUpX = random(100, width - 100);
                      powerUpY = random(100, height - 100);
                      powerUpActive = false;
                    }

                    void activateRandomPowerUp() {
                      int effect = int(random(3));
                      switch(effect) {
                        case 0: // Fireball mode
                          fireballActive = true;
                          ballColor = color(255, 165, 0);
                          ballSize = 25;
                          break;
                        case 1: // Extended paddle
                          paddleHeight = originalPaddleHeight * 1.5;
                          break;
                        case 2: // Speed boost
                          paddleSpeed = 7;
                          break;
                      }
                    }

                    void resetPowerUps() {
                      fireballActive = false;
                      paddleHeight = originalPaddleHeight;
                      paddleSpeed = 5;
                      ballColor = color(255);
                      ballSize = 20;
                      spawnPowerUp();
                    }

                    void updateComboMultiplier() {
                      comboMultiplier = min(consecutiveHits / 2 + 1, 5);
                    }

                    void keyPressed() {
                      if (key == '1') {
                        currentMedium = "air";
                        updateMediumFactor();
                      } else if (key == '2') {
                        currentMedium = "water";
                        updateMediumFactor();
                      } else if (key == '3') {
                        currentMedium = "oil";
                        updateMediumFactor();
                      }
                    }

                    void updateMediumFactor() {
                      mediumFactor = mediums.get(currentMedium).factor;
                    }

The demo is an early version of the game interface, featuring a universal medium selection and a single material type for the ball. This was later expanded to allow medium and material selection for both the playground and the ball, enabling each player to strategize and make the game more engaging.

Fifteen students, aged 12 to 16, participated in the workshop, engaging in hands-on activities and discussions to deepen their understanding of the concepts.