Running From The Ghost

I wanted to make a spooky game for Halloween. So I did.

This is a game where one’s goal is to run away from the ghost, the scariest series of ellipses ever. The game itself runs in Processing and interfaces with Arduino. The controller consists of a circuit which has two force sensitive resistors that are used to control the player character’s legs. (The circuit’s box, which is not unlike the casing of a mummy, was lovingly crafted from tape, cardboard, and a box of Ritz Crackers.)
IMG_9596run fingersInitially, I was planning to have more types of enemies to run away from. It would be fun if the player character and enemies cycled, so that whoever you were were caught by last is who you play as next. It would also be interesting to implement different movement types, such as jumping, standing, and different walking/running speeds, so that each enemy has a different type of action that is best suited for a successful escape.
arduinoRunnerSketchAs for minor changes to the existing game, it would be more visually pleasing if the player character had arms, knees, and a more worried expression (the latter of which could be accomplished by the inclusion of eyebrows). fritzingDiagram_Runner_bbI’d say I spent my time 1/6th on the circuit, 1/6th on the circuit’s box, 2/6ths on the game’s mechanic, and 2/6ths on the visual look. (That’s 2/6ths physically making things and 4/6ths programming things. This does not take into account how long documentation took.)

Oh, and be sure to scroll down to the end of the code. Happy Halloween!

//Miranda Jacoby
//EMS Interactivity Section 4
//majacoby@andrew.cmu.edu
//Copyright Miranda Jacoby 2014

//Code for interfacing with Arduino provided by Golan Levin

//Big thanks to Matt for helping me
//figure out how to implement the counter

// This Processing program reads serial data for two sensors,
// presumably digitized by and transmitted from an Arduino. 
// It displays two rectangles whose widths are proportional
// to the values 0-1023 received from the Arduino.

// Import the Serial library and create a Serial port handler
import processing.serial.*;
Serial myPort;   

PImage gameover;
// Hey you! Use these variables to do something interesting. 
// If you captured them with analog sensors on the arduino, 
// They're probably in the range from 0 ... 1023:
int valueA;  // Sensor Value A
int valueB;  // Sensor Value B
int legPosy1;//70
int legPosy2;//70
int legPosx1;
int legPosx2;

//Gradient Variables
int Y_AXIS = 1;
color b1, b2;
color ghostCol = color(245, 249, 247);
int i = 0;
int j = 0;
int h = 150;
int toothWidth = 20;

boolean leftDown = true;
int runCounter = 50;

//------------------------------------
void setup() {
  size(800, 600);

  gameover = loadImage("scare.tif");

  b1 = color(27, 73, 85);//color(0, 102, 153);
  b2 = color(104, 20, 0);

  // List my available serial ports
  int nPorts = Serial.list().length; 
  for (int i=0; i < nPorts; i++) {
    println("Port " + i + ": " + Serial.list()[i]);
  } 

  // Choose which serial port to fetch data from. 
  // IMPORTANT: This depends on your computer!!!
  // Read the list of ports printed by the code above,
  // and try choosing the one like /dev/cu.usbmodem1411
  // On my laptop, I'm using port #4, but yours may differ.
  String portName = Serial.list()[5]; 
  myPort = new Serial(this, portName, 9600);
  serialChars = new ArrayList();
}

