W1522 Ping Then Pong

From Coder Merlin
Revision as of 15:54, 25 May 2022 by Jeff-strong (talk | contribs) (Editorial review and minor corrections)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder
Signed Pong cabinet
Pong

Prerequisites[edit]

Research[edit]

Background[edit]

As we learned in the previous lab, the render event handler is invoked by the system periodically to render objects to the canvas. Another event handler that we haven't used yet is the calculate event handler. We'll take advantage of this behavior to move the ball during each cycle, without relying on events produced by the user.

Experiment[edit]

Getting Started[edit]

Continue from the previous project; we'll be editing all of our files there. Enter into the Sources directory of the project.

john-williams@codermerlin: cd ~/Experiences/W1521/Sources/ScenesShell/

A Smarter Ball[edit]

Let's teach our ball to move on its own, given a velocity. Add the following properties to your Ball:

    var velocityX : Int
    var velocityY : Int

In your Ball.init() constructor, initialize both velocityX and velocityY to 0.

Add another method, Ball.changeVelocity() to your ball:

    func changeVelocity(velocityX:Int, velocityY:Int) {
        self.velocityX = velocityX
        self.velocityY = velocityY
    }

We now need to add some "brains" to our ball so that it "knows" how to move based on its velocity. Add another method, Ball.calculate(canvasSize:Size):

     override func calculate(canvasSize: Size) {
        ellipse.center += Point(x: velocityX, y: velocityY) 
    }
Start button green arrow
Run the program and view in a browser before continuing. Be sure that you understand the role of each method.

A Moving Ball[edit]

Update your InteractionLayer to make the ball an instance variable. This change enables us to easily access the ball later.

class InteractionLayer : Layer {
    let ball = Ball()

    init() {
        // Using a meaningful name can be helpful for debugging
        super.init(name:"Interaction")

        // We insert our RenderableEntities in the constructor
        insert(entity: ball, at: .front)
      }
}

Now, provide the ball with an initial velocity:

class InteractionLayer : Layer {
    let ball = Ball()

    init() {
        // Using a meaningful name can be helpful for debugging
        super.init(name:"Interaction")

        // We insert our RenderableEntities in the constructor
        insert(entity: ball, at: .front)
        ball.changeVelocity(velocityX: 3, velocityY: 5) 
      }
}
Start button green arrow
Run the program and view in a browser before continuing. Ensure that the application behaves as expected. What happens when you move the mouse on the canvas? Is this what you expected? Why or why not?

Hit Testing[edit]

The process of determining whether an on-screen, graphical object, such as a ball, intersects with another on-screen, graphical object is called hit-testing. We'll use hit-testing to determine if our ball intersects with the edge of the canvas. A straightforward (yet sometimes inaccurate) method to perform hit-testing involves drawing an imaginary rectangle around objects of interest and then checking to see whether this minimum bounding rectangle overlaps the bounding rectangle of any other objects of interest.

Update the Ball.calculate(canvasSize:) method as follows:

    override func calculate(canvasSize:Size) {
        // First, move to the new position
        ellipse.center += Point(x:velocityX, y:velocityY)

        // Form a bounding rectangle around the canvas
        let canvasBoundingRect = Rect(size:canvasSize)

        // Form a bounding rect around the ball (ellipse)
        // After this is working, move the code to your boundingRect() function
        let ballBoundingRect = Rect(topLeft:Point(x:ellipse.center.x-ellipse.radiusX, y:ellipse.center.y-ellipse.radiusY),
                                    size:Size(width:ellipse.radiusX*2, height:ellipse.radiusY*2))

        // Determine if we've moved outside of the canvas boundary rect
        let tooFarLeft = ballBoundingRect.topLeft.x < canvasBoundingRect.topLeft.x
        let tooFarRight = ballBoundingRect.topLeft.x + ballBoundingRect.size.width > canvasBoundingRect.topLeft.x + canvasBoundingRect.size.width

        let tooFarUp = /***** THIS IS AN EXERCISE LEFT TO THE READER *****/
        let tooFarDown = /***** THIS IS AN EXERCISE LEFT TO THE READER *****/

        // If we're too far to the left or right, we bounce the x velocity
        if tooFarLeft || tooFarRight {
            velocityX = -velocityX
        }

        // If we're too far to the top or bottom, we bound the y velocity
        /***** THIS IS AN EXERCISE LEFT TO THE READER *****/
    }

Exercises[edit]

ExercisesExercisesIcon.png
  1. Complete the Ball.calculate(canvasSize:) to properly handle bounces along the y axis
  2. Implement a power bounce such that immediately after a collision with the canvas edge the ball accelerates to twice its original velocity then slows back to that original velocity over several frames
  3. Whenever a collision occurs, deform the ball (squish it) in the direction of the collision then restore it back to its original dimensions over several frames
  •  M1522-28  Complete  Merlin Mission Manager  Mission M1522-28.

Key Concepts[edit]

Key ConceptsKeyConceptsIcon.png
  • Refactoring is a very important process. It's equivalent to rewriting a draft of an English paper to improve it and is an ongoing process for any successful project.
  • Hit Testing
  • Minimum Bounding Rectangle