Jump to content

Script in an object affects all copies of that object


Core
 Share

Recommended Posts

I have a switch that opens a door. When it is used, it also lights up a light in switch panel. It works by changing material to emissive and back to black.

This is the script in my door switch:

Don't mind variable names "messageshow", I use the same timer for text messages and keeping light lit for certain time.

if self.activateLight == true then
        self.messageshow = true
            if self.messageshow == true and self.messagetimecount > Time:GetCurrent() then
                --Activation light on
                self.activationSurface:SetMaterial(self.activateMaterial)
            else
                self.messageshow = false
                self.activateLight = false
                self.activationSurface:SetMaterial(self.offMaterial)
            end
    end

 

Problem is, that it lights up ALL switch panels I have in the scene. Why?

Link to comment
Share on other sites

Because materials are shared between model instances to save space. If you use the editor to add models you can't get around this as that's what the editor does. If you can load a model in code you can control this behavior and tell it to use it's own material via the Entity:Copy() function I think? Once of those has an option to use it's own material.

Clearly you'd want to place these in the editor so you know where they are, so maybe a workaround would be in it's script do a copy on the entity itself in the Start() function and replace the editor model with your new copy while setting the pos/rot of the new copy unshared material model to the one you placed in the editor and hide/release the editor version. So basically the editor version of the model is just acting as a placeholder for it's position/rotation to the one you load in code.

Link to comment
Share on other sites

Ah, I see. Then I need to figure out another way. Your suggestion seems plausible, but lot of work a.d extra code, especially if I want to use lots of interactive emissive lights... What about decals, if I show/hide them instead of switching material? Never used decals before.

Link to comment
Share on other sites

The copy and swap isn't a lot of code really. You'd attach a script to one of these things and inside its Start function copy self.entity where it's materials aren't shared and then set its position and rotation to what self.entity is and then hide self.entity. Save as prefab and place as many as you want. Might not be the most efficient way though but it's a couple lines of code only.

 

The shader way is probably the most efficient way but more complex. Id it would be nice if josh offered a flag for each model if it's material is shared between instances or not.

Link to comment
Share on other sites

Tried @Rick's suggestion with this simple code (at this point I'm just trying to copy switch panel object), but it crashes the map. What am I missing?

 

local switchPanel = self.entity:Copy()
	switchPanel:SetPosition(0,0,0)
	switchPanel:SetRotation(0,0,0)

 

Link to comment
Share on other sites

It crashes the very first line. Even if I comment out other two lines. But I realized one thing... I'm using Flowgraph editor. If I copy entity, I guess flow graph editor does not work anymore with the copy? Here is the full script:

 

--UseTimer variables
Script.NullVar 				= nil --choice "Use Timer" "Version: 3, By: Einlander"
Script.enabled 			= true 	-- bool "Enabled"
Script.HoldTime 		= 7 	-- Float "Hold Time" -- How long the use key needs to be held
Script.CumulativeTime 	= false -- bool "Cumulative Time" -- The timer does not reset when the use key is let go
Script.ShowGraphics		= true	-- bool "Show Graphics" -- Draws the completion bar on screen
Script.DisableOnComplete= false 	-- bool "Stop when Done" -- Disables script on completion


--Technical variables
Script.enabled=true--bool "Enabled"
Script.soundfile=""--path "Sound" "Wav Files (*.wav):wav"
Script.working = false --bool "Working"
Script.repairing = false
Script.power = false --bool "Power"
Script.activationSurface = nil
Script.poweredSurface = nil
Script.activateMaterialPath = nil --path "Activate Material" "Material Files (*.mat):mat"
Script.activateMaterial = nil
Script.poweredMaterialPath = nil --path "Powered Material" "Material Files (*.mat):mat"
Script.poweredMaterial = nil
Script.offMaterialPath = nil --path "Off Material" "Material Files (*.mat):mat"
Script.offMaterial = nil
Script.activateLight = false
Script.playerInventory = nil

--Notifications
Script.messagetimecount = 0
Script.text1 = "Replacing Circuitboard" --string "Repair Message"
Script.text2 = "Bad Circuitboard" --string "Broken Message"
Script.text4 = "Out of Power" --string "No Power Message"
Script.text5 = "Switch" --string "Content Msg."
Script.messagetime = 1000 --int "Message Time"
Script.messageshow = false
Script.message2show = true
Script.messageoffset = 70
Script.messageoffset2 = 150
Script.smoke = "" --entity "Smoke"
--Notifications

