Jump to content

How to pass variables between scripts? (Lua)


bansama
 Share

Recommended Posts

I picked up Leadwerks on Steam earlier this week. As such I have very little experience with it. I also currently have no experience with Lua. So forgive me if this question is a little silly.

 

As the title suggests, I would like to pass variables between scripts. For example, I want one script tied to an entity/object in the scene to manage what happens when there is a player collision with it. I want another script to manage text output to the screen based on such collisions.

 

How would I pass a variable set in the collision script to the text output one? Could someone please point me in the right direction?

Link to comment
Share on other sites

It's basically the same i did earlier today. You have to set a "target" in your colission script, once you did that you can access functions within the target script using for e.g. self.target.script:<function>()

 

Here's the code i did so far, it passes a damage value (named xDamage) from this code, to the "FPSPlayer" script (Atually: Its more to the "entity carrying the FPSPlayer script" than to the script itself) using its builtin "Hurt()" function. You just have to make sure that the "self.target" somehow point's to your Character, i did this using the "GetEntityNeighbors" function like done in the "EnemyAI" Script.

 

-- give access to code in "GetEntityNeighbors.lua"
import "Scripts/Functions/GetEntityNeighbors.lua"
Script.target = nil --entity "Target"
Script.damage = 0 -- float "Damage"
Script.teamid = 2 --choice "Team" "Neutral,Good,Bad"
function Script:Use()
 -- load entities within range (30) of self.entity into "entities", only load entities that have scripts attached
 local entities = GetEntityNeighbors(self.entity,30,true)
 local k,entity
 -- loop thrrough the result of "entities". k = key, entity = result
 for k,entity in pairs(entities) do
		 -- only select entities with teamid == 1 ("good")
		 if entity.script.teamid == 1 then
				 -- and if the entity has at least 1 health remaining
				 if entity.script.health>0 then
						 local d = self.entity:GetDistance(entity)
						 -- set self.target = resulting entity
						 self.target = entity
				 end
		 end
 end
 -- prepare random function
 math.randomseed(os.time())
 if self.target ~= nil then
		 -- randomize the damage dealt a bit, setting the "min damage" value
		 if self.damage > 0 then
				 if self.damage > 5 then
						 minDmg = self.damage - 4
				 else
						 if self.damage > 2 and self.damage <= 5 then
								 minDmg = self.damage - 2
						 else
								 minDmg = self.damage
						 end
				 end
				 -- actual code randomizing the damage. Damage is dealt between "minDmg" and "self.damage" (the value is set in the assets browser)
				 xDamage = math.floor(Math:Random(minDmg, self.damage + 1))
				 -- fire function "Hurt" at self.target --> thus the player or entite that was found before.
				 self.target.script:Hurt(xDamage, self.target)
		 end
 end
end

 

My bad, didnt say that.... forgive me tongue.png welcome to Leadwerks bansama smile.png

Link to comment
Share on other sites

One way is to use flowgraphs. Make a new script file called something like TextCollision and copy the code from here: http://leadwerks.wikidot.com/wiki:collision-enter-exit

 

Add a script level parameter called Text (Script.Text = "" --string). This makes it so you can reuse this script many times and display different text on each instance. Attach this to some csg brush and change it's physics to Trigger (and probably paint the invisible material on it). Create a function like:

 

function Script:GetText()--arg
   return self.Text         -- this is whatever you named your script level variable to store the text
end

 

