RETURN TO VARUNRAMESH.NET

Runaway Robot - A Mobile Runner Game

Wednesday, March 29th 2017


This quarter, I worked with three other students on a mobile game called Runaway Robot. The game, made in Unity, is a side-scrolling runner featuring mechanics like gravity reversal. All of the music, all of the code, and a large portion of the art was made from scratch. The code is open source and can be found at https://github.com/Kahraymer/Runner-Game.

One interesting part of the game that I worked on is the jump mechanic, which has a couple of interesting features.

  1. The gravity and initial jump velocity are calculated from the desired maxJumpHeight (the height at the top of the arc), and maxJumpTimeToApex (time to reach the top of the jump arc). This approach is based off of Kyle Pittman’s GDC Talk. While it doesn’t result in physically accurate jumps, it lets us tune the jump arc more intuitively. For example, a “tight” jump has a small maxJumpTimeToApex and a “looser” jump has a long maxJumpTimeToApex. maxJumpHeight is determined by the size of obstacles, which in turn is constrained by the size of the screen and the size of the player on the screen.

  2. The jump controller allows the player to queue up a jump while they are falling, but before they actually hit the floor. Almost all games do this, since a player that is jumping repeatedly in a pattern might try to jump a few frames before the character actually hits the ground. We need to allow for some temporal flexibility so that the player doesn’t miss a jump, thus destroying their momentum. This is implemented below through the rebound boolean which, if true, triggers a jump immediately upon landing.

  3. The jump can be modulated depending on how long the player holds the jump button. This is implemented by temporarily increasing gravity when the button is released. Gravity is returned to normal once the player reaches the top of their arc. Unfortunately, this led to a situation in which not all “taps” had equal arcs, because some “taps” lasted for slightly more frames than others. To normalize all “taps,” I implemented a minJumpTime, where the jump cannot be terminated until airTime >= minJumpTime. This effectively gives us a min and a max jump height, so it is easy to design our obstacles around these contraints.

  4. I supported double jump, though we dropped this in the final game. In a pair of jumps, both jumps can be modulated, resulting in a wide variety of trajectories. Unfortunately, our player is too big on the screen. If we reduce jump height such that the player can never go off the top of the screen, than a single min-jump becomes so small that it is meaningless. Because of this, we decided to stick with a single, modulatable jump, as that still gave us a decent spread of jump arcs.

  5. The gravity can be inverted, so the jump controller accounts for that.

The code below is taken from PlayerController.cs, which implements the jumping mechanic.

public class PlayerController : MonoBehaviour {
  // These three tuneable values affect the 'feel' of the jump.
  [Tooltip("The height of a max jump.")]
  public float maxJumpHeight;
  [Tooltip("The time to apex of a max jump.")]
  public float maxJumpTimeToApex;
  [Tooltip("The minimum amount of time before a jump can be terminated.")]
  public float minJumpTime;

  // Enable or disable double jump.
  [Tooltip("Can the player double jump?")]
  public bool canDoubleJump;

  // This mask only matches "Ground" objects, so that we can't jump off a coin.
  public LayerMask groundMask;
  // This transform is a BoxCollider parented to the player which is used to
  // check the space under the player.
  public Transform groundCheck;

  // This field tracks the amount of time the player has spent in air after
  // jumping. Should be equal to 0 if the player is grounded or if the player
  // just started their second jump.
  private float airTime = 0.0f;

  // This enum tracks the current state in the jump.
  private enum JumpPhase { Grounded, PreJump, Rising, TerminatedRising, Falling }
  private JumpPhase jumpPhase = JumpPhase.Grounded;

  // Track whether or not the player should rebound when it hits the ground.
  private bool rebound = false;
  // Check whether or not we are on the second jump of a pair.
  private bool secondJump = false;

  private Rigidbody2D rigidBody;
  void Start () { this.rigidBody = GetComponent<Rigidbody2D> (); }

  // Track whether or not gravity is inverted.
  private bool inverted = false;
  public bool Inverted {
    get { return inverted; }
    set { inverted = value; }
  }

  // Update is called once per frame
  void Update () {
    bool jump = Input.GetButtonDown ("Jump");

    // Note that the PreJump phase is used because physics must be applied in
    // `FixedUpdate`, but inputs have to be collected in `Update`.

    if (canDoubleJump && !secondJump && jumpPhase != JumpPhase.Grounded && jump) {
        airTime = 0.0f; // Reset air-time.
        rebound = false; // Reset the rebound flag.
        secondJump = true; // We are on our second jump.
        jumpPhase = JumpPhase.PreJump; // Move to the PreJump phase.
    }

    if (jumpPhase == JumpPhase.Grounded && (jump || rebound)) {
        rebound = false; // Reset the rebound flag.
        secondJump = false; // We are on our first jump.
        jumpPhase = JumpPhase.PreJump; // Move to the PreJump phase.
    }

    // Queue up a jump if the player tries to jump while we are falling.
    if (jumpPhase == JumpPhase.Falling && jump) {
      rebound = true;
    }
  }