//------------------------------------
void draw() {
  // Process the serial data. This acquires freshest values. 
  processSerial();

  //A is left leg, B is right leg
  valueA = (int)map(valueA, 0, 1023, 0, 100);
  valueB = (int)map(valueB, 0, 1023, 0, 100);

  legSwitch();
  println(runCounter);

  setGradient(0, 0, width, height, b1, b2, Y_AXIS);
  drawScenery();
  drawEnemy();
  drawHill();
  drawPlayer(valueA, valueB);
  if (runCounter < 0){    image(gameover, 0, 0);    }   // draw a pink rectangle displaying valueA:   //fill (255, 200, 200);      //rect (0, 0, valueA, 100);   // draw a blue rectangle displaying valueB:   //fill (200, 200, 255);    //rect (0, 100, valueB, 100);    //fill (0);    // draw the letters A and B:   //text ("A", 20, 60);    //text ("B", 20, 160);   //println("A "+ valueA);   //println("B "+ valueB); } //--------------------------------------------------------------- // The processSerial() function acquires serial data byte-by-byte,  // as it is received, and when it is properly captured, modifies // the appropriate global variable.  // You won't have to change anything unless you want to add additional sensors.  /* The (expected) received serial data should look something like this:    A903  B412  A900  B409  A898  B406  A895  B404  A893  B404  ...etcetera.  */ ArrayList serialChars;      // Temporary storage for received serial data int whichValueToAccum = 0;  // Which piece of data am I currently collecting?  boolean bJustBuilt = false; // Did I just finish collecting a datum? void processSerial() {   while (myPort.available () > 0) {
    char aChar = (char) myPort.read();

    // You'll need to add a block like one of these 
    // if you want to add a 3rd sensor:
    if (aChar == 'A') {
      bJustBuilt = false;
      whichValueToAccum = 0;
    } else if (aChar == 'B') {
      bJustBuilt = false;
      whichValueToAccum = 1;
    } else if (((aChar == 13) || (aChar == 10)) && (!bJustBuilt)) {
      // If we just received a return or newline character, build the number: 
      int accum = 0; 
      int nChars = serialChars.size(); 
      for (int i=0; i < nChars; i++) {          int n = (nChars - i) - 1;          int aDigit = ((Integer)(serialChars.get(i))).intValue();          accum += aDigit * (int)(pow(10, n));       }       // Set the global variable to the number we captured.       // You'll need to add another block like one of these        // if you want to add a 3rd sensor:       if (whichValueToAccum == 0) {         valueA = accum;         // println ("A = " + valueA);       } else if (whichValueToAccum == 1) {         valueB = accum;         // println ("B = " + valueB);       }       // Now clear the accumulator       serialChars.clear();       bJustBuilt = true;     } else if ((aChar >= 48) && (aChar <= 57)) {
      // If the char is between '0' and '9', save it.
      int aDigit = (int)(aChar - '0'); 
      serialChars.add(aDigit);
    }
  }
}

void setGradient(int x, int y, float w, float h, color c1, color c2, int axis ) {

  noFill();

  if (axis == Y_AXIS) {  // Top to bottom gradient
    for (int i = y; i <= y+h; i++) {
      float inter = map(i, y, y+h, 0, 1);
      color c = lerpColor(c1, c2, inter);
      stroke(c);
      line(x, i, x+w, i);
    }
  }
}

void drawPlayer(int legPosy1, int legPosy2) {
  noStroke();
  pushMatrix();
  translate(250, 210);

  //shadow
  fill(5, 40, 4);
  ellipse(0, 275, 200, 100);

  //player's right leg top
  stroke(0);
  strokeWeight(20);
  pushMatrix();
  translate(40, 170);
  rotate(6);
  line(0, 0, 0, 100 - legPosy1); //70 is variable
  popMatrix();
  //player's right leg bottom

  //player's left leg top
  stroke(0);
  strokeWeight(20);
  pushMatrix();
  translate(-40, 170);
  rotate(-6);
  line(0, 0, 0, 100 - legPosy2); //70 is variable
  popMatrix();

  //player's left leg top

  //player's body
  noStroke();
  fill(88, 3, 1);
  triangle(0, -20, 100, 170, -100, 170);
  //player's head
  fill(147, 125, 97);
  ellipse(0, 0, 130, 90);
  //player's eyes
  //player's right eye
  pushMatrix();
  translate(30, 0);
  fill(255);
  ellipse(0, 0, 40, 40);
  fill(0);
  ellipse(10, 0, 15, 15);
  popMatrix();
  //player's left eye
  pushMatrix();
  translate(-30, 0);
  fill(255);
  ellipse(0, 0, 40, 40);
  fill(0);
  ellipse(10, 0, 15, 15);
  popMatrix();

  popMatrix();
}

