Assignment confusion in Lua - lua

I am analysing Fishies projest from sample codes of Corona and i couldn't understand that assignment.
background = ( backgroundLandscape == background and backgroundPortrait ) or backgroundLandscape
Here is the full code:
-- Seed randomizer
local seed = os.time();
math.randomseed( seed )
display.setStatusBar( display.HiddenStatusBar )
-- Preload the sound file (theoretically, we should also dispose of it when we are completely done with it)
local soundID = audio.loadSound( "bubble_strong_wav.wav" )
-- Background
local halfW = display.viewableContentWidth / 2
local halfH = display.viewableContentHeight / 2
-- Create a table to store all the fish and register this table as the
-- "enterFrame" listener to animate all the fish.
local bounceAnimation = {
container = display.newRect( 0, 0, display.viewableContentWidth, display.viewableContentHeight ),
reflectX = true,
}
local backgroundPortrait = display.newImage( "aquariumbackgroundIPhone.jpg", 0, 0 )
local backgroundLandscape = display.newImage( "aquariumbackgroundIPhoneLandscape.jpg", -80, 80 )
backgroundLandscape.isVisible = false
local background = backgroundPortrait
-- Handle changes in orientation for the background images
local backgroundOrientation = function( event )
-- TODO: This requires some setup, i.e. the landscape needs to be centered
-- Need to add a centering operation. For now, the position is hard coded
local delta = event.delta
if ( delta ~= 0 ) then
local rotateParams = { rotation=-delta, time=500, delta=true }
if ( delta == 90 or delta == -90 ) then
local src = background
-- toggle background to refer to correct dst
background = ( backgroundLandscape == background and backgroundPortrait ) or backgroundLandscape
background.rotation = src.rotation
transition.dissolve( src, background )
transition.to( src, rotateParams )
else
assert( 180 == delta or -180 == delta )
end
transition.to( background, rotateParams )
audio.play( soundID ) -- play preloaded sound file
end
end
-- Add a global listener
Runtime:addEventListener( "orientation", backgroundOrientation )
--
-- Fishies
local numFish = 10
local file1 = "fish.small.red.png"
local file2 = "fish.small.blue.png"
--
-- Define touch listener for fish so that fish can behave like buttons.
-- The listener will receive an 'event' argument containing a "target" property
-- corresponding to the object that was the target of the interaction.
-- This eliminates closure overhead (i.e. the need to reference non-local variables )
local buttonListener = function( event )
if "ended" == event.phase then
local group = event.target
-- tap only triggers change from original to different color
local topObject = group[1]
if ( topObject.isVisible ) then
local bottomObject = group[2]
-- Dissolve to bottomObject (different color)
transition.dissolve( topObject, bottomObject, 500 )
-- Restore after some random delay
transition.dissolve( bottomObject, topObject, 500, math.random( 3000, 10000 ) )
end
-- we handled it so return true to stop propagation
return true
end
end
--
--
--
-- Add fish to the screen
for i=1,numFish do
-- create group which will represent our fish, storing both images (file1 and file2)
local group = display.newGroup()
local fishOriginal = display.newImage( file1 )
group:insert( fishOriginal, true ) -- accessed in buttonListener as group[1]
local fishDifferent = display.newImage( file2 )
group:insert( fishDifferent, true ) -- accessed in buttonListener as group[2]
fishDifferent.isVisible = false -- make file2 invisible
-- move to random position in a 200x200 region in the middle of the screen
group:translate( halfW + math.random( -100, 100 ), halfH + math.random( -100, 100 ) )
-- connect buttonListener. touching the fish will cause it to change to file2's image
group:addEventListener( "touch", buttonListener )
-- assign each fish a random velocity
group.vx = math.random( 1, 5 )
group.vy = math.random( -2, 2 )
-- add fish to animation group so that it will bounce
bounceAnimation[ #bounceAnimation + 1 ] = group
end
--
-- Function to animate all the fish
function bounceAnimation:enterFrame( event )
local container = self.container
container:setFillColor( 0, 0, 0, 0) -- make invisible
local containerBounds = container.contentBounds
local xMin = containerBounds.xMin
local xMax = containerBounds.xMax
local yMin = containerBounds.yMin
local yMax = containerBounds.yMax
local orientation = self.currentOrientation
local isLandscape = "landscapeLeft" == orientation or "landscapeRight" == orientation
local reflectX = nil ~= self.reflectX
local reflectY = nil ~= self.reflectY
-- the fish groups are stored in integer arrays, so iterate through all the
-- integer arrays
for i,v in ipairs( self ) do
local object = v -- the display object to animate, e.g. the fish group
local vx = object.vx
local vy = object.vy
if ( isLandscape ) then
if ( "landscapeLeft" == orientation ) then
local vxOld = vx
vx = -vy
vy = -vxOld
elseif ( "landscapeRight" == orientation ) then
local vxOld = vx
vx = vy
vy = vxOld
end
elseif ( "portraitUpsideDown" == orientation ) then
vx = -vx
vy = -vy
end
-- TODO: for now, time is measured in frames instead of seconds...
local dx = vx
local dy = vy
local bounds = object.contentBounds
local flipX = false
local flipY = false
if (bounds.xMax + dx) > xMax then
flipX = true
dx = xMax - bounds.xMax
elseif (bounds.xMin + dx) < xMin then
flipX = true
dx = xMin - bounds.xMin
end
if (bounds.yMax + dy) > yMax then
flipY = true
dy = yMax - bounds.yMax
elseif (bounds.yMin + dy) < yMin then
flipY = true
dy = yMin - bounds.yMin
end
if ( isLandscape ) then flipX,flipY = flipY,flipX end
if ( flipX ) then
object.vx = -object.vx
if ( reflectX ) then object:scale( -1, 1 ) end
end
if ( flipY ) then
object.vy = -object.vy
if ( reflectY ) then object:scale( 1, -1 ) end
end
object:translate( dx, dy )
end
end
-- Handle orientation of the fish
function bounceAnimation:orientation( event )
print( "bounceAnimation" )
for k,v in pairs( event ) do
print( " " .. tostring( k ) .. "(" .. tostring( v ) .. ")" )
end
if ( event.delta ~= 0 ) then
local rotateParameters = { rotation = -event.delta, time=500, delta=true }
Runtime:removeEventListener( "enterFrame", self )
self.currentOrientation = event.type
for i,object in ipairs( self ) do
transition.to( object, rotateParameters )
end
local function resume(event)
Runtime:addEventListener( "enterFrame", self )
end
timer.performWithDelay( 500, resume )
end
end
Runtime:addEventListener( "enterFrame", bounceAnimation );
Runtime:addEventListener( "orientation", bounceAnimation )
-- This function is never called,
-- but shows how we would unload the sound if we wanted to
function unloadSound()
audio.dispose(soundID)
soundID = nil
end

Lua has a slightly strange behaviour when it comes to ands and ors.
The expression a and b evaluates to a if a is considered false (only nil and false is considered false, all other values including 0 are considered true) and if a is considered true the expression evaluates to b.
The expression a or b evaluates to a if a is considered true and b if a is considered false.
Note: in both cases if the expression evaluates to a that value of b isn't even evaluated. This is called short circuit logic. In and if a is false, the and can't possibly be true, so there is no point in wasting computation time to evaluate b. Similarly, if a is true in a or b there is no point to evaluate b as the or can't possibly be false.
The construct cond and valiftrue or valiffalse (or the equivalent, due to operator precedence, (cond and valiftrue) or valiffalse) is equivalent to other language's ternary if statement, with one caveat: valiftrue must not evaluate to false. If that is the case, the whole expression will always evaluate to valiffalse. (Try and reason it out, that's the best way I find to get a grip on this construct.)

