Variable in Player State in Lua not updating as expected - lua

In the following code, the value of self.player.x is assigned to self.x is assigned at init and tweaked a little bit to the left or right when the state is entered. The PlayerPilotState then manipulates self.player.x every frame and my expectation is that self.x would update along with it. Items such as the tilemap, which are assigned to player as def.tilemap when the player is instantiated, do update as the map changes. How do I ensure that variables such as self.x will update themselves as well? I know that I can update that variable under update every frame, but it seems ineloquent and since I'm fairly new to coding, I don't understand why the player.tilemap does update whereas this variable doesn't. Thank you!
function PlayerPilotState:init(pilot, passenger)
self.player = pilot
self.passenger = passenger
self.animation = Animation {
frames = {2, 3, 2, 8},
interval = 0.1
}
self.player.currentAnimation = self.animation
-- x value at middle of players to make collisions more readable
-- and scalable (for left/right pilot/passenger cases)
self.x = self.player.x
end
function PlayerPilotState:enter(params)
-- determine which side passenger is riding on
self.ridingSide = params.ridingSide
if self.ridingSide == 'left' then
self.x = self.player.x - 1/2 * self.player.width
else
self.x = self.player.x + 1/2 * self.player.width
end
end

This is a common misconception for people coming from non-scripted languages.
Note that self.x and self.player.x are numbers so when you use assignment:
assert(type(self.player.x) == "number")
self.x = self.player.x -- copy
you are making a copy of that number.
On the other hand when dealing with tables or userdata objects then assignment works by making a reference:
assert(type(self.player) == "table")
self.ref = self.player -- reference
Generally speaking you can't really have one number synchronized in the way you have described. It wouldn't be efficient either because you will have to make a redundant "copy" of that value in memory.
This is a question of encapsulation and how/where your data is stored. If you are not sure how to redesign your code remember that "values that change together, belong together". Good luck!

Related

How to run code when any object with the same name is touched

So I'm trying to develop a small coin collecting game on Roblox, and am pretty new to scripting. Basically Every 0.25 - 1.5 seconds, a small part is cloned from (-254, 2, -255) (one corner of the baseplate), to (254, 2, 255) (the opposite corner). That works, but im trying to loop over every object in workspace named coin, and when one is touched, run code (for now im just trying to destroy the object but ill probably just update the Coins leaderstat). It doesn't give me any errors, it just doesnt work. I've also looked all over the internet, and cant find anything.
Code in ServerScriptStorage (spawns cubes and already works, but showed it for help.):
local runservice = game:GetService("RunService")
local interval = math.random(0.25, 1.5)
local coin = game.ServerStorage.coin
local counter = 0
local x = math.random(-254, 254)
local z = math.random(-255, 255)
runservice.Heartbeat:Connect(function(step)
counter = counter + step
if counter >= interval then
counter = counter - interval
local copy = coin:Clone()
copy.Parent = workspace
copy.Position = Vector3.new(x, 2, z)
x = math.random(-254, 254)
z = math.random(-255, 255)
interval = math.random(0.25, 1.5)
end
end)
script in desktop that handles the touching:
for _, v in pairs(workspace:GetChildren()) do
if v.Name == "coin" then
print("foo")
end
end
I hope this is enough to help!
Well as you are new to scripting in roblox let me give you your answer with good practices that may help you a lot.
First in this scenario you dont need to use Heartbeat, instead you could simple use a while loop or a recursive function and a simple wait().
Also you better create a "Coins" Folder in workspace in order to not check other objects
local waitTime = math.random(25,150)/100 --random time between 0.25 and 1.5
while true do --forever loop
wait(waitTime) --waits desired time
local coin = game.ServerStorage.coin:Clone() --cloning your coin
coin.Parent = workspace.Coins --Coins folder
coin.Position = Vector3.new(math.random(0,10),2,math.random(0,10)) --you must use your own position
coin.Touched:Connect(function(hitPart) --here is the touched function
local plr = game.Players:FindFirstChild(hitPart.Parent.Name) --check if the hitPart is part of a player
if plr then
plr.leaderstats.Coins.Value = plr.leaderstats.Coins.Value + 1--here you can increment your coins value in your own value
coin:Destroy()--destroys the coin
end
end)
waitTime = math.random(25,150)/100 --set a new random value to wait next
end
Also you mentioned something about loop every coin in workspace, thats why I said it is better to create a separate folder. So I made a localscript inside StarterPlayerScripts with the following code:
local RunService = game:GetService("RunService") --service
RunService.RenderStepped:Connect(function() --function on every game frame
for i,v in pairs(workspace.Coins:GetChildren()) do --loop on every coin
v.Orientation = Vector3.new(v.Orientation.X,v.Orientation.Y+5,v.Orientation.Z) --increasing Orientation just on Y in order to rotate them
end
end)
I'm doing this on localscript because is just a visual effect and it is never a good idea to send that many functions that quickly serverside. Here is the game I made for you:
https://www.roblox.com/games/5842250223/Help-for-TextBasedYoutube
You can edit the place.
In other words to answer "How to run code when any object with the same name is touched?"
You need to set the function for the object when creating it.
Edit: Also is not a good idea to send to many request to the server in short periods of time, I would recommend you to create a coin ever 2 to 3 seconds or more.

