My game requires certain objects to wrap around the screen (x,y).
My app borrows some code from the “Space Shooter” template since my game has several scenes, so the mechanics are there, I just had to make my own scenes (instructions, about, splash scene, game scene, plus I borrowed the high score one too).
As currently written, the Space Shooter template creates the player’s ship in the scene:create function, and some event listeners as well. Laser shots are created later in the game logic (when you fire). They could’ve created the ship later, say, after a timer execution, but that’s not the path they chose, presumably for the sake of simplicity.
I added buttons, they work fine - rotate left, rotate right, thrust, fire laser. I then added screen wrap, and that’s where I encounter issues.
The code for the ship wrapping the screen works just fine. Ergo, I thought the same code would work for the laser. It does not. For the sake of simplicity (again), I copy-pasted the function, renamed it, and reassigned the objects (newLaser instead of ship).
Am I right to believe that since the ship was created in the scene:create, the code runs fine, but not so for the laser, created afterwards? I thought a workaround would be to create an offscreen laser to see if the logic would apply, but no dice.
Here’s some of the relevant code:
Wrap ship:
local function wrapShip( event ) if event.phase == "began" then if event.other.type == "bumper" then local function wrap() if ship.x \> display.contentWidth then ship.x = 0 end if ship.x \<0 then ship.x = display.contentWidth end if ship.y \> display.contentHeight then ship.y = 0 end if ship.y \<0 then ship.y = display.contentHeight end end timer.performWithDelay ( 1, wrap ) end end end
Wrap laser:
local function wrapLaser( event ) if event.phase == "began" then if event.other.type == "bumper" then local function wrap2() if newLaser.x \> display.contentWidth then newLaser.x = 0 end if newLaser.x \<0 then newLaser.x = display.contentWidth end if newLaser.y \> display.contentHeight then newLaser.y = 0 end if newLaser.y \<0 then newLaser.y = display.contentHeight end end timer.performWithDelay ( 1, wrap2 ) end end end
There’s a number of things that could be going wrong, depending on how the code we can’t see is structured.
You have one instance of ‘ship’, that is created in scene:create and (hopefully) will be defined as ‘local ship’ at the top of the lua file.
However, at the point you add the event listener to newLaser, unlike ship, it doesn’t exist. I’d expect this line to throw an error?
From what you’ve said, a new instance of ‘newLaser’ is created each time the player fires, but I would guess this ‘newLaser’ is local to the function that spawns it. If you added the listener at this point, I think the function would get called, but would break when it tries to access ‘newLaser’, which won’t be in scope. It might work if you changed those references to event.target.
you potentially have two problems: an out-of-scope problem and/or an in-scope mis-referencing problem. (the difference in your case is pretty esoteric since they’re related)
when inside (ie while running) wrapLaser() does “newLaser” refer to the SAME object that you expect it to? (you can try to debug ti with some print statements - the wacky number following the word “table” will be its memory address, and will be unique per simultaneously-existing instances)
(we must assume that newLaser at least refers to SOMETHING else you’d be throwing a “attempt to reference nil” -type error)
given that you have multiple lasers, and the implied module-level scope of wrapLaser, your wrap code might work on ONE of them, maybe the most recently created one?, but there’s no way it works on ALL of them - the “newLaser” reference can only point to one thing at a time.
as per nick, we don’t have enough info on what newLaser is or how created, or the scope of wrapLaser() at the time of newLaser’s creation, etc…
Part of solid programming is being semantically correct. An easy way to explain this, is in HTML you can style any tag to look how you want it, but it doesn’t make sense to make a bullet list out of <p> paragraph tags when you should use unordered list <ul> and list item <li> tags. Semantics matters here as well. The reason we have scene:create() and two phases of scene:show() is that’s when you need to do things.
Is it needed to create the scene? If yes, then do it in scene:create(). You don’t need to create your laser blasts to create the scene. You don’t even need them to show the scene either. You want to create them as part of game play/human input.
In the case of the ship, you certainly need to start with a ship and it should be on screen when the scene shows, so it makes sense to create it in scene:create(). However you will need to create the ship after your ship is destroyed, which is why the function restoreShip() is not part of any scene. It’s scoped to be available scene-wide so it can be called from game play.
fireLaser() falls in this category too. It’s outside of any scene function so that it can be called when needed by the ship’s “tap” event listener.
Scope can be a little challenging to master. The simple way to think about it is if you want that function, object or variable available everywhere in the scene, declare it and code it in the scene’s main chunk outside of any other function or logic block.
Thanks for the reply. Here’s the relevant code (some items like gameLoop and restoreShip are not included because they are not being used (the are REMmed - Remember REM from BASIC? :) ) and also to not to make this post too cumbersome. Please excuse if anything is not properly tabbed, as I had a bit of trouble this morning with the tabbing:
local composer = require( "composer" ) local scene = composer.newScene() local physics = require( "physics" ) physics.start() physics.setGravity( 0, 0 ) local widget = require("widget") local btnLeft, btnRight, btnThrust, btnFire -- Create buttons with widget local btnLeft = widget.newButton( { width = 160, height = 160, defaultFile = "BtnLeft.png", onEvent = touchLeft } ) btnLeft.x = 080 btnLeft.y = 560 btnLeft.alpha = 0.25 local btnRight= widget.newButton( { width = 160, height = 160, defaultFile = "BtnRight.png", onEvent = touchRight } ) btnRight.x=244 btnRight.y=560 btnRight.alpha = 0.25 local btnThrust= widget.newButton( { width = 160, height = 160, defaultFile = "BtnThrust.png", onEvent = handlebtnThrustEvent } ) btnThrust.x=892 btnThrust.y=560 btnThrust.alpha = 0.25 local btnFire= widget.newButton( { width = 160, height = 160, defaultFile = "BtnFire.png", onEvent = handlebtnFireEvent } ) btnFire.x=1056 btnFire.y=560 btnFire.alpha = 0.25 -- Initialize variables local lives = 3 local score = 0 local died = false local asteroidsTable = {} local ship local laser local newLaser local gameLoopTimer local livesText local scoreText local backGroup local mainGroup local uiGroup local explosionSound local fireSound local musicTrack local touchedLeft = false local touchedRight = false local touchedThrust = false -- ----------------------------------------------------------------------------------- -- In-game functions -- ----------------------------------------------------------------------------------- local function updateText() livesText.text = "Lives: " .. lives 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=40, bounce=0 } ) -- 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 local function fireLaser() audio.play( fireSound ) btnFire.alpha = 0.5 local newLaser = display.newImageRect( mainGroup, "laser.png", 12,32 ) physics.addBody( newLaser, { density = 0.0, friction = 0.0, bounce = 0, radius=30, isSensor=true } ) newLaser.isBullet = true newLaser.myName = "laser" local h = ship.height local posX = h \* math.sin( math.rad( ship.rotation )) local posY = h \* math.cos( math.rad( ship.rotation )) newLaser.x = ship.x + posX newLaser.y = ship.y - posY newLaser.rotation = ship.rotation local function getLinearVelocity(rotation, velocity) local angle = math.rad(rotation) return { xVelocity = math.sin(angle) \* velocity, yVelocity = math.cos(angle) \* -velocity } end local newLaserLinearVelocity = getLinearVelocity(newLaser.rotation, 900) newLaser:setLinearVelocity(newLaserLinearVelocity.xVelocity, newLaserLinearVelocity.yVelocity) newLaser:toBack() end local function touchLeft (event) if event.phase == "began" then touchedLeft = true btnLeft.alpha = 0.50 elseif event.phase == "ended" then touchedLeft = false btnLeft.alpha = 0.25 end end local function touchRight (event) if event.phase == "began" then touchedRight = true btnRight.alpha = 0.5 elseif event.phase == "ended" then touchedRight = false btnRight.alpha = 0.25 end end local function rotateLeft() if touchedLeft == true then ship.rotation = ship.rotation - 6 print( ship.rotation ) if ship.rotation \< -360 then ship.rotation = 360 end elseif touchedLeft == false then return false end end local function rotateRight() if touchedRight == true then ship.rotation = ship.rotation + 6 print(ship.rotation) if ship.rotation \> 360 then ship.rotation = 0 end elseif touchedRight == false then return false end end local function touchThrust (event) if event.phase == "began" then touchedThrust = true btnThrust.alpha = 0.5 elseif event.phase == "ended" then touchedThrust = false btnThrust.alpha = 0.25 end end local function angle2VecDeg (angle) angle = angle\*math.pi/180 return math.cos(angle), math.sin(angle) end local function thrustFwd() if touchedThrust == true then -- If thrust key is pressed local vecX, vecY = angle2VecDeg( ship.rotation-90 ) ship:applyForce ( vecX, vecY, ship.x, ship.y ) end end local function wrapShip( event ) if event.phase == "began" then if event.other.type == "bumper" then local function wrap() if ship.x \> display.contentWidth then ship.x = 0 end if ship.x \<0 then ship.x = display.contentWidth end if ship.y \> display.contentHeight then ship.y = 0 end if ship.y \<0 then ship.y = display.contentHeight end end timer.performWithDelay ( 1, wrap ) end end end local function wrapLaser( event ) if event.phase == "began" then if event.other.type == "bumper" then local function wrap2() if newLaser.x \> display.contentWidth then newLaser.x = 0 end if newLaser.x \<0 then newLaser.x = display.contentWidth end if newLaser.y \> display.contentHeight then newLaser.y = 0 end if newLaser.y \<0 then newLaser.y = display.contentHeight end end timer.performWithDelay ( 1, wrap2 ) end end end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create scene function scene:create( event ) local sceneGroup = self.view physics.pause() backGroup = display.newGroup() sceneGroup:insert( backGroup ) mainGroup = display.newGroup() sceneGroup:insert( mainGroup ) uiGroup = display.newGroup() sceneGroup:insert( uiGroup ) local background = display.newImageRect( backGroup, "nebula.png", 1136,640 ) background.x = display.contentCenterX background.y = display.contentCenterY ship = display.newImageRect( mainGroup, "PlayerShip.png",40,60 ) ship.x = display.contentCenterX ship.y = display.contentHeight - 100 physics.addBody( ship, { density = 0.0, friction = 0.0, bounce = 0, radius=30, isSensor=true } ) ship.linearDamping = 0.5 ship.alpha = 0.8 ship.myName = "ship" newLaser = display.newImageRect( mainGroup, "laser.png",40,60 ) physics.addBody( newLaser, { density = 0.0, friction = 0.0, bounce = 0, radius=30, isSensor=true } ) newLaser.x = display.contentCenterX newLaser.y = display.contentHeight - 100 newLaser.Name = "laser" livesText = display.newText( uiGroup, "Lives: " .. lives, 920, 60, "Hyperspace.ttf", 48 ) livesText:setFillColor( 0,1,1 ) scoreText = display.newText( uiGroup, "Score: " .. score, 140, 60, "Hyperspace.ttf", 48 ) scoreText:setFillColor( 0,1,1 ) btnLeft:addEventListener("touch", touchLeft) btnRight:addEventListener("touch", touchRight) btnThrust:addEventListener("touch", touchThrust) btnFire:addEventListener( "tap", fireLaser ) Runtime:addEventListener("enterFrame", rotateLeft) Runtime:addEventListener("enterFrame", rotateRight) Runtime:addEventListener("enterFrame", thrustFwd) ship:addEventListener( "collision", wrapShip ) newLaser:addEventListener( "collision", wrapLaser ) explosionSound = audio.loadSound( "audio/explosion.wav" ) fireSound = audio.loadSound( "audio/fire.wav" ) musicTrack = audio.loadStream( "audio/80s-Space-Game\_Looping.wav") end -- ----------------------------------------------------------------------------------- -- Scene event show/hide/destroy functions -- ----------------------------------------------------------------------------------- function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then elseif ( phase == "did" ) then physics.start() audio.play( musicTrack, { channel=1, loops=-1 } ) end end function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then timer.cancel( gameLoopTimer ) btnLeft:removeSelf() btnRight:removeSelf() btnThrust:removeSelf() btnFire:removeSelf() elseif ( phase == "did" ) then Runtime:removeEventListener( "collision", onCollision ) physics.pause() audio.stop( 1 ) composer.removeScene( "game" ) end end function scene:destroy( event ) local sceneGroup = self.view audio.dispose( explosionSound ) audio.dispose( fireSound ) audio.dispose( musicTrack ) end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene
You may notice I created a newLaser in scene.create(); that was to avoid getting the Nil Value error on line “newLaser:addEventListener( “collision”, wrapLaser )” .
OK, so taking back a step, reading the posts above and searching elsewhere, the main problem at hand is that an event listener exists for an object that does not yet exist.
The questions are thus:
Has the event listener been placed in a wrong position? It seems so. It’s either that, or that the object simply doesn’t exist yet.
Can the same event listener be used for several objects of the same type ( a bunch of bullets, or a bunch of asteroids, or a bunch of ships, etc.)? I don’t know yet.
If #2 is “no”, do I make an event listener every time I create an object? I don’t know yet.
No, you can’t add an event listener to an object that doesn’t exist yet. You generally add them at the point you create them.
Yes, you can share an event listener between multiple objects. They don’t even need to be the same type of object, as long as you have a way of differentiating between them. This code appears to have a property called “type” given to each object for that purpose.
Rather than identify the laser that has been hit by its direct reference (newLaser), you access it by event.target and check whether it is of type “laser”.
and once you’ve done this as per @nick_sherman, you might realize that you probably don’t need separate wrapShip and wrapLaser functions, just one “wrapGenericThingy” function with a reference to that thingy.
Dang, this is exactly what the Star Explorer demo does; I just didn’t realize it. The collision function uses an if statement, thus if no asteroid is there, no prob, right? You REM line “gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )” to validate the game does not crash, and surely enough, it doesn’t.
I guess it was a long week at work. Let’s get to rewriting the code then.
The solution to fire multiple lasers and each follow their own path wrapping the screen was to not treat the lasers as a collision with a bumper, but rather place the x,y logic within the gameLoop:
for i = #laserTable, 1, -1 do local thisLaser = laserTable[i] if (thisLaser.x \> display.contentWidth + (fudgeNum \* .5)) then thisLaser.x = -(fudgeNum \* .5) else if (thisLaser.x \< -(fudgeNum \* .5)) then thisLaser.x = display.contentWidth + (fudgeNum \* .5) else if (thisLaser.y \< -(fudgeNum \* .5)) then thisLaser.y = display.contentHeight + (fudgeNum \* .5) else if (thisLaser.y \> display.contentHeight + (fudgeNum \* .5)) then thisLaser.y = -(fudgeNum \* .5) end end end end end
Please pardon the tabs if the code is out of tab above. You will notice that the lasers are tracked via a table. That way they all go about their own direction. All that’s left is to destroy the stray laser after about a second or so if it does not collide with an asteroid or an enemy craft.
Thanks much to all of you who nudged me in the right rotation (direction!).
There’s a number of things that could be going wrong, depending on how the code we can’t see is structured.
You have one instance of ‘ship’, that is created in scene:create and (hopefully) will be defined as ‘local ship’ at the top of the lua file.
However, at the point you add the event listener to newLaser, unlike ship, it doesn’t exist. I’d expect this line to throw an error?
From what you’ve said, a new instance of ‘newLaser’ is created each time the player fires, but I would guess this ‘newLaser’ is local to the function that spawns it. If you added the listener at this point, I think the function would get called, but would break when it tries to access ‘newLaser’, which won’t be in scope. It might work if you changed those references to event.target.
you potentially have two problems: an out-of-scope problem and/or an in-scope mis-referencing problem. (the difference in your case is pretty esoteric since they’re related)
when inside (ie while running) wrapLaser() does “newLaser” refer to the SAME object that you expect it to? (you can try to debug ti with some print statements - the wacky number following the word “table” will be its memory address, and will be unique per simultaneously-existing instances)
(we must assume that newLaser at least refers to SOMETHING else you’d be throwing a “attempt to reference nil” -type error)
given that you have multiple lasers, and the implied module-level scope of wrapLaser, your wrap code might work on ONE of them, maybe the most recently created one?, but there’s no way it works on ALL of them - the “newLaser” reference can only point to one thing at a time.
as per nick, we don’t have enough info on what newLaser is or how created, or the scope of wrapLaser() at the time of newLaser’s creation, etc…
Part of solid programming is being semantically correct. An easy way to explain this, is in HTML you can style any tag to look how you want it, but it doesn’t make sense to make a bullet list out of <p> paragraph tags when you should use unordered list <ul> and list item <li> tags. Semantics matters here as well. The reason we have scene:create() and two phases of scene:show() is that’s when you need to do things.
Is it needed to create the scene? If yes, then do it in scene:create(). You don’t need to create your laser blasts to create the scene. You don’t even need them to show the scene either. You want to create them as part of game play/human input.
In the case of the ship, you certainly need to start with a ship and it should be on screen when the scene shows, so it makes sense to create it in scene:create(). However you will need to create the ship after your ship is destroyed, which is why the function restoreShip() is not part of any scene. It’s scoped to be available scene-wide so it can be called from game play.
fireLaser() falls in this category too. It’s outside of any scene function so that it can be called when needed by the ship’s “tap” event listener.
Scope can be a little challenging to master. The simple way to think about it is if you want that function, object or variable available everywhere in the scene, declare it and code it in the scene’s main chunk outside of any other function or logic block.
Thanks for the reply. Here’s the relevant code (some items like gameLoop and restoreShip are not included because they are not being used (the are REMmed - Remember REM from BASIC? :) ) and also to not to make this post too cumbersome. Please excuse if anything is not properly tabbed, as I had a bit of trouble this morning with the tabbing:
local composer = require( "composer" ) local scene = composer.newScene() local physics = require( "physics" ) physics.start() physics.setGravity( 0, 0 ) local widget = require("widget") local btnLeft, btnRight, btnThrust, btnFire -- Create buttons with widget local btnLeft = widget.newButton( { width = 160, height = 160, defaultFile = "BtnLeft.png", onEvent = touchLeft } ) btnLeft.x = 080 btnLeft.y = 560 btnLeft.alpha = 0.25 local btnRight= widget.newButton( { width = 160, height = 160, defaultFile = "BtnRight.png", onEvent = touchRight } ) btnRight.x=244 btnRight.y=560 btnRight.alpha = 0.25 local btnThrust= widget.newButton( { width = 160, height = 160, defaultFile = "BtnThrust.png", onEvent = handlebtnThrustEvent } ) btnThrust.x=892 btnThrust.y=560 btnThrust.alpha = 0.25 local btnFire= widget.newButton( { width = 160, height = 160, defaultFile = "BtnFire.png", onEvent = handlebtnFireEvent } ) btnFire.x=1056 btnFire.y=560 btnFire.alpha = 0.25 -- Initialize variables local lives = 3 local score = 0 local died = false local asteroidsTable = {} local ship local laser local newLaser local gameLoopTimer local livesText local scoreText local backGroup local mainGroup local uiGroup local explosionSound local fireSound local musicTrack local touchedLeft = false local touchedRight = false local touchedThrust = false -- ----------------------------------------------------------------------------------- -- In-game functions -- ----------------------------------------------------------------------------------- local function updateText() livesText.text = "Lives: " .. lives 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=40, bounce=0 } ) -- 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 local function fireLaser() audio.play( fireSound ) btnFire.alpha = 0.5 local newLaser = display.newImageRect( mainGroup, "laser.png", 12,32 ) physics.addBody( newLaser, { density = 0.0, friction = 0.0, bounce = 0, radius=30, isSensor=true } ) newLaser.isBullet = true newLaser.myName = "laser" local h = ship.height local posX = h \* math.sin( math.rad( ship.rotation )) local posY = h \* math.cos( math.rad( ship.rotation )) newLaser.x = ship.x + posX newLaser.y = ship.y - posY newLaser.rotation = ship.rotation local function getLinearVelocity(rotation, velocity) local angle = math.rad(rotation) return { xVelocity = math.sin(angle) \* velocity, yVelocity = math.cos(angle) \* -velocity } end local newLaserLinearVelocity = getLinearVelocity(newLaser.rotation, 900) newLaser:setLinearVelocity(newLaserLinearVelocity.xVelocity, newLaserLinearVelocity.yVelocity) newLaser:toBack() end local function touchLeft (event) if event.phase == "began" then touchedLeft = true btnLeft.alpha = 0.50 elseif event.phase == "ended" then touchedLeft = false btnLeft.alpha = 0.25 end end local function touchRight (event) if event.phase == "began" then touchedRight = true btnRight.alpha = 0.5 elseif event.phase == "ended" then touchedRight = false btnRight.alpha = 0.25 end end local function rotateLeft() if touchedLeft == true then ship.rotation = ship.rotation - 6 print( ship.rotation ) if ship.rotation \< -360 then ship.rotation = 360 end elseif touchedLeft == false then return false end end local function rotateRight() if touchedRight == true then ship.rotation = ship.rotation + 6 print(ship.rotation) if ship.rotation \> 360 then ship.rotation = 0 end elseif touchedRight == false then return false end end local function touchThrust (event) if event.phase == "began" then touchedThrust = true btnThrust.alpha = 0.5 elseif event.phase == "ended" then touchedThrust = false btnThrust.alpha = 0.25 end end local function angle2VecDeg (angle) angle = angle\*math.pi/180 return math.cos(angle), math.sin(angle) end local function thrustFwd() if touchedThrust == true then -- If thrust key is pressed local vecX, vecY = angle2VecDeg( ship.rotation-90 ) ship:applyForce ( vecX, vecY, ship.x, ship.y ) end end local function wrapShip( event ) if event.phase == "began" then if event.other.type == "bumper" then local function wrap() if ship.x \> display.contentWidth then ship.x = 0 end if ship.x \<0 then ship.x = display.contentWidth end if ship.y \> display.contentHeight then ship.y = 0 end if ship.y \<0 then ship.y = display.contentHeight end end timer.performWithDelay ( 1, wrap ) end end end local function wrapLaser( event ) if event.phase == "began" then if event.other.type == "bumper" then local function wrap2() if newLaser.x \> display.contentWidth then newLaser.x = 0 end if newLaser.x \<0 then newLaser.x = display.contentWidth end if newLaser.y \> display.contentHeight then newLaser.y = 0 end if newLaser.y \<0 then newLaser.y = display.contentHeight end end timer.performWithDelay ( 1, wrap2 ) end end end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create scene function scene:create( event ) local sceneGroup = self.view physics.pause() backGroup = display.newGroup() sceneGroup:insert( backGroup ) mainGroup = display.newGroup() sceneGroup:insert( mainGroup ) uiGroup = display.newGroup() sceneGroup:insert( uiGroup ) local background = display.newImageRect( backGroup, "nebula.png", 1136,640 ) background.x = display.contentCenterX background.y = display.contentCenterY ship = display.newImageRect( mainGroup, "PlayerShip.png",40,60 ) ship.x = display.contentCenterX ship.y = display.contentHeight - 100 physics.addBody( ship, { density = 0.0, friction = 0.0, bounce = 0, radius=30, isSensor=true } ) ship.linearDamping = 0.5 ship.alpha = 0.8 ship.myName = "ship" newLaser = display.newImageRect( mainGroup, "laser.png",40,60 ) physics.addBody( newLaser, { density = 0.0, friction = 0.0, bounce = 0, radius=30, isSensor=true } ) newLaser.x = display.contentCenterX newLaser.y = display.contentHeight - 100 newLaser.Name = "laser" livesText = display.newText( uiGroup, "Lives: " .. lives, 920, 60, "Hyperspace.ttf", 48 ) livesText:setFillColor( 0,1,1 ) scoreText = display.newText( uiGroup, "Score: " .. score, 140, 60, "Hyperspace.ttf", 48 ) scoreText:setFillColor( 0,1,1 ) btnLeft:addEventListener("touch", touchLeft) btnRight:addEventListener("touch", touchRight) btnThrust:addEventListener("touch", touchThrust) btnFire:addEventListener( "tap", fireLaser ) Runtime:addEventListener("enterFrame", rotateLeft) Runtime:addEventListener("enterFrame", rotateRight) Runtime:addEventListener("enterFrame", thrustFwd) ship:addEventListener( "collision", wrapShip ) newLaser:addEventListener( "collision", wrapLaser ) explosionSound = audio.loadSound( "audio/explosion.wav" ) fireSound = audio.loadSound( "audio/fire.wav" ) musicTrack = audio.loadStream( "audio/80s-Space-Game\_Looping.wav") end -- ----------------------------------------------------------------------------------- -- Scene event show/hide/destroy functions -- ----------------------------------------------------------------------------------- function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then elseif ( phase == "did" ) then physics.start() audio.play( musicTrack, { channel=1, loops=-1 } ) end end function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then timer.cancel( gameLoopTimer ) btnLeft:removeSelf() btnRight:removeSelf() btnThrust:removeSelf() btnFire:removeSelf() elseif ( phase == "did" ) then Runtime:removeEventListener( "collision", onCollision ) physics.pause() audio.stop( 1 ) composer.removeScene( "game" ) end end function scene:destroy( event ) local sceneGroup = self.view audio.dispose( explosionSound ) audio.dispose( fireSound ) audio.dispose( musicTrack ) end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene
You may notice I created a newLaser in scene.create(); that was to avoid getting the Nil Value error on line “newLaser:addEventListener( “collision”, wrapLaser )” .
OK, so taking back a step, reading the posts above and searching elsewhere, the main problem at hand is that an event listener exists for an object that does not yet exist.
The questions are thus:
Has the event listener been placed in a wrong position? It seems so. It’s either that, or that the object simply doesn’t exist yet.
Can the same event listener be used for several objects of the same type ( a bunch of bullets, or a bunch of asteroids, or a bunch of ships, etc.)? I don’t know yet.
If #2 is “no”, do I make an event listener every time I create an object? I don’t know yet.
No, you can’t add an event listener to an object that doesn’t exist yet. You generally add them at the point you create them.
Yes, you can share an event listener between multiple objects. They don’t even need to be the same type of object, as long as you have a way of differentiating between them. This code appears to have a property called “type” given to each object for that purpose.
Rather than identify the laser that has been hit by its direct reference (newLaser), you access it by event.target and check whether it is of type “laser”.