This code creates a cannon and 3 balloons, the cannon should shoot out a bullet that'll destroy balloons, along with the words. DUring the process the cannon should rotate and when i release my finger from the screen it shoots. For some reason it doesn't respond, cannon not rotating nor any bullet is shot.
local score = 0
local scoreText
local scoreForLevelComplete
local background
local infoBar
local restartBtn
local cannon
local levelNum
local cannonCharge = {}
local shot = {}
local cannonBall
local impulse = 0
local balloons = {}
local cannonCharge = {}
local shot = {}
function scene:createScene(event)
local group = self.view
background = display.newImage( "bkg_clouds.png")
group:insert(background)
background.x = 230
background.y = 195
scoreText = display.newText( "0", 0, 0, native.systemFont, 32 )
scoreText:setFillColor( 0,0, 0 )
scoreText.x = 87
scoreText.y = 28
group:insert( scoreText )
questionText = display.newText('a', display.contentCenterX, display.contentWidth/4, native.systemFont, 40)
group:insert(questionText)
infoBar = display.newImage ("infoBar.png")
group:insert(infoBar)
infoBar.x = 10
infoBar.y = 25
restartBtn = display.newImage ("restartBtn.png")
group:insert(restartBtn)
restartBtn.x = 470
restartBtn.y = 300
cannon = display.newImage ("cannon.png")
group:insert(cannon)
cannon.x = 10
cannon.y = 270
cannon.anchorX = 0.5
cannon.anchorY = 0.5
restartBtn.isVisible = true
local balloon = display.newImage ('balloon_fat_red.png', 495, 125)
group:insert(balloon)
balloon = display.newImage ('balloon_fat_red.png', 495, 175)
group:insert(balloon)
balloon = display.newImage ('balloon_fat_red.png', 495, 225)
group:insert(balloon)
local balloonText1 = display.newText('\227\129\130', 495, 125)
balloonText1:setFillColor( 1,1, 0 )
local balloonText2 = display.newText('\227\129\132', 495, 170)
balloonText2:setFillColor( 1,1, 0 )
local balloonText3 = display.newText('\227\129\134', 495, 225)
balloonText3:setFillColor( 1,1, 0 )
balloon.name = 'balloon'
physics.addBody(balloon)
balloon.bodyType = 'static'
table.insert(balloons, balloon)
group:insert(balloonText1)
group:insert(balloonText2)
group:insert(balloonText3)
function ballCollision(e)
if (e.other.name == 'balloon') then
scene.updateScore()
e.target:removeSelf()
print ('remove balloon text')
e.other:removeSelf()
audio.play(pop)
end
end
function cannonCharge:touch(e)
if(e.phase == 'began') then
impulse = 0
cannon.isVisible = true
Runtime:addEventListener('enterFrame', charge)
end
end
function charge()
local degreesPerFrame = 0.5
cannon.rotation = cannon.rotation - degreesPerFrame
impulse=impulse-0.2
if(cannon.rotation < -46) then
cannon.rotation = -46
impulse = -3.2
end
end
function shot:touch(e)
if(e.phase == 'ended') then
Runtime:removeEventListener('enterFrame', charge)
cannon.isVisible = true
cannon.rotation = 0
cannonBall = display.newImage('cannon ball.png', 84, 220)
physics.addBody(cannonBall, {density = 1, friction = 0, bounce = 0})
group:insert(cannonBall)
-- Shoot cannon ball
cannonBall:applyLinearImpulse(3, impulse, cannonBall.x, cannonBall.y )
--Collision listener
cannonBall:addEventListener ('collision', ballCollision)
end
end
end
This is my enterscene function
function scene:enterScene( event )
local group = self.view
background:addEventListener('touch', cannonCharge)
background:addEventListener('touch', shot)
end
I know the Corona docs say that listener can be a table object when call addEventListener('event', listener) but I have never seen or used that. There is no advantage in posted code to have functions defined inside the createScene since they are global and you already have a bunch or module-local variables. Try pulling the listeners out and making them regular functions:
local canon
...
local cannonCharge = function(event)
if event.phase == 'began' then
impulse = 0
cannon.isVisible = true
Runtime:addEventListener('enterFrame', charge)
end
end
local shot = function(event)
...
end
local function charge()
...
end
... other local functions ...
function scene:createScene(event)
...
end
Also, confirm that your touch listeners are being called by printing something inside each one.
Finally, and most importantly, you only added the last balloon to the physics so the bullet can only collide with that one balloon. The same way that you had to add group:insert(balloon) after each balloon created, you should have physics.addBody(balloon, ...) after each group insert. So do this:
local balloon1 = display.newImage ('balloon_fat_red.png', 495, 125)
local balloon2 = display.newImage ('balloon_fat_red.png', 495, 175)
local balloon3 = display.newImage ('balloon_fat_red.png', 495, 225)
group:insert(balloon1)
group:insert(balloon2)
group:insert(balloon3)
physics.addBody(balloon1)
physics.addBody(balloon2)
physics.addBody(balloon3)
balloon1.bodyType = 'static'
balloon2.bodyType = 'static'
balloon3.bodyType = 'static'
table.insert(balloons, balloon1)
table.insert(balloons, balloon2)
table.insert(balloons, balloon3)
There is a lot of code duplication there, and adding more balloons requires many lines to change, so you might as well factor out the duplicate code into a function:
local function createBalloon(x, y)
local balloon = display.newImage ('balloon_fat_red.png', x, y)
group:insert(balloon)
physics.addBody(balloon)
balloon.bodyType = 'static'
table.insert(balloons, balloon)
end
createBalloon(495, 125)
createBalloon(495, 175)
createBalloon(495, 225)
which has the advantage that if you need more balloon you won't forget any settings, and any new settings put in createBallon so all balloons have same config (except for function parameters like x,y etc).
Update: Determine which balloon in collision handler
Depends why you need to know which of the balloons. For example if it's because balloon 1 gives 10 pts while 3 gives 30, then there are better ways: you can add your fields to objects, like you could have balloon1.points = 10, balloon2.points = 30 etc (you would make that a function argument of createBalloon) and in collision handler just use score = score + e.other.points. You should only need to use Local Collision Handling because only need to know when the cannon ball collides with balloons. To figure out if e.other is a balloon, easiest is to add a property when you create balloon:
local function createBalloon(x, y, balloonText)
local balloon = ...
...
balloon.isBalloon = true -- only balloon objects will have this
balloon.label = balloonText
end
Couple notes on the above: another custom property is label since you want to remove the balloon text in the collision handler, easiest is to have a property for it. But do not remove objects involved in collision in the collision handler, as explained in that document, use delayed removal. So your handler becomes
function ballCollision(e)
if e.other.isBalloon then
scene.updateScore(e.other.points)
timer.performWithDelay(1, e.target.removeSelf, e.target)
timer.performWithDelay(1, e.other.removeSelf, e.target)
e.other.label:removeSelf() -- this is ok: not involved in collision
audio.play(pop)
end
end
You have declared balloons as a array and using the balloon
as a variable while assigning images to them. So you need to declare 3 separate balloon object like balloon text or if you are using array then you need to declare like this.
for i=1,3 do
balloon[i] = display.newImage ('balloon_fat_red.png', 495, 225)
group:insert(balloon[i])
end
So it will identify which balloon you want to shoot.
Related
I have just started to learn LUA in school, but i cannot find many helpful tutorials o the internet to aid in my learning. I have made a simple game (which doesn't work yet, i realize that) and a main menu. However, when i try to start the app, it gives me this error:
/Users/jordanmcbride/Desktop/Lua Projects/Tapinator/main.lua:47: attempt to index global 'showCredits' (a nil value)
stack traceback:
/Users/jordanmcbride/Desktop/Lua Projects/Tapinator/main.lua:47: in main chunk
[Finished in 9.4s]
I have looked the error, and I cannot seem to understand how to fix it. The other questions have said something about returning the function returning a nil value, and that I should add a return statement to the end, but that doesn't work either.
Here is the code # line 47.
function showCredits.touch(e)
playButton.isVisible = false
creditsButton.isVisible = false
creditsView = display.newImage('credits.png', 0, display.contentHeight)
lastY = name.y
transition.to(name, {time = 300, y = display.contentHeight * 0.5 - title.height - 25})
transition.to(creditsView, {time = 300, y = display.contentHeight * 0.5 + creditsView.height, onComplete = function() creditsView:addEventListener('tap', hideCredits) end})
end
Here is my full code, in case the problem lies elsewhere:
display.setStatusBar(display.HiddenStatusBar)
radius = 40
smallTime = 200
bigTime = 800
score = 0
scoreInc = 2000
--HomePage
local name
local playButton
local creditsButton
local homePage
--Credits
local creditsPage
--Sounds
local circleSpawn = audio.loadSound( "circle_spawn.wav" )
local circleTap = audio.loadSound( "circle_tap.wav" )
function Main()
name = display.newImage('title.png', display.contentWidth / 2, 53)
name:scale( .5, .5 )
playButton = display.newImage('playButton.png', display.contentWidth / 2, 245)
playButton:scale( .5, .5 )
creditsButton = display.newImage('creditsButton.png', display.contentWidth / 2, 305)
creditsButton:scale( .5, .5 )
homePage = display.newGroup(name, playButton, creditsButton)
startButtonListeners('add')
end
function showCredits.touch(e)
playButton.isVisible = false
creditsButton.isVisible = false
creditsView = display.newImage('credits.png', 0, display.contentHeight)
lastY = name.y
transition.to(name, {time = 300, y = display.contentHeight * 0.5 - title.height - 25})
transition.to(creditsView, {time = 300, y = display.contentHeight * 0.5 + creditsView.height, onComplete = function() creditsView:addEventListener('tap', hideCredits) end})
end
function hideCredits.touch(e)
transition.to(creditsView, {time = 300, y = display.contentHeight, onComplete = function() creditsButton.isVisible = true playButton.isVisible = true creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
transition.to(name, {time = 300, y = lastY});
end
function startButtonListeners(action)
if(action == 'add') then
playButton:addEventListener('touch', playGame)
creditsButton:addEventListener('touch', showCredits)
else
playButton:removeEventListener('touch', playGame)
creditsButton:removeEventListener('touch', showCredits)
end
end
Main()
printScore = display.newText("Score: " .. tostring(score), display.contentWidth-80, 40, native.systemFontBold, 20)
-- A function that creates random circles
function generateCircle ()
-- Creates a new circle between 0 (the left most bounds) and the width of the display (being the content width), and also
-- 0 (the upper most bounds) and the height of the display (being the content height). The radius of the circle is 'radius'
x = math.random(radius, display.contentWidth-radius)
y = math.random(80, display.contentHeight)
score = score + scoreInc
myCircle = display.newCircle( x, y, radius )
myCircle:setFillColor( math.random(), math.random(), math.random() )
delayTime = math.random(smallTime, bigTime)
score = score + scoreInc
printScore.text = "Score:"..tostring(scores)
local spawnChannel = audio.play( circleSpawn )
timer.performWithDelay( delayTime, generateCircle )
end
generateCircle()
function myCircle:touch( event )
local tapChannel = audio.play( circleTap )
myCircle:removeSelf()
end
myCircle:addEventListener( "touch", myCircle )
The answer by greatwolf will work. But just a tip from my experience. One way I like to create functions is to try to define the name of the function first near the top of the lua file. Then I will define the function later on in the file. Something like this:
--function preallocation
local onPlayTap
local onSoundOnTap
local onSoundOffTap
local onCreditsTap
local onHelpTap
---------------------------------------------------------------------------------
-- Custom Function Definitions
---------------------------------------------------------------------------------
--Called when Sound On Button is tapped, turn off sound
onSoundOnTap = function(event)
end
--Called when Sound Off Button is tapped, turn on sound
onSoundOffTap = function(event)
end
--Called when Credits button is tapped, shows credits
onCreditsTap = function(event)
end
--Called when Help button is tapped, shows help
onHelpTap = function(event)
end
--Callback to Play button. Moves scene to Level Picker Scene
onPlayTap = function(event)
end
What this does is allow each function to be called by any other function in the file. If you do it the way you are doing it by adding the function name before the function like so:
local showCredits = {}
function showCredits.touch(e)
end
local hideCredits = {}
function hideCredits.touch(e)
end
your showCredit function will not be able to call the hideCredits function below it because the hideCredits variable has not been defined yet when the showCredit function was defined. Although this may not effect your current game, in future apps or games, you may need to call functions inside of other functions. To make this work properly, predefine all your function variables first, then define all your function afterwards. Hope this helps.
I created 3 balloons(only need 3) and each of them contains a specific word and one of the words is the right answer. I shoot a ball from the cannon and hit the balloons. Assume the balloon 3 holds the balloonText3 which is the right answer, if i hit it the ball and the text disappears. For some reason, the ballCollision(event) doesn't run, i used print statement to confirm it. Whats wrong here?
local cannonBalls = display.newGroup()
...
local shot = function(event)
if(event.phase == 'ended') then
Runtime:removeEventListener('enterFrame', charge)
cannon.isVisible = true
cannon.rotation = 0
cannonBall = display.newImage('cannon ball.png', 84, 220)
physics.addBody(cannonBall, {density = 1, friction = 0, bounce = 0})
cannonBalls:insert(cannonBall)
print ('shot')
-- Shoot cannon ball
cannonBall:applyLinearImpulse(3, impulse, cannonBall.x, cannonBall.y )
--Collision listener
print('event listener')
cannonBall:addEventListener ('collision', ballCollision)
end
end
function ballCollision(event)
if (event.other.name =='balloon3') then
scene.updateScore()
print('Ball is colliding')
timer.performWithDelay(1, e.target.removeSelf, e.target)
timer.performWithDelay(1, e.other.removeSelf, e.target)
balloonText3.isVisible = false
audio.play(pop)
end
end
function scene:createScene(event)
local group = self.view
...
local balloon1 = display.newImage ('balloon_fat_red.png', 495, 60)
local balloon2 = display.newImage ('balloon_fat_red.png', 495, 115)
local balloon3 = display.newImage ('balloon_fat_red.png', 495, 160)
group:insert(balloon1)
group:insert(balloon2)
group:insert(balloon3)
physics.addBody(balloon1)
physics.addBody(balloon2)
physics.addBody(balloon3)
balloon1.bodyType = 'static'
balloon2.bodyType = 'static'
balloon3.bodyType = 'static'
table.insert(balloons, balloon1)
table.insert(balloons, balloon2)
table.insert(balloons, balloon3)
local balloonText1 = display.newText('\227\129\130', 495, 60)
balloonText1:setFillColor( 1,1, 0 )
local balloonText2 = display.newText('\227\129\132', 495, 115)
balloonText2:setFillColor( 1,1, 0 )
local balloonText3 = display.newText('\227\129\134', 495, 160)
balloonText3:setFillColor( 1,1, 0 )
group:insert(balloonText1)
group:insert(balloonText2)
group:insert(balloonText3)
end
I'm going to assume that you put the print statement in the if block of ballCollision, because I can't see anything else wrong with the listener registration. Also, I don't see anywhere in the code where you set the name attribute of the balloon object. So either
add balloon3.name = "balloon3" (and similarly for 1 and 2) after setting bodyType,
or (probably simpler)
use if event.other == balloons[3] then ... in the collision handler. You don't need parentheses around the condition in Lua.
When i pressed the credits button from the menu it gave me an error "?:0: attempt to call method 'dispatchEvent' (a nil value) ?:in function 'gotoScene'...scene_menu.lua:35: in function ...scene_menu.lua:33>?:in function "...Whats the problem here?
This is my menu page
-- scene_menu.lua
local storyboard = require( "storyboard" )
local scene = storyboard.newScene()
-- Clear previous scene
storyboard.removeAll()
--forward references
local background
local title
local playGame
local tutorial
local credits
display.setStatusBar(display.HiddenStatusBar)
function playgameBtn( event )
transition.to(playGame, {time = 1000, alpha = 0, xScale = 2, yScale = .6})
storyboard.gotoScene("playgame", "fade", 1000)
end
function tutorialBtn (event)
transition.to(tutorial, {time = 1000, alpha = 0, xScale = 2, yScale = .6})
storyboard.gotoScene("tutorial", "fade", 1000)
end
function creditsBtn (event)
transition.to(credits, {time = 1000, alpha = 0, xScale = 2, yScale = .6})
storyboard.gotoScene("credits", "fade", 1000)
end
function scene:createScene(event)
local group = self.view
background = display.newImage( "bkg_clouds.png")
group:insert(background)
background.x = 240
background.y = 195
title = display.newText("Shoot the Balloons!", 250, 100, native.systemFont, 25 )
title:setFillColor( 1,1, 0 )
playgame = display.newImage("Play Button.png")
group:insert(playgame)
playgame.x = 250
playgame.y = 150
tutorial = display.newImage("Tutorial Button.png")
group:insert(tutorial)
tutorial.x = 250
tutorial.y = 200
credits = display.newImage("Credits Button.png")
group:insert(credits)
credits.x = 250
credits.y = 250
end
function scene:enterScene( event )
playgame:addEventListener("tap", playgameBtn)
tutorial:addEventListener("tap", tutorialBtn)
credits:addEventListener("tap", creditsBtn)
end
function scene:exitScene(event)
playgame:removeEventListener("tap", playgameBtn)
tutorial:removeEventListener("tap", tutorialBtn)
credits:removeEventListener("tap", creditsBtn)
end
function scene:destroyScene(event)
end
scene:addEventListener("createScene", scene)
scene:addEventListener("enterScene", scene)
scene:addEventListener("exitScene", scene)
scene:addEventListener("destroyScene", scene)
return scene
This is my main.lua
-- main.lua
display.setStatusBar( display.HiddenStatusBar )
local storyboard = require "storyboard"
storyboard.gotoScene( "scene_splash" )
change the name credits.lua to the other name or change the variable name credits global variable,i think its due to the conflict of name in this line:storyboard.gotoScene("credits", "fade", 1000)
I ran this code and it gave me an error attempt to index global 'self' (a nil value), in this scene i'm creating the Question1 of the game, inside which includes creating cannon, balloons and other game elements. I checked but i'm not sure whats wrong here.
function scene.createScene()
local group = self.view ---line 27 where i got the error
scoreText = display.newText( "0", 0, 0, globals.font.bold, 32 )
scoreText.x = display.contentCenterX
scoreText.y = 32
group:insert( scoreText )
background = display.newImage( "bkg_clouds.png")
group:insert(background)
background.x = 240
background.y = 195
questionText = display.newText('a', display.contentCenterX, display.contentWidth/4, native.systemFont, 40)
group:insert(questionText)
infoBar = display.newImage ("infoBar.png")
group:insert(infoBar)
background.x = 200
background.y = 100
restartBtn = display.newImage ("restartBtn.png")
group:insert(restartBtn)
background.x = 470
background.y = 300
cannon = display.newImage ("cannon.png")
group:insert(cannon)
background.x = 10
background.y = 270
cannon.anchorX = 0.5
cannon.anchorY = 0.5
restartBtn.isVisible = true
function createBalloons(a, b)
for i = 1, a do
for j = 1, b do
local balloon = display.newImage ('balloon_fat_red.png', 465+ (i * 30), 80 + (j * 50))
balloon.balloonText1 = display.newText(hiragana_array[x+1], 495, 125)
balloon.balloonText2 = display.newText(hiragana_array[x+2], 495, 175)
balloon.balloonText3 = display.newText(hiragana_array[x+3], 495, 225)
balloon.balloonText1:setFillColor( 1,1, 0 )
balloon.balloonText2:setFillColor( 1,1, 0 )
balloon.balloonText3:setFillColor( 1,1, 0 )
balloon.name = 'balloon'
physics.addBody(balloon)
balloon.bodyType = 'static'
table.insert(balloons, balloon)
end
end
target.text = #balloons
end
function cannonCharge:touch(e)
if(e.phase == 'began') then
impulse = 0
cannon.isVisible = true
Runtime:addEventListener('enterFrame', charge)
end
end
function charge()
local degreesPerFrame = 1
cannon.rotation = cannon.rotation - degreesPerFrame
impulse=impulse-0.2
if(cannon.rotation < -46) then
cannon.rotation = -46
impulse = -3.2
end
end
function shot:touch(e)
if(e.phase == 'ended') then
Runtime:removeEventListener('enterFrame', charge)
cannon.isVisible = false
cannon.rotation = 0
local cannonBall = display.newImage('cannon ball.png', 84, 220)
physics.addBody(cannonBall, {density = 1, friction = 0, bounce = 0})
group:insert(cannonBall)
-- Shoot cannon ball
cannonBall:applyLinearImpulse(dir, impulse, cannonBall.x, cannonBall.y )
--Collision listener
cannonBall:addEventListener ('collision', ballCollision)
end
end
function ballCollision(e)
if (e.other.name == 'balloon') then
scene.updateScore()
e.target:removeSelf()
print ('remove balloon text')
e.other:removeSelf()
audio.play(pop)
end
end
cannonBall:applyLinearImpulse(dir, impulse, cannonBall.x, cannonBall.y )
--Collision listener
cannonBall:addEventListener ('collision', ballCollision)
scene.view:insert( ballCollision )
end
You probably need function scene:createScene(). Note the colon instead of the dot in your original code.
Your function should be like this.
function scene:createScene( event )
local group = self.view
-----------------------------------------------------------------------------
-- CREATE display objects and add them to 'group' here.
-- Example use-case: Restore 'group' from previously saved state.
-----------------------------------------------------------------------------
end
Here is the reference:
http://docs.coronalabs.com/api/library/storyboard/
I have a little problem and I'm searching for an easy solution,
in my game, if a bullet touches a specific ennemy, this target should be deleted and respawn otherwhere,
I'm using the self-collision event to make the instructions single to each ennemy,
the problem is that self-collision function only works if he recognise the target, but as I delete it the first time in my collision function, that ennemy doesn't exist anymore for my function. It works only the first time.
I hope that my problem is understandable,
here is an example of the code:
local ennemy
ennemy = display.newRect(0,0, 20, 50)
transition.to( ennemy, {time = 2000, x = 240, y = 160} )
local function onCollision(self,event)
display.remove( bullet )
display.remove( ennemy )
ennemy = display.newRect(0,0, 20, 50)
transition.to( ennemy, {time = 2000, x = 240, y = 160} )
end
ennemy.collision = onCollision
ennemy:addEventListener( "collision", ennemy )
You only do addEventListener() for the first enemy object, after a collision you have a brand new enemy object which also needs to have it's collision listener setup.
local onCollision -- forward declare onCollision() so we can use it from addEnemy()
local enemy
local function addEnemy()
enemy = display.newRect(0,0, 20, 50)
-- new object so need to assign collision listener again
enemy.collision = onCollision
enemy:addEventListener( "collision", enemy )
transition.to( enemy, {time = 2000, x = 240, y = 160} )
end
local function onCollision(self,event)
display.remove( bullet )
display.remove( enemy )
addEnemy()
end
-- Add first enemy…
addEnemy()