was about to post in detail but #JPvdMerwe got it spot on.
To be precise,
background = ( backgroundLandscape == background and backgroundPortrait ) or backgroundLandscape
translates to
if backgroundLandscape == background then
background = backgroundPortrait
else
background = backgroundLandscape
end
EDIT
As #JPvdMerwe pointed out, in this case, if backgroundPortrait is false then
background = backgroundLandscape
will be executed all the time.

Related

Attempt to call global (a nil value)

I'm trying to spawn asteroids in this game every few seconds. But whenever I run the game, I get an error that says
stack traceback:
main.lua:277: in function '_listener'
?: in function '?'
?: in function <?:172>
I've tried moving some of the code around but it's not helping. My professor looked at my code and doesn't know exactly what the issue is, but that the loop that I'm using is missing a something. Here is all the code that includes "createAsteroid"
EDIT: I added my entire main.lua file to this question. The separate files that I do have in this project are the character and the background image, everything else besides the config file is in here. I did try deleting an extra end In the gameLoop function but when I did, I got an error that basically said it expected an end there.
display.setStatusBar( display.HiddenStatusBar )
------------------------------
-- RENDER THE SAMPLE CODE UI
------------------------------
local sampleUI = require( "sampleUI.sampleUI" )
local physics = require( "physics" )
physics.start()
math.randomseed( os.time() )
------------------------------
-- CONFIGURE STAGE
------------------------------
local composer = require( "composer" )
local mainScene = display.newGroup()
display.getCurrentStage():insert( mainScene )
----------------------
-- BEGIN SAMPLE CODE
----------------------
-- Frequently used variables
local centerX = display.contentCenterX
local centerY = display.contentCenterY
local originX = display.screenOriginX
local originY = display.screenOriginY
local width = display.actualContentWidth
local height = display.actualContentHeight
local score = 0
local died = false
local asteroidsTable = {}
local player
local gameLoopTimer
local scoreText
local uiGroup = display.newGroup() -- Display group for UI objects like the score
-- Load background and character from "background.lua" and "character.lua" respectively
local background = require( "background" )
mainScene:insert( background )
local character = require( "character" )
mainScene:insert( character )
local asteroid = display.newImage( "asteroid.png", 180, -50 )
asteroid.rotation = 5
physics.addBody( asteroid, { density=3.0, friction=0.5, bounce=0.3, radius=25 })
-------------------------
-- BEGIN MOVEMENT LOGIC
-------------------------
-- Movement logic variables
local movementSpeed = 1.5
local moving = false
local moveDirection = "down"
-- Sets if the character should move and updates sprite animation
local function setMoving( state )
moving = state
if ( moving ) then
character:play()
else
character:pause()
character:setFrame(2)
end
end
-- Sets the direction that the player should move and updates the character's sprite facing
local function setMovementDirection( direction )
-- Don't change anything if we haven't altered direction
if ( moveDirection == direction ) then
return
end
-- Update the sprite playback
moveDirection = direction
character:setSequence( "walk-" .. direction )
-- Refresh animation playback, which can pause after changing the sprite sequence
setMoving( moving )
end
-- Set movement magnitudes
local movementX = 0
local movementY = 0
local function setMovement( x, y )
local updatedMovement = false
-- Horizontal movement checks
if ( movementX ~= x and nil ~= x ) then
movementX = x
updatedMovement = true
end
-- Abort if nothing is updating
-- We do this since axis/key events can fire multiple times with the same values
if ( not updatedMovement ) then
return
end
-- Determine movement direction
if ( 0 ~= movementX or 0 ~= movementY ) then
-- Favor horizontal animations over vertical ones
if ( math.abs( movementX ) >= math.abs( movementY ) ) then
if ( 0 < movementX ) then
setMovementDirection( "right" )
else
setMovementDirection( "left" )
end
else
if ( 0 < movementY ) then
setMovementDirection( "down" )
else
setMovementDirection( "up" )
end
end
end
-- Update moving animation/variable
if ( 0 == movementX and 0 == movementY ) then
setMoving( false )
else
setMoving( true )
end
end
-- Handle character translation on the screen per frame
local function onFrameEnter()
if ( 0 ~= movementX ) then
character.x = character.x + ( movementSpeed * movementX )
setMoving( true )
end
if ( 0 ~= movementY ) then
character.y = character.y + ( movementSpeed * movementY )
setMoving( true )
end
end
Runtime:addEventListener( "enterFrame", onFrameEnter )
---------------------------
-- BEGIN INPUT CODE: TOUCH
---------------------------
local padGraphic, padButtonUp, padButtonDown, padButtonLeft, padButtonRight
-- Determine if we have a joystick connected or not
local inputDevices = system.getInputDevices()
local function getHasJoystick()
for i = 1, #inputDevices do
if ( "joystick" == inputDevices[i].type ) then
return true
end
end
return false
end
local hasJoystick = getHasJoystick()
-- If we don't have any controllers found, create a virtual D-pad controller
if ( not hasJoystick ) then
-- Called when one of the virtual D-pad buttons are used
local function onTouchEvent( event )
local phase = event.phase
local targetID = event.target.id
if ( "began" == phase or "moved" == phase ) then
if ( "up" == targetID ) then
setMovement( 0, -1 )
elseif ( "down" == targetID ) then
setMovement( 0, 1 )
elseif ( "left" == targetID ) then
setMovement( -1, 0 )
elseif ( "right" == targetID ) then
setMovement( 1, 0 )
elseif ( "padGraphic" == targetID ) then
setMovement( 0, 0 )
end
elseif ( "ended" == phase or "cancelled" == phase ) then
-- An alternative to checking for "cancelled" is to set focus on the control
-- However, we don't want an incoming phone call to bug out input
if ( "up" == targetID or "down" == targetID ) then
setMovement( nil, 0 )
elseif ( "left" == targetID or "right" == targetID ) then
setMovement( 0, nil )
end
end
return true
end
-- Display score
scoreText = display.newText( uiGroup, "Score: " .. score, 400, 40, native.systemFont, 36 )
-- Hide the status bar
display.setStatusBar( display.HiddenStatusBar )
local function updateText()
scoreText.text = "Score: " .. score
end
local function createAsteroid()
local newAsteroid = display.newImageRect( mainGroup, objectSheet, 1, 102, 85 )
table.insert( asteroidsTable, newAsteroid )
physics.addBody( newAsteroid, "dynamic", { radius=25, bounce=0.8 } )
newAsteroid.myName = "asteroid"
local whereFrom = math.random( 3 )
if ( whereFrom == 1 ) then
-- From the left
newAsteroid.x = -60
newAsteroid.y = math.random( 500 )
newAsteroid:setLinearVelocity( math.random( 40,120 ), math.random( 20,60 ) )
elseif ( whereFrom == 2 ) then
-- From the top
newAsteroid.x = math.random( display.contentWidth )
newAsteroid.y = -60
newAsteroid:setLinearVelocity( math.random( -40,40 ), math.random( 40,120 ) )
elseif ( whereFrom == 3 ) then
-- From the right
newAsteroid.x = display.contentWidth + 60
newAsteroid.y = math.random( 500 )
newAsteroid:setLinearVelocity( math.random( -120,-40 ), math.random( 20,60 ) )
end
newAsteroid:applyTorque( math.random( -6,6 ) )
end
-- Create the visuals for the on-screen D-pad
local padSize = 200
padGraphic = display.newImageRect( mainScene, "pad.png", padSize, padSize )
padGraphic.x = originX + padSize/2 - 40
padGraphic.y = height + originY - padSize/2 + 40
padGraphic.alpha = 0.35
padGraphic.id = "padGraphic"
padGraphic:addEventListener( "touch", onTouchEvent )
-- Creates one of the invisible virtual D-pad buttons
local function createPadButton( buttonID, offsetX, offsetY )
local btn = display.newRect( mainScene, padGraphic.x+offsetX, padGraphic.y+offsetY, padSize/5, padSize/5 )
btn:addEventListener( "touch", onTouchEvent )
btn.id = buttonID
btn.isVisible = false
btn.isHitTestable = true
return btn
end
-- Create buttons for handling the D-pad input
padButtonUp = createPadButton( "up", 0, padSize/-5 )
padButtonDown = createPadButton( "down", 0, padSize/5 )
padButtonLeft = createPadButton( "left", padSize/-5, 0 )
padButtonRight = createPadButton( "right", padSize/5, 0 )
end
-----------------
-- GAME LOOP --
-----------------
local function gameLoop()
-- Create new asteroid
createAsteroid()
-- Remove asteroids which have drifted off screen
for i = #asteroidsTable, 1, -1 do
local thisAsteroid = asteroidsTable[i]
if ( thisAsteroid.x < -100 or
thisAsteroid.x > display.contentWidth + 100 or
thisAsteroid.y < -100 or
thisAsteroid.y > display.contentHeight + 100 )
then
display.remove( thisAsteroid )
table.remove( asteroidsTable, i )
end
end
end
timer.performWithDelay(1000, gameLoop, 0)
--------------------------------------------------
-- BEGIN INPUT CODE: KEYBOARD & BASIC CONTROLLER
--------------------------------------------------
-- Detect if a joystick axis is being used
local joystickInUse = false
-- Keyboard input configuration
local keyUp = "up"
local keyDown = "down"
local keyLeft = "left"
local keyRight = "right"
-- Called when a key event has been received
local function onKeyEvent( event )
local keyName = event.keyName
local phase = event.phase
-- Handle movement keys events; update movement logic variables
if ( not joystickInUse ) then
if ( "down" == phase ) then
if ( keyUp == keyName ) then
setMovement( nil, -1 )
elseif ( keyDown == keyName ) then
setMovement( nil, 1 )
elseif ( keyLeft == keyName ) then
setMovement( -1, nil )
elseif ( keyRight == keyName ) then
setMovement( 1, nil )
end
elseif ( "up" == phase ) then
if ( keyUp == keyName ) then
setMovement( nil, 0 )
elseif ( keyDown == keyName ) then
setMovement( nil, 0 )
elseif ( keyLeft == keyName ) then
setMovement( 0, nil )
elseif ( keyRight == keyName ) then
setMovement( 0, nil )
end
end
end
return false
end
Runtime:addEventListener( "key", onKeyEvent )
------------------------------------------
-- BEGIN INPUT CODE: ADVANCED CONTROLLER
------------------------------------------
-- We only support advanced controllers when one is detected
if ( getHasJoystick ) then
-- Detect axis event updates
local function onAxisEvent( event )
local value = event.normalizedValue
local axis = event.axis.type
local descriptor = event.axis.descriptor
-- We only care about "x" and "y" input events
-- However, touch-screen events can fire these as well so we filter them out
if ( ( "x" ~= axis and "y" ~= axis ) or ( string.find( descriptor, "Joystick" ) == nil ) ) then
return
end
-- Detect zero movement at a certain cutoff so we don't get the character moving very, very slowly
if ( math.abs(value) < 0.15 ) then
value = 0
end
-- Based on which axis type we are dealing with, set movement variables
if ( "x" == axis ) then
setMovement( value, nil )
elseif ( "y" == axis ) then
setMovement( nil, value )
end
-- Some devices will send both up/down/left/right keys and the axis value
-- We let our code know that we are using a joystick value so they do not conflict
if ( 0 ~= value ) then
joystickInUse = true
else
joystickInUse = false
end
end
Runtime:addEventListener( "axis", onAxisEvent )
end
local function onCollision( event )
if ( ( obj1.myName == "player" and obj2.myName == "asteroid" ) or
( obj1.myName == "asteroid" and obj2.myName == "player" ) )
then
display.remove( player )
else
ship.alpha = 0
timer.performWithDelay( 1000, restoreShip )
end
end
Runtime:addEventListener( "collision", onCollision )
Go to line 277 in your file main.lua
You will find a function call createAsteroid(). Within this scope createAsteroid is not defined.
So let's see if we can find a definition for createAsteroid in this file.
Line 218: local function createAsteroid() ...
Check if the function call is within the scope of that function...
No! It is inside an if-statement while the function call is outside that statement. As createAsteroid is local to that if-statement it is unknown (nil) inside gameLoop and hence may not be called.
Finding such scope issues could be very easy if you had proper indentation!

