Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
/* //<>//

 Interactive Toy Assignment
 
 Orbit
 
 Nathan Powless-Lynes@2015.10.7
 
 A series of planet-like objects interact with each other due to gravity. Gravity works just like it does in reality, using
 the same formula from physics, except the gravitational constant is different. The player is also able to contribute to
 the largest planet's movement using WASD, making for limetless opportunities to play with the orbits of the moons.
 The player also has the ability to reverse their own planet's gravity using space, allowing them to push away the moons if they
 get too close.
 
 This is a toy, so there are no definite goals. If you like, you can set them for yourself. For example:
  - Can I get the moons to resume orbit around me after they have stopped?
  - Can I get the green moon to orbit the red moon without my planet interfering?
  - Can I get the green moon to orbit me really closely, and then launch it at the red moon by reversing gravity?
 How you choose to play Orbit is up to you! Have fun!
 
 Version History:
 
 Version 1.0: the player's planet and a moon to orbit it. The player's planet is slightly affected greenPositionY gravity, but is
 mostly controlled greenPositionY the player.
 
 Version 1.1: adds a second moon, made eveDistanceY object attracted to each other.playerPositionX
 
 Version 1.2: made the player unattracted to the moons.
 
 Version 1.3: placed the moons perfectly so moon b orbits moon a and moon a orbits the player. Also added puffs of smoke that
 come out of the player when accelerating
 
 Version 1.4: changed the colour of moon a to red. Moons are now referred to as Red and Green. Also added the ability to
 reverse the force of gravity coming from the player
 
 Version 1.5: added a starry background. Made pulses come out of the player, illustrating the pull or push of gravity
 
 Version 1.6 (current): fixed issue that made it difficult to maintain orbit. Added separate function for determining distance.
 Also added instructional text that fades in, but dissappears when the player proves they know how the controls work
 by moving and reversing gravity with WASD and space
 
 */

//declaring global variables

/*
player variables

These are variables that determine how big and massive the player will be. The player's mass will affect how much the other
planets are attracted to thie player. Other variables indicate the player's position and speed in both directions.

*/

float playerPositionX;//x-coordinate
float playerPositionY;//y-coordinate
float playerSpeedX;//speed in the x-direction
float playerSpeedY;//speed in the y-direction
float playerMass;//mass (affects attraction)
float playerSize;//width and height

/*
moon variables

These are the variables for the red and green moons. They show how big and massive they are. The red moon has a greater
mass, so it is more attracted to the player than the green moon is. Their speed and position for both directions are
included as well.

*/

//red variables
float redPositionX;//x-coordinate
float redPositionY;//y-coordinate
float redSpeedX;//speed in the x-direction
float redSpeedY;//speed in the y-direction
float redMass;//mass (affects attraction)
float redSize;//width and height

//green variables
float greenPositionX;//x-coordinate
float greenPositionY;//y-coordinate
float greenSpeedX;//speed in the x-direction
float greenSpeedY;//speed in the y-direction
float greenMass;//mass (affects attraction)
float greenSize;//width and height

/*
directional controls variables

These variables are used to show which direction(s) the player is accelerating in. They are activated by WASD,
allowing the player to take control of their planet.

*/

boolean accelerateRight = false;//true when accelerating right
boolean accelerateLeft = false;//true when accelerating left
boolean accelerateUp = false;//true when accelerating up
boolean accelerateDown = false;//true when accelerating down

/*
distance variables

These variables are used when measuring the distance between the planets. The distance is used for the
formulas for attraction, furthermore deciding whether or not attraction occurs at all.

*/

//from the player to the red moon
float redPlayerDistance;//magnitude
float redPlayerDistanceX;//in x-direction
float redPlayerDistanceY;//in y-direction

//from the player to the green moon
float greenPlayerDistance;//magnitude
float greenPlayerDistanceX;//in x-direction
float greenPlayerDistanceY;//in y-direction

//from the red moon to the green moon
float redGreenDistance;//magnitude
float redGreenDistanceX;//in x-direction
float redGreenDistanceY;//in y-direction

/*
reversal of gravity

This variable is used to test whether gravity is reversed or not. Reverse gravity is activated by pressing
the space bar. When gravity is reversed, rather than attracting the moons, the player's planet repels them.

*/
boolean reverseGravity;//true when player repels the moons

