Working with Zoom - Rewriting Simple Pool Sample Code with pinch and zoom

Hi Guys,

I’m developing a game where I want to allow the player to work with zoom. I was studing the simple pool Corona Sample Code as I want to use the same control method where user touches the object and move the finger to control power and direction.

To aplly the zoom I just used the following comands:
stage = display.getCurrentStage( )
stage.xScale = 1.5
stage.yScale = 1.5

then I just reconfigured the stage x and y values so that I could see the white ball on the screen and touch it.

If you guys try to do the same you will see that the logic to select the white ball and move the finger to adjust power and direction doesn’t work properly.

I was wondering that games like angry birds should have a very similar logic and I wanted to know how to solve this issue. Can you guys help to understand what I have to do or indicate a good tutorial about it?

If someone is able to rewrite the simple pool sample code with the possibility of using pinch and zoom to play it would be wonderfull and I’m sure it would really help other begginers like me!!!

Best regards,

Tarcisio [import]uid: 140109 topic_id: 26890 reply_id: 326890[/import]

Do not scale stage. Scale the display group the objects have been put in. I’ve not looked at the simple pool demo recently, so I’m not sure if they are already in a child display group - you might have to do that yourself, but it would be straight forward.

To scale with two fingers you need to enable multi touch and ensure that the first touch has its focus set to the object (i assume the cue ball.)

Each time a touch listener registers a touch you should add it to a collection. When the second touch is registered by the Runtime you simply catch the touch and use the first and second to scale the child display group.

The first touch will be caught by the Runtime touch listener, but you simply add it to your collection and pass it on, by returning false. The second would not be passed on, of course.

I will try to make a little demo later, but if you think about my logic you should be able to apply it. I’ve done it before and its possibly the simplest multi-touch-scaling method available. [import]uid: 8271 topic_id: 26890 reply_id: 109188[/import]

Ok, so, this preoccupied me a little today. Thought I would implement it myself.

The following code is the whole Simple Pool Plus main.lua.

I’ve implemented scaling by adding all the main game display groups to a new parent display group ‘gamegroup’. The scaling is centred on the mid point between the two touch points and works reasonably well, but not as well as I’d like. The scaling has been throttled to between 1 and 3.

Just so you know - line 113 is there so that on device it will behave just fine but on the simulator the first touch is never removed, so the second touch can be entered by mouse. To re-enter the first touch you’ll have to restart the sim.

Look for the calls to :contentToLocal to see where scaling is taken into account during touch operations on the cue ball. Other touch operations have been ignored but I had to set every touch event listener to return true, because those were missing.

[lua]–
– Abstract: SimplePoolPlus sample project
– A basic game of billiards using the physics engine
– (This is easiest to play on iPad or other large devices, but should work on all iOS and Android devices)

– Version: 1.3

– Sample code is MIT licensed, see http://developer.anscamobile.com/code/license
– Copyright © 2010-2011 ANSCA Inc. All Rights Reserved.

– History
– 1.3 Changed ball file names from 1.png to ball_1.png (for 1.png through 15.png)

local physics = require(“physics”)

system.activate( “multitouch” )

physics.start()

–physics.setDrawMode(“hybrid”) --Set physics Draw mode
physics.setScale( 60 ) – a value that seems good for small objects (based on playtesting)
physics.setGravity( 0, 0 ) – overhead view, therefore no gravity vector
display.setStatusBar( display.HiddenStatusBar )

–Constants
local spriteTime = 100 – sprite itteration time
local feltColorIndex = 1
local animationStop = 2.8 – Sprite Animation Stops Below this velocity
local screenW, screenH = display.contentWidth, display.contentHeight
local viewableScreenW, viewableScreenH = display.viewableContentWidth, display.viewableContentHeight --Screen Size properties
local ballBody = { density=0.8, friction=1.0, bounce=.7, radius=15 }
local onePlayer = 1
local twoPlayer = 2
local options = 3
–Foward Declarations
local gameOver
local mode
–Load Audio
local buzzAudio = audio.loadSound(“buzz.mp3”)
local collisionAudio = audio.loadSound(“ballsCollide.mp3”)
– overall game group
local gamegroup = display.newGroup()

–Setup Splash Screen

function splash()

splashGroup = display.newGroup()

