Day 4: Hard Pong (Ping)

In today’s class, we discussed the principles behind what makes an “animation” – the rapid succession of images that create the illusion of movement. We discussed the difference between display rates of monitors (refresh rate) and game engines (frame rates), and covered a little bit of “vector math” which we will use to position and move objects.

Finally we generated a quick script to get the ball moving. Simple and possibly choppy, but it gets the idea across.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ping: MonoBehaviour
{
    public GameObject myBall;
    public float speed;
    public Vector3 direction;

    // Start is called before the first frame update
    void Start()
    {
        myBall.transform.position = new Vector3();
    }
    // Update is called once per frame
    void Update()
    {
        myBall.transform.position = myBall.transform.position + (direction * speed);
    }
}

Next, we discussed principles of animation, and applied some changes to regulate and smooth our motion.

1. Normalized direction

Currently, our “direction” values create a vector of indeterminate length. Because we express our movement here as speed * direction, we have implied that we only want Direction to contribute to the heading of the motion, not to have an effect on the velocity. To do this, we want to normalize our direction vector. Normalizing values let’s us bring them to a scale or standard. In terms of modifying our vector, the Normalize( ) command will find the magnitude of a Vector3 (the distance from [0,0,0] to the point expressed by [x,y,z]) and divide all 3 vector values by that number, resulting in a Vector3 with a magnitude of 1.0.

This means that our scalar value speed will be the distance traveled (the magnitude of our motion vector) in each frame.

2. Smoothed motion

We made our ball appear to move smoothly, independent of our frame rate. Frame rate depends on the amount of time it takes to render a frame and execute all commands in between. As we saw in class, this rate can vary greatly from frame to frame. (To see this for yourself, click on the Stats button in the top bar of your Game window while the game is playing and look for the number next to “fps” (frames per second).

At first, we made our ball move a fixed amount in each frame (expressed as direction * speed). In order to ensure that the ball appeared to move consistently over time rather than stutter, we introduced an additional multiplier – “delta time” – which returns the numeric value for seconds elapsed since the last frame was drawn. (This is generally a very low number – most games aim to run at 60 fps, leaving only 16.67ms between frames. ) By using “delta time”, we are incorporating the passage of time in the real world, and so a speed value of “2” now becomes “2 unit/second” when multiplied by Time.deltaTime.

3. Moving Paddles

Next, we created a paddle object, converted it into a prefab (by dragging it from our Hierarchy to our Assets folder) and placed prefab instances of the paddle on the left and right of the field of play, with x-values of -40 and 40.

We defined a new function MovePaddles( ), which we call from Update( ) so that it runs every frame. We defined public variables for the GameObjects leftPaddle and rightPaddle (and associated them in the browser), and created a paddleSpeed value to control the motion. In the function definition, we made the following steps:

  1. Get the position of the paddles (leftPadPos, rightPadPos)
  2. Test certain keys to see if they are pressed, and if they are, increment or decrement the x-value of the corresponding paddle position.
  3. Pass the new positions back to the paddles.

This creates the effect of moving the left and right paddle when we press the W, S, up arrow, and down arrow keys.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ping : MonoBehaviour
{
    public GameObject myBall;
    public GameObject leftPaddle;
    public GameObject rightPaddle;

    public Vector3 direction;
    public float speed;

    public float paddleSpeed;

    // Start is called before the first frame update
    void Start()
    {
        myBall.transform.position = new Vector3();  // Reset the ball to 0,0,0

        // normalize the direction vector
        direction.Normalize();
    }

    // Update is called once per frame
    void Update()
    {
        // move the ball
        myBall.transform.position = myBall.transform.position + (direction * speed * Time.deltaTime);

        // move the paddles
        MovePaddles();

    }

    private void MovePaddles()
    {
        // get the current position of the paddles
        Vector3 leftPadPos = leftPaddle.transform.position;
        Vector3 rightPadPos = rightPaddle.transform.position;

        // adjust that position based on the keys pressed
        if (Input.GetKey("up"))
        {
            rightPadPos.z = rightPadPos.z + (paddleSpeed * Time.deltaTime);
        }
        if (Input.GetKey("down"))
        {
            rightPadPos.z = rightPadPos.z - (paddleSpeed * Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.W))
        {
            leftPadPos.z += (paddleSpeed * Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.S))
        {
            leftPadPos.z -= (paddleSpeed * Time.deltaTime);
        }


        // put the new position back into the object's transform
        rightPaddle.transform.position = rightPadPos;
        leftPaddle.transform.position = leftPadPos;

    }

}