/*
gravitational constant

This variable is used in the gravity formulas. Although technically might not be necessary, to make the
gravity more like reality, a constant is needed. This formula is taken straight from grade 12 physics,
so it is a legitimate gravity formula. The constant is changed from reality to make it easier to
achieve orbit.

*/
float G;

/*
pulse variables

The player's planet emmits pulses every so often, and a variable is used to measure the time since the
last pulse. There is another variable that represents the duration of each pulse. Both variables are
used in determining the intensity of the colour of the pulse.

*/

int pulseTime;
int pulseDuration;

/*
smoke variables

It may seem like there are a lot of variables for the smoke, but it's really just the same 3 variables
repeated once for each puff of smoke. The first two determine the x and y position of the puff, and
the third modifies the translucency, width and height of the puff.

There are also variables that are nonspecific to each puff. One acts as a timer, showing when the last
puff was made. Another keeps track of which puff is to be drawn next in the sequence. The last
variable merely shows when the "engine" is on, or when the player is accelerating.

*/

int lastPuff = 0;//lastPuff since last puff
//three coordinates for puffs so we can have three on the screen at a time
float puff1PositionX;//x-coordinate
float puff1PositionY;//y-coordinate
float puff2PositionX;//x-coordinate
float puff2PositionY;//y-coordinate
float puff3PositionX;//x-coordinate
float puff3PositionY;//y-coordinate
float puff1Mod;//modifies alpha and width
float puff2Mod;//modifies alpha and width
float puff3Mod;//modifies alpha and width
byte whichPuff;//shows which puff in the sequence must be drawn next
boolean engineOn;//whether or not the player is accelerating

/*
text variable

Variables that say whether the player has moved or reversed gravity yet. These are used to
determine whether or not the instructional text should be displayed.

*/

boolean moved;
boolean reversedOnce;

//setting up the canvas and initializing variables
void setup()
{
  /*
  set up
  
  Declare the canvas size and get rid of the stroke. Also draw a background.
  
  */
  
  size(400, 400);//canvas size
  noStroke();
  background(0);

  /*
  initializing variables
  
  Just has all the variables initialized at the same place. The functions of the variables are all
  specified above. I could have initialized them up there, but this hopefully makes the
  program more readable.
  
  */
  
  playerSpeedX = 0;
  playerSpeedY = 0;
  playerMass = 20;
  playerSize = 30;
  playerPositionX = width/2;
  playerPositionY = height/2;

  redSpeedX = -1.95;
  redSpeedY = 0;
  redMass = 10;
  redSize = 20;
  redPositionX = width/2;
  redPositionY = height/2 - 100;

  greenSpeedX = -4;
  greenSpeedY = 0;
  greenMass = 5;
  greenSize = 10;
  greenPositionX = width/2;
  greenPositionY = height/2 - 120;
  
  reverseGravity = false;
  
  G = 2;
  
  pulseTime = 0;
  pulseDuration = 1200;
  
  whichPuff = 1;
  engineOn = false;
  
  moved = false;
  reversedOnce = false;
}


/*
draw

Updates positions, speeds and acceleration of the planets and draws everything in the game.
Also draws text if the player has not used the controls yet.

*/

void draw()
{
  //UPDATE

  //check how far the planets are from each other
  updateDistance();

  //check acceleration and adjust speed
  redToPlayer();
  redToGreen();
  greenToPlayer();
  playerAcceleration();

  //update the planetoids' current position
  updatePlayerPosition();
  updateRedPosition();
  updateGreenPosition();
  
  //update the puffs of smoke
  updateSmoke();

  //DRAW

  //a translucent background, resulting in trails as the planets move
  fill(0, 0, 0, 60);
  rect(0, 0, width, height);
  
  //check if the player has figured out the controls yet. If not,
  //tell them the controls
  if(!moved)
  {
    fill(constrain(0.05*millis(), 0, 255));
    text("Use WASD to move", 10, 40);
  }
  if(!reversedOnce)
  {
    fill(constrain(0.05*millis(), 0, 255));
    text("Hold space to reverse your gravity", width - 190, 40);
  }

  //draw the stars
  drawStars();

  //draw the moons
  drawGreen();
  drawRed();

  //draw the player
  drawPulse();
  drawSmoke();
  drawPlayer();
}

