teaching machines

CS 491: Lecture 3 – Peggle

Dear students:

Last time we started working on a game of Peggle. The real thing is full of glitz. We’ll keep working on ours today, but it won’t quite match the professionals:

I’ve expanded our controller a bit so it has both a push button switch to fire and a potentiometer to aim the cannon.

This leads us to a little segment I like to call Program This:

Person A, pretend you’re an Arduino. You’ve got two inputs connected to you. One is a digital input—a button for which you will send out 0 or 1. The other is analog—a potentiometer for which you will send out 0-255. You only send an input’s data when it changes.

Persons B and C, imagine you are the receivers of the data.

Person A, start generating messages. Decide at random which input fires an event. Persons B and C, what needs to be true for you to understand what’s going on?

An Arduino program that reads both the button and the potentiometer might look like this:

#include "Arduino.h"
#define THRESHOLD 5

// I use this to toggle between debugging with the
// serial monitor (plaintext) and communicating with
// Unity (binary).
/* #define write println */

int old_potentiometer;
int old_button = LOW;

void setup() {
  Serial.begin(9600);
  pinMode(7, INPUT);

  emitPotentiometer(analogRead(A5));
}

void emitPotentiometer(int potentiometer) {
  int byte = map(potentiometer, 0, 1023, 0, 255);
  Serial.write(0);
  Serial.write(byte);
  old_potentiometer = potentiometer;
}

void loop() {
  int potentiometer = analogRead(A5);
  if (abs(old_potentiometer - potentiometer) > THRESHOLD) {
    emitPotentiometer(potentiometer);
  }

  int button = digitalRead(7);
  if (button != old_button) {
    if (button == LOW) {
      Serial.write(1);
      Serial.write(1);
    }
    old_button = button;
    delay(10);
  }
}

We solve the communication problem by establishing a protocol. Each packet is two bytes. The first indicates the device: 0 for the potentiometer, 1 for the button. The second indicates the state of the device: a value in 0-255 for the potentiometer, and a 1 for the button, as we only care about release events.

Here’s our checklist to get this game up and running:

  • Implement our serial protocol. We interoperate with the Arduino program above by reading in two-byte chunks:
    while (serial.BytesToRead >= 2) {
      int device = serial.ReadByte();
      int reading = serial.ReadByte();
    
      // Potentiometer?
      if (device == 0) {
        float angle = map(reading, 0, 255, -60, 60);
        transform.eulerAngles = new Vector3(0, 0, angle);
      }
    
      // Button?
      else if (device == 1) {
      }
    }
    
  • Add pegs. Add a single sphere object to the scene. Convert it to a prefab so that altering the pegs’ properties can be done just once. Add an unlit material and drag it onto the peg prefab.
  • Similarly, create a bullet and make a prefab out of it. But we don’t want an instance of this until the player hit’s the fire button. To do this, we add public to our CannonController:
    public GameObject bulletPrefab;
    
    We drag the prefab onto the CannonController in the inspector. Then when the button is pressed down, we make an instance of the prefab:
    GameObject bullet = Instantiate(bulletPrefab);
    
    When we test this, we’ll find that it doesn’t show up in the right spot. We can refine the Instantiate call to provide the instance’s initial position and rotation. Let’s have the bullet start at the cannon’s origin:
    GameObject bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);
    
  • Add some motion to the bullet. Generally, each object that participates in collisions and physics-driven motion needs two components: a rigidbody that describes its physical properties, and a collider that describes its spatial extent. Our bullet already has a collider, albeit a 3D one. We’ll remove it and add a 2D circle collider and a 2D rigidbody. When we playtest, the bullets will drop. Fun.
  • Add some velocity to the bullet. As you’ll soon learn, game development is mostly about tweaking parameters. So let’s add a public variable so that we can change the bullet’s speed easily:
    public float bulletSpeed;
    
    Now, we can give a bullet some oomph through its rigidbody:
    bullet.GetComponent<Rigidbody2D>().velocity = -transform.up * bulletSpeed;
    
    This is idiomatic Unity. We ask an object for a component and then tweak the component. I use the cannon’s orientation to direct the bullet.
  • Make the bullets bounce of the pegs. We must add a collider and rigidbody to our pegs. However, the pegs won’t be moving, so we set the rigidbody to be Kinematic. We need the rigidbody because we need to apply a bouncy physics material. We’ll have to fidget more with parameters. I achieved something passible with a gravity scale of 2 on the bullets, 0.8 bounciness, and a bullet speed of 20. But everyone’s scene will be different.
  • Delete pegs when a bullet hits them. In the real game, we’re supposed to wait until the end of the shot before the pegs disappear. If we have time, we’ll do that. But initially, let’s just delete them right away. How do we know when there’s a hit? Unity has callbacks for physics events:
    void OnCollisionEnter2D(Collision2D collision) {
      Destroy(collision.gameObject);
    }
    
    Eventually, we may have other objects with colliders that we don’t want to have disappear. We can filter out just the pegs by giving them a tag:
    void OnCollisionEnter2D(Collision2D collision) {
      if (collision.gameObject.CompareTag("Peg")) {
        Destroy(collision.gameObject);
      }
    }
    
  • Delete bullets when they go off screen. Otherwise they live forever. There are many ways to solve this. We’ll use a big box collider around the camera, a “live zone.” As long as the bullet stays in the live zone, it will persist. When it exits, we destroy it. This collider is not used for physical collisions but as a sensor. To keep the collider from influencing the motion of the bullets, we must make it a Trigger. We can then add a slightly different callback:
    void OnTriggerExit2D(Collider2D collider) {
      Destroy(gameObject);
    }
    

Here’s your TODO list:

  • Be ready to share your prototypes next Monday.
  • Read Level 4 of Schreiber’s Game Design Concepts.
  • For Wednesday, share a photo of a paper prototype artifact you have designed on Slack #general.
  • Consider attending the next Chippewa Valley Developer’s Group meeting on Tuesday evening. The talk is on building an emulator for gaming hardware from the 1970s. They meet at the Lazy Monk Brewery. Food is free, but RSVP. (Drinks are not free.) For Friday, share a few sentences about the talk and the community on our private Slack channel for an extra credit participation point.

See you next time!

Sincerely,

P.S. It’s time for a haiku!

sadf

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *