Difference between revisions of "W1522 Ping Then Pong"
(Created page with "DRAFT ICON thumb|Signed Pong Cabinet thumb|Pong == Prerequisites == * Project-1521|1521 Moving Al...") |
|||
Line 183: | Line 183: | ||
# Complete the '''Ball.calculate(canvasSize:)''' to properly handle bounces along the y axis | # Complete the '''Ball.calculate(canvasSize:)''' to properly handle bounces along the y axis | ||
# 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 | # 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 | ||
# Deform the ball (squish it) in the direction of the collision for a single frame whenever a collision occurs | |||
== Key Concepts == | == Key Concepts == |
Revision as of 07:36, 14 February 2019
Prerequisites[edit]
Research[edit]
- Read Pong (introduction only)
- Read Refactoring
- Read Minimum Bounding Rectangle (introduction only)
- Read Hit Testing
Background[edit]
As we learned in the previous lab, the update event handler is invoked by the system periodically to refresh the canvas. 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 with the previous project.
Enter into the Sources directory of the project.
cd ~/projects/IgisShell-MovingAlong/Sources/IgisShell/
First Steps[edit]
Let's start by refactoring our code. Edit file "main.swift":
emacs main.swift
Refactoring[edit]
Edit the file by finding the definition of the Painter class's update() method. We'll break apart the functionality into two separate methods. The first method will handle logic and calculation. The second method will handle the actual painting. Begin by creating the following two procedures, copying the code from the update method.
// This method will perform calculations for our project but will not perform any painting
func calculate(canvasSize:Size) {
// Set the coordinates to the center of the screen if they've not yet been set
if !coordinatesSet {
let canvasCenter = Point(x:canvasSize.width/2, y:canvasSize.height/2)
ball.center = canvasCenter
coordinatesSet = true
}
}
// This method will handle the painting of our objects to the canvas
func paint(canvas:Canvas, canvasSize:Size) {
clearScreen(canvas:canvas, canvasSize:canvasSize)
paintSkyAndGround(canvas:canvas, canvasSize:canvasSize)
paintBall(canvas:canvas, canvasSize:canvasSize)
}
Next, we'll invoke the above methods from our update method.
override func update(canvas:Canvas) {
if let canvasSize = canvas.canvasSize {
calculate(canvasSize:canvasSize)
if needToDraw {
paint(canvas:canvas, canvasSize:canvasSize)
needToDraw = false
}
}
}
Run the project.
View the results in the browser as you did earlier. Ensure that the application behaves as expected.
More Refactoring[edit]
We'll continue refactoring. Rather than treat the ball as a simple ellipse, we'll create a class for it, in its own file. Split the emacs screen and open a new file named "Ball.swift". Inside this file, place the following text:
class Ball {
var ellipse : Ellipse
init(size:Int) {
ellipse = Ellipse(center:Point(x:0, y:0), radiusX:size, radiusY:size, fillMode:.fillAndStroke)
}
func paint(canvas:Canvas) {
}
func move(to:Point) {
ellipse.center = to
}
}
We've organized the Ball class so that it has a specific size, determined at the time it is created and a method that can ask the ball to paint itself on the canvas.
Now, make the following changes:
- In "main.swift", change the type of the ball property from an Ellipse to your new Ball type.
- Remove the body from your paintBall method in "main.text" and move it to the Ball.paint() method.
- Update your Ball.paint() method to paint the ellipse belonging to the ball
- Delete the paintBall method from "main.swift"
- Update your paint() method in "main.swift" to invoke ball.paint(canvas:canvas)
- Update your init() constructor in "main.swift" to create a new Ball object.
- Update any references to ball.center in "main.swift" to invoke the method on the ball, ball.move(to:)
Helpful hint: Take your time and ensure that you've completed each one of the above steps before proceeding.
Run the project.
View the results in the browser as you did earlier. Ensure that the application behaves as expected.
A Smarter Ball[edit]
Let's teach our ball to move on it's 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 it's velocity. Add another method, Ball.calculate(canvasSize:Size):
func calculate(canvasSize:Size) {
ellipse.center.moveBy(offsetX:velocityX, offsetY:velocityY)
}
Because we'll be updating the ball's position on every frame, we'll eliminate the needToDraw flag because it will always be true. Edit your "main.swift" file and remove all occurrences of needToDraw.
Run the project.
View the results in the browser as you did earlier. Ensure that the application behaves as expected.
A Moving Ball[edit]
Update your Painter.init() constructor by adding a method invocation to change the velocity of the ball:
ball.changeVelocity(velocityX:10, velocityY:5)
The ball now knows its velocity, but we'll have to invoke the Ball.calculate() method to provide with an opportunity to move. Update your Painter.calculate() method by adding the following (after the conditional):
ball.calculate(canvasSize:canvasSize)
Run the project.
View the results in the browser as you did earlier. Ensure that the application behaves as expected.
Question: 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 termed hit-testing. We'll use hit-testing to determine if our ball intersects with the edge of the canvas. A straight-forward (yet sometimes inaccurate) method to perform hit-testing involves drawing an imaginary rectangle around objects of interest and then checking to see whether or not this minimum bounding rectangle overlaps the bounding rectangle of any other objects of interest.
Update the Ball.calculate(canvasSize:) method as follows:
func calculate(canvasSize:Size) {
// First, move to the new position
ellipse.center.moveBy(offsetX:velocityX, offsetY:velocityY)
// Form a bounding rectangle around the canvas
let canvasBoundingRect = Rect(topLeft:Point(x:0, y:0), size:canvasSize)
// Form a bounding rect around the ball (ellipse)
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]
- Complete the Ball.calculate(canvasSize:) to properly handle bounces along the y axis
- 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
- Deform the ball (squish it) in the direction of the collision for a single frame whenever a collision occurs
Key Concepts[edit]
- 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