//measure the distance between each planet
void updateDistance()
{
  //distance is the difference in their coordinates
  redPlayerDistanceX = playerPositionX - redPositionX;
  redPlayerDistanceY = playerPositionY - redPositionY;
  greenPlayerDistanceX = playerPositionX - greenPositionX;
  greenPlayerDistanceY = playerPositionY - greenPositionY;
  redGreenDistanceX = redPositionX - greenPositionX;
  redGreenDistanceY = redPositionY - greenPositionY;

  //magnitude of distance (pythagorean theorem)
  redPlayerDistance = sqrt(redPlayerDistanceX*redPlayerDistanceX + redPlayerDistanceY*redPlayerDistanceY);
  greenPlayerDistance = sqrt(greenPlayerDistanceX*greenPlayerDistanceX + greenPlayerDistanceY*greenPlayerDistanceY);
  redGreenDistance = sqrt(redGreenDistanceX*redGreenDistanceX + redGreenDistanceY*redGreenDistanceY);
}

//checks attraction between the red moon and the player
void redToPlayer()
{
  //variables
  //local gravity
  float g;//magnitude
  float gx;//in x-direction
  float gy;//in y-direction
  
  //make sure they are close enough together to issue attraction
  if (redPlayerDistance < 200)
  {
    //universal formula for gravity
    g = (G*playerMass*redMass)/(redPlayerDistance*redPlayerDistance);

    //if gravity is reversed, repel instead of attracting
    if (reverseGravity)
    {
      gx = -abs(g*(redPlayerDistanceX/redPlayerDistance));
      gy = -abs(g*(redPlayerDistanceY/redPlayerDistance));
    } else
    {
      gx = abs(g*(redPlayerDistanceX/redPlayerDistance));
      gy = abs(g*(redPlayerDistanceY/redPlayerDistance));
    }

    //accelerating in x-direction
    if (playerPositionX > redPositionX)
    {
      //playerSpeedX = constrain(playerSpeedX - 0.1*gx, -8, 8);
      redSpeedX = constrain(redSpeedX + gx, -8, 8);
    } else
    {
      //playerSpeedX = constrain(playerSpeedX + 0.1*gx, -8, 8);
      redSpeedX = constrain(redSpeedX - gx, -8, 8);
    }

    //accelerating in y-direction
    if (playerPositionY > redPositionY)
    {
      //playerSpeedY = constrain(playerSpeedY - 0.1*gy, -8, 8);
      redSpeedY = constrain(redSpeedY + gy, -8, 8);
    } else
    {
      //playerSpeedY = constrain(playerSpeedY + 0.1*gy, -8, 8);
      redSpeedY = constrain(redSpeedY - gy, -8, 8);
    }
  }//if too far from both other planets, slow it down
  else if (redGreenDistance > 200)
  {
    redSpeedX = 0.99*redSpeedX;
    redSpeedY = 0.99*redSpeedY;
  }
}

//checks attraction between player and the green moon
void greenToPlayer()
{
  //variables
  //local gravity
  float g;//magnitude
  float gx;//in x-direction
  float gy;//in y-direction
  
  //make sure they are close enough together to issue attraction
  if (greenPlayerDistance < 200)
  {
    //universal formula for gravity
    g = (G*playerMass*greenMass)/(greenPlayerDistance*greenPlayerDistance);

    //if gravity is reversed, repel instead of attracting
    if (reverseGravity)
    {
      gx = -abs(g*(greenPlayerDistanceX/greenPlayerDistance));
      gy = -abs(g*(greenPlayerDistanceY/greenPlayerDistance));
    } else
    {
      gx = abs(g*(greenPlayerDistanceX/greenPlayerDistance));
      gy = abs(g*(greenPlayerDistanceY/greenPlayerDistance));
    }

    //accelerating in x-direction
    if (playerPositionX > greenPositionX)
    {
      //playerSpeedX = constrain(playerSpeedX - 0.1*gx, -8, 8);
      greenSpeedX = constrain(greenSpeedX + gx, -8, 8);
    } else
    {
      //playerSpeedX = constrain(playerSpeedX + 0.1*gx, -8, 8);
      greenSpeedX = constrain(greenSpeedX - gx, -8, 8);
    }

    //accelerating in y-direction
    if (playerPositionY > greenPositionY)
    {
      //playerSpeedY = constrain(playerSpeedY - 0.1*gy, -8, 8);
      greenSpeedY = constrain(greenSpeedY + gy, -8, 8);
    } else
    {
      //playerSpeedY = constrain(playerSpeedY + 0.1*gy, -8, 8);
      greenSpeedY = constrain(greenSpeedY - gy, -8, 8);
    }
  }//if too far from both other planets, slow it down
  else if (redGreenDistance > 200)
  {
    greenSpeedX = 0.99*greenSpeedX;
    greenSpeedY = 0.99*greenSpeedY;
  }
}

