Find Nature - Leo - Flora
Context and significance
In class, we learned about sensors and actuators, and during a group project, we used cardboard to build prototypes. This inspired me to combine both in interactive projects. Cardboard acts as the base for sensors and actuators: sensors take in input, and actuators produce a response. The Kinetic Wearables project deepened this by showing how sensors and actuators enable different motions, creating varied interactions when detecting sound or light or whatever. It also taught me the importance of making interactions meaningful and not repetitive, refining the design without overcomplicating it. And interaction is an ongoing engagement and communication between a system and people beyond words. For Find Nature, we designed a whac-a-mole game for the youth. Instead of a mole, we replaced with other animals like sheep, frog and owl with delicate decoration around it. Kids may build certain connections to the nature by playing this to some extent as we also designed a little inviting hand with touching sensor attached behind so that kids can interact with the animals popping up randomly by touching little hands to let animals down. To futher refine it, I might add some audio effects like different sounds of those animals. When the hands are touched, corresponding animal's sound will play to create more immersive and interactive experinces for kids, making them feel closer to the nature and animals.
Conception and design
In order to enable users to interact with these animals, I connect the servos that are attched with animal patterns to the touching sensors. This ensures responsive and instant feedbacks from the project.
The stepper motor placed in the middle is redesigned to a cloud with different decorations. With this rotating during the game, more motions are added to enhance user experience. For the cloud, cotton is the best choice. But it is hard to attach and stablize the cloud, so we use needles and buttons to fix it on the platform connected to the stepper motor.
We planned to stick printed patterns on the outside, but we use threads of different colors instead. Because printed patterns and the background cardboard are mismatched and might look messy. After adding flowers made of button and plastic colorful springs, the outside looks less monotonous and delicate.
The touching sensor is covered by printed cartoon hands to make it cute. Cartoon animal patterns are attached front and back so that users can see the cute animals from both sides instead of seeing bare cardboard from the back. To hide the messy circuit inside, we filled in cotton. We initially used fog machine to do so, but we have to fill in fog mannually everytime and the fog will disappear in a minute, so we use cotton instead.
Fabrication and production
At the begining, I revise the code based on the one I used for kinetic wearable to connect the touching sensors to servos. By doing so, when the sensor is touched, the corresponding servo rotate 90 degrees. Otherwise, it rotates back. The servo is then connected to wooden sticks where printed animal can be attched. After that, I glued the servos onto the cardboard. In addition, I also incorperate the code for stepper motor learned in recitation to the code here.
Videos:
https://drive.google.com/file/d/1g8UJiF0852Sh3FMTAfp9LbuQ4AMH2CAk/view?usp=share_link
https://drive.google.com/file/d/1xmKgbN9xsndX5w45LpuHyoEMCHx74CV9/view?usp=share_link
Code:
#include <Servo.h>
Servo myservo;
Servo myservo1;
Servo myservo2;
int ctsPin = 7;
int ctsPin1 = 12;
int ctsPin2 = 13;
#include <AccelStepper.h>
int DIR_PIN = 2;
int STEP_PIN = 3;
int EN_PIN = 4;
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
void setup(){
Serial.begin(9600);
pinMode(ctsPin, INPUT);
myservo.attach(9);
myservo1.attach(10);
myservo2.attach(11);
myservo.write(0);
myservo1.write(0);
myservo2.write(0);
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW);
stepper.setMaxSpeed(500);
stepper.setSpeed(500);
}
void loop()
{
stepper.runSpeed();
int ctsValue = digitalRead(ctsPin);
if (ctsValue == HIGH){
Serial.println("TOUCHED");
myservo.write(120);
}
else{
Serial.println("not touched");
myservo.write(0);
}
int ctsValue1 = digitalRead(ctsPin1);
if (ctsValue1 == HIGH){
Serial.println("TOUCHED");
myservo1.write(120);
}
else{
Serial.println("not touched");
myservo1.write(0);
}
int ctsValue2 = digitalRead(ctsPin2);
if (ctsValue2 == HIGH){
Serial.println("TOUCHED");
myservo2.write(120);
}
else{
Serial.println("not touched");
myservo2.write(0);
}
delay(400);
}
After I finish the code, we glue the cardboards together and use tape to fix stepper motor in the middle. With materials like thread, buttons, cotton found in the studio, we decorate the outside cardboards. I learned the tricks to glue thread and make little flowers from Catherine, then we worked on different cardboards separately to increase efficiency.
During user testing, many users feel the interaction is not enough and lose interest after touching the little hands repeatedly. One of the user suggest making it into a whac-a-mole game, which may engage users more. Also, the fog method to hid the circuits inside scares the users and also creates a bad smell. Therefore, to deal with these, I change the code and add a buzzer to play different music so that users know when to start and whether they win or lose. After a music with rising tone, the user has to hit at least three out of six to win. The winning music is "twinkle twinkle little star" while the failure sound is a falling tone. Besides, we replace the fog with cottons as well. I cut out the baffle board with a hole for stepper motor With the baffle, the circuits and the steper motor can be protected from being tangled by cotton. And these makes our project Find Nature.
Videos:
https://drive.google.com/file/d/1doSk5n5yrG00Mf9rSYyYAIiVIDV3adfL/view?usp=share_link
https://drive.google.com/file/d/1is2oRZEl1_G6re__41VlzP5aakLQyfIQ/view?usp=share_link
Code:
#include <Servo.h>
#include <AccelStepper.h> // Include the AccelStepper library
// Create servo objects
Servo servo1;
Servo servo2;
Servo servo3;
// Define the touch sensor pins
const int touchSensor1 = 7;
const int touchSensor2 = 12;
const int touchSensor3 = 13;
const int buzzerPin = 8; // Buzzer connected to pin 8
// Define stepper motor pins
const int DIR_PIN = 2; // Direction pin of stepper motor
const int STEP_PIN = 3; // Step pin of stepper motor
const int EN_PIN = 4; // Enable pin of stepper motor
// Variables to store sensor states
int sensorState1 = 0;
int sensorState2 = 0;
int sensorState3 = 0;
// Counter for correct hits and total attempts
int hitCounter = 0;
int attemptsCounter = 0;
const int maxAttempts = 6;
// Track which servo is currently active
int activeServo = 0;
// Timing variables
unsigned long servoActivatedTime = 0;
const long reactionTime = 2000; // 2 seconds to react
// Stepper motor settings
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN); // Define stepper
// Define the notes of the "Twinkle Twinkle Little Star" melody
int twinkleMelody[] = {
262, 262, 392, 392, 440, 440, 392, // C C G G A A G
349, 349, 330, 330, 294, 294, 262 // F F E E D D C
};
// Define the duration of each note (in ms)
int twinkleDurations[] = {
500, 500, 500, 500, 500, 500, 1000, // 7 notes
500, 500, 500, 500, 500, 500, 1000 // 7 notes
};
// Define the notes of the failure melody
int failureMelody[] = {
330, 294, 262, 220, 196, 196 // E D C A G G (simple descending tones)
};
// Define the duration of each failure note (in ms)
int failureDurations[] = {
400, 400, 400, 400, 400, 800
};
// Define the notes of the intro melody (start game)
int startGameMelody[] = {
392, 440, 494, 523 // G A B C (ascending)
};
// Define the duration of each intro note (in ms)
int startGameDurations[] = {
400, 400, 400, 800
};
void setup() {
// Attach servos to pins
servo1.attach(9);
servo2.attach(10);
servo3.attach(11);
// Set touch sensor and buzzer pins as input/output
pinMode(touchSensor1, INPUT);
pinMode(touchSensor2, INPUT);
pinMode(touchSensor3, INPUT);
pinMode(buzzerPin, OUTPUT); // Buzzer pin is an output
// Stepper motor configuration
pinMode(EN_PIN, OUTPUT); // Enable pin as output
digitalWrite(EN_PIN, LOW); // Enable the stepper motor
stepper.setMaxSpeed(1000); // Set the maximum speed for the stepper motor
stepper.setSpeed(100); // Set the stepper motor speed
randomSeed(analogRead(0)); // Seed the random number generator
// Play the intro melody before the game starts
playIntroMelody();
}
void loop() {
unsigned long currentMillis = millis();
// Run the stepper motor continuously (unless stopped for melody)
stepper.runSpeed(); // Non-blocking continuous rotation
// Randomly choose one servo to activate if none are active
if (activeServo == 0 && attemptsCounter < maxAttempts) {
activeServo = random(1, 4); // Randomly select servo 1, 2, or 3
servoActivatedTime = currentMillis; // Record the time when the servo pops up
attemptsCounter++; // Increase the attempt count
if (activeServo == 1) {
servo1.write(110); // Servo 1 pops up
} else if (activeServo == 2) {
servo2.write(110); // Servo 2 pops up
} else if (activeServo == 3) {
servo3.write(110); // Servo 3 pops up
}
}
// Automatically reset the servo if 2 seconds have passed without a hit
if (currentMillis - servoActivatedTime >= reactionTime) {
resetServo();
}
// Check if sensor 1 is touched and servo 1 is active
sensorState1 = digitalRead(touchSensor1);
if (activeServo == 1 && sensorState1 == HIGH) {
hitServo(1);
}
// Check if sensor 2 is touched and servo 2 is active
sensorState2 = digitalRead(touchSensor2);
if (activeServo == 2 && sensorState2 == HIGH) {
hitServo(2);
}
// Check if sensor 3 is touched and servo 3 is active
sensorState3 = digitalRead(touchSensor3);
if (activeServo == 3 && sensorState3 == HIGH) {
hitServo(3);
}
// Check if the game is over (6 attempts made)
if (attemptsCounter >= maxAttempts) {
if (hitCounter >= 3) {
playSuccessMelody(); // Play success melody (Twinkle Twinkle Little Star)
} else {
playFailureMelody(); // Play failure melody
}
resetGame(); // Reset game after showing result
}
}
// Function to reset servos and active servo after timeout or hit
void resetServo() {
if (activeServo == 1) {
servo1.write(0); // Reset Servo 1
} else if (activeServo == 2) {
servo2.write(0); // Reset Servo 2
} else if (activeServo == 3) {
servo3.write(0); // Reset Servo 3
}
activeServo = 0; // Reset active servo
}
// Function to handle a successful hit
void hitServo(int servoNumber) {
if (servoNumber == 1) {
servo1.write(0); // Hit! Servo 1 goes back down
} else if (servoNumber == 2) {
servo2.write(0); // Hit! Servo 2 goes back down
} else if (servoNumber == 3) {
servo3.write(0); // Hit! Servo 3 goes back down
}
hitCounter++; // Increase hit counter
activeServo = 0; // Reset active servo
delay(500); // Small delay to allow reaction time
}
// Function to reset game variables after the game is over
void resetGame() {
// Reset all servos to 0 (lower all "moles")
servo1.write(0);
servo2.write(0);
servo3.write(0);
delay(1000); // Small delay before playing the next melody
// Reset hit counter and attempts
hitCounter = 0; // Reset hit counter
attemptsCounter = 0; // Reset attempts counter
// Play the intro melody to restart the game
playIntroMelody();
}
// Function to play the intro melody before the game starts
void playIntroMelody() {
for (int i = 0; i < 4; i++) {
int noteDuration = startGameDurations[i];
tone(buzzerPin, startGameMelody[i], noteDuration); // Play the note
delay(noteDuration * 0.50); // Brief pause between notes
noTone(buzzerPin); // Stop the tone
}
}
// Function to play the success melody (Twinkle Twinkle Little Star)
void playSuccessMelody() {
// Disable the stepper motor while playing the melody
digitalWrite(EN_PIN, HIGH);
for (int i = 0; i < 14; i++) {
int noteDuration = twinkleDurations[i];
tone(buzzerPin, twinkleMelody[i], noteDuration); // Play the note
delay(noteDuration * 0.50); // Brief pause between notes
noTone(buzzerPin); // Stop the tone
}
// Re-enable the stepper motor after the melody finishes
digitalWrite(EN_PIN, LOW);
}
// Function to play the failure melody
void playFailureMelody() {
// Disable the stepper motor while playing the melody
digitalWrite(EN_PIN, HIGH);
for (int i = 0; i < 6; i++) {
int noteDuration = failureDurations[i];
tone(buzzerPin, failureMelody[i], noteDuration); // Play the note
delay(noteDuration * 0.50); // Brief pause between notes
noTone(buzzerPin); // Stop the tone
}
// Re-enable the stepper motor after the melody finishes
digitalWrite(EN_PIN, LOW);
}
*This was adapted from here:
*https://forum.arduino.cc/t/whack-a-mole-game/1112219
Conclusions
The goal of our project was to create an interactive experience that both draws users in and encourages ongoing engagement. By using decorative animal figures that pop up randomly, we aimed to enhance the interaction beyond a simple touch-response model, aligning with our definition of interactive engagement. The unpredictability of the animals’ movements kept users attentive and interested, creating a scenario that immerses them in playful, dynamic interactions. However, a limitation was that the current setup offers a single-level mode without flexibility in difficulty or multiplayer options.
If given more time, we would expand the project to include varying difficulty levels and a multiplayer mode, enhancing replayability and broadening the interaction scope. Besides, differeny animals sounds can be added to create a more inmmersive experience. Despite minor setbacks, such as wiring problems and coding mistakes, these challenges underscored the importance of having a clear circuit and coding ability. Well-arranged circuit can make your life much easier while coding can save your time without restarting everything as we just changed code to make it a whac-a-mole game after user testing. Ultimately, we are glad that users found joy in engaging with the animal figures and satisfaction in mastering the game, validating our design choices. For me, this experience attaches importance to onboarding as well as a continuous interaction that keeps users tuned,
Disassembly
Appendix
The user when smelling the fog during user testing