Attempt to perform arithmetic on field 'y' (a nil value)

I'm new here and I'm struggling with this error in Corona, when game ends (lives=0) and I try to remove the background (that is moving with a function "move"):
"Attempt to perform arithmetic on field 'y' (a nil value)"
in line "background.y = background.y + 4"
Is there anybody who can explain me what is the mistake?
THE CODE:
--add PHYSICS
local physics = require( "physics" )
physics.start()
physics.setGravity( 0, 0 )
local lives = 1
local died = false
--###
--add background
background = display.newImageRect( "background.png", 800, 14000 )
background.x = display.contentCenterX
background.y = 730
background.myName = "background"
--add bottle
bottiglia = display.newImageRect( "bottiglia.png", 41, 104 )
physics.addBody( bottiglia, "dynamic", { radius=45, bounce=0.5 } )
bottiglia.x = display.contentCenterX
bottiglia.y = 10
bottiglia.myName = "bottiglia"
--function move
local function move()
bottiglia.y = bottiglia.y + 4
background.y = background.y + 4
end
Runtime:addEventListener( "enterFrame", move )
--###
--add player
studente = display.newImageRect( "studente.png", 98, 79 )
studente.x = display.contentCenterX
studente.y = display.contentHeight - 100
physics.addBody( studente, { radius=40, isSensor=true } )
studente.myName = "studente"
--###
--function collision
local function onCollision( event )
if ( event.phase == "began" ) then
local obj1 = event.object1
local obj2 = event.object2
if ( ( obj1.myName == "studente" and obj2.myName == "bottiglia" ) or
( obj1.myName == "bottiglia" and obj2.myName == "studente" ) )
then
if ( died == false ) then
died = true
-- lives update
lives = lives - 1
livesText.text = "Lives: " .. lives
if ( lives == 0 ) then
display.remove( studente )
display.remove( background)
timer.performWithDelay( 100, endGame )
end
else
studente.alpha = 0
timer.performWithDelay( 500, restoreStudente )
end
end
end
end
Runtime:addEventListener( "collision", onCollision )
livesText = display.newText( "Lives: " .. lives, 200, 80, native.systemFont, 36 )
--thank you all
The Runtime listener (move function) is working all the time. It changes position of bottiglia and background objects but since background does not exist any more you get an error.
A simple solution is to remove the global listener using Runtime:removeEventListener() before you remove the background object.
Use Runtime:removeEventListener("enterFrame", move)
If you don't want remove listener, you can add checks for nil:
--function move
local function move()
if (bottiglia ~= nil and bottiglia.y ~= nil) then
bottiglia.y = bottiglia.y + 4
end
if (background~= nil and background.y ~= nil) then
background.y = background.y + 4
end
end
This is also pretty risky to use such global variables: bottiglia and background.
You can make it little safer if make them as (or something like that):
myGlobalsVars = { }
myGlobalsVars.myGlobalsVars = display.newGroup()
myGlobalsVars.background = display.newGroup()