local splashBG = display.newImage(“splashBG.png”, true, 20, 1, true)
splashGroup:insert(splashBG)

local onePlayerButton = display.newImage( “onePlayerButton.png”, 200, 550, true)
onePlayerButton.id = onePlayer
splashGroup:insert(onePlayerButton)

local twoPlayerButton = display.newImage( “twoPlayerButton.png”, 200, 700, true)
twoPlayerButton.id = twoPlayer
splashGroup:insert(twoPlayerButton)

local optionsButton = display.newImage( “optionsButton.png”, 200, 850, true)
optionsButton.id = options
splashGroup:insert(optionsButton)

local fillBot = display.newImage( “fill_bkg.png”, true )
fillBot.rotation=180; fillBot.x = 384; fillBot.y = 1117
splashGroup:insert(fillBot)

onePlayerButton:addEventListener(“touch”, init)
twoPlayerButton:addEventListener(“touch”, init)
optionsButton:addEventListener(“touch”, init)
–Buzzing Splashing screen animation
function buzzLogo()
local buzzGroup = display.newGroup()
local randBuzz = math.random(1,6)
local animationOne = 1
local animationTwo = 2

if randBuzz == animationOne then
local buzzOn = display.newImage( “neonOn.png”, 150, -50, true)
buzzGroup:insert(buzzOn)
audio.play(buzzAudio)
timer.performWithDelay(1000, function() buzzOn:removeSelf(); end, 1)

elseif randBuzz >= animationTwo then
local buzzOff = display.newImage( “neonOff.png”, 150, -50, true)
buzzGroup:insert(buzzOff)
timer.performWithDelay(1000, function() buzzOff:removeSelf() end, 1)
end
end

buzzTimer = timer.performWithDelay(400, buzzLogo, -1) --Loops buzz animation