  void FixedUpdate() {
    // Calculate gravity.
    float gravity = (-2 * maxJumpHeight) / (maxJumpTimeToApex * maxJumpTimeToApex);
    if (inverted) gravity *= -1;
    if (jumpPhase == JumpPhase.TerminatedRising) gravity *= 3;

    // Calculate initial jump velocity.
    float jumpVelocity = (2 * maxJumpHeight) / maxJumpTimeToApex;

    // Apply the difference between real gravity and desired gravity.
    rigidBody.AddForce (new Vector2(0, gravity) - Physics2D.gravity);

    // Increment airTime.
    if (jumpPhase == JumpPhase.Grounded) airTime = 0.0f;
    else airTime += Time.fixedDeltaTime;

    // In the PreJump phase, simply apply the initial velocity.
    if (jumpPhase == JumpPhase.PreJump) {
      rigidBody.velocity = new Vector2 (
        rigidBody.velocity.x, inverted ? -jumpVelocity : jumpVelocity);
      jumpPhase = JumpPhase.Rising;
    }

    // During the Rising phase, check to see if the jump has been terminated,
    // upon which switch to the TerminatedRising phase where gravity is
    // temporarily stronger.
    if (jumpPhase == JumpPhase.Rising) {
      bool letgo = !Input.GetButton ("Jump");
      if (airTime >= minJumpTime && letgo)
        jumpPhase = JumpPhase.TerminatedRising;
    }

    if (inverted ? rigidBody.velocity.y > 0 : rigidBody.velocity.y < 0)
      jumpPhase = JumpPhase.Falling;

    if (jumpPhase == JumpPhase.Falling) {
      if (groundCheck.GetComponent<Collider2D>().IsTouchingLayers(groundMask))
        jumpPhase = JumpPhase.Grounded;
    }
  }
}
View Comments

Pulling a Page's Modified Date from Git

Tuesday, March 28th 2017


This blog is a static site that’s generated from a series of markdown files. The ‘publish date’ for each of the blog posts is taken from a YAML frontmatter section in the post’s markdown source. Most blogging systems track when a post was last modified, and display that in the page, as well as in the page’s metadata. If I wanted to track the last modified date, it would be somewhat annoying to do manually, as I would have to edit the date in the frontmatter every time I wanted to change something in the post.

However, since my blog posts and code are stored in git, I can very easily extract the last modified date from the git repository itself. The snippet below extracts the last modified date of a file in the ISO format.

git log -1 --date=iso-strict --format="%ad" ${filename}

Edit: A Jekyll Plugin

I just found out that there is a Jekyll plugin for this purpose. It uses git log if the site is in a Git repo, otherwise it uses File::mtime - determinator.rb.

View Comments

A Script for Resumable Lecture Videos

Saturday, May 21st 2016


During Autumn quarter this year, all of my class lectures were recorded and available online. I initially tried to attend every lecture, but the ability to skip class and watch it later was too tempting. I soon found myself waking up at 2PM everyday and watching all my lectures through the SCPD video system.

This lifestyle presented couple of problems. First, the SCPD player was annoying to use since bad internet connections would force me to reload and lose my place in the video. Second, I found myself rebooting frequently to switch operating systems – this would also cause me to lose my place. I could use the download feature on the SCPD site to solve the first problem, but the second still remained.

Instead of fixing my life, I decided to fix my technology. What I really needed was a video player that could remember where I left off. This would also allow me to split my lectures into parts and rotate between them (I found that this prevents me from getting bored while watching 2-hr lectures). I looked at a wide variety of video players, but I didn’t find any ideal solutions. Most players don’t remember positions, and those that do typically only remember positions on the last video viewed. Furthermore, I often switch OS’s (primarily OSX and Linux), and I want my timecodes to work on both (and ideally be synchronized). Given these constraints, I figured the only solution was to make my own tool.

I obviously didn’t create a video player from scratch – instead I used MPlayer, a GUI-less video player that is launched from the command-line. What makes MPlayer awesome is that it outputs video progress to stdout as the video plays. Using this feature, I was able to write a simple wrapper script to launch MPlayer and read/store the timecodes.

MPlayer is interesting since, as the video progresses, the application actually erases the previous line in order to write the new timecode. It does so using “\r” (carriage return) to return to the beginning of the line and rewrite the previous data. From there, it was as simple as splitting the stdout stream on that code and extracting the video position with a regex. The timecode is saved every five seconds. When the same video is opened again, the wrapper script uses the “-ss” flag to pass in the saved timecode.

In order to share timecodes between machines, the timecodes are stored in a folder in my home directory, indexed by the SHA-1 hash of the video file’s contents. I then set up that folder to be a symlink into my Dropbox and voila! Cross-platform synchronized video resuming in less than 100 lines of code, which you can download and use here.

This simple project shows how existing tools can be recombined to solve a daily problem and boost productivity. I’ve encountered tons of these little hacks (every engineer has a couple!) and I’m always blown away by the cool ideas that people think up. Leave a comment if you have any similar projects you’d like to share!

View Comments

Blue Screen - Ludum Dare 30 Entry

Friday, August 29th 2014


Last weekend, I participated in Ludum Dare, a game jam where you make a game from scratch in under 48 hours. The theme this time was “Connected Worlds.”

The game I developed is titled Blue Screen, where you travel between the real world and cyber space. Blue screens serve as the boundary between these worlds. I might have bitten off more than I could chew for this one, so it’s pretty short, but I hope that the central conceit works. All art, code, music, and sound was created within the duration of the competition. You can play the game on itch.io.

If you can’t run the game, then you can check out a video of a full playthrough below.

View Comments

ASCII Bell Character

Sunday, August 17th 2014


The other day, I accidentally printed a ton of binary data to my terminal. Upon doing so, my computer started to beep incessantly - which startled me a bit. I looked through the list of processes, closing all the terminals, but the beeps persisted.

Turns out this is a feature, and not me going insane. The ASCII value 7 is the Bell character, one of several control codes or non-printed characters in ASCII. Apparently this dates back to the days of teletypewriters, where it was used to ring a physical bell (as opposed to just a digital sound).

This article points out that it can even happen in the case of the Unicode bullet.

It just so happened that my data was full of 0x07, and all the beeps queued up, running the entire time as I searched Google and Wikipedia for solutions. The answer in my this case was to use the command net stop beep, found on this Stack Overflow post.

View Comments