Receiving "Attempt to index global 'sprite' (a nil value)" in Lua

Im attempting to make a simple game app and keep running into this problem. I stopped programming in Lua for a few years so I don't exactly remember how to fix this. Anyway, my code is as follows:
EDIT: Here is the entire file. Still trying to figure out the formatting of Stack Overflow. Error occurs at line 76.
module(..., package.seeall)
-- Main function - MUST return a display.newGroup()
function new()
local localGroup = display.newGroup()
---------
local Rad = math.rad
local Sin = math.sin
local Cos = math.cos
local Pi = math.pi
local Atan2 = math.atan2
local radD = 180 / Pi
local DegR = Pi / 180
local touchPoint = display.newCircle(localGroup, -50, -50, 20)
touchPoint.isFocus = false
touchPoint.alpha = 0
function GetDistanceFromObjects(obj1, obj2)
local xDist = obj1.x - obj2.x
local yDist = obj1.y - obj2.y
local dist = Sqrt((xDist * xDist) + (yDist * yDist))
return dist
end
function getAngleDeg(inX1, inY1, inX2, inY2)
local xDist = inX2 - inX1
local yDist = inY2 - inY1
local angRad = Atan2(yDist, xDist)
return angRad * radD + 90
end
require "sprite"
function VectorFromAngle(inAngle, inVelocity)
local vx = Cos(Rad(inAngle-90))
local vy = Sin(Rad(inAngle-90))
if(inVelocity ~= nil)then
vx = vx * inVelocity
vy = vy * inVelocity
end
return vx,vy
end
require ( "physics" )
physics.start()
physics.setGravity( 1, 1 )
--( x, y )
--physics.setDrawMode ( "hybrid" )
math.randomseed(os.time())
local background = display.newImage("yazd.jpeg")
localGroup:insert(background)
--width of image divided by # of pics lined up from left to right (in the sprite) = the first #
--height of image divided by # of pics lined up from top to bottom (in the sprite) = the second #
local birdSheet = sprite.newSpriteSheet( "enemy.jpg", 59, 50 )
local birdSet = sprite.newSpriteSet(birdSheet, 1, 1)
-- images 1-14
sprite.add( birdSet, "bird", 1, 1, 200, 0 )
-- play 1-14, each image every 200 ms, 0 = loop count, which is infinite
local bird1 = sprite.newSprite( birdSet )
bird1.x = 40 -- starting point
bird1.y = 40 -- starting point
bird1.xScale = 0.5 --scale down x
bird1.yScale = 0.5 --scale down y
bird1:prepare("bird") --prepare sprite sequence
bird1:play() --play sprite
localGroup:insert(bird1)
--only local to this group
local killSheet = sprite.newSpriteSheet("explosion.png", 100, 100)
local killSet = sprite.newSpriteSet(killSheet, 1, 9)
sprite.add(killSet, "kill", 1, 9, 200, 1)
local birdCount = 1
local transDirection12
local function transDirection1()
bird1.xScale = 0.5
transition.to(bird1, {time=math.random(200,500), x = math.random(200,490), y = math.random(10,310), alpha = (math.random(9,100))/100, onComplete = transDirection12})
end
transDirection12 = function()
bird1.xScale = 0.5
transition.to(bird1, {time= math.random(200,500), x = math.random(200,490), y = math.random(10,310), alpha = (math.random(9,100))/100, onComplete = transDirection1})
end
transDirection1()
-- local transDirection1 declares what will be used (local function)
-- transDirection1 = function
-- following it are the function qualities
-- declares it will use object/image called bird1 and scales it to .5
-- time = ____ means it will take a certain time, between ____ and ____ to complete the transition
-- x=____ means that is where it will move to on the x axis
-- y=____ means that is where it will move to on the y axis
-- alpha = ___ means the is how transparent it will be
-- onComplete = ________ means that when the action is complete, it will call another function
-- The next function has the same qualities as transDirection1, but the onComplete part calls transDirection1 and they continue to loop
-- transDirection1() declares transDirection1 so the app knows about it and can use it
-- the other trans do not need to be declared because they are part of transDirection1, which is already declared
--(x, y, size.x, size.y)
local player = display.newImage( "mk11.png" )
player.x = 240
player.y = 260
player.xScale = .5
player.yScale = .5
localGroup:insert( player )
-- add physics to all the objects wanted: (object wanted, "static" or "dynamic")
physics.addBody(player, "static", {radius=30, isSensor = true})
physics.addBody(bird1, "static", {radius=23})
local function shoot(inPointX, inPointY)
-- (start at the x of the player + 10, also start at the y of the player, the radius of the circle is 5)
local bullet = display.newImage( "bullet2.png" )
bullet.x = player.x
bullet.y = player.y
-- add physics to the object, which is the bullet.
-- Make the bullet "dynamic" or moving
physics.addBody(bullet, "dynamic")
bullet.isFixedRotation = true
localGroup:insert( bullet )
local velocity = 300
local vx, vy = VectorFromAngle(player.rotation, velocity)
bullet.rotation = player.rotation
bullet:setLinearVelocity(vx, vy)
end
function RotateToTouchPoint(inPointX, inPointY)
local ang = getAngleDeg(player.x, player.y, inPointX, inPointY)
player.rotation = ang
end
local function ScreenTouchListener(event)
local phase = event.phase
if(phase == "began")then
if(touchPoint.isFocus == false)then
touchPoint.alpha = 1
touchPoint.x = event.x
touchPoint.y = event.y
display.getCurrentStage():setFocus(touchPoint, event.id)
touchPoint.isFocus = true
RotateToTouchPoint(event.x, event.y)
shoot(event.x, event.y)
end
elseif(touchPoint.isFocus)then
if(phase == "moved")then
touchPoint.x = event.x
touchPoint.y = event.y
RotateToTouchPoint(event.x, event.y)
elseif(phase == "ended" or phase == "cancelled")then
display.getCurrentStage():setFocus(touchPoint, nil)
touchPoint.isFocus = false
touchPoint.alpha = 0
end
end
return true
end
local function gotShot (event)
event.target:removeSelf()
event.other:removeSelf()
local explosion = sprite.newSprite(killSet)
explosion.x, explosion.y = event.target.x, event.target.y
explosion:prepare("kill")
explosion:play()
localGroup:insert( explosion )
birdCount = birdCount - 1
-- when there are no more birds, remove the runtime event listener and perform the
-- function with a delay of 500 m.s. The function changes the scene to test.lua
if "ended" then
if birdCount == 0 then
Runtime:removeEventListener("touch", ScreenTouchListener)
timer.performWithDelay(500, function()
director:changeScene("mainPage") end, 1)
end
end
end
bird1:addEventListener("collision", gotShot)
Runtime:addEventListener("touch", ScreenTouchListener)
---------
-- MUST return a display.newGroup()
return localGroup
end
Any help is appreciated!
The error message is perfectly clear -- the variable sprite used at this line:
local bird1 = sprite.birdSheet( birdSet )
has a nil value, meaning it has not been initialized or was set to nil. You need to show the earlier code where you should have set it up.
(After OP updates)
I think this line
require "sprite"
should actually be
sprite = require "sprite"
You can read more in modules tutorial here:
http://lua-users.org/wiki/ModulesTutorial