Then place your pivot in your scene and make a script that does the drawing of the text to the screen (the thing is you could just have this collision script do the drawing if you wanted but if you don't for some reason this is another way). This script should have a function for setting the text that it should display defined like:

 

function Script:SetText(text)--in
   self.text = text
end

function Script:PostRender(context)
   if self.text ~= "" then
       context:DrawText(fill this in)
   end
end

 

Open the Flowgraph from the menu bar. Drag in your csg brush and your pivot. Drag a line from the CollisionEnter output to the SetText input. You'll see a little dot in that line. That shows up because SetText() takes a parameter and this dot represents this parameter. Drag the GetText() (which had the --arg after it in the function def) to this dot in the middle.

 

This is one way to pass variables around between objects when outputs raise inputs.

Link to comment
Share on other sites

This is one way to pass variables around between objects when outputs raise inputs.

 

Thanks for the reply. I guess I don't fully understand it though as I can't get it to work (yet). I copied the text in the script you linked to, and added Script.Text = "" (I've tried this alternately with both text and Text in case the difference matters as I notice you have alternated between the two). In the same file I added the Script:GetText() example you provided and created the SetText counterpart in the display script.

 

In the flow editor GetText() is shown in <>, but SetText() is not. Does this indicate a problem?

 

I added a little if/elseif loop to the function I'm using to output text to check if self.text is empty, which it is. So I think I'm still not passing the variables correctly. I'm not even sure how I should be calling the functions. When I try to set a variable with text = SetText(text), I get an error about attempting to call global "SetText" (nil value). So I guess my PHP knowledge is not going to help me here.

 

The reason I want to do this instead of just outputting the text with the collision script is that I figure the need to pass variables will be important, and outputting to the screen is a good way for me to test if something I code works or not.

Link to comment
Share on other sites

CollisionScript (attach to some csg)

Script.entered = false
Script.exited = false
Script.hadCollision = false

Script.Text = "" --string

function Script:GetText()--arg
return self.Text
end

function Script:UpdatePhysics()
if self.entered then
if self.hadCollision == false then
if self.exited == false then
self.exited = true
self.component:CallOutputs("OnExit")
self.entered = false
end
end
end

self.hadCollision = false
end

function Script:Collision(entity, position, normal, speed)
self.hadCollision = true

self.component:CallOutputs("OnCollide")

if self.entered == false then
self.component:CallOutputs("OnEnter")
self.entered = true
self.exited = false
end
end

 

 

DrawTextScript (attach to a pivot)

Script.Text = "" --string

function Script:SetText(text)--in
self.Text = text
end

function Script:PostRender(context)
context:DrawText(self.Text, 0, 0)
end

 

 

Bring both pivot and csg into the FLowgraph and you should see the outputs/inputs/args. Drag CollisionEnter to SetText, then drag GetText to the little dot on the line between CollisionEnter and SetText

 

This was done off memory but it should work.

Link to comment
Share on other sites

You can also in App.lua just make a table for "global" storage which is usable by all other scripts.

 

Inn App.lua Start()

 

self.data = {}

 

Then in object scripts you can read and write to this table, and just use the the entity as index.

 

-- write some data about the local entity.
App.data[self.entity] = somedata

-- get some data on another entity
local value = App.data[entity]

HP Omen - 16GB - i7 - Nvidia GTX 1060 6GB

Link to comment
Share on other sites

This was done off memory but it should work.

 

This does indeed work. Which means I probably made an error. Based on this, I'll look tomorrow at the first two scripts I tried to use and see if I can figure out why they didn't work.

 

Thank you.

 

@shadmar Thanks. I'll look at that too.

@Mordred That was a little over my head, but thanks.

Link to comment
Share on other sites

While I think Shadmar is great and the things he provides this community are amazing, I would warn anyone, especially people maybe newer to programming, to try and avoid global variables. They will eventually make your program a nightmare to maintain. The code will eventually become a spaghetti mess of dependencies that almost nobody but yourself will be able to follow, and after a couple months even you won't be able to follow it. They are convenient, but deadly. The tight coupling that they can also introduce sucks.

 

You can make an entire game without global variables which will yield many benefits. A google search as to why globals are bad will explain these.

  • Upvote 1
Link to comment
Share on other sites

I joined a team once to make a game from GameDev. I can't even tell you how horrible of a nightmare their code was. Everything, I honestly mean that, was global. One giant switch statement to control the entire game. They would complain about some other developer who would say they need to fix this, but the reality is they were very new to programming, very determined, but very new, and globals were the easy route to take because you didn't have to think about designing how things would interact, but that's really what is meant by a good design. That's 1/2 the challenge to making good maintainable code.

 

I would challenge anyone who uses globals on a regular basis to think about how they would remove those globals. Once you do that over and over again, then you won't go back.

Link to comment
Share on other sites

While I understand that globals are bad (it's the same with PHP and I expect all languages), I'm still left wondering how I can pass variables actually set within a script to another script or function. Say I wanted to pass the remaining amount of ammo, or wanted to define a longer string (for a journal entry or similar) can that still be done with the flow editor? Is there a way to do it without the flow editor?

 

In other words, how can pass information I can't realistically set with the defined input box that appears in Leadwerks when --string, etc. is used?

Link to comment
Share on other sites

You can connect entities that have scripts attached to other entities. Let's say you have a HUD script that needs the Ammo count from the player. In the player script you can define a HUD parameter as entity: Script.HUD = nil --entity. Then drag and drop the HUD pivot that you have the HUD script attached to it. Now inside player you have access to all of the HUD scripts functionality via: self.HUD.script. You can call functions, assign variables, whatever.

 

Another way to do it with flowgraphs, would be to fire an output each time the weapon is fired. Then in the player script have a GetAmmoCount()--arg, and in the HUD script have SetAmmo(count). Then you can link the Shoot or Fire output to the HUD's SetAmmo(), connecting the GetAmmoCount() arg function.

 

If you aren't going to set the information with the boxes, then how will you get it? Well, you could load a file to get it. What file? Well that could be the information you hold in the input box. What file to open, read, and set a variable. Then you can still use the same GetText()--arg idea.

 

There are no real need for globals. I'm trying to understand why you think you would need globals in the situations you mentioned.

 

You can also loop through every entity that is loaded with:

 

for x=0,App.world:CountEntities()-1 do
local entity = App.world:GetEntity(x)
if entity:GetKeyValue("type") == "player" then
self.target = entity
end
end

 

You could do this in the Start() function of a script to get entities that you care about and store them in a variable to use later. You'd have to set your own key inside said entities to identify them. However, this isn't that much different (and is slower) than just assigning things via the script parameters.

  • Upvote 1
Link to comment
Share on other sites

I'm trying to understand why you think you would need globals in the situations you mentioned.

 

Did I say that? It wasn't my intention so apologies for any confusion. I'm trying to avoid globals. Thanks for the information on linking scripts and the idea of using another file to store information in. I'll be trying to work out how to use both.

Link to comment
Share on other sites

Well, once you get the handle to that entity in a script, you can access its own script and do whatever you want. The most direct way to do this is use a script where one of the fields is another entity.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

You can connect entities that have scripts attached to other entities. Let's say you have a HUD script that needs the Ammo count from the player. In the player script you can define a HUD parameter as entity: Script.HUD = nil --entity. Then drag and drop the HUD pivot that you have the HUD script attached to it. Now inside player you have access to all of the HUD scripts functionality via: self.HUD.script. You can call functions, assign variables, whatever.

 

Hey Rick, that sounds quite interesting, but somehow i do not get the "hang" on it. So, would be cool if you (or someone else) could explain it to me a bit further.

 

I'd like to do the following:

Generate a script named "Skillsystem", that script contains several "Script.XYZ" variables holding stats (like strength and such --> yes i know i maybe could use tables but i'm not that far into the code yet). How do i acces the value stored in "strength" from another script and how do i write into the "Skillsystem" script new values?

 

A simple link between "HUD" and "FPSPlayer" didnt work. I did enter "Script.hud = nul --entity "HUD"" in the FPSPlayer script and i linked the entity carrying "Skillsystem.lua" onto it. Skillsystem.lua again has the player as "target". So from what i read the idea was to get access to functions / variables in this manor:

self.HUD.script:<Function>()

or

self.HUD.strength

or smth. like this, well, it always returns an error like "attemt to index field 'HUD' (a nil value)".

 

So basically i added this line to FPSPlayer.lua

- Script.hud = nil --entity "HUD"

and want to call it this way:

- context:DrawText("Strength" .. self.HUD.strength, 200 ,200)

 

while strength is not a function but only a variable defined in "HUD" (while HUD points to skillsystem.lua)

Link to comment
Share on other sites

You need to access it via the actual variable name (hud in this case) NOT it's label name "HUD". Lua is case sensitive. If you have Script.hud = nil --entity "HUD", then you access it's functions/variables with:

 

self.hud.script:Function()

 

self.hud.script.strength = 5

 

 

The part in "s is just a label name to show in the editor and not the name of the actual variable.

Link to comment
Share on other sites

  • 2 weeks later...

Just wanted to say thanks for the help, I'm now starting to understand how to pass variables between scripts using the flow editor and defining entities to be dragged and dropped with "Script.entityName = nil --entity", etc.This has allowed me to fetch the player entity's position when they collide with a trigger box and to output that as string displayed along with debug/FPS information.

 

This now raises some more questions, but I figure it'll be better to make new topics for those.

Link to comment
Share on other sites

This has allowed me to fetch the player entity's position when they collide with a trigger box

 

Note that the entity that collided with a trigger box gets passed into the Collision() function by the engine so that should be all you need to know if it's the player or not, by either checking it's key values (entity:GetKeyValue("tag")) or by checking if it has certain variables/functions in the script (if entity.script.GetType ~= nil then type = entity.script:GetType()).

 

Generally you'll pass entities around through script level parameters only if you can't get it another way. For example I have an enemy spawner object that spawns bombs. These bombs need a target to move towards, but the bombs are created at run-time so I can't directly set this in the editor. However, my spawner entity (just a pivot) is setup in the editor at design time and so it can have a parameter that is the target (player or something else) as a script parameter and it can pass on that information to the bombs it creates when it dynamically creates them.

Link to comment
Share on other sites

 

Note that the entity that collided with a trigger box gets passed into the Collision() function by the engine so that should be all you need to know if it's the player or not

 

I specifically want their location. Now if that gets passed into Collision(), then great. How would I fetch it from there?

Link to comment
Share on other sites

So I looked at this code for getting entities:

 

for x=0,App.world:CountEntities()-1 do
local entity = App.world:GetEntity(x)
if entity:GetKeyValue("type") == "player" then
self.target = entity
end
end

 

And changed it so that it printed out entity names. Doing that, I notice it doesn't fetch the name of entities with collision set as a scene/have a mass of 0. So if I have the my floor as an entity with a mass of 0, set as a scene, is there no way to get its name?

 

I really want define the floor as a parent for a pressure pad so I can get the local coords of the player in respect to that pressure plate.

 

I tried setting the pressure plate as the parent for the player and lets just say bad things happened.

 

I also wonder if it's best to fetch entities with such a loop as opposed to specifically defining the one I wish to use via Script. Wouldn't the faster of the two be better -- and wouldn't the fastest be Script? Or am I misunderstanding something?

Link to comment
Share on other sites

So if I have the my floor as an entity with a mass of 0, set as a scene, is there no way to get its name?

 

If you need to do this, create an empty script (I call my dummy.lua) and attach it to that entity. CSG objects will get collapsed and not be seen by themselves unless they have mass or a script attached.

 

I really want define the floor as a parent for a pressure pad so I can get the local coords of the player in respect to that pressure plate.

 

?? What are you trying to do with this? I assume the pressure pad itself is a model/entity? You can attach a script to that model that looks for the player colliding (in Collision() check the entity's name that gets passed in and see if it's player). Then you have the player position from there.

 

 

I also wonder if it's best to fetch entities with such a loop as opposed to specifically defining the one I wish to use via Script. Wouldn't the faster of the two be better -- and wouldn't the fastest be Script? Or am I misunderstanding something?

 

Looping like that will always be slower than getting your entities another way. I would avoid looping like that if at all possible, and generally it's very possible to avoid having to do that. If you do have to loop, looping on startup will be better than looping during playing.

Link to comment
Share on other sites

If you need to do this, create an empty script (I call my dummy.lua) and attach it to that entity. CSG objects will get collapsed and not be seen by themselves unless they have mass or a script attached.

 

Aha! Now I remember reading about that somewhere. Thanks for reminding me. I tried giving it mass as a scene object and it spun off into the void killing me. Was funny to watch, but not very helpful.

 

?? What are you trying to do with this? I assume the pressure pad itself is a model/entity? You can attach a script to that model that looks for the player colliding (in Collision() check the entity's name that gets passed in and see if it's player). Then you have the player position from there.

 

This is where it gets a bit complicated to explain. But basically, like the teleport tutorial (I think that was in one of your topics) I want to teleport the player between different points on the map. However, I want to do so in a manner that player doesn't know it's happening. I figure to do this, I need two identical locations in different parts of the map. When they hit the pressure plate, they should teleport. But in a manner that keeps their current position and rotation relative to the pressure plate they hit. Looking at the teleport tutorial, it didn't look like the player was keeping this information.

 

If you've played Stanley's Parable, then that should give you an idea of why I want to seamlessly transport players, so that I may change the world around them without them realising it.

 

Looping like that will always be slower than getting your entities another way. I would avoid looping like that if at all possible, and generally it's very possible to avoid having to do that. If you do have to loop, looping on startup will be better than looping during playing.

 

That makes sense. Thanks.

Link to comment
Share on other sites

So any suggestions on the best way to fetch player position? Am I right in thinking I will need to specify something as a parent in order to actually get local coordinates? It makes sense to me in theory, but I won't be able to try it out in practice until Saturday.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...