Corona Active Battle Scene in Composer - lua

I am currently working on a card battle game. In the main battle scene I am trying to show the cards actively battling one another with their health bars decreasing and eventually weapons moving around the scene as well.
At present, when it enters the battle loop, the display freezes, but I have been logging what is happening and the battle is still happening, just behind the scene. I have separated the battle loop into its own function at the top of the code and call that function with a tap event.
I verify that it is running by using the print statements within the while loop which prints out the current health of the card and the name of the card to the console. The current health of the cards are changing, it's just not changing scenes, but instead freezing on the old one, without actively displaying what is happening.
Here is the code for the entire scene:
function battleScene(playerCards, enemyCards, allCards, cardHealth)
while not checkIfDead(playerCards) and not checkIfDead(enemyCards) do
for i=1, 6 do
if allCards[i]~=0 then
allCards[i]:battle()
end
print( allCards[i]:getCurHealth().." "..allCards[i]:getName() )--The test to see current health of card
cardHealth[i]:setHealth(allCards[i]:getCurHealth(),allCards[i]:getHealth())
if checkIfDead(playerCards) or checkIfDead(enemyCards) then
break
end
usleep(2000)
end
end
end
---------------------------------------------------------------------------------
-- "scene:show()"
function scene:show( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
-- Called when the scene is still off screen (but is about to come on screen).
elseif ( phase == "did" ) then
--The current health of each card is set to max
--and then the card is rendered along with health bars
local card1=test1:render()
card1.x=display.contentCenterX-100
card1.y=display.contentCenterY-100
sceneGroup:insert(card1)
local card1Health=HealthBar:new()
card1Health.x=display.contentCenterX-100
card1Health.y=display.contentCenterY-40
card1Health:setHealth(test1:getCurHealth(), test1:getHealth())
sceneGroup:insert(card1Health)
playerCards={test4, test5, test6}
enemyCards={test1, test2, test3}
for i=1, 3 do
if playerCards[i]:getClass()=="Tank" or playerCards[i]:getClass()=="Damage" then
playerCards[i]:setBattleSet(enemyCards)
else
playerCards[i]:setBattleSet(playerCards)
end
end
for i=1, 3 do
if enemyCards[i]:getClass()=="Tank" or enemyCards[i]:getClass()=="Damage" then
enemyCards[i]:setBattleSet(playerCards)
else
enemyCards[i]:setBattleSet(enemyCards)
end
end
local allCards={test1, test2, test3, test4, test5, test6}
bubbleSort(allCards)
local cardHealth= {card1Health,card2Health,card3Health,card4Health,card5Health,card6Health}
local startBattleButton=display.newText( "Start Battle", 0, 0, globals.font.regular, 18 )
startBattleButton.x = display.contentCenterX
startBattleButton.y = display.contentCenterY
local function onTap(event)
startBattleButton.isVisible=false
battleScene(playerCards, enemyCards, allCards, cardHealth)
end
startBattleButton:addEventListener( "tap", onTap )
sceneGroup:insert(startBattleButton)
if checkIfDead(playerCards) then
win=false
end
end
end

The problem is that your battle scene function is looping and modifying the scene, however the scene engine updates the scene only between event handling calls. I.e., if your tap function gets called and you modify it, you will see the changes only after the tap function returns and the scene engine has processed the new scene state.
So instead of doing this:
function battleScene(args)
while condition do
do stuff
end
end
do instead
function battleScene(args)
if condition then
do stuff
timer.performWithDelay(2, function() battleScene(args) end)
end
end
This executes "do stuff" when the condition is true, and schedules a call to battleScene for later. The battleScene will return immediately after that, given the display engine the chance to update GUI, and 2 ms later, will call battleScene again, until eventually the condition is false and no re-call will be scheduled. Note that I had to create a temp anonymous function because battleScene takes arguments whereas performWithDelay does not pass any arguments to the scheduled function, but they can be given implicitly as upvalues via anonymous function. To be clear, if battleScene had taken no args, you could have just done this:
function battleScene()
if condition then
do stuff
timer.performWithDelay(2, battleScene)
end
end

Related

Is there a way to add time to a Wait class inside an If statement?

I started learning LUA a few days ago, started my own project inside Tabletop Simulator game, but I've hit a brick wall. I can't add time to a Wait class.
This is an example of what I tried:
function state_check()
--This function checks if the state of obj_1 and obj_2 is 0 or 1
end
function press_button()
--This function activates other functions based on the state of obj_1 and obj_2
i = 1 --Function starts with a wait of 1 second
if obj_1_state == 1 then --If state is 1, then the function is triggered and 1 second is added to i
Wait.time(func_1, i)
i = i + 1
end
if obj_2_state == 1 then
Wait.time(func_2, i)
end
end
I need for the function to check the first part and if true, do the second part 1 second later. If not, do the second part normally and skip the "i = i + 1".
My problem is that the function does everything at the same time. I know I'm doing something wrong, but I can't figure out what. Is there a way to create some for of gate to do everything in order or anything similar?
Your code seems correct.
I don't know what is the problem.
But I know that one of possible solutions is to follow the "callback hell" style of programming:
local function second_part(obj_2_state)
if obj_2_state == 1 then
Wait.time(func_2, 1)
end
end
local function first_part(obj_1_state, obj_2_state)
if obj_1_state == 1 then
Wait.time(
function()
func_1()
second_part(obj_2_state)
end, 1)
else
second_part(obj_2_state)
end
end
function press_button()
local obj_1_state, obj_2_state = --calculate states of obj_1 and obj_2 here
first_part(obj_1_state, obj_2_state)
end

How do I update a variable as the game runs?

You see, I'm used to Pygame and the like and there was always the concept of a "main loop". Corona seems to work like a GUI, handling events more implicitly. The thing is, I have a score value:
local score = 0
And when I do something like:
while running do
score = score + 1
end
Corona just crashes. I guess this is not the way to do this (The score gets updated as the game runs). How do I update the variable all the time? (Without touching or tapping or whatever).
The "game loop" in Corona is created by adding a listener to the Runtime for enterFrame events. This listener is called once per frame and can be used to update anything you like.
You could read this guide to interactivity and event detection for more information.
I agree with #GoojajiGreg. Try
local function loop( event )
print( "enterFrame called at time: " .. event.time )
end
Runtime:addEventListener( "enterFrame", loop)

Remote event problems

I'm making a game using ROBLOX about the tank battle system of Dragon Quest Heroes Rocket Slime (although not using 100% GUI's)
Since I didn't know how to make ammo become locked onto a path in normal ROBLOX, I decided to use a gui to show the ammo "firing"
To do this, I have a remote event that fires a function inside the main script of the GUI system when ammo is loaded
cannon.Touched:connect(function(v)
if fireable[v.Name] and v.Parent == workspace then
event:FireAllClients("Left",v.Name)
v:Destroy()
end
end)
Then, the GUI gets the correct sprite for the ammo loaded and fires it out of the right tank (the first argument in the FireAllClients part)
This is one of the two if statements for firing ammo (the other one is literally the same except that it's for the right side tank)
local tank = tankFiring == "Left" and tank1 or tankFiring == "Right" and tank2
if tank == tank1 then
print("yo!")
script.Fire:Play()
local ammoFrame = sp.Ammo:Clone()
ammoFrame.Parent = tank
ammoFrame.Visible = true
ammoFrame.Position = UDim2.new(0,120,0,68)
playAnimation("Cannon Fire",UDim2.new(0,120,0,68-25),tank.Frame)
ammoFrame.Image = ammoTypes[type]["img"]
ammoFrame.Size = ammoTypes[type]["Size"]
repeat
wait(.1)
ammoFrame.Rotation = ammoTypes[type]["Rotatable"] == true and ammoFrame.Rotation + 15 or 0
ammoFrame.Position = ammoFrame.Position + UDim2.new(0,1,0,0)
until
tank2:FindFirstChild("Ammo") and isTouching(ammoFrame,tank2:GetChildren()[3]) or isTouching(ammoFrame,tank2) or ammoFrame == nil
if tank2:FindFirstChild("Ammo") and isTouching(ammoFrame,tank2:GetChildren()[3]) then
script.Collision:Play()
local lastAmmoPos = ammoFrame.Position
playAnimation("Explosion",lastAmmoPos-UDim2.new(0,15,0,25),tank.Frame)
ammoFrame:Destroy()
tank2:GetChildren()[3]:Destroy()
end
if isTouching(ammoFrame,tank2) then
script.Collision:Play()
ammoFrame:Destroy()
workspace["Tank2"].Health.Value = workspace["Tank2"].Health.Value - ammoTypes[type]["dmg"]
end
end
The problem with this, is that if a player joins AFTER the ammo has been shot, they will not see the ammo on the GUI
Is there anyway to fix this? I can't just FireAllClients again, since that'll just fire another piece of ammo for all the players.
Have a service that keeps track of what ammo is in what state. Then all clients first asks the servers of current ammo states when joining, and then subscribes to further changes.

LUA scripting switching on/off a script

I'm writing a script in LUA/logitech scripting API. The script should perform the following: mouse key 4 switch on/off the scriptmouse key 5 switch from one feature to other ( force move and autoattack )
The code is the follow:
forceMove = false
on = false
function OnEvent(event, arg)
--OutputLogMessage("event = %s, arg = %s\n", event, arg);
if IsMouseButtonPressed(5) then
forceMove = not forceMove
while(on) do
if(forceMove) then
ForceMove()
else
StartAttack()
end
end
ReleaseMouseButton(5)
end
if IsMouseButtonPressed(4) then
on = not on
ReleaseMouseButton(4)
end
end
function StartAttack()
PressAndReleaseMouseButton(1)
Sleep(1000)
end
function ForceMove()
MoveMouseWheel(1)
Sleep(20)
MoveMouseWheel(-1)
end
but once in game,if i activate the script with mouse button 4, i get stuck in "force move" mode, and "auto attack" mode never works. Can't figure why.
When you press mouse button 5, you activate the 'force move' mode. If the 'on' mode is simultaneously enabled, you result in a infinite loop:
while(on) do
if(forceMove) then
ForceMove()
else
StartAttack()
end
end -- loops regardless of mouse buttons
You will stay here forever, regardless of the mouse buttons you press.
You need to move to executing code out of the mouse event handler. The handler should only update values like forceMove, another function is needed to carry out the action. In these function, you only do ONE step, not many.
Then you check again for pressed mouse buttons, carry out the actions and so on.
Code example:
function update()
if IsMouseButtonPressed(4) then
on = not on
end
if IsMouseButtonPressed(5) then
forceMove = not forceMove
end
end
function actions()
if on then
if forceMove then
ForceMove()
end
end
end
How to put it together:
You have to use some kind of loop, but ideally the game engine should do this for you. It would look something like this:
local is_running = true
while is_running do
update()
actions()
end
Now, if you press a button, you save the current state in some global variables which are accessed both by update and actions. The functions get called every cycle (which can be the calculation of one frame). Assuming you don't press any further buttons, update() does nothing, so forceMove and on remain the same.
This way, you have a continuous movement without a loop in action().

Lua Corona SDK Conflicting Event Listeners

[SOLVED] Thanks for looking, but I figured it out. I needed to un-nest the return true statements in some of my if conditions.
I have just started learning Lua this week, and I have started to program a 2D side-scroller game using Corona SDK. In this game, the player's character is moved by pressing buttons displayed on the screen much like a virtual game pad. These buttons work perfectly, however, the problem is that I have a
Runtime:addEventListener("tap", onScreenTap)
event listener that then calls the shoot() function to fire a projectile from the player whenever a tap is registered. This results in a projectile firing every time I lift the touch from one of the movement buttons.
Is there any way I can stop the shoot function from calling when I finish touching one of the movement keys? I have tried
display.getCurrentStage:setFocus()
and also putting
return true
at the end of the movement functions but nothing seems to work.
You can use this basics in every touch function you have.. Or just this for all touch events. Combining touch events in a single function may solve your problem.
function inBounds( event )
local bounds = event.target.contentBounds
if event.x > bounds.xMin and event.x < bounds.xMax and event.y > bounds.yMin and event.y < bounds.yMax then
return true
end
return false
end
function touchHandler( event )
if event.phase == "began" then
-- Do stuff here --
display.getCurrentStage():setFocus(event.target)
event.target.isFocus=true
elseif event.target.isFocus == true then
if event.phase == "moved" then
if inBounds( event ) then
-- Do stuff here --
else
-- Do stuff here --
display.getCurrentStage():setFocus(nil)
event.target.isFocus=false
end
elseif event.phase == "ended" then
-- Do stuff here --
display.getCurrentStage():setFocus(nil)
event.target.isFocus=false
end
end
return true
end
By the way, if you try to use this in Runtime, it will throw and error. You can add an event listener to background or just put some control mechanisnms like
if event.target then
-- blah blah
end

Resources