corona sdk pinch to zoom

i am using the function contained in corona sample codes for the pinch to zoom. But since i am developing an app, i need to make every single place of interest zoommable. Do i have to copy and paste the horribly long function for every single image or can i redirect every image on just one function? I am new to lua, here is the pinch to zoom function included in the sample code:
function background:touch( event )
local result = true
local phase = event.phase
local previousTouches = self.previousTouches
local numTotalTouches = 1
if ( previousTouches ) then
-- add in total from previousTouches, subtract one if event is already in the array
numTotalTouches = numTotalTouches + self.numPreviousTouches
if previousTouches[event.id] then
numTotalTouches = numTotalTouches - 1
end
end
if "began" == phase then
-- Very first "began" event
if ( not self.isFocus ) then
-- Subsequent touch events will target button even if they are outside the contentBounds of button
display.getCurrentStage():setFocus( self )
self.isFocus = true
previousTouches = {}
self.previousTouches = previousTouches
self.numPreviousTouches = 0
elseif ( not self.distance ) then
local dx,dy
if previousTouches and ( numTotalTouches ) >= 2 then
dx,dy = calculateDelta( previousTouches, event )
end
-- initialize to distance between two touches
if ( dx and dy ) then
local d = math.sqrt( dx*dx + dy*dy )
if ( d > 0 ) then
self.distance = d
self.xScaleOriginal = self.xScale
self.yScaleOriginal = self.yScale
print( "distance = " .. self.distance )
end
end
end
if not previousTouches[event.id] then
self.numPreviousTouches = self.numPreviousTouches + 1
end
previousTouches[event.id] = event
elseif self.isFocus then
if "moved" == phase then
if ( self.distance ) then
local dx,dy
if previousTouches and ( numTotalTouches ) >= 2 then
dx,dy = calculateDelta( previousTouches, event )
end
if ( dx and dy ) then
local newDistance = math.sqrt( dx*dx + dy*dy )
local scale = newDistance / self.distance
print( "newDistance(" ..newDistance .. ") / distance(" .. self.distance .. ") = scale(".. scale ..")" )
if ( scale > 0 ) then
self.xScale = self.xScaleOriginal * scale
self.yScale = self.yScaleOriginal * scale
end
end
end
if not previousTouches[event.id] then
self.numPreviousTouches = self.numPreviousTouches + 1
end
previousTouches[event.id] = event
elseif "ended" == phase or "cancelled" == phase then
if previousTouches[event.id] then
self.numPreviousTouches = self.numPreviousTouches - 1
previousTouches[event.id] = nil
end
if ( #previousTouches > 0 ) then
-- must be at least 2 touches remaining to pinch/zoom
self.distance = nil
else
-- previousTouches is empty so no more fingers are touching the screen
-- Allow touch events to be sent normally to the objects they "hit"
display.getCurrentStage():setFocus( nil )
self.isFocus = false
self.distance = nil
self.xScaleOriginal = nil
self.yScaleOriginal = nil
-- reset array
self.previousTouches = nil
self.numPreviousTouches = nil
end
end
end
return result
end
You could probably use a closure to accomplish this, assuming all of your points of interest are alike.
Example:
local function createPOI(properties)
local pointOfInterest = createPointOfInterest() -- code to create your object
--set properties based on argument
function pointOfInterest:touch(e)
--do stuff
end
pointOfInterest:addEventListner("touch")
return pointOfInterest
end
Corona has some code here that might be useful. Check the 'Multiple Touches' section.

Corona SDK: Remove display object from table

I have been working on a tower defense game. So basically I have a loop that makes a table of display objects that are tanks and adds physics bodies to them. Then it "gives the tanks" to a function called moveTank to actually move them.
However to conserve memory I want to remove the physics body and display obejct itself once it is offscreen. The code for that stars at line 234. In the simulator, if you test just removing the physics body everything works fine. However, if I remove the display object (maybe I'm doing it wrong?) it removes the image, but some other functions that use it give an error saying that I'm trying to compare a number with a nil value# line 229. The nil value is the object is removed and I set its property isMoving and isAlive to false so why is it even trying to perform the operation??
Can anyone help me finish this part of trying to remove the display object with no errors?
local storyboard = require( "storyboard" )
local scene = storyboard.newScene()
local physics = require "physics"
physics.start()
physics.setGravity( 0, 0)
physics.setDrawMode("hybrid")
local screenW, screenH, halfW, halfH = display.contentWidth, display.contentHeight, display.contentWidth*0.5, display.contentHeight*0.5
local tanks = {}
local tickCnt = 0
local TOTAL_TANKS = 2
local tankCnt = 1
function AddCommas( number, maxPos )
local s = tostring( number )
local len = string.len( s )
if len > maxPos then
-- Add comma to the string
local s2 = string.sub( s, -maxPos )
local s1 = string.sub( s, 1, len - maxPos )
s = (s1 .. "," .. s2)
end
maxPos = maxPos - 3 -- next comma position
if maxPos > 0 then
return AddCommas( s, maxPos )
else
return s
end
end
function tickCntFunct()
if tickCnt <= 2000 then
tickCnt = tickCnt + 25
print("tick count", tickCnt)
else
tickCnt = 0
end
end
timer.performWithDelay(10, tickCntFunct, 0)
function moveTank(aTank)
local mSpeed = 150
rSpeed = 6
if aTank.isAlive == true and aTank.isKilled == false then
print("aTank.x = ", aTank.x)
print("aTank.y = ", aTank.y)
print("tank rotation = ", aTank.rotation)
local disP1 = math.sqrt((math.abs(aTank.x - pointer1.x))^2 + (math.abs(aTank.y - pointer1.y))^2)
local disP2 = math.sqrt((math.abs(aTank.x - pointer2.x))^2 + (math.abs(aTank.y - pointer2.y))^2)
local disP3 = math.sqrt((math.abs(aTank.x - pointer3.x))^2 + (math.abs(aTank.y - pointer3.y))^2)
local disP4 = math.sqrt((math.abs(aTank.x - pointer4.x))^2 + (math.abs(aTank.y - pointer4.y))^2)
removeTanknumber = 0
if aTank.x < 0 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
if aTank.rotation < 180 and disP1 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation + rSpeed
end
if aTank.rotation >= 180 and disP1 < 1 then
aTank.rotation = 180
aTank:setLinearVelocity( 0, mSpeed)
end
if aTank.rotation > 90 and disP2 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation - rSpeed
end
if aTank.rotation <= 90 and disP2 < 1 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
if aTank.rotation > 0 and disP3 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation - rSpeed
end
if aTank.rotation <= 0 and disP3 < 1 then
aTank.rotation = 0
aTank:setLinearVelocity( 0, -1*mSpeed)
end
if aTank.rotation < 90 and disP4 < 1 then
aTank:setLinearVelocity( 0, 0)
aTank.rotation = aTank.rotation + rSpeed
end
if aTank.rotation >= 90 and disP4 < 1 then
aTank.rotation = 90
aTank:setLinearVelocity( mSpeed, 0)
end
end
end
timer.performWithDelay(1, moveTank, 0)
function scene:createScene( event )
local group = self.view
pointersGroup = display.newGroup()
mapGroup = display.newGroup()
-- create a grey rectangle as the backdrop
map = display.newImage( "map1.png" )
map:setReferencePoint(display.TopLeftReferencePoint)
map.x = 0
map.y = 0
spawner = display.newImage("pointer.png")
spawner:setReferencePoint(display.CenterReferencePoint)
spawner.y = 210
spawner.x = -40
pointer1 = display.newImage("pointer.png")
pointer1:setReferencePoint(display.CenterReferencePoint)
pointer1.x = 210
pointer1.y = 210
pointer2 = display.newImage("pointer.png")
pointer2:setReferencePoint(display.CenterReferencePoint)
pointer2.x = 210
pointer2.y = 390
pointer3 = display.newImage("pointer.png")
pointer3:setReferencePoint(display.CenterReferencePoint)
pointer3.x = 510
pointer3.y = 390
pointer4 = display.newImage("pointer.png")
pointer4:setReferencePoint(display.CenterReferencePoint)
pointer4.x = 510
pointer4.y = 90
sideBlock = display.newImage("side_block.png")
physics.addBody( sideBlock, "static", { friction=0.5, bounce=0.3 } )
sideBlock:setReferencePoint(display.CenterReferencePoint)
sideBlock.x = screenW - 100
sideBlock.y = screenH/2
-- all display objects must be inserted into group
pointersGroup:insert( spawner )
pointersGroup:insert( pointer1 )
pointersGroup:insert( pointer2 )
pointersGroup:insert( pointer3 )
pointersGroup:insert( pointer4 )
pointersGroup:insert( sideBlock )
mapGroup:insert( map )
group:insert( pointersGroup )
group:insert( mapGroup )
end
function scene:enterScene( event )
local group = self.view
for i = 1, TOTAL_TANKS do
-- create 5 tanks, place them off screen, set their status to isAlive and not isMoving
table.insert(tanks, display.newImage("tank.png"))
print("memory (all spawned): ", AddCommas( system.getInfo("textureMemoryUsed"), 9 ) .. " bytes" )
tanks[i]:setReferencePoint(display.CenterReferencePoint)
tanks[i]:scale(0.75, 0.75)
tanks[i].x = spawner.x
tanks[i].y = spawner.y
tanks[i].isAlive = true
tanks[i].isMoving = false
tanks[i].isKilled = false
end
local function gameLoop(event)
-- normally should not have loops inside the game loop, because
-- if there is too much going on during each frame call it can cause issues
-- but for moving only 5 tanks, this is how you can do it.
-- each tank will call the same function above (moveTank)
-- have a variable that you would check here, to see if 'so many ticks or seconds'
-- has passed, and if so, set the isMoving to true for the next tank .. will just use
-- a simple incremented variable 'tickCnt'
if tickCnt > 2000 and tankCnt <= TOTAL_TANKS then
physics.addBody( tanks[tankCnt], { density=3.0, friction=0.5, bounce=0.3 } )
tanks[tankCnt].isMoving = true
print("tankCnt= ", tankCnt)
tankCnt = tankCnt + 1
print("memory (moving): ", AddCommas( system.getInfo("textureMemoryUsed"), 9 ) .. " bytes" )
end
for i=1, TOTAL_TANKS do
if tanks[i].isMoving == true and tanks[i].isAlive == true and tanks[i].isKilled == false then
moveTank(tanks[i])
end
end
for i = 1, TOTAL_TANKS do
if tanks[i].x >= 40 and tanks[i].isKilled == false then
tanks[i].isKilled = true
tanks[i].isMoving = false
tanks[i].isAlive = false
physics.removeBody( tanks[i] )
display.remove(tanks[i])
tanks[i] = nil
end
end
end
Runtime:addEventListener("enterFrame", gameLoop)
physics.start()
end
function scene:exitScene( event )
local group = self.view
physics.stop()
end
function scene:destroyScene( event )
local group = self.view
package.loaded[physics] = nil
physics = nil
end
--------------------------------------------------------------------------------------- --
-- END OF YOUR IMPLEMENTATION
-----------------------------------------------------------------------------------------
-- "createScene" event is dispatched if scene's view does not exist
scene:addEventListener( "createScene", scene )
-- "enterScene" event is dispatched whenever scene transition has finished
scene:addEventListener( "enterScene", scene )
-- "exitScene" event is dispatched whenever before next scene's transition begins
scene:addEventListener( "exitScene", scene )
-- "destroyScene" event is dispatched before view is unloaded, which can be
-- automatically unloaded in low memory situations, or explicitly via a call to
-- storyboard.purgeScene() or storyboard.removeScene().
scene:addEventListener( "destroyScene", scene )
-----------------------------------------------------------------------------------------
return scene
Try to loop from last tank to first.

Resources