Corona / Lua Function scope - lua

I'm new to Corona. I'm not sure how to solve this.
In main I am creating 2 local objects, player and enemy.
Player has a function called takeDamage.
When I try to call player.takeDamage from within enemy, it can't see the function.
I presume it's because main owns both objects and they don't know about each other.
How can I have Enemy call that function so that it can deal damage to Player?
main.lua contains:
-- Create player character
local player = require("player");
player = player.new();
-- Create enemy character
local enemy = require("enemy");
enemy = enemy.new();
I think I could make player global, but from what I know, that would not be a best practice.
Any help would be greatly appreciated.

If it is safe to assume that there will be only one "player" instance, you can make it global. Otherwise, you would have to do something along these lines:
-- main.lua
local player = require 'player'.new()
local enemy = require 'enemy'.new()
enemy:setPlayer(player)
-- enemy.lua
...
function enemy:setPlayer(player)
self.player = player
end
And later use self.player in the enemy code to reference the player. Note that this will consume memory, because the player reference will be copied to every enemy instance.
One side note, I do not consider it good to call a module and an instance the same, i.e. player. It will become unclear later if you mean the module, or the instance. A safe way would be to call the modules as Player and Enemy, and the code will look like this:
Player = require 'Player'
local player = Player.new()

Ideally, you don't want the player referencing the enemy directly or the enemy referencing the player. Instead, you could poll each participant in a game loop and update the game state based on the result.
local player = make_player()
local enemy = make_enemy()
--game loop - in a real game this isn't a busy while
while true do
local move = player.move()
local result = validate_move(move)
if result.is_valid then
update_position(player, result)
end
move = enemy.move()
result = validate_move(move)
if result.is_valid then
update_position(enemy, result)
end
local attack = player.attack()
local attack_result = validate_attack(attack)
if attack_result.hit then
update_health(enemy, attack_result)
end
-- more game logic here
render(player)
render(enemy)
end
In a real game, state change would be driven by events - touch events, drawing events, etc. My busy loop (while true do) illustrates the general idea.
You'll also have to use your imagination for missing implementation.
There are many ways to model a game. I'm not suggesting this is the best. It does, however, show one way of decoupling interacting elements.

Related

Why Aren't The Sounds in my Sound Group not working?

