Lab 4: FSR game input

Description

I used the force sensitive resistor as an input for a game in Processing where the player flies an airplane to dodge walls. The force on the FSR determines the height of the airplane, which travels past obstacles at a constant speed. Originally, I wanted to have a trail following the airplane to show where it had been, but to improve the feel of the game, it was reduced to very small flames behind airplane to give a sense of the movement. An LED on the Arduino correlates its brightness with the FSR force for debugging when the serial connection has trouble.

I built a force diffuser from paper towels sandwiched between two stacks of sticky notes. The sticky note stacks were approximately the right size to be pushed with a hand, and the paper towels worked well because the amount of force diffused could be easy modulated by adding or removing paper towels. The sticky note stacks could be replaced with any hard object of the same size. While diffusing force is very useful for measuring forces outside of the FSR’s range, I found that when using the FSR as an input device for a game, it was best to have as little diffusing as possible to allow a single finger to have the most control.

Materials

  • Arduino
  • 1 Force Sensitive Resistor (FSR)
  • 1 LED
  • 1 22 KΩ resistor
  • 1 10 KΩ resistor
  • 10 wires

Images

Sticky Note force diffuser
Screenshot from FSR game

Arduino Code

int forceSensorPin = A0; // select the input pin for the rate potentiometer
int ledPin = 9; // select the pin for the LED
int forceValue = 0; // variable to store rate pot value
void setup() {
// declare the ledPin as an OUTPUT:
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}

void loop() {
// read the value from the sensors:
forceValue = analogRead(forceSensorPin);

// turn the ledPin on, convert from 0-1023 scale to 0-255
analogWrite(ledPin, forceValue / 4);
// stop the program for 10 milliseconds:
delay(10);
// turn the ledPin off:
digitalWrite(ledPin, LOW);
Serial.print(forceValue);
Serial.println();
}

Processing Code

/* PROCESSING FORCE GAME
* ----------------------
*
* Using a force input, guide an airplane
* to avoid obstacles.
*
* Created 23 September 2016
* Sam Meyer
*/
import processing.serial.*;
// Change this to the portname your Arduino board
String portname = "/dev/cu.usbmodem1411"; // or "COM5"
Serial port;
String buf="";
int cr = 13; // ASCII return == 13
int lf = 10; // ASCII linefeed == 10

int serialVal = 0;
int xPos = 1000;
int rectHeight = 50;
float rand;
boolean gameOver = false;

EvilRectangle er;
ArrayList erList;
int rCounter = 0;
long iDraw = 0;
int rectRate = 60;
PlayerIcon airplane;

class PlayerIcon {
float x;
float y;
float y2; // green flame
float y3; // blue flame

PlayerIcon() {
x = 150;
y = 0;
y2 = 0;
y3 = 0;
}

void draw() {
//blue flame
fill(0, 0, 255);
//ellipse(x3, y3, 100, 15);
triangle(x - 50, y3 + 8, x - 50, y3 - 8, x - 70, y3);
triangle(x - 50, y3 + 8, x - 50, y3 - 8, x - 48, y3);
// green flame
fill(0, 128, 0);
//ellipse(x2, y2, 100, 23);
triangle(x - 50, y2 + 12, x - 50, y2 - 12, x - 60, y2);
triangle(x - 50, y2 + 12, x - 50, y2 - 12, x - 45, y2);
// airplane
fill(255, 255, 255);
ellipse(x, y, 100, 25);
triangle(x - 10, y + 50, x - 10, y - 50, x + 30, y);
triangle(x - 50, y + 20, x - 50, y - 20, x - 20, y);
}

void setY(float val) {
// move y values back through the following shapes
y3 = y2;
y2 = y;
y = val;
}
}

class EvilRectangle {
float x;
float y;
float w;
float h;
EvilRectangle(float a, float b, float c, float d) {
x = a;
y = b;
w = c;
h = d;
}

void draw() {
fill(255, 0, 0);
rect(x, y, w, h);
}

boolean checkCollision(float xC, float yC) {
return (xC > x) && (xC < x + w) && (yC > y) && (yC < y + h); } } void setup() { size(1000,600); frameRate(30); smooth(); background(40,40,40); noStroke(); port = new Serial(this, portname, 9600); er = new EvilRectangle(xPos, 600-rectHeight, 55, rectHeight); erList = new ArrayList();
erList.add(er);
airplane = new PlayerIcon();
}

void draw() {

if (gameOver) {
fill(200);
textSize(52);
text("Crashed!\nDistance: " + str(int(iDraw)), 100, 200);
} else {
// increment counters
iDraw += 1;
rCounter += 1;

// erase the screen
background(40, 40, 40);

// draw the airplane
airplane.setY(max(50, 600 - serialVal/2 - 50));
airplane.draw();

// check for collsions
for (EvilRectangle er : erList) {
if (er.checkCollision(airplane.x + 50, airplane.y) ||
er.checkCollision(airplane.x - 50, airplane.y) ||
er.checkCollision(airplane.x, airplane.y + 50) ||
er.checkCollision(airplane.x, airplane.y - 50)) {
gameOver = true;
}
}

// draw the rectangle
for (EvilRectangle er : erList) {
er.draw();
er.x -= 5;
}

// draw current level
textSize(16);
fill(200);
text("Current\nlevel: " + str(61 - rectRate), 10, 40);

// add new rectangles
if (rCounter >= rectRate) {
rCounter = 0;
if (iDraw < 240) { rectHeight = 50 * int(iDraw) / 30; } else { rectHeight = int(random(50, 400)); } // increase speed if (iDraw > 400 & rectRate > 25) {
rectRate -= 1;
}

// pick rectangle direction
rand = random(1);
if (rand < 0.4) { // bottom rect erList.add(new EvilRectangle(xPos, 600-rectHeight, 55, rectHeight)); } else if (rand > 0.7) {
// top and bottom
erList.add(new EvilRectangle(xPos, 600-rectHeight/2, 55, rectHeight/2));
erList.add(new EvilRectangle(xPos, 0, 55, rectHeight/2));
} else {
// top rect
erList.add(new EvilRectangle(xPos, 0, 55, rectHeight));
}

// clean up rectangles that have left the screen
for (int i = erList.size() - 1; i >= 0; i--) {
if (erList.get(i).x < -100) { erList.remove(i); } } // reset rCounter rCounter = 0; } } } // called whenever serial data arrives void serialEvent(Serial p) { int c = port.read(); if (c != lf && c != cr) { buf += char(c); } if (c == lf) { serialVal = int(buf); // println("val="+serialVal); buf = ""; } }

Leave a Reply