end
–[[start: touch list handling]]–
– All this does is manage a list of touch events. This is used to decide whether to implement scaling or just drop touches through to the next listener.
local touchlist = {}
function touchlist:add( event )
if (event.phase == “began”) then
touchlist[#touchlist+1] = event
else
for i=#touchlist, 1, -1 do
if (touchlist[i].id == event.id) then
if (event.phase == “moved”) then
touchlist[i] = event
break
else
if (system.getInfo(“environment”) == “device” and #touchlist > 1) then
table.remove( touchlist, i )
end
break
end
end
end
end
return #touchlist
end
–[[end: touch list handling]]–
– returns the distance between points a and b
function lengthOf( a, b )
local width, height = b.x-a.x, b.y-a.y
return math.sqrt(width*width + height*height)
end
–Setup Gameplay Stage
function gameStage()

stageGroup = display.newGroup()
gamegroup:insert( stageGroup )

–[[start: multi-touch scaling handling]]–
– This section has been added only after return true was added to all the other touch listeners in the program.
– This code simply adds each touch received by the lowest display group to a collection and when there
– is enough to provide scaling (two) then scaling is performed and the touch events are no longer dropped through.
function stageGroup:touch( event )
local count = touchlist:add( event )
print('stageGroup: '…event.phase,count)

if (count == 1) then
– nothing
elseif (count == 2) then
– handle scaling here
local init = lengthOf( {x=touchlist[1].xStart,y=touchlist[1].yStart}, {x=touchlist[2].xStart,y=touchlist[2].yStart} )
local len = lengthOf( touchlist[1], touchlist[2] )
local scale = len/init – * stageGroup.initScale
print(len,init,scale)
if (scale>1 and scale<3) then
local x, y = gamegroup:contentToLocal( (touchlist[1].x+touchlist[2].x)/2, (touchlist[1].y+touchlist[2].y)/2 )
gamegroup.xReference, gamegroup.yReference = x, y – centre scaling at the midpoint of the touch points
gamegroup.xScale, gamegroup.yScale = scale, scale
end
– handle translation here

return true
end

– don’t handle scaling, just pass the touch events through
return false
end
stageGroup:addEventListener(“touch”,stageGroup)
–[[end: multi-touch scaling handling]]–

local table = display.newImage( “table_bkg.png”, true) – “true” overrides Corona’s downsizing of large images on smaller devices
stageGroup:insert(table)
table.x = 384
table.y = 512

local tableTopColor = display.newImage( “table”…feltColorIndex…".png", true) – “true” overrides Corona’s downsizing of large images on smaller devices
stageGroup:insert(tableTopColor)
tableTopColor.x = 384
tableTopColor.y = 512

local fillTop = display.newImage( “fill_bkg.png”, true )
stageGroup:insert(fillTop)
fillTop.x = 384; fillTop.y = -93

local fillBot = display.newImage( “fill_bkg.png”, true )
stageGroup:insert(fillBot)
fillBot.rotation=180; fillBot.x = 384; fillBot.y = 1117

local endBumperShape = { -212,-18, 212,-18, 190,2, -190,2 }
local sideBumperShape = { -18,-207, 2,-185, 2,185, -18,207 }
local bumperBody = { friction=0.5, bounce=0.5, shape=endBumperShape }

local bumper1 = display.newImage( “bumperEnd”…feltColorIndex…".png" )
stageGroup:insert(bumper1)
physics.addBody( bumper1, “static”, bumperBody )
bumper1.x = 384; bumper1.y = 58

local bumper2 = display.newImage( “bumperEnd”…feltColorIndex…".png" )
stageGroup:insert(bumper2)
physics.addBody( bumper2, “static”, bumperBody )
bumper2.x = 384; bumper2.y = 966; bumper2.rotation = 180

– Override the shape declaration above, but reuse the other body properties
bumperBody.shape = sideBumperShape

local bumper3 = display.newImage( “bumperSide”…feltColorIndex…".png" )
stageGroup:insert(bumper3)
physics.addBody( bumper3, “static”, bumperBody )
bumper3.x = 157; bumper3.y = 279

local bumper4 = display.newImage( “bumperSide”…feltColorIndex…".png" )
stageGroup:insert(bumper4)
bumper4.x = 611; bumper4.y = 279; bumper4.rotation = 180
physics.addBody( bumper4, “static”, bumperBody)

local bumper5 = display.newImage( “bumperSide”…feltColorIndex…".png" )
stageGroup:insert(bumper5)
physics.addBody( bumper5, “static”, bumperBody )
bumper5.x = 157; bumper5.y = 745

local bumper6 = display.newImage( “bumperSide”…feltColorIndex…".png" )
stageGroup:insert(bumper6)
physics.addBody( bumper6, “static”, bumperBody )
bumper6.x = 611; bumper6.y = 745; bumper6.rotation = 180

pauseBtn = display.newImage(“pause.png”,720,0 )
pauseBtn:addEventListener(“touch”,
function()
local gameState = “paused”
return restartMenu(gameState)
end
)

if mode == onePlayer then

–Setup Up Player Score Board for only one player
local solidScoreBoard = display.newImage(“solidScore.png”)
stageGroup:insert(solidScoreBoard)
solidScoreBoard.y = 500; solidScoreBoard.x = 53;

solidTotal = 0
solidScoreText = display.newText(solidTotal, 0, 0, native.systemFontBold, 30 )
solidScoreText:setTextColor( 0, 0, 0, 255 )
solidScoreText.x = 50; solidScoreText.y = 400; solidScoreText.rotation = 270

timer.performWithDelay(500,
function()
local player1 = display.newImage(“player1.png”)
stageGroup:insert(player1)
player1.y = display.contentHeight/2; player1.x = display.contentWidth/2; player1.alpha = 0;
local player1 = transition.to( player1, { alpha= 1, time=400, x=53, y=540 } )
end
,1)

elseif mode == twoPlayer then

–Setup Up Player Score Board for two players
local solidScoreBoard = display.newImage(“solidScore.png”)
stageGroup:insert(solidScoreBoard)
solidScoreBoard.y = 500; solidScoreBoard.x = 53;

local stripeScoreBoard = display.newImage(“stripeScore.png”)
stageGroup:insert(stripeScoreBoard)
stripeScoreBoard.y = 500; stripeScoreBoard.x = 715; stripeScoreBoard.rotation = 180

solidTotal = 0
solidScoreText = display.newText(solidTotal, 0, 0, native.systemFontBold, 30 )
solidScoreText:setTextColor( 0, 0, 0, 255 )
solidScoreText.x = 50; solidScoreText.y = 400; solidScoreText.rotation = 270

stripeTotal = 0
stripeScoreText = display.newText(stripeTotal, 0, 0, native.systemFontBold, 30 )
stripeScoreText:setTextColor( 0, 0, 0, 255)
stripeScoreText.x = 718; stripeScoreText.y = 602; stripeScoreText.rotation = 90

–Player one animations
timer.performWithDelay(250,
function()
local playerOneAnimation = display.newImage(“player1.png”)
stageGroup:insert(playerOneAnimation)
playerOneAnimation.y = display.contentHeight/2 ; playerOneAnimation.x = display.contentWidth/2; playerOneAnimation.alpha = 0;
local playerOneAnimation = transition.to( playerOneAnimation, { alpha= 1, xScale= 1, yScale=1.0, time=400, x=53, y=540 } )
end
,1)

– Player two animations
timer.performWithDelay(1000,
function()
local playerTwoAnimation = display.newImage(“player2.png”)
stageGroup:insert(playerTwoAnimation)
playerTwoAnimation.y = display.contentHeight/2; playerTwoAnimation.x = display.contentWidth/2; playerTwoAnimation.alpha = 0; playerTwoAnimation.rotation = 180;
local playerTwoAnimation = transition.to( playerTwoAnimation, { alpha= 1, xScale= 1, yScale=1.0, time=400, x=715, y=460 } )
end
,1)

end

ballProperties()
end

–Sets up all pool ball functions
function ballProperties()

gameBallGroup = display.newGroup() --Create ball group
gamegroup:insert( gameBallGroup )

local v = 0
local reqForce = .18
local maxBallSounds = 4

–OnCollision Audio
function ballCollisionAudio( self, event )
local force = event.force
local objId = event.other.id
local reqId = “spriteBall”

if force >= reqForce and reqId == objId and v <= maxBallSounds then – Audio Play back is determined by the amount of force
audio.play(collisionAudio)
v = v + 1
timer.performWithDelay(1000, function() v = v - 1 end, 1)
end
end

–Create cueball
cueball = display.newImage( “ball_white.png” )
gameBallGroup:insert(cueball)
cueball.x = display.contentWidth/2; cueball.y = 780
physics.addBody( cueball, ballBody )
cueball.linearDamping = 0.3
cueball.angularDamping = 0.8
cueball.isBullet = true – force continuous collision detection, to stop really fast shots from passing through other balls
cueball.type = “cueBall”
cueball.collision = onCollision
cueball:addEventListener(“collision”, cueball) – Sprite balls start animation on Collision with cueball
cueball.postCollision = ballCollisionAudio --Sets Event Listener for Audio on Collision
cueball:addEventListener( “postCollision”, cueball )

–Creat Rotating target
target = display.newImage( gamegroup, “target.png” )
target.x = cueball.x; target.y = cueball.y; target.alpha = 0;

–Ball Properties Table

local ballTable = {
{type = “solid”},
{type = “solid”},
{type = “solid”},
{type = “solid”},
{type = “solid”},
{type = “solid”},
{type = “solid”},
{type = “eightBall”},
{type = “stripe”},
{type = “stripe”},
{type = “stripe”},
{type = “stripe”},
{type = “stripe”},
{type = “stripe”},
{type = “stripe”}
}

local spriteInstance = {}
local n = 1
for i = 1, 5 do
for j = 1, (6-i) do
spriteInstance[n] = display.newImage(“ball_” … n … “.png”)
gameBallGroup:insert(spriteInstance[n])
physics.addBody(spriteInstance[n], ballBody)
spriteInstance[n].x = 279 + (j*34) + (i*15)
spriteInstance[n].y = 185 + (i*27)
spriteInstance[n].linearDamping = 0.3 – simulates friction of felt
spriteInstance[n].angularDamping = 2 – stops balls from spinning forever
spriteInstance[n].isBullet = true – If true physics body always awake
spriteInstance[n].active = true – Ball is set to active
spriteInstance[n].bullet = false – force continuous collision detection, to stop really fast shots from passing through other balls
spriteInstance[n].id = “spriteBall”
spriteInstance[n].type = ballTable[n].type
spriteInstance[n].postCollision = ballCollisionAudio
spriteInstance[n]:addEventListener( “postCollision”, spriteInstance[n] )
n = n + 1
end
end
cueball:addEventListener( “touch”, cueShot ) – Sets event listener to cueball
setPockets()

end

–Reset cueball
function resetCueball()
cueball.alpha = 0
cueball.x = 384
cueball.y = 780
cueball.xScale = 2.0; cueball.yScale = 2.0
local dropBall = transition.to( cueball, { alpha=1.0, xScale=1.0, yScale=1.0, time=400 } )
end
–Sets up pockets
function inPocket( self, event )

local fallDown = transition.to( event.other, { alpha=0, xScale=0.3, yScale=0.3, time=200 } )
local object = event.other

event.other:setLinearVelocity( 0, 0 )

if event.other.type == “cueBall” then
timer.performWithDelay( 50, resetCueball )

elseif event.other.type == “solid” and event.other.active == true then
event.other.active = false
–Update Solid Score
solidTotal = solidTotal + 1
solidScoreText.text = solidTotal
elseif event.other.type == “stripe” and event.other.active == true and mode == 2 then
event.other.active = false – Prevents balls from
–Update Stripe Score
stripeTotal = stripeTotal + 1
stripeScoreText.text = stripeTotal
elseif event.other.type == “eightBall” then
gameOver()
end

end

– Create pockets
function setPockets()
local pocket = {}
for i = 1, 3 do
for j = 1, 2 do
local index = j + ((i-1) * 2) – a counter from 1 to 6

– Add objects to use as collision sensors in the pockets
local sensorRadius = 20
pocket[index] = display.newCircle( -389 + (515*j), -436 + (474*i), sensorRadius )
stageGroup:insert(pocket[index])

– (Change this value to “true” to make the pocket sensors visible)
pocket[index].isVisible = false
physics.addBody( pocket[index], { radius=sensorRadius, isSensor=true } )
pocket[index].id = “pocket”
pocket[index].bullet = false
pocket[index].collision = inPocket
pocket[index]:addEventListener( “collision”, pocket[index] ) – add table listener to each pocket sensor

end
end
end

– Shoot the cue ball, using a visible force vector
function cueShot( event )

local t = event.target
local phase = event.phase

if “began” == phase then
display.getCurrentStage():setFocus( t )
t.isFocus = true

– Stop current cueball motion, if any
t:setLinearVelocity( 0, 0 )
t.angularVelocity = 0

target.x = t.x
target.y = t.y

startRotation = function()
target.rotation = target.rotation + 4
end

Runtime:addEventListener( “enterFrame”, startRotation )

local showTarget = transition.to( target, { alpha=0.4, xScale=0.4, yScale=0.4, time=200 } )
myLine = nil

elseif t.isFocus then

if “moved” == phase then

if ( myLine ) then
myLine.parent:remove( myLine ) – erase previous line, if any
end
local x, y = gamegroup:contentToLocal( event.x, event.y ) – adjust for the scaling
myLine = display.newLine( gamegroup, t.x,t.y, x,y ) – added targetting line to scaled game group
myLine:setColor( 255, 255, 255, 50 )
myLine.width = 15

elseif “ended” == phase or “cancelled” == phase then

display.getCurrentStage():setFocus( nil )
t.isFocus = false

local stopRotation = function()
Runtime:removeEventListener( “enterFrame”, startRotation )
end

local hideTarget = transition.to( target, { alpha=0, xScale=1.0, yScale=1.0, time=200, onComplete=stopRotation } )

if ( myLine ) then
myLine.parent:remove( myLine )
end

– Strike the ball!
local cueShot = audio.loadSound(“cueShot.mp3”)
audio.play(cueShot)
local x, y = gamegroup:contentToLocal( event.x, event.y ) – adjust for the scaling
t:applyForce( (t.x - x), (t.y - y), t.x, t.y )
end
end

return true – Stop further propagation of touch event
end

–Displays game over screeen
function gameOver()

gameOverGroup = display.newGroup()

local overSplash = display.newImage( “game_over.png”, true )
gameOverGroup:insert(overSplash)
overSplash.alpha = 0
overSplash.xScale = 1.5; overSplash.yScale = 1.5

local showGameOver = transition.to( overSplash, { alpha=1.0, xScale=1.0, yScale=1.0, time=500 } )
cueball:removeEventListener( “touch”, cueShot )

local gameState = “gameOver”
return restartMenu(gameState)

end

–In game restart menu
function restartMenu(gameState)
pauseBtn:removeSelf()
menuGroup = display.newGroup()

if gameState == “paused” then
local backDrop = display.newRect(0, 0, screenW, screenH )
menuGroup:insert(backDrop)
backDrop:setFillColor(0, 0, 0,100)
end

local restartGameImage = display.newImage(“newGameButton.png”,200,200,true )
menuGroup:insert(restartGameImage)
restartGameImage.state = gameState
restartGameImage:addEventListener(‘touch’, restartGame)

return true
end
–Restart Game
function restartGame( event )
local gameState = event.target.state
if event.phase == “ended” then
if gameState == “gameOver” then
gameOverGroup:removeSelf()
end
stageGroup:removeSelf() – Removes All Stage Objects
gameBallGroup:removeSelf() --Removes Ball Objects
menuGroup:removeSelf() --Removes Menu Group Objects
timer.performWithDelay(1000, splash, 1) – Calls splash screen
end

return true
end

–Displays options menu
function optionMenu()

local stroke = 5
local optionsGroup = display.newGroup() --Create options menu group

local function changeFelt(event)
local feltObject = event.target
local feltColor = event.target.id
if event.phase == “ended” then
feltColorIndex = feltColor
feltObject.selected = true
optionsGroup:removeSelf()
feltObject:setStrokeColor(16,255,4)
timer.performWithDelay(200,
function()

transition.to( splashGroup, { alpha= 1, xScale= 1, yScale=1.0, time=1000} )
end
,1)

end

end

local optionsBG = display.newImage(“optionsBG.png”,-5,540,true)
optionsGroup:insert(optionsBG)

local greenFelt = display.newImage( “sample1.png”, 49, 701, true )
optionsGroup:insert(greenFelt)
greenFelt:setStrokeColor(255,255,255)
greenFelt.strokeWidth = stroke
greenFelt.id = “1”
greenFelt:addEventListener(‘touch’, changeFelt)

local purpleFelt = display.newImage( “sample2.png”, 219, 701, true )
optionsGroup:insert(purpleFelt)
purpleFelt:setStrokeColor(255,255,255)
purpleFelt.strokeWidth = stroke
purpleFelt.id = “2”
purpleFelt:addEventListener(‘touch’, changeFelt)
local redFelt = display.newImage( “sample3.png”, 392, 701, true )
optionsGroup:insert(redFelt)
redFelt:setStrokeColor(255,255,255)
redFelt.strokeWidth = stroke
redFelt.id = “3”
redFelt:addEventListener(‘touch’, changeFelt)

local tealFelt = display.newImage( “sample4.png”, 565, 701, true )
optionsGroup:insert(tealFelt)
tealFelt:setStrokeColor(255,255,255)
tealFelt.strokeWidth = stroke
tealFelt.id = “4”
tealFelt:addEventListener(‘touch’, changeFelt)

local selectFeltText = display.newText(“Select a table color!”, 150, 625, systemFontBold, 50 )
optionsGroup:insert(selectFeltText)
selectFeltText:setTextColor(255,255,255)

end

function init( event )

mode = event.target.id

if mode == onePlayer or mode == twoPlayer then

timer.cancel(buzzTimer)-- Remoes buzzing animation

splashGroup:removeSelf()-- Removes splash screen objects

timer.performWithDelay(800, gameStage, 1)

elseif mode == options then
local splashButtonAnimation = transition.to( splashGroup, { alpha= 0, xScale= 1, yScale=1.0, time=400} ) – Transitions splash Menu to options Menu
optionMenu()
end

return true
end

– Set Up Splashh Screen
splash()

–Sets game splash button listeners[/lua]
[import]uid: 8271 topic_id: 26890 reply_id: 109234[/import]

Thank you Brother!

I’m not with my mac now so can’t test your code but I really apreciate your help. This should help other members too as I think simple pool is a very good example.

Thanks again,

Tarcisio [import]uid: 140109 topic_id: 26890 reply_id: 109247[/import]

No problem. I’ve made the two important parts obvious, but there are a few other places you should check for the ‘gamegroup’, ‘contentToLocal’ and ‘return true’.

Also, I’m sure someone can improve on the centering of the scaling, which is a bit rough. [import]uid: 8271 topic_id: 26890 reply_id: 109249[/import]