How can I get values from multiple instances of a class?

I am making a roguelike in Love2D as a hobby project. My approach is to try and use as much of the native capabilities of Lua and the Love2D (0.10.1) API as possible, without relying on fancy libraries like middleclass or HUMP, so as to learn more about the language.
After reading PiL's chapters on OOP and seeing the power there, I decided to set up a Mob class (using metamethods to emulate class functionality) that encompasses the players, monsters, and other NPCs (anything that can move). So, far, it's working beautifully, I can create all kinds of instances easily that share methods and all that stuff. But there's a lot of things I don't know how to do, yet, and one of them is holding my prototype up from further progress.
Setting up collision with the map itself wasn't too bad. My maps are tables full of tables full of integers, with 0 being the floor. The game draws "." and "#" and "+" and such to denote various inanimate objects, from each table. Player 1 moves using the numpad, and their position is tracked by dividing their raw pixel position by 32 to create a grid of 32x32 "tiles". Then, inside love.keypressed(key), I have lines like:
if key == "kp8" and currentmap[player1.grid_y - 1][player1.grid_x] == 0 then
player1.grid_y = player1.grid_y - 1
and so on, with elseifs for each key the player can press. This prevents them from walking through anything that isn't an open floor tile in the map itself.
But, I'm trying to implement some kind of "collision detection" to prevent MOBs from walking through each other and to use in writing the rules for combat, and this is trickier. I had a method in place to calculate the distance between mobs, but I'm told this might eventually cause rounding errors, plus it had to be written for each combination of mobs I want to test, individually.
What I'd like to know is: Is there a known (preferably elegant) way to get all instances of a particular class to pass some number of values to a table?
What I'd like to do is "ask" every Mob on a given map where they are, and have them "report" self.grid_x and self.grid_y to another layer of map that's just for tracking mobs (1 if self.is_here is true, 0 if not, or similar), that gets updated every turn. Then, I could implement collision rules based on coordinates being equal, or maybe a foo.is_here flag or something.
I have only vague ideas about how to proceed, however. Any help would be appreciated, including (and maybe especially) feedback as to a better way to do what I'm trying to do. Thanks!
A simple idea is to store "who is here" information for every cell of the field and update this information on every move of every object.
function create_game_field()
-- initialize a table for storing "who is here" information
who_is_here = {}
for y = 1,24 do
who_is_here[y] = {}
for x = 1,38 do
who_is_here[y][x] = 0
end
end
end
function Mob:can_move(dx, dy)
local u = currentmap[self.y + dy][self.x + dx]
local v = who_is_here[self.y + dy][self.x + dx]
if u == 0 and v == 0 then
return true
else
end
end
function Mob:move(dx, dy)
-- update "who is here"
who_is_here[self.y][self.x] = 0
self.x, self.y = self.x + dx, self.y + dy
who_is_here[self.y][self.x] = 1
end
function Mob:who_is_there(dx, dy) -- look who is standing on adjacent cell
return who_is_here[self.y + dy][self.x + dx] -- return mob or nil
end
function Mob:roll_call()
who_is_here[self.y][self.x] = 1
end
Usage example:
-- player1 spawns in at (6,9) on the grid coords
player1 = Mob:spawn(6,9)
-- player1 added to who_is_here
player1:roll_call()
Then, in love.keypressed(key):
if key == "kp8" and player1:can_move(0, -1) then
player1:move(0, -1)
end
There are a few ways you could get all your instances data but one of the simpler ones is probably to have them all be added to a table when they are created. Providing you add the entire table for that instance, all the values will update in the main table because it acts like a collection of pointers.
function mob:new( x, y, type )
self.x = 100
self.y = 200
self.type = type
-- any other declarations you need
table.insert(allMobs, self)
return self
end
Here we insert all the mobs into the table 'allMobs'. Once we have that we can simply iterate through and get all our coordinates.
for i, v in ipairs(allMobs) do
local x, y = v.x, v.y
-- Do whatever you need with the coordinates. Add them to another table, compare
-- them to others, etc.
end
Now we have a table with all our mobs in it and a way to access each of their positions. If you have any further inquiries then let me know.