function Script:Powered(power) --in
	self.power = true
end

function Script:Start()
	--local switchPanel = self.entity:Copy()
	--switchPanel:SetPosition(0,0,0)
	--switchPanel:SetRotation(0,0,0)
	--self.entity:Release()
	self.totaltime = 0
	if self.soundfile then
		self.sound = Sound:Load(self.soundfile)
	end
	self.activationSurface = self.entity:GetSurface(1)
	self.poweredSurface = self.entity:GetSurface(2)
	self.offMaterial = Material:Load(self.offMaterialPath)
	self.poweredMaterial = Material:Load(self.poweredMaterialPath)
	self.activateMaterial = Material:Load(self.activateMaterialPath)
	self.activationSurface:SetMaterial(self.offMaterial)
	self.poweredSurface:SetMaterial(self.offMaterial)
	
end

function Script:Use(player)
	if self.enabled == false then return end--UseTimer code
	self.playerInventory = player.script:GetInventory()
	self.messagetimecount = Time:GetCurrent() + self.messagetime
	if self.working == false then
		if self.playerInventory.playerItems[1] ~= nil then
			if self.playerInventory.playerItems[1].name == "Circuitboard" then
				--System:Print("Using Circuitboard")
				self:Begin() --UseTimer code
			end
		end
	end
	if self.power == true then
		if self.working == true then
			if self.sound then self.entity:EmitSound(self.sound) end
			self.component:CallOutputs("Use")
			self.activateLight = true
		end
	end
end

--UseTimer code
function Script:Begin()
	if self.enabled == false then return end
	self.started = true	
	self.complete = false
	self.repairing = true
	--if (self.totaltime >= (self.HoldTime - 1)*1000) then			
			--self.totaltime = 0			
	--end
	if (self.CumulativeTime == false) then
		self.totaltime = 0
	end	
	self.component:CallOutputs("started")
end

function Script:Complete() 	
	if self.enabled == false then return end
	self.complete = true
	self.working = true
	self.repairing = false
	self.totaltime = 0
	self.smoke:SetLoopMode(false, false);
	self.playerInventory.playerItems[1] = nil
	if self.sound then self.entity:EmitSound(self.sound) end
	if (self.DisableOnComplete == true) then
		self:Disable()
	end
	self.component:CallOutputs("complete")
	--System:Print("complete")	
end


function Script:Enable()--in
	if self.enabled==false then
		self.enabled=true
		self.component:CallOutputs("Enable")
		self.health=1
	end
end

function Script:Disable()--in
	if self.enabled then
		self.enabled=false
		self.component:CallOutputs("Disable")
		self.health=0
	end
end

function Script:Release()
	if self.sound then self.sound:Release() end
end


function Script:UpdatePhysics()
	if self.enabled == false then return end
	if self.complete == true then return end	
	if ((self.started == true) or (self.held == true) or (self.released == true)) == true then
		if self.totaltime >= ((self.HoldTime-1) * 1000) then
			self:Complete()
		end
	end
	if self.started == true then
		if (window:KeyDown(Key.E) == true) then
			self.started = false
			self.held = true			
			self.timestart = Time:GetCurrent()			
			return
		end
	end
	if self.held == true then
		if (window:KeyDown(Key.E) == true) then			
			self.held = true
			self.timeend = Time:GetCurrent()
			self.totaltime  = self.totaltime  + (self.timeend - self.timestart)
			self.timestart = Time:GetCurrent()			
			self.component:CallOutputs("running")
			self.timeend = Time:GetCurrent()	
		else
			self.held=false
			self.released = true
			self.timeend = Time:GetCurrent()	
			self.totaltime  = self.totaltime  + (self.timeend - self.timestart)
			self.timestart = Time:GetCurrent()
		end	
		return
	end
	if self.released == true then
		self.timeend = Time:GetCurrent()
		self.totaltime  = self.totaltime  + (self.timeend - self.timestart)
		self.released = false			
		return
	end	
	if self.activateLight == true then
		self.messageshow = true
			if self.messageshow == true and self.messagetimecount > Time:GetCurrent() then
				--Activation light
				self.activationSurface:SetMaterial(self.activateMaterial)
			else
				self.messageshow = false
				self.activateLight = false
				self.activationSurface:SetMaterial(self.offMaterial)
			end
	end
	if self.power == true and self.working == true then
		self.poweredSurface:SetMaterial(self.poweredMaterial)
		else
		self.poweredSurface:SetMaterial(self.offMaterial)
	end