//checks attraction between the moons
void redToGreen()
{
  //variables
  //local gravity
  float g;//magnitude
  float gx;//in x-direction
  float gy;//in y-direction
  
  //make sure they are close enough together to issue attraction
  if (redGreenDistance < 200)
  {
    //universal formula for gravity
    g = (G*redMass*greenMass)/(redGreenDistance*redGreenDistance);
    gx = abs(g*(redGreenDistanceX/redGreenDistance));
    gy = abs(g*(redGreenDistanceY/redGreenDistance));

    //accelerating in x-direction
    if (redPositionX > greenPositionX)
    {
      redSpeedX = constrain(redSpeedX - 0.1*gx, -8, 8);
      greenSpeedX = constrain(greenSpeedX + gx, -8, 8);
    } else
    {
      redSpeedX = constrain(redSpeedX + 0.1*gx, -8, 8);
      greenSpeedX = constrain(greenSpeedX - gx, -8, 8);
    }

    //accelerating in y-direction
    if (redPositionY > greenPositionY)
    {
      redSpeedY = constrain(redSpeedY - 0.1*gy, -8, 8);
      greenSpeedY = constrain(greenSpeedY + gy, -8, 8);
    } else
    {
      redSpeedY = constrain(redSpeedY + 0.1*gy, -8, 8);
      greenSpeedY = constrain(greenSpeedY - gy, -8, 8);
    }
  } else
  {
    //if red is too far from both other planets, slow it down
    if(redPlayerDistance > 200)
    {
      redSpeedX = 0.99*redSpeedX;
      redSpeedY = 0.99*redSpeedY;
    }
    //if green is too far from both other planets, slow it down
    if(greenPlayerDistance > 200)
    {
      greenSpeedX = 0.99*greenSpeedX;
      greenSpeedY = 0.99*greenSpeedY;
    }
  }
}

/*
updateSmoke

Uses randomness to assign a place for each of the three puffs of smoke. Uses a variable to keep track of the
puff that it is referring to. This makes it so three puffs can appear at once.

*/

void updateSmoke()
{
  //setting up smoke from the player's "engine"
  if (engineOn)
  {
    //for the puffs of smoke
    if (lastPuff == 0)
    {
      lastPuff = millis();
    }
    if (lastPuff != 0 && (millis() - lastPuff) >= 100 - 2*sqrt(playerSpeedX*playerSpeedX + playerSpeedY*playerSpeedY))
    {
      if (whichPuff == 1)
      {
        puff1PositionX = playerPositionX + random(-0.3*playerSize, 0.3*playerSize);
        puff1PositionY = playerPositionY + random(-0.3*playerSize, 0.3*playerSize);
        puff1Mod = 200;
        whichPuff = 2;
      } else if (whichPuff == 2)
      {
        puff2PositionX = playerPositionX + random(-0.3*playerSize, 0.3*playerSize);
        puff2PositionY = playerPositionY + random(-0.3*playerSize, 0.3*playerSize);
        puff2Mod = 200;
        whichPuff = 3;
      } else
      {
        puff3PositionX = playerPositionX + random(-0.3*playerSize, 0.3*playerSize);
        puff3PositionY = playerPositionY + random(-0.3*playerSize, 0.3*playerSize);
        puff3Mod = 200;
        whichPuff = 1;
      }
      lastPuff = millis();
    }
  } else
  {
    lastPuff = 0;
  }
}

