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.

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.

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.

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.