end

function Script:WritePercentage(number) --in
	System:Print(tostring(number))
end

--[[
Description: Calculates Percent Complete
Parameters: none
Notes: Has a flowgraph ARGUMENT that sends out the percentage completed
]]
function Script:Percentage() --arg
	return math.floor((self.totaltime / ((self.HoldTime-1)*1000))*100)
end


function Script:PostRender(context)
	context:SetBlendMode(Blend.Alpha)
	context:SetFont(font2)
	context:SetColor(hudColorSolid)
	if self.working == false then
		if self.repairing == true and self.held == true then
				--show notification at center of the screen
				local X = math.floor((context:GetWidth() - font2:GetTextWidth(self.text1)))/2
				local Y = math.floor((context:GetHeight() - (font2:GetHeight()-self.messageoffset)))/2
				context:DrawText(self.text1, X, Y)
		else
			self.messageshow = true
			if self.messageshow == true and self.messagetimecount > Time:GetCurrent() then
				--show notification at center of the screen
				local X = math.floor((context:GetWidth() - font2:GetTextWidth(self.text2)))/2
				local Y = math.floor((context:GetHeight() - (font2:GetHeight()-self.messageoffset)))/2
				context:SetColor(hudErrorColor)
				context:DrawText(self.text2, X, Y)
			else
				self.messageshow = false
			end
		end
	end
	if self.power == false and self.repairing == false then
		self.messageshow = true
		if self.messageshow == true and self.messagetimecount > Time:GetCurrent() then
			--show notification at center of the screen
			local X = math.floor((context:GetWidth() - font2:GetTextWidth(self.text4)))/2
			local Y = math.floor((context:GetHeight() - (font2:GetHeight()-self.messageoffset2)))/2
			context:SetColor(hudErrorColor)
			context:DrawText(self.text4, X, Y)
		end
		self.messageshow = false
	end
	if self.enabled == false then return end
	if self.complete == true then return end
	if ((self.started == true) or (self.held == true) or (self.released == true)) == false then return end
		context:SetBlendMode(Blend.Alpha)	
		window = Window:GetCurrent()		
		local segment = Vec2(window:GetWidth() *.45,window:GetHeight() *.49)
		context:SetColor(hudBackColor)	
		context:DrawRect(segment.x-8, segment.y+55 , window:GetWidth() - (segment.x*2),window:GetHeight() - (segment.y*2))
		context:SetColor(hudColorSolid)	
		context:DrawRect(segment.x-4, segment.y+4+55 , (window:GetWidth() - ((segment.x+4)*2))*(self:Percentage()*.01) , window:GetHeight() - ((segment.y+4)*2))
end

 

Link to comment
Share on other sites

It might be in an infinite loop. Since this entity has a script attached that copies itself inside Start() it's copy might be calling Start as well making another copy rinse and repeat. Put a System:Print() at the start of Start() to see if s bunch show up. if that's the case there are ways around this. 

Link to comment
Share on other sites

On 7/14/2017 at 3:17 PM, Core said:

I have a switch that opens a door. When it is used, it also lights up a light in switch panel. It works by changing material to emissive and back to black.

Problem is, that it lights up ALL switch panels I have in the scene. Why?

The workaround in LE2 for a simple ON/OFF switch with an emissive material was to just change the color of the model which via shader would change the texture for the emissive output. Just for fun, I converted this setup to LE4, and gave it a modified simple pushbutton script that uses the switch sound ("switch.lua") and a shader that works in LE4 ("switch.shader"). Hope it helps.

LE2switch.zip

LE2switch.jpg

  • Upvote 1

Win7 64bit / Intel i7-2600 CPU @ 3.9 GHz / 16 GB DDR3 / NVIDIA GeForce GTX 590

LE / 3DWS / BMX / Hexagon

macklebee's channel

Link to comment
Share on other sites

21 minutes ago, GorzenDev said:

move certain variable declarations inside the start() function.
since declaring Script.variablename will make them shared across all instances of the script.

That's only if the variable is a table.

Link to comment
Share on other sites

I got tricked by this several times.

--Common variables like strings, numbers and bool
Script.test1 = "test1" -- Variable has a unique instance per script
local test2 = "test2"  --Variable is shared between all instances of the script
test3 = "test3" --Global, used by everone and everything (insert 'your mother..' joke)