void drawEnemy() {
  noStroke();
  fill(ghostCol);
  pushMatrix();
  translate(600, 300);
  scale(2 * (1.0 - runCounter/100.0));
  //ghost's body
  ellipse(0, 0, 210, 400);
  ellipse(0, 100, 210, 200);
  ellipse(0, 0, 200, 400);
  for (j = 0; j < 4; j++) {
    if ((j == 1) || (j == 2)) {
      h = 170;
    } else if ((j == 0) || (j == 3)) {
      h = 150;
    }
    ellipse(-75 +(j*50), h, 60, 90);
  }
  //ghost's eyes
  //ghost's right eye
  pushMatrix();
  translate(-40, 0);
  fill(250, 200, 200);
  ellipse(0, -100, 50, 50);
  fill(200, 150, 150);
  ellipse(0, -100, 40, 40);
  fill(150, 100, 100);
  ellipse(0, -100, 30, 30);
  fill(100, 50, 50);
  ellipse(0, -100, 20, 20);
  fill(50, 0, 0);
  ellipse(0, -100, 10, 10);
  popMatrix();
  //ghost's left eye 
  pushMatrix();
  translate(40, 0);
  fill(250, 200, 200);
  ellipse(0, -100, 50, 50);
  fill(200, 150, 150);
  ellipse(0, -100, 40, 40);
  fill(150, 100, 100);
  ellipse(0, -100, 30, 30);
  fill(100, 50, 50);
  ellipse(0, -100, 20, 20);
  fill(50, 0, 0);
  ellipse(0, -100, 10, 10);
  popMatrix();

  //ghost's mouth
  fill(250, 200, 200);
  ellipse(0, 20, 125, 175);
  fill(200, 150, 150);
  ellipse(0, 20, 105, 155);
  fill(150, 100, 100);
  ellipse(0, 20, 85, 135);
  fill(100, 50, 50);
  ellipse(0, 20, 65, 115);
  fill(50, 0, 0);
  ellipse(0, 20, 45, 95);
  for (i = 0; i < 5; i++) {     fill(ghostCol);     ellipse(-45 +(i*23), -52, 30, 45);   }   //ghost's right row of teeth   fill(250, 250, 225);   ellipse(-46, 35, toothWidth, 65);   fill(250, 250, 200);   ellipse(-46 +(10), 50, toothWidth, 65);   fill(250, 250, 225);   ellipse(-46 +(23), 65, toothWidth, 65);   fill(250, 250, 200);   ellipse(-46 +(33), 70, toothWidth, 65);   //ghost's left row of teeth   fill(250, 250, 225);   ellipse(-46 +(92), 35, toothWidth, 65);   fill(250, 250, 200);   ellipse(-46 +(79), 50, toothWidth, 65);   fill(250, 250, 225);   ellipse(-46 +(69), 65, toothWidth, 65);   fill(250, 250, 200);   ellipse(-46 +(59), 70, toothWidth, 65);   //ghost'sfront tooth   fill(250, 250, 225);   ellipse(-46 +(46), 75, toothWidth, 65);   popMatrix(); } void legSwitch() {   if (valueA > valueB + 50 && leftDown==false) {
    leftDown = true;
    runCounter+=10;
  } else if (valueB > valueA + 50 && leftDown==true) {
    leftDown = false;
    runCounter+=10;
  }
  runCounter = constrain(runCounter, 0, 100);

  ghostApprocah();
}

void ghostApprocah() {
  runCounter--;
}

void drawHill(){
  noStroke();
  fill(5, 47, 4);
  ellipse(width/2.5, height + (height/8), 1000, 700);
}

void drawScenery(){
    noStroke();
    fill(5, 27, 24);
    ellipse(width/2, height, 500, 700);
    ellipse(width/4, height, 600, 850);
}

scare

Comments are closed.