//draw the player
void drawPlayer()
{
  //the glow
  fill(0, 100, 255);
  ellipse(playerPositionX, playerPositionY, playerSize + 1.5, playerSize + 1.5);
  //body
  fill(0, 50, 255);
  ellipse(playerPositionX, playerPositionY, playerSize, playerSize);
  //the eye
  fill(0, 100, 255);
  ellipse(playerPositionX, playerPositionY, 3*playerSize/5, 3*playerSize/5);
}

//draws the smoke that comes out of the player
void drawSmoke()
{
  //drawing the smoke
  //puff1
  fill(130, 150, 255, puff1Mod);
  ellipse(puff1PositionX, puff1PositionY, 0.2*(255 - puff1Mod), 0.2*(255 - puff1Mod));
  puff1Mod -= 5;
  //puff2
  fill(150, 130, 255, puff2Mod);
  ellipse(puff2PositionX, puff2PositionY, 0.2*(255 - puff2Mod), 0.2*(255 - puff2Mod));
  puff2Mod -= 5;
  //puff3
  fill(150, 150, 235, puff3Mod);
  ellipse(puff3PositionX, puff3PositionY, 0.2*(255 - puff3Mod), 0.2*(255 - puff3Mod));
  puff3Mod -= 5;
}

//draws the "gravity pulses" coming out of the player
void drawPulse()
{
  //check time since last pulse
  if (millis() - pulseTime >= pulseDuration || pulseTime == 0)
  {
    //reset timer
    pulseTime = millis();
  }

  //colour of pulses depends on distance

  noFill();
  strokeWeight(20);
  //check if the player's gravity is reversed
  if (reverseGravity)
  {
    stroke(255 - redPlayerDistance, 255 - greenPlayerDistance, 255, 100 + 0.1*(pulseTime - millis()));
    ellipse(playerPositionX, playerPositionY, 400*(millis() - pulseTime)/pulseDuration, 400*(millis() - pulseTime)/pulseDuration);
  } else
  {
    stroke(255 - redPlayerDistance, 255 - greenPlayerDistance, 255, -20 - 0.1*(pulseTime - millis()));
    ellipse(playerPositionX, playerPositionY, 400 - 400*(millis() - pulseTime)/pulseDuration, 400 - 400*(millis() - pulseTime)/pulseDuration);
  }
  noStroke();
}

//locate and draw the red moon
void drawRed()
{
  //the glow
  fill(255, 60, 0);
  ellipse(redPositionX, redPositionY, redSize+1.5, redSize+1.5);
  //the body
  fill(160, 30, 0);
  ellipse(redPositionX, redPositionY, redSize, redSize);
  //the eye
  fill(200, 30, 0);
  ellipse(redPositionX, redPositionY, 3*redSize/5, 3*redSize/5);
}

//locate and draw the green moon
void drawGreen()
{
  //the glow
  fill(122, 255, 0);
  ellipse(greenPositionX, greenPositionY, greenSize+1.5, greenSize+1.5);
  //the body
  fill(60, 120, 0);
  ellipse(greenPositionX, greenPositionY, greenSize, greenSize);
  //the eye
  fill(75, 150, 0);
  ellipse(greenPositionX, greenPositionY, 3*greenSize/5, 3*greenSize/5);
}

//checks for manual acceleration of the player
void playerAcceleration()
{
  //vertical
  if (accelerateUp)
  {
    playerSpeedY -= 0.1;
  }
  if (accelerateDown)
  {
    playerSpeedY += 0.1;
  }

  //horizontal
  if (accelerateRight)
  {
    playerSpeedX += 0.1;
  }
  if (accelerateLeft)
  {
    playerSpeedX -= 0.1;
  }
  playerSpeedX = 0.99 * playerSpeedX;
  playerSpeedY = 0.99 * playerSpeedY;
}

/*
updatePlayerPosition

Set the player's position by adding their speed to their previous position. Also constrains the
value so they won't go off-screen.

*/

void updatePlayerPosition()
{
  //adjust position based on speed
  playerPositionY = constrain(playerPositionY + playerSpeedY, 0 + playerSize/2, height - playerSize/2);
  playerPositionX = constrain(playerPositionX + playerSpeedX, 0 + playerSize/2, width - playerSize/2);

  if (playerPositionX >= width - playerSize/2 || playerPositionX <= 0 + playerSize/2)
  {
    playerSpeedX = -playerSpeedX;
  }

  if (playerPositionY >= height - playerSize/2 || playerPositionY <= 0 + playerSize/2)
  {
    playerSpeedY = -playerSpeedY;
  }
}