--Tables
Script.table1 = {} --Shared between all instances of the script
Script.table2 = nil --Declare in start

function Script:Start()
	self.table2 = {} --Table has a unique instance per script
end

 

  • Upvote 1
Link to comment
Share on other sites

All instances of a model placed in the editor share the material no matter how it's set. So it doesn't matter where or how he sets the material for this model, whenever he set it's for 1 all instances will automatically change as well. The same model placed x times in the editor are setup to share the same material.

To get different materials for the same model you either have to make it in code because then you can control that setting or use an atlas shader that mimic different textures. For an atlas shader it still uses the same material but per instance you can control what part of the atlas texture to use which results in different textures on that model.

In this case he can just use the shader Mack provided since he's not really looking for completely different textures. If you had a character and wanted them to seem like they are wearing different clothes or textures then an atlast shader would be one way to go vs making copies as you'd at least save on the model memory and speed of loading if loading a new one in real-time since they are instanced vs Copy() which reloads a new instance which takes longer and can't be used in real-time.

Link to comment
Share on other sites

3 minutes ago, Rick said:

All instances of a model placed in the editor share the material no matter how it's set. So it doesn't matter where or how he sets the material for this model, whenever he set it's for 1 all instances will automatically change as well. The same model placed x times in the editor are setup to share the same material.

i am not saying you are wrong in anything you said.

sorry i should have explained it proper in my first post.

personaly i would not have created theses materials in the editor at all but rather load the texture and create a new material for every instance of the script that runs the start() function as AggrorJon points out nicely.

Link to comment
Share on other sites

But creating unique instances of the material won't matter. It's the model and all instances of it that points to the last material that's assigned to it. When you set any material on an instance of a model all instances will get that material. It's the model that is saying all instances of myself will point to the last assigned material on me. That's why you have to copy the model or load it at runtime with the unmanaged flag to make that model not share materials.

So simply loading unique materials in start won't give the desired effect since all of those models are still instances when placed via the editor. In that case whatever script was last to run will set the material and all instances of that model will now be sharing that material.

Link to comment
Share on other sites

8 hours ago, Rick said:

But creating unique instances of the material won't matter. It's the model and all instances of it that points to the last material that's assigned to it. When you set any material on an instance of a model all instances will get that material. It's the model that is saying all instances of myself will point to the last assigned material on me. That's why you have to copy the model or load it at runtime with the unmanaged flag to make that model not share materials.

So simply loading unique materials in start won't give the desired effect since all of those models are still instances when placed via the editor. In that case whatever script was last to run will set the material and all instances of that model will now be sharing that material.

Yeppers. Basically any item that is an asset (model,material,texture,etc) will need to be created, loaded as un-managed, or copied to prevent it from affecting any other previous instance of that asset. Has to be done via code as currently no way to load unique copies in editor. Other discussions about this:

1 hour ago, Core said:

Thanks for the help and discussion and @macklebee  for the shader example! Awesome. I'm away from computer for the next few days, I'll continue after that.

Glad to help. I was just going to link to the LE2 switch converted to LE4 in the workshop or forum but was surprised when I couldn't find it. This switch was the convenient workaround for the same problem of instances in the Editor back in the LE2 days.

Win7 64bit / Intel i7-2600 CPU @ 3.9 GHz / 16 GB DDR3 / NVIDIA GeForce GTX 590

LE / 3DWS / BMX / Hexagon

macklebee's channel

Link to comment
Share on other sites

  • 2 months later...

Finally had time to get back to this one. I tried macklebees example and it worked like a charm, but I had actually no idea how it worked... So I got back to Ricks copying suggestion. And decided to create parent for my switch with copying script, copy manager if you will, to prevent it duplicating it self forever. It actually worked, but it lost all the flowgraph attachments, naturally, because it was not the same entity anymore.

Here is the code I came up to create unique entity with unique materials.

Script.createCopy = false
Script.originalModel = nil
Script.finalModel = nil

function Script:Start()
	if self.createCopy == false then
		self.originalModel = self.entity:FindChild("SwitchPanel1_5")
		self.finalModel = tolua.cast(self.originalModel:Copy(), "Entity")
		self.originalModel:Hide()
		self.createCopy = true
	end
end

So next step was to move all my switch code to parent, and create new script for 3d model, with functions including material changing code. And call those functions from parent entitys script when I needed materials to change. And so finally, it does what it supposed to do!

 

 

Thank you all for your help!

20170921235220_1.jpg

  • Upvote 1
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...