Name: Chiu Chi Kuan
Matric No: A0234100E
The Physics Engine supports 2D objects only.
There are four main components in the Physics Engine:
Physics World
Colliders
RigidBody
Solvers
Other classes and structs include:
Transform
: Defines the position, scale and z-axis rotation of a 2D objectWorldObject
: Consists of aTransform
property.Collision
: Consists of theContactPoints
of the collision and the two bodies involved in the collisionMaterial
: Consists of only one attribute as for now, restitution, which affects the response of the body after a collision
Physics world can be seen as the environment of which all physics enabled bodies run in. Environment wide effects such as gravity and drag are applied here. It maintains a collections of bodies (RigidBody
). At every step, it checks for collision between the bodies, resolves the collision, and updates their position, velocity accordingly.
Method
step(deltaTime)
- Forwards the state of the world by deltatime, applying gravity, drag, resolving collision and update each bodies' position in the time slice
There are three types of colliders supported,
CircleCollider
- Defined by a radiusBoxCollider
- Defined by the half width and half height of the rectanglePolygonCollider
- Defined by the vertices of the polygon
A collider defines the physical outline of the object it is attached to. The main function of a collider object is to testCollision
against another collider. Each concrete collider pair type, e.g. CircleCollider
and BoxCollider
, PolygonCollider
and PolygonCollider
runs an algorithm to determine if two collider objects have collided and if so return a ContactPoints
object that contains the position of the contacts points, the normal and depth of the collision.
Collision detection that involves a PolygonCollider
is based on the Separation Axis Theorem. BoxCollider
is considered a special case of Polygon Collider
. The properties of a collider need not be changed when an object scales, rotates or transforms in any way. The collider bounds adjusts according to the Transform
of the body it is attached to.
A RigidBody
inherits from a WorldObject
but is physics enabled through the following properties:
material
velocity
mass
collider
The top three properties translates to their real world counter parts. The collider defines the physical bounds of the body, enabling the RigidBody
to react to collisions. The velocity
of the RigidBody
can be changed by applying force or impulse via applyForce
and applyImpulse
respectively.
A special property isTrigger
converts the RigidBody
from a solid impassable object to a trigger zone.
There are six lifecycle methods for RigidBody
:
onCollisionEnter
: Invoked when this body just collides with another body (both are solid bodies)onTriggerEnter
: Same asonCollisionEnter
(Either one or both of the colliding bodies are trigger zones)onCollisionStay
: Invoked for the duration that the other colliding body have yet to separate (both are solid bodies)onTriggerStay
: Same asonCollisionStay
(Either one or both of the colliding bodies are trigger zones)onCollisionExit
: Invoked when the other colliding body just separated from this body (both are solid bodies)onTriggerExit
: Same asonTriggerExit
(Either one or both of the colliding bodies are trigger zones)
Solvers
are responsible for collision resolution. There are currently two solvers:
PositionSolver
: Separates the two colliding bodies by moving each by the least possible distanceImpulseSolver
: Applies impulse of the two collidingRigidBody
accounting for the momentum and material
The peggle game engine is built atop of the Physics Engine with added utilities to support the Peggle gameplay. There are three core components in the peggle game engine:
GameWorld
RenderAdaptor
EventLoop
GameWorld
is powered by its physicsWorld
property. GameWorld
asks physicsWorld
to resolve the physical states of all the physics enabled objects (RigidBody
) in the world. GameWorld
is the environment where all game objects interact, specifically the pegs, the cannon ball, and the invisible walls along the top, left and right boundaries of the world.
Properties
pegRemovalTimeInterval
: The time threshold where a peg will be prematurely removed after prolonged contact with aCannonBall
orLoidPeg
(Zombie peg).pegRemovalHitCount
: Once a peg has been hit by aCannonBall
orLoidPeg
for this number of times, it will be removed prematurely.collidedPegBodies
: ASet
ofNormalPeg
that have been hit during on peggle shot iteration, waiting to be faded then removed after the shot completes.allPegBodies
: ASet
of all pegs and blocks in the world. (Block is considered a peg, as there is no behaviour difference except the score which is actually handles externally viaGameModeAttachment
)`graphicObjects
: All world objects that will be rendered onto the display.gameModeAttachment
: AGameModeAttachment
activates and deactivates selected properties such astimer
,ballCounter
,civTally
etc. and configures a correspondingScoreSystem
andWinLoseEvaluator
to support the game mode chosen by the player.eventLoop
: TheEventLoop
that will advance the world state forward.coroutines
: ASet
of coroutine to be executed at every step that affects the world state.playState
: The play state of the game, whether it is has been won, lost, in progress etc..onStepComplete
: AnArray
of closures that take in aCollection
ofWorldObjects
and process them accordingly.
Methods
step(deltaTime)
: Advances the state of the world by delta time, executing thephysicsWorld
update, coroutines in theCoroutine
set, asking thegameModeAttachment
to evaluate the world to check if the game has been won, lost or is still in progress.addObject(WorldObject)
: Adds aWorldObject
to the world. There are several overloaded methods with the same name but increasing specificity. Purpose is to add an object to all the data structures relevant to the components the object. For instance, if the gameobject isRenderable
, the added object is inserted into thegraphicObjects
set.startSimulation()
: Asks theeventLoop
to start and advance the world forward.tryFinalizeShot()
: Checks whether the nextCannonBall
can be fired (All collided pegs have faded and all ball-like objects have exited the screen)
GameWorld
maintains the state of the world, including the bucket, the cannon ball and all the pegs on the board. Once a routine completes, it is removed from the coroutines
set. One direct application of a Coroutine
will be animations. GameWorld
has a fixed dimension of 820 X 980
. To cater for different view dimensions the scaling is handled by RenderAdaptor
.
RenderAdaptor
implements GameSystem
and subscribes its adaptScene
closure property to onStepCompleted
closure array. The class is directly observed by the view, acting like a view model. @Published
properties include numOFBalls
, prettyTimeLeft
, civTally
etc.. As mentioned above, since the GameWorld
is fixed size the RenderAdaptor
determine a stretch ratio to fit the world onto the display area and applies these ratio onto WorldObjectVM
via access from graphicObjects
an array of WorldObjectVM
.
WorldObject
demands a scaleFactor
used to stretch the position and scale of the visibleObject
. The scaleFactor
is assigned from RenderAdaptor
.
GameSystem
: Implementers must define the closure propertyadaptScene(Collection<WorldObject>)
, processing the world objects accordingly.Renderable
: Implementers have aSpriteContainer
property to enable the UI to display the object.Animated
: A protocol that extendsRenderable
. Contains aspritesheet
,animationSequences
andframeRate
Fadable
: A protocol that demands aPegRB
. It has a closurefade
that reduces the opacity of the peg object.WinLoseEvaluator
: Implementers must specify the type ofScoreSystem
associated with the evaluator and define theevaluateGameState
function.ScoreSystem
: Implementers register their responses to relevant game events that determines the score viaregisterListeners
.GameModeAttachment
: Couples aScoreSystem
,WinLoseEvaluator
andWorldConfig
object together defining a Game Mode e.g. Operation Eden. Implementers must definesetUpWorld
that sets theGameWorld
to the state needed e.g.ballCounter
,civTally
is active while thetimer
is inactive inGameWorld
forOperation Strix
aka standard mode.Audible
: Implementers must specify the propertyaudioClip
.
VisibleRigidBody
inherits RigidBody
and implements Renderable
defining objects that are physics enabled and displayable. PegRB
is the parent of all pegs including blocks. It inherits VisibleRigidBody
and possesses the function makeFlipRotator
that returns a closure that can be wrapped into a Coroutine
and added to coroutines
in GameWorld
to rotate the pegs by 180 degrees about the world center. Note: Trivial properties and methods are omitted from the guide.
NormalPeg
: A peg that is faded and removed if collided.HostilePeg
: InheritsNormalPeg
with the added propertycaptureReward
which could be captured by theScoreSystem
via events inGameWorld
CivilianPeg
: InheritsNormalPeg
with the added propertydeathPenalty
which could be captured by theScoreSystem
via events inGameWorld
LoidPeg
: Aka Zombie peg, when hit by aCannonBall
turns into a pseudo cannon ball that could aid the player in clearing the hostile pegsBoomPeg
: When hit byCannonBall
, peg explodes and removes pegs in its collision radius immediately also applying an impulse onto any dynamic bodies in range.ConfusePeg
: InheritsConfusePeg
. When hit by aCannonBall
or dynamicLoidPeg
would triggerGameWorld
to rotate all static pegs and blocks by 180 degrees.ChancePeg
: ImplementsFadable
. When hit has a probability of rewarding players a free ball. Once rewarded, the peg will fade.BondPeg
: Aka Spooky peg. When hit by aCannonBall
or dynamicLoidPeg
, the peg will triggerGameWorld
to shut the bucket and add a spook charge to theCannonBall
which is consumed when ball exits the screen to allow the ball to reappear at the top of the screen continuing its trajectory.
Cannon
is responsible for determining the initial velocity and launch location of the CannonBall
when fired. It implements Animated
as it has a sprite progression for firing. To fire the cannon, player taps on the screen, cannon will rotate to point in that direction and fire the CannonBall
provided the previous shot has been completed.
A final class that is not supposed to be instantiated. The class statically initializes the palette
Standard game mode is titled Operation Strix. Objective of the player is to clear all the Hostile pegs
namely the orange and green colored pegs without killing more than the heuristically computed number of civilians indicated at the bottom left corner of the screen. The game mode is supported by CivilianScoreSystem
and StandardEvaluator
.
Beat high score mode is titled Operation Eden. There will be a countdown atop and a target score below. Objective is to reach the target score before the timer expires. The game mode is supported by BaseScoreSystem
and TimedHighScoreEvaluator
. Countdown start value and target score is again heuristically calculated.
Siam mode is titled Operation Gigi. Players will specify the number of balls that they need to fire without hitting any peg on the screen. The game mode is supported by NoScoreSystem
and NoHitEvaluator
.
Aside from the ScoreSystem
and WinLoseEvaluator
, the heuristic computation for the parameters of the first two game modes is done within WorldConfig
via the closure property configurer
.
BaseScoreSystem
computes the score update by streak length and the pegs that have been hit per shot. The longer the "net streak" the higher the multiplier. Hitting civilian pegs deduct the score, and hitting hostile pegs increases the score based on the capture reward. Hence, a streak can magnify not only the plus but the minus as well if many civilians are killed by the shot.
Bucket
oscillates at the bottom of the screen. IfisTrigger
is true and aCannonBall
enters the bucket, the ball is "recycled" replenishing the ball count.Wall
: ARigidBody
subclass used to bound the play areaBallRecycler
: ARigidBody
subclass serving as a trigger zone that destroys theCannonBall
when it fall below the screen and asks theGameWorld
to remove the pegs that were hitCoroutine
: Takes in a routine function to be executed and a completion callback to be executed when the routine function completes after repeated invocation in the event loop.
GameView
can be entered from BoardView
when the player clicks on the GO! button on ControlPanelView
. GameView
takes the board state from BoardView
and delegates renderAdaptor
to initialize the world state accordingly. Players can launch the cannon ball by tapping on the screen and a cannon ball will fire towards the direction, subject to the effects of gravity. GameView
have several subviews and subview within subviews, but in essence there is the play area the top bar which hosts the timer and the ball count if either or both is required for the mode and the bottom bar which holds the civilian death tally, the target score and score stack to the right if either of them or some of them are needed.
Similar to GameView
, in essense ControlPanelView
consists of a top bar, a board area where players can place the peg and a bottom bar. The top bar provides buttons for player to return to the main menu, load an existing level, save an existing level/ a new level (by the level name entered in the text field) or reset the board to an empty state. Since player can enter play mode from the level designer, there is also a picker under the text field that allows player to choose the game mode.
The MenuView
allows player to choose between going into the SelectionView
to play a preloaded or previously designed level or going into the BoardView
to design and possibly play a level.
SelectionView
contains rows of levels where player can select a mode from the picker and start playing by pressing the right arrow button at the end of the level row.
The bottom bar consists of a palette to the right, which is a collection of peg types along with a block the player can use to design a level, an information button to the right that hints at the behaviour of each peg type and a delete peg button at the right end that enables player to tap and delete any peg on the board.
TrackPlayer
is responsible for playing all the background music and SFX for the game. It merely provides two functions playBGM(trackName)
and playSFX(trackName)
. Only one bgm can be played at a time while up to 10 sfx tracks can be played on top of one another. More than that the sfx track will not be processed and emitted.
Note: For Operation Strix, there must be at least one orange or green peg in the level (hostile peg) for meaningul gameplay.
- Layout several pegs and blocks in the level designer at least one of each type.
- Tap on a peg, a scaling and rotation panel should appear below.
- If the peg is circular, the panel should have radius and rotation sliders.
- If the peg is non-circular, the panel should have width, height and rotation sliders.
- Move the rotation slider, the peg should rotate accordingly.
- Move the radius/width/height slider, the peg/block should scale accordingly.
- If radius/width/height/rotation stops reacting to their sliders, the peg should be colliding with the board boundaries or other pegs.
- Repeat 2-7 for each peg type.
- Save the board under "Slider test"
- Reset the board.
- Load "Slider test", the board should reappear.
- Press Start, the game board should match the design board.
- Fire cannon balls at the transformed pegs, the collision response should match the graphics.
Boom Peg
Loid Peg (aka zombie peg)
Bond Peg (aka spooky peg)
Franky Peg (aka chance peg)
Cannon Ball
Confuse Peg
Civilian Peg (aka blue peg)
- Layout some blue pegs, along with orange and green pegs.
- Fire the cannon at the blue pegs.
- Upon first collision with a cannon ball or dynamic loid peg the blue peg should light up.
- Upon second collision with a cannon ball or dynamic loid peg the blue peg should turn grey.
- If in Operation Strix game mode, the civilian death tally at the bottom left corner of the play screen should increment by one.
- When the ball and all dynamic loid pegs exit the screen, the greyed pegs should fade and be removed.
Boom Peg
- Layout a boom peg, near another orange, green, blue or spooky peg, and near another boom peg.
- Have at least one orange peg on the board such that you can Start playing the level in Operation Strix game mode.
- Fire the cannon ball at the boom peg/pegs. The boom peg should explode. The explosion raius visually should be 5 times of that of the boom peg in question.
- If another boom peg is caught in the explosion, it should explode too triggering a chain reaction of sort.
- If any orange, green, blue or bond peg is caught in the explosion. They should fade immediately.
- If a dynamic loid peg or cannon ball is caught in the explosion, their trajectory should be affected realistically by an impulse.
- Back to step 1, scale the boom peg the explosion radius should scale accordingly for steps 4-6.
- If Operation Strix or Operation Eden is chosen, the score line of the pegs should reflect the civilian and hostile pegs removed.
- If Operation Gigi_ is chosen, hitting the boom peg should result in game loss.
- Note: Multiple boom peg explosions successively can accelerate dynamic bodies such as loid peg and cannon ball to a speed that collision detection fails for object below a threshold size
Bond Peg
- Layout two or more bond pegs, along with other pegs of your choice.
- Press Start, fire the cannon ball at the bond pegs.
- Upon any collision, you should hear a dog bark sfx being played.
- Upon collision with the cannon ball, the bond peg sprite should change.
- If the ball hits the bucket before exiting the screen, the bucket should act like a solid body instead of a trigger causing the ball to bounce off.
- When the ball exits the screen, the collided pegs should not fade just yet.
- The ball should reappear at the top of the screen at the same x-axis position.
- Each collision with a different bond peg will grant the ball an extra "charge" for it to reappear. Each time the ball exits the screen, one of these charges is consumed.
- Before the "charge" of the ball reaches 0, steps 4-6 should repeat.
- When all charges are expended and the ball exits the screen, all collided pegs accumulated through iterations should fade and be removed,
- The scoreline if present should not be affected directly by collision with bond peg.
- If Operation Gigi_ is chosen, hitting the bond peg should result in game loss.
Loid Peg
- Layout two or more loid pegs, along with other pegs of your choice.
- Press Start, fire the cannon ball at the loid pegs.
- Upon collision with a cannon ball or another dynamic loid peg, the original static loid peg should become dynamic affected by gravity and the impulse from the initial impact.
- If a loid peg collides with a hostile or civilian peg, the hostile and civilian pegs should react identically to how they react to a cannon ball.
- A loid peg should rebound more drastically than a cannon ball upon collision.
- Collided pegs should only fade and be removed once cannon ball (charges expended) and all dynamic loid pegs exit the screen.
- The scoreline if presented should react to a collision between loid peg and another the same way it reacts to cannon ball and another.
- If Operation Gigi_ is chosen, hitting the loid peg should result in game loss.
Franky Peg
- Layout two or more Franky pegs, along with other pegs of your choice.
- Press Start, fire the cannon ball at the franky pegs.
- There is a 1/3 chance that a franky peg will react to a collision with a loid peg or cannon ball
- The peg sprite should change followed by fade and removal if a reaction is triggered.
- A reaction should grant you a free ball.
- The scoreline if present should not be affected directly by collision with franky peg.
- If Operation Gigi_ is chosen, hitting the bond peg should result in game loss.
Confuse Peg
- Layout two or more confuse pegs, along with other pegs of your choice.
- Press Start, fire the cannon ball at the confuse pegs.
- Upon collision with a loid peg, the board should rotate by 180 degrees.
- Multiple collisions with a confuse peg within the same cannon shot would not trigger further flipping.
- Across different shots, however, collision with confuse pegs should trigger board flip.
- The confuse peg should fade and be removed once cannon ball and all dynamic loid pegs exit the screen.
-
- The scoreline if present should not be affected directly by collision with confuse peg.
- If Operation Gigi_ is chosen, hitting the bond peg should result in game loss.
- Head to level designer, tap on the picker Operation Strix, a drop down an additional two options Operation Eden and Operation Gigi should be visible.
- Select Operation Gigi, a subview should appear that allows you to adjust the number of balls.
- Repeat 1-2 at level selection view.
Operation Strix
- Either via one of the levels in selection view or from level designer, select Operation Strix and press Start.
- Play the game. There should be a ball count on the top right corner, a tombstone icon indicating the number of civilian death over allowed civilian death at the bottom left corner and a score at the bottom right corner.
- Hitting a blue more than twice should case civiliam death tally to increase by one.
- Score update should occur after every cannon shot completes. Orange peg rewards 150 base points, green peg rewards 500 base points, blue peg deducts 150. Actual change in score should be amplified for a longer net streak.
- If civilian death exceeds the allowed death, a game loss pop up should emerge.
- If ballcount reaches 0 and not all orange and green pegs are cleared, a game loss pop up should emerge.
- If all green and blue pegs are cleared before ball count reaches 0 and the 5 is not violated, a game win pop up with your final score should emerge.
- Repeat steps 1-7 with a drastically different board layout, the allowed civilian death and number of balls given should change sensibly.
Operation Eden
- Either via one of the levels in selection view or from level designer, select Operation Eden and press Start.
- Play the game. There should timer in the middle of the top bar counting down. There should be a target score and score at the bottom right corner.
- Score calculation should be identical to that of Operation Strix.
- If the target score is reached before the timer expires, a game won popup with your score should emerge.
- If 4 never evaluates to true by the time the timer expires the game lost popup with your score should emerge.
- Repeat steps 1-5 with a drastically different board layout, the timer start time and target score should change sensibly.
Operation Gigi
- Either via one of the levels in selection view or from level designer, select Operation Gigi.
- Provide the number of balls to clear.
- The moment the ball collides with a peg excluding blocks, a game lost popup should appear.
- If all balls are cleared without hitting a peg excluding blocks, a game won popup should appear.
- Repeat steps 1-4 with different ball count input, the same criteria should apply.
Level Savin and Loading
- There should be three pre-loaded levels arranged in no particular order: "Orphanage", "Assassin?!" and "Find Gigi" available at the level selection view.
- Go to level designer, layout a board of pegs save it under "Befriend Damien".
- Head to the selection view, "Befriend Damien" should be added to the list of available levels.
- Play "Befriend Damien" in any game mode, the game board should match that of the design.
- Load one of the pre-loaded levels into level designer, adjust the peg and block layout.
- Go to selection view, start the pre-loaded level you just changed, the changes should be reflected.
- Exit the app, clear the window.
- Start the app again, and head to selection view and start the pre-loaded level you changed again, the changes should remain.
Screen adaption
- Try the pre-loaded levels across various devices, the game area should be appropriately letter boxed.
- Each peg should be scaled without distortion and preserve the same relative layout.
There were some shortcomings of my previous design. First, after the feedback from ps3, I realized none of my game specific classes should have inherited rigidbody cause that results in tight coupling of the game engine and physics engine. Although it did not impede my development, it does present concerns on the independence of my peggle game engine. Secondly, I did not abstract out the concept of shapes for peg in PS2. Then I limited pegs to circular objects. As such, to accomodate for triangles and blocks I had to remove the concept of radius from the peg and add a wrapper around peg consisting of a shaped collider for level designer to support collision detection Lastly, related to the first point if I have decoupled my game objects away from rigidbody, I will likely have a mapper that translate collision between two rigidobjects in the physics world to one between peggle game objects. This would allow me to create a custom parent classes for peggle game objects. As such I can add in support to group gameobjects under another gameobject recursively, which would make confuse peg implementation alot neater by simply rotating a parent gameobject of all pegs on the board.
The technical debt I had to clean up was to move event loop out of the peggle game engine which increasingly looks like a God class through my development. In addition, related to my second point in the paragraph above, I had to tweak my model and move the concept of shape elsewhere.
If I get to redo the application, first and foremost I will have opted into POP a lot earlier. Towards the end as there were more and more variants that shared behaviours yet does not conform completely to a sense of hierarchy, the overriding implementation becomes nasty. Though I did made an effort to refactor some bits into protocols such Fadable
, Animated
more could be done.