/*
update RedPosition

Set the red moon's position by adding its speed to its previous position. Also constrains the
value so it won't go off-screen.

*/

void updateRedPosition()
{
  redPositionX = constrain(redPositionX + redSpeedX, 0 + redSize/2, width - redSize/2);
  redPositionY = constrain(redPositionY + redSpeedY, 0 + redSize/2, height - redSize/2);

  if (redPositionX >= width - redSize/2 || redPositionX <= 0 + redSize/2)
  {
    redSpeedX = -redSpeedX;
  }

  if (redPositionY >= height - redSize/2 || redPositionY <= 0 + redSize/2)
  {
    redSpeedY = -redSpeedY;
  }
}

/*
updateGreenPosition

Set the green moon's position by adding its speed to its previous position. Also constrains the
value so it won't go off-screen.

*/

void updateGreenPosition()
{
  greenPositionX = constrain(greenPositionX + greenSpeedX, 0 + greenSize/2, width - greenSize/2);
  greenPositionY = constrain(greenPositionY + greenSpeedY, 0 + greenSize/2, height - greenSize/2);

  if (greenPositionX >= width - greenSize/2 || greenPositionX <= 0 + greenSize/2)
  {
    greenSpeedX = -greenSpeedX;
  }

  if (greenPositionY >= height - greenSize/2 || greenPositionY <= 0 + greenSize/2)
  {
    greenSpeedY = -greenSpeedY;
  }
}

/*
drawStars

This draws the stars using a loop. Normally, this would result in a grid-like pattern, a variable is used
to offset the stars every so often, resulting in a Milky Way style pattern

*/

void drawStars()
{
  //a variable that counts the number of stars placed, some being offset
  byte starCount = 0;
  //draw stars
  for (int x = 0; x < (width/40 + 1); ++x)
  {
    for (int y = 0; y < (height/40 + 2); ++y)
    {
      fill(random(100, 170));

      //check offset
      if (starCount == 1)
      {
        ellipse(48*x - 10*y, 42*y + 3*x, 3, 3);
        ++starCount;
      } else if (starCount == 4)
      {
        ellipse(32*x - 10*y, 39*y + 3*x, 3, 3);
        starCount = 0;
      } else
      {
        ellipse(45*x - 10*y, 40*y + 3*x, 3, 3);
        ++starCount;
      }
    }
  }
}

//checks for a key press
void keyPressed()
{
  if (key == 'w' || key == 'W')
  {
    moved = true;
    accelerateUp = true;
    engineOn = true;
  }
  if (key == 'a' || key == 'A')
  {
    moved = true;
    accelerateLeft = true;
    engineOn = true;
  }
  if (key == 's' || key == 'S')
  {
    moved = true;
    accelerateDown = true;
    engineOn = true;
  }
  if (key == 'd' || key == 'D')
  {
    moved = true;
    accelerateRight = true;
    engineOn = true;
  }
  if (key == ' ')
  {
    reversedOnce = true;
    if (!reverseGravity)
    {
      pulseTime = millis() - pulseTime;
      reverseGravity = true;
    }
  }
}

//checks if a key has been released
void keyReleased()
{
  if (key == 'w' || key == 'W')
  {
    accelerateUp = false;
  }
  if (key == 'a' || key == 'A')
  {
    accelerateLeft = false;
  }
  if (key == 's' || key == 'S')
  {
    accelerateDown = false;
  }
  if (key == 'd' || key == 'D')
  {
    accelerateRight = false;
  }
  if (key == ' ')
  {
    if (reverseGravity)
    {
      pulseTime = millis() - pulseTime;
      reverseGravity = false;
    }
  }

  //checks if the "engine" is still on
  if (!accelerateUp && !accelerateDown && !accelerateLeft && !accelerateRight)
  {
    //resets the "puff machine" eveDistanceY lastPuff the "engine" stops
    lastPuff = 0;
    engineOn = false;
  }
}