A unique environment per script in Lua 5.3

I would like to be able to have a chunk of Lua code (a "script") that could be shared among enemy types in a game but where each instance of a script gets a unique execution environment. To illustrate my problem, this is my first attempt at what a script might look like:
time_since_last_shoot = 0
tick = function(entity_id, dt)
time_since_last_shoot = time_since_last_shoot + dt
if time_since_last_shoot > 10 then
enemy = find_closest_enemy(entity_id)
shoot(entity_id, enemy)
time_since_last_shoot = 0
end
end
But that fails since I'd be sharing the global time_since_last_shoot variable among all my enemies. So then I tried this:
spawn = function(entity)
entity.time_since_last_shoot = 0;
end
tick = function(entity, dt)
entity.time_since_last_shoot = entity.time_since_last_shoot + dt
if entity.time_since_last_shoot > 10 then
enemy = find_closest_enemy(entity)
shoot(entity, enemy)
entity.time_since_last_shoot = 0
end
end
And then for each entity I create a unique table and then pass that as the first argument when I call the spawn and tick functions. And then somehow map that table back to an id at runtime. Which could work, but I have a couple concerns.
First, it's error prone. A script could still accidentally create global state that could lead to difficult to debug problems later in the same script or even others.
And second, since the update and tick functions are themselves global, I'll still run into issues when I go to create a second type of enemy which tries to use the same interface. I suppose I could solve that with some kind of naming convention but surely there's a better way to handle that.
I did find this question which seems to be asking the same thing, but the accepted answer is light on specifics and refers to a lua_setfenv function that isn't present in Lua 5.3. It seems that it was replaced by _ENV, unfortunately I'm not familiar enough with Lua to fully understand and/or translate the concept.
[edit] A third attempt based on the suggestion of #hugomg:
-- baddie.lua
baddie.spawn = function(self)
self.time_since_last_shoot = 0
end
baddie.tick = function(self, dt)
entity.time_since_last_shoot = entity.time_since_last_shoot + dt
if entity.time_since_last_shoot > 10 then
enemy = find_closest_enemy(entity)
shoot(entity, enemy)
entity.time_since_last_shoot = 0
end
end
And in C++ (using sol2):
// In game startup
sol::state lua;
sol::table global_entities = lua.create_named_table("global_entities");
// For each type of entity
sol::table baddie_prototype = lua.create_named_table("baddie_prototype");
lua.script_file("baddie.lua")
std::function<void(table, float)> tick = baddie_prototype.get<sol::function>("tick");
// When spawning a new instance of the enemy type
sol::table baddie_instance = all_entities.create("baddie_instance");
baddie_instance["entity_handle"] = new_unique_handle();
// During update
tick(baddie_instance, 0.1f);`
This works how I expected and I like the interface but I'm not sure if it follows the path of least surprise for someone who might be more familiar with Lua than I. Namely, my use of the implicit self parameter and my distinction between prototype/instance. Do I have the right idea or have I done something weird?
For your first issue (accidentally creating globals), you can rely on a linter like luacheck or a module that prevents you from creating globals like strict.lua from Penlight.
And then, why not just make things local? I mean both time_since_last_shoot and tick. This leverages closures, one of the most useful features of Lua. If you want different tick functions, each with its own variables, you can do something like this:
local function new_tick()
local time_since_last_shoot = 0
return function(entity_id, dt)
time_since_last_shoot = time_since_last_shoot + dt
if time_since_last_shoot > 10 then
local enemy = find_closest_enemy(entity_id)
shoot(entity_id, enemy)
time_since_last_shoot = 0
end
end
end
local tick_1 = new_tick()
local tick_2 = new_tick()
Of course, you could also use the environment for this, but here I think local variables and closure are a better solution to the problem.
The way _ENV works in 5.3 is that global variable are "syntactic" sugar for reading fields from the _ENV variable. For example, a program that does
local x = 10
y = 20
print(x + y)
is equivalent to
local x = 10
_ENV.y = 20
_ENV.print(x + _ENV.y)
By default, _ENV is a "global table" that works like you would expect global variables to behave. However, if you create a local variable (or function argument) named _ENV then in that variable's scope any unbound variables will point to this new environment instead of point to the usual global scope. For example, the following program prints 10:
local _ENV = {
x = 10,
print=print
}
-- the following line is equivalent to
-- _ENV.print(_ENV.x)
print(x)
In your program, one way to use this technique would be to add an extra parameter to your functions for the environment:
tick = function(_ENV, entity, dt)
-- ...
end
then, any global variables inside the function will actually just be accessing fields in the _ENV parameter instead of actually being global.
That said, I'm not sure _ENV is the best tool to solve your problem. For your first problem, of accidentally creating globals, a simpler solution would be to use a linter to warn you if you assign to an undeclared global variable. As for the second problem, you could just put the update and tick functions in a table instead of having them be global.

What is the paramater equivilant to?

function onTouch(part)
local human = part.Parent:findFirstChild("Humanoid")
if (human == nil) then
return
end
human.Health = human.Health - 10
end
script.Parent.Touched:connect(onTouch)
I'm new to coding in lua, and it is my first time using functions. I want to know what the "part" is equal to so that I can find out how to set up the human variable
local human = part.Parent:findFirstChild("Humanoid")
without using the "part," like what can I plug in so that works without even setting up part, because I want to do something with it in a loop:
local burnaffect = false
--local a = 0
function onTouch(part)
local human = part.Parent:findFirstChild("Humanoid")
if (human == nil and burnaffect == false) then
return
end
a = 0
burnaffect = true
end
script.Parent.Touched:connect(onTouch)
while burnaffect == true do
local part = --????
local human = part.Parent:findFirstChild("Humanoid")
human.Health = human.Health - 10
end
The code may seem confusing but I'm fairly new so I don't know what is best yet.
It looks like what you're trying to do is have a player "set on fire" when they touch a certain brick. The code below does exactly this, and I'll explain it line by line afterwards.
function onTouch(part)
local human = part.Parent:findFirstChild("Humanoid")
if (human == nil) then
return
end
local fire = Instance.new("Fire", part)
while (human.Health > 0) do
human.Health = human.Health - 10
wait(0.1)
end
end
script.Parent.Touched:connect(onTouch)
So, I'll start going through this.
function onTouch(part)
We need to define the function first and give it a name so that we can reference it in the Touched event later. The part parameter is the Part object that touched the script.Parent object and caused the Touched event to fire. So, ROBLOX will automatically call this function whenever something touches your script.Parent and automatically input the Part that touched it as that first parameter, part.
local human = part.Parent:findFirstChild("Humanoid")
This will get the Parent of the Part that touched the block (because if a player is touching the block, it isn't going to give us the Character, it's going to give us an Arm or a Leg in the part variable, because that's the actual Part that touched it, so we need to get that part's Parent. Then, once we have the Parent, we want to get the Humanoid object inside of the Character. Then, we put that Humanoid object inside of the human variable (if we could find one, otherwise we put nil into that human variable).
if (human == nil) then
We want to check if human is nil (which would mean we couldn't find a Humanoid object in the line before this one, which means whatever touched this isn't a real Character, so we'll want to return (which means stop running the function immediately).
local fire = Instance.new("Fire", part)
This line isn't necessary, I added it because I thought if you wanted to simulate burning, this would help. You can leave it out if you'd like. It will create a new Instance of type Fire, and places it inside of the part. That is to say, if a player's Leg touches this part, a Fire will be put inside of that leg, which will make it appear to ignite in flames.
while (human.Health > 0) do
We want to keep looping until the player dies (has a human.Health value of 0 or less) so we tell the loop to keep going while human.Health is greater than 0.
human.Health = human.Health - 10
We'll deincrement the human.Health value by 10, and then wait(0.1), which will cause the script to wait for 1/10 of a second (you can change this to a different number, but it is important to keep it as if you remove it, the loop will run extremely fast and kill the player immediately. If you want this to happen, you can remove the wait, but if you wanted to kill the player immediately, you could just set human.Health = 0 in the first place.
If you have any questions, feel free to ask! Hope this answered your question.
I remember when I used to use Lua for Roblox. The part is the part in the game that gets touched. You need to reference it so you can find the humanoid object it belongs to or lack of so your code can tell if it is a humanoid that touched it or not. Let me know if you have any further questions.

How to effectively handle object removal of objects with Corona SDK

just starting to play around with the awesome corona sdk.
I started building a simple shooter game.
I have the following code :
-- Global Variables
local shot = audio.loadSound('shot.mp3')
local bg = display.newImage('bg.png')
local shoot = {}
local Main = {}
local Init = {}
local bullets = display.newGroup()
function update()
if(bullets.numChildren ~= 0) then
for i = 1, bullets.numChildren do
bullets[i].y = bullets[i].y - 8
-- Destroy Offstage Bullets
if(bullets[i].y < (-bullets[i].height-5)) then
-- bullets[i]:removeSelf()
bullets:remove(bullets[i])
display.remove(bullets[i])
return
end
end
end
end
-- Initialisation functions
function Init ()
display.setStatusBar(display.HiddenStatusBar)
local movieclip = require('movieclip')
local physics = require('physics')
physics.start()
physics.setGravity(0, 0)
end
function shoot:tap(e)
for i = 1, 15 do
local bullet = display.newImage('bullet.png')
bullet.x = 150
bullet.y = 470
bullet.name = 'bullet'
physics.addBody(bullet)
bullets.insert(bullets, bullet)
end
audio.play(shot)
end
-- Main routine
function Main ()
Init()
bg:addEventListener('tap', shoot)
Runtime:addEventListener('enterFrame', update)
end
Main()
For now it 'works'; but when the bullet comes of the screen the whole 'game' slows down and I can clearly see that each bullet is removed which slows down the game.
Maybe i'm not doing it right; also tried the :removeSelf() function; same results.
I was facing the same problem...And got the soln for this:
you are removing objects using for loop:
if you remove objects in for loop say: you deleted object at index 1, then object 2 moves to object 1...so when it loops to object to it wont check object 2(bcoz its moved to object 1's place and you are checking object 3)
for j = 1, buttonGroup.numChildren do
buttonGroup[1]:removeSelf(); --this removes all childrens
end
for j = 1, buttonGroup.numChildren do
buttonGroup[j]:removeSelf(); --this removes alternative childrens
end
I hope its useful
I fought for a long long time with object removal for my game which uses a lot of tabbed views. The solution I found was to use "= nil", ":removeSelf()" and ".alpha = 0" on all objects which need removing. If they are all added to the same group, and nothing else is in it, that could be used too, but doesn't always work for various reasons as to how groups are set up behind the scenes.
It would seem what you've done there is remove the contents of "bullets" but that's just a reference, so where you say each bullet is being removed, it's actually only being dropped from the array - the object itself still exists and needs to be nil'd in order to prevent a memory leak and a slowing of the app (you will probably have found that each bullet slows the app more, right?)
Add this condition after your removes and you should be set:
if(not(bullet == nil)) then
bullet.alpha = 0;
bullet:removeSelf();
bullet = nil;
end
its easier to simply create a table like this
local bulletCache = {}
Then in your bullet creation code add
table.insert(bulletCache, myBulletObject)
then in your exit code and/or your destroy code say
for i = 1, #bulletCache do
--[[
--Below is not needed but just to keep your code consitant
pcall(function()
bullet.alpha = 0
end)
--]]
pcall(function()
bullet:removeSelf()
end)
pcall(function()
bullet = nil
end)
end
First of all, never try to execute any kind of loop in GameLoop. It does drop fps of game, because it takes usually more memory.
Now, it seems that you just want to destroy bullets after disappearing from the screen and you are using physics too, so why you are not taking advantage of that?
Here are the steps you should follow. (If you find any query or difficulty about it then ask me)
1.Draw a Static Physical Line little bit above the screen. Let say y is -20.
local _yLimit = -20
local _limitLine = display.newLine(0,_yLimit,display.contentWidth,_yLimit)
_limitLine.anchorX = 0
_limitLine.anchorY = 0
2.Add all bullets as a physical object.
3.Apply force at the Bottom Center of bullet, rather then transforming it. [Assuming that bullet have rectangular physical shape, otherwise decide the equilibrium point of the shape].
4.Check for collisions with "collision" event listener.
5.Destroy bullets on collision
Thats so simple :)
Let me know if you still have problems regarding this.

Resources