I was trying to make a music player for my game, however, when I was trying to get my sound to play it refused to work. The games output works before and after the sound, but I can't hear anything. I tried using both a folder and sound group (what I'm using currently) and both did not work. How would I fix this? I presume it has something to do with client-server but I am not sure.
local ss = game:WaitForChild("SoundService")
local rp = game:WaitForChild("ReplicatedStorage")
local list = ss.Music:GetChildren()
rp.SongOn.OnServerEvent:Connect(function(plr)
repeat
local num = math.random(1, #list)
print(num)
local track = list[num]
local name = track.Name
print(name)
plr.PlayerGui.Overhead.Notch.SongTitle.Text = track.Name
local song = ss.Music:WaitForChild(name)
print("played")
wait(track.TimeLength)
print("waited length")
until
rp.SongOff.OnServerEvent
end)
You never play anything. You don't actually reproduce the Sound. To run a Sound, use Sound:Play()
https://create.roblox.com/docs/reference/engine/classes/Sound
Your repeat until condition is also wrong. The music will not stop playing once that event is fired, and it will actually not matter at all - when the loop finishes running once, it will compare if literally rp.SongOff.OnServerEvent evaluates to true. OnServerEvent is the literal event itself, which will be true since it is not false or nil. So the loop will stop running when it runs once.
Instead, you likely want to make a function that plays the music, and run this function whenever:
Playing music is requested with the remote
The playing music naturally ends (https://create.roblox.com/docs/reference/engine/classes/Sound#Ended)
And then, bind to that stop music remote a function that stops the sound.
Also you should have some more shame, you didn't even try to hide you're making a nazi game

Why only does idle play from my animation script?

So I'm making a custom animation script for a custom character on Roblox. The AI works fine but it will almost always only play idle animations even with humanoid events.
What should normally happen is when the monster is Idle the idle animation should play. When walking the walk animation should play. And when the AI attacks, the attack animation should play.
I've dried commenting out the idle animation part, but then no animations play at all.
Here's the code:
local myHuman = script.Parent.Humanoid
local walkAnim = myHuman:LoadAnimation(script.Parent.Walk)
local idleAnim = myHuman:LoadAnimation(script.Parent.Idle)
local jumpAnim = myHuman:LoadAnimation(script.Parent.Jump)
myHuman.Running:Connect(function(speed)
if speed > 3 then
walkAnim:Play()
else
walkAnim:Stop()
idleAnim:Play()
end
end)
myHuman.Jumping:Connect(function()
jumpAnim:Play()
end)
myHuman.Died:Connect(function()
for i,v in pairs(myHuman:GetPlayingAnimationTracks()) do
v:Stop()
end
end)
Try adding a print instead of the animation and see what happens and tell me if it prints anything.
myHuman.Running:Connect(function(speed)
if speed > 3 then
print("walk")
else
print("start idle")
end
end)
Edit: https://devforum.roblox.com/t/getplayinganimationtracks-is-deprecated/1075650 I believe that you must do
myHuman.Animator:getPlayingAnimationTracks()

Temporarily deactivate physical collision between two Nodes(Swift 2, IOS)

I'm using SpriteKit, and I'm having trouble getting one of my objects to shoot out another object, without colliding with it at the moment that it shoots it. However, as soon as they are no longer within range of contact each-other, I want them to be able to contact with each-other for the rest of the game.
Here is what I have tried-
var allCategory: UInt32 = 1;
var nillCategory: UInt32 = 2;
var bufferNode: SKNode?
bufferNode = self.childNodeWithName("player")
bufferNode!.physicsBody!.collisionBitMask = nillCategory;
bufferNode!.physicsBody!.categoryBitMask = nillCategory;
shootNewPlayer(touchLocation)
runAction(SKAction.sequence([SKAction.waitForDuration(1),SKAction.runBlock(removeBuffer)]))
}
func removeBuffer(){
bufferNode!.physicsBody!.collisionBitMask = allCategory;
bufferNode!.physicsBody!.categoryBitMask = allCategory;
}
By default, all my objects in the scene have a collisionBitMask and categoryBitMask of "allCategory." My solution was to temporarily change its categories to nill. This had absolutely no affect. I also want to avoid this solution since for 1 second the player would no longer interact with objects, which could cause bugs (With the code above it still interacts).
Any ideas on how I can get the object to shoot a new player without it flying off in a bazaar direction? Thanks!
Turn off object2 collision by default. (Do this with collisionBitMask = 0)
Enable object contactBitMask with the flag for object1.
On the didEndContact Method, enable the collision object2 for object1 when the condition of object1 contacting object2 is met.
This will allow you to avoid things like timers and checking the update loop constantly.
In English, you are saying: When object 2 no longer is touching object 1, object 2 is now able to collide with object 1.
Depending on circumstance, you may want to remove the contact test after you enable collision

Lua userdata being garbage collected before it should be

I'm new to Lua. I'm trying to create a game using Cocos2d-x v3.1rc0.
I'm running into an issue where it appears that one of the objects, created by the Cocos2d-x lib, is being garbage collected before it should be.
Here is a class to keep track of "prey" on the screen. Each prey references frames/animations that will be displayed depending on the state of the prey.
Prey = {
sprite = false -- Main prey sprite.
-- Frames used for movement, getting hit, knocked out, etc.
, frame = {
idle = false -- idle frames
, move = false -- move frames
, rest = false -- resting frames
}
, state = false -- The current state of the Prey.
}
Here is the constructor:
function Prey:new()
local o = {}
setmetatable(o, self)
self.__index = self
return o
end
Here is where the frames are associated to the prey:
function Prey:setFrames(frames)
--[[ Moving ]]--
self.frame.move = cc.Animation:createWithSpriteFrames({frames[1], frames[2], frames[3]}, 1.0)
cclog("move frames: " .. #self.frame.move:getFrames())
--[[ Resting ]]--
self.frame.rest = cc.Animation:createWithSpriteFrames({frames[4], frames[5]}, 2.0)
cclog("rest frames: " .. #self.frame.rest:getFrames())
end
The above will print the following:
cocos2d: [LUA-print] move frames: 3
cocos2d: [LUA-print] rest frames: 2
However, when I attempt to call the following method, the frame.move and frame.rest variables appear to be garbage collected because an error is raised when I attempt to access them. Please note that this method is called every tick:
function Prey:tick()
cclog("state: " .. self.state)
local animation = false
-- Moving
if (self.state == PreyState.MOVING)
then
cclog("moving: " .. #self.frame.move:getFrames())
animation = self.frame.move
elseif (self.state == PreyState.RESTING)
then
cclog("resting: " .. #self.frame.rest:getFrames())
-- Resting
animation = self.frame.rest
end
end
When the cclog calls are being made for either of the two conditions the following error is displayed. Please note that I know that this specific instance of Prey has not been garbage collected because self.state was set to idle before I made the call to the tick method. It also retains self.state on subsequent calls to this method.
cocos2d: [LUA-print] state: 2
cocos2d: [LUA-print] ----------------------------------------
cocos2d: [LUA-print] LUA ERROR: [string "Prey.lua"]:189: invalid 'cobj' in function 'lua_cocos2dx_Animation_getFrames'
After looking at many articles describing how objects are retained, it appears that it's possible that my Animation object is being garbage collected. But I have no idea why! The reference to the Cocos2d-x object should be strong, right?
Here are some articles I've read regarding the subject:
http://www.tutorialspoint.com/lua/lua_object_oriented.htm
http://lua-users.org/wiki/WeakTablesTutorial
http://www.lua.org/pil/17.html
http://phrogz.net/lua/LearningLua_ValuesAndMetatables.html
http://lua-users.org/wiki/GarbageCollectionTutorial
UPDATE 1
The following code is what causes the issue:
-- Create the instance of our prey object here...
prey = new Prey:new()
local function tick()
prey:tick()
end
scheduleID = cc.Director:getInstance():getScheduler():scheduleScriptFunc(tick, 0, false)
However, when I attempt to simply call prey:tick() outside of the scheduler I get NO errors. I need this code to be ran every tick... what am I missing? In this particular scenario I have made the prey a global variable so that the tick method could access the only instance of Prey. I did this to simplify this particular test. However, I'd like to make prey local and make the scheduler run the tick function for every instance of Prey.
The objects are being garbage collected. I have ran several more tests and came to the conclusion that you can not associate any Cocos2d objects to a variable without them later being garbage collected on the next cycle. Instead, you have to use Cocos2d-x's libs to cache your textures and then query for those textures at the time you need them.
It's difficult to illustrate, but what the end result was to call cc.Director:getInstance():getTextureCache():getTextureForKey("texture.png") at the time I needed the texture and re-create the frames needed for each animation when the state changed for the object. I'm going to look for a way to cache sprites so I don't have to re-create them. However, this resolved my issue. So the lesson is that Cococ2d objects get garbage collected on the next cycle. The only objects that are retained are those that are cached internally by Cocos2d. I could be wrong about this, but this is what I have observed.
So my process is to first add the image to the texture cache at load time:
local texture = cc.Director:getInstance():getTextureCache():addImage("texture.png")
And then calling the following later on:
local texture = cc.Director:getInstance():getTextureCache():getTextureForKey("texture.png")
I would really appreciate any insight into this. How are you guys handling this? Is this the right process? Are there any articles related to caching sprites, textures, etc for later use? Thank you!
UPDATE 1
Yes. The objects are being released from within the Cocos2d-x library. You can get around this by caching the sprite frames. This is the code I used, in Lua, to cache the sprite frames. After they are cached, any reference you hold to the SpriteFrame, from within your Lua code, will continue to point to an active instance of the frame.
cc.SpriteFrameCache:getInstance():addSpriteFrame(frame, name)
And to get the SpriteFrame back out of the cache:
local frame = cc.SpriteFrameCache:getInstance():getSpriteFrame(name)

event.other is nil in onCollision after resuming the scene with storyboard library [CORONA sdk]

I'm having a strange error developing with Corona SDK relating to collision-detection and the storyboard lib.
Here is my onCollision listener:
local function onBottomBorderCollision( event )
if (event.other.isBall) then
local index = table.indexOf( ballArray, other )
table.remove( ballArray, index )
event.other:removeSelf( )
event.other = nil
updateLife(false)
updateScore(false)
end
end
This works fine at first launch, but after getting back to the menu screen (using storyboard.goToScene("menu")) and replaying the game, now this listener will trigger the following error every time one of my ball hits the bottom border:
attempt to index field "other"(a nil value)
I do create the proper listeners in scene:onEnterScene(scene) so it's not something to do with them, moreover this other listener never generate the error:
local function onPlayerCollision( event )
if(event.other.isBall) then
xVel, yVel = event.other:getLinearVelocity( )
event.other:setLinearVelocity( event.other.XLinearVelocity, yVel )
end
end
I am stuck right now... please help!
Actually, this type of error is caused mainly due to to some active function call/timers/transitions, which is not yet cancelled before changing the scene.
attempt to index field "other"(a nil value)
The above error mentions that any object/ it's property is being called/fetched, but it is not in the current occurrence or the scene. So, check whether you are cancelling your timers, transitions and runtime event listeners.
In your case, it may be due to the failure in cancellation of collision detection function onBottomBorderCollision in runtime. If you are calling it in enterFrame, then you have to cancel it before scene change. YOu can do it as below:
Runtime:removeEventListener("enterFrame",onBottomBorderCollision)
Update: You can't stop the physics engine while collision check is running. So do as follows:
function actualSceneChange()
physics.stop() -- stop physics here
-- call scene chage
director:changeScene("menuPageName") -- call your scene change method
end
function initiatingSceneChange()
physics.pause() -- pause physics here
-- stop unwanted event listeners
Runtime:removeEventListener("enterFrame",onBottomBorderCollision)
if(timer_1)then timer.cancel(timer_1) end -- stop all your timers like this.
if(trans_1)then transition.cancel(trans) end -- stop all your transitions like this.
timer.performWithDelay(1000,actualSceneChange,1)
end
I believe the issue is because storyboard saves some variables in memory when you switch scenes. The display objects gets destroyed but the reference remains. You are probably initializing your display objects in CreateScene function which gets called only once if you do not remove your scene.
Destroying the scene in the enter scene function of menu would probably fix the issue.
This is done by the Storyboard.removeScene function.
On the "EnterScene" Function of menu.lua, add in a line to remove the scene. eg. if your scene name is game:
storyboard.removeScene("game")
You can know more about difference between destroyScene and purgeScene here

Resources