Jump to content

Sample Sliding Door Script in Turbo / Leadwerks 5

Josh

319 views

I've successfully converter the sliding door script over to the new API. This will all look very familiar, but there are some cool points to note.

  • The entity mass can be retrieved and set with a property as if it was just a regular variable. Underneath the hood, the engine is making method calls.
  • I decided to prefix most field names with "slidingdoor_" to prevent other scripts from accidentally interfering. The "enabled" value, however, is meant to be shared.
  • The Entity is the script object. No more "entity.script.entity.script..." stuff to worry about.
  • All of this actually works in the new engine.
Entity.enabled=true--bool "Enabled"
Entity.slidingdoor_openstate=false--bool "Start Open"
Entity.slidingdoor_distance=Vec3(1,0,0)--Vec3 "Distance"
Entity.slidingdoor_movespeed=1--float "Move speed" 0,100,3
Entity.slidingdoor_opensoundfile=""--path "Open Sound" "Wav File (*wav):wav|Sound"
Entity.slidingdoor_closesoundfile=""--path "Close Sound" "Wav File (*wav):wav|Sound"
Entity.slidingdoor_loopsoundfile=""--path "Loop Sound" "Wav File (*wav):wav|Sound"
Entity.slidingdoor_manualactivation=false--bool "Manual activate"
Entity.slidingdoor_closedelay=2000--int "Close delay"

function Entity:Start()

	self:EnableGravity(false)
  
	if self.mass==0 then self.mass=10 end
	--In Leadwerks 4:
	--if self.entity:GetMass()==0 then self.entity:SetMass(10) end
	-- :)
  
	self.slidingdoor_sound={}
	self.slidingdoor_sound.open = LoadSound(self.slidingdoor_opensoundfile)
	self.slidingdoor_sound.loop = LoadSound(self.slidingdoor_loopsoundfile)
	self.slidingdoor_sound.close = LoadSound(self.slidingdoor_closesoundfile)
	
	if self.slidingdoor_sound.loop~=nil then
		self.slidingdoor_loopsource = CreateSource()
		self.slidingdoor_loopsource:SetSound(self.slidingdoor_sound.loop)
		self.slidingdoor_loopsource:SetLoopMode(true)
		self.slidingdoor_loopsource:SetRange(50)
	end

	--if self.slidingdoor_manualactivation==false then self.Use=nil end

	self.slidingdoor_opentime=0
	
	--Create a motorized slider joint
	local position = self:GetPosition(true)
  
  	--You could also do this:
	--local position = self.mat[3].xyz
  
	local pin=self.slidingdoor_distance:Normalize()
	self.slidingdoor_joint=CreateSliderJoint(position.x,position.y,position.z,pin.x,pin.y,pin.z,self)
	if self.openstate then
		self.slidingdoor_openangle=0
		self.slidingdoor_closedangle=self.slidingdoor_distance:Length()
	else
		self.slidingdoor_openangle=self.slidingdoor_distance:Length()
		self.slidingdoor_closedangle=0
	end
	self.slidingdoor_joint:EnableMotor(true)
	self.slidingdoor_joint:SetMotorSpeed(self.slidingdoor_movespeed)
end

function Entity:Use()
	self:Open()
end

function Entity:Toggle()
	if self.enabled then
		if self.slidingdoor_openstate then
			self:Close()
		else
			self:Open()
		end
	end
end

function Entity:Open()
	if self.enabled then
		self.slidingdoor_opentime = CurrentTime()
		if self.slidingdoor_openstate==false then
			if self.slidingdoor_sound.open then
				self:EmitSound(self.slidingdoor_sound.open)
			end
			self.slidingdoor_joint:SetTarget(self.slidingdoor_openangle)
			self.slidingdoor_openstate=true			
			if self.slidingdoor_loopsource~=nil then
				self.slidingdoor_loopsource:SetPosition(self:GetPosition(true))
				if self.slidingdoor_loopsource:GetState()==SOURCE_STOPPED then
					self.slidingdoor_loopsource:Play()
				end
			end
		end
	end
end

function Entity:Close()
	if self.enabled then
		if self.slidingdoor_openstate then
			if self.slidingdoor_sound.close then
				self:EmitSound(self.slidingdoor_sound.close)
			end
			self.slidingdoor_joint:SetTarget(self.slidingdoor_closedangle)
			self.slidingdoor_openstate=false
			if self.slidingdoor_loopsource~=nil then
				self.slidingdoor_loopsource:SetPosition(self:GetPosition(true))
				if self.slidingdoor_loopsource:GetState()==0 then
					self.slidingdoor_loopsource:Play()
				end
			end
		end
	end
end

function Entity:Disable()
	self.enabled=false
end

function Entity:Enable()
	self.enabled=true
end

function Entity:Update()
	
	--Disable loop sound
	if self.slidingdoor_sound.loop~=nil then
		local angle
		if self.slidingdoor_openstate then
			angle = self.slidingdoor_openangle
		else
			angle = self.slidingdoor_closedangle
		end
		if math.abs(self.slidingdoor_joint:GetAngle()-angle)<0.1 then
			if self.slidingdoor_loopsource:GetState()~=SOURCE_STOPPED then
				self.slidingdoor_loopsource:Stop()
			end
		else
			if self.slidingdoor_loopsource:GetState()==SOURCE_STOPPED then
				self.slidingdoor_loopsource:Resume()
			end
		end
		if self.slidingdoor_loopsource:GetState()==SOURCE_PLAYING then
			self.slidingdoor_loopsource:SetPosition(self:GetPosition(true))
		end
	end
	
	--Automatically close the door after a delay
	if self.slidingdoor_closedelay>0 then
		if self.slidingdoor_openstate then
			if CurrentTime()-self.slidingdoor_opentime>self.slidingdoor_closedelay then
				self:Close()
			end
		end
	end

end

 

  • Like 1


7 Comments


Recommended Comments

Just a suggestion/request. I am not part of the Turbo Engine /Leadwerks Next beta, but would it be possible to change the way Script Properties work? Currently Leadwerks Handles the Script properties with hints comments as seen here:

Entity.myPath = "" --path "File location" "Texture File (*tex):tex|Texture"
Entity.myChoice = 1 --choice "Choice list" "Monster, Zombie, Alien"
Entity.myEditChoice = "Monster" --choiceedit "Choice list" "Monster, Zombie, Alien"
Entity.myEntity = nil --entity "Some entity

 

 

Would it be possible to change it to a more programmatic method such as this:

-- First Initialization Style: Explicit (they all result in the same function)
Entity._scriptProperties.myPath = {}
Entity._scriptProperties.myPath.Type = TurboEngine.ScriptProperties.Path -- Engine Enum
Entity._scriptProperties.myPath.Label = "File Location"
Entity._scriptProperties.myPath.Filter = "Texture File (*tex):tex|Texture"

-- Second  Initialization Style: Inline (they all result in the same function)
Entity._scriptProperties.myChoice = {Type = TurboEngine.ScriptProperties.Choice, Label = "Choice List", Default = 1 , List = {"Monster", "Zombie", "Alien"}}

-- Second  Initialization Style: Named As You Go (they all result in the same function)
Entity._scriptProperties.myEditChoice["Type"] = TurboEngine.ScriptProperties.ChoiceEdit
Entity._scriptProperties.myEditChoice["Label"] = "Choice List"
Entity._scriptProperties.myEditChoice["Default"] = 1
Entity._scriptProperties.myEditChoice["List"] = {"Monster", "Zombie", "Alien"}

Some of the benefits are:

  • The editor would just need to load the a scripts _scriptsproperties instead of needing to rely on reading the script and finding the comments
  • Creates a standardized interface to add properties
  • It further opens up the option of editor scripting
  • Creates the option of dynamic population of properties and choices (You can select 1 choice and another choice changes it's value
  • Easier to serialize

Some downsides:

  • Scripts will need to be rewritten
  • More verbose (will need to write more code to make something seemingly simple happen)
  • Requires a higher level of lua understanding (Will need to know how to use named tables and nested tables)
  • Potentially time consuming

This is something that I have been thinking about for a while and since it looks like you are taking the opportunity to use the Turbo Engine to make breaking changes I thought i might bring it up.

  • Like 1

Share this comment


Link to comment

Something I did not cover in the last post, because I forgot about it: Methods to get the chosen values from the script properties. There would need to be a standardized way to get the values that were set. First the choice would need to be created at runtime. To access it you can have either:

 1. A universal variable that you can always expect to have the value ie: 

chosenValue = Entity._scriptProperties.myEditChoice.Value

2. A specific variable per each property type ie:

chosenPathValue = Entity._scriptProperties.myPath.SelectedPath
chosenChoiceValue = Entity._scriptProperties.myEditChoice.SelectedChoice

Either way will eventually have it's issues as people push it to it's limits. One will be easier to remember, the other is more structured and more explicit.

There may be better solutions for this, but I can't come up with them.

Share this comment


Link to comment

Here is my take on it:

Script.properties.enabled = {}
Script.properties.enabled.label = "Enabled"
Script.properties.enabled.value = true
Script.properties.enabled.interface = "checkbox"

Script.properties.slidingdoor_distance = {}
Script.properties.slidingdoor_distance.label = "Movement"
Script.properties.slidingdoor_distance.value = Vec3(1,0,0)
Script.properties.slidingdoor_distance.interface = "vec3"

Script.properties.slidingdoor_closedelay = {}
Script.properties.slidingdoor_closedelay.label = "Close Delay"
Script.properties.slidingdoor_closedelay.value = 2000
Script.properties.slidingdoor_closedelay.interface = "integer"
Script.properties.slidingdoor_closedelay.min = 0 --disallow negative numbers

Script.properties.slidingdoor_opensoundfile = {}
Script.properties.slidingdoor_opensoundfile.label = "Open Sound"
Script.properties.slidingdoor_opensoundfile.value = ""
Script.properties.slidingdoor_opensoundfile.interface = "path"
Script.properties.slidingdoor_opensoundfile.filter = "Wav File (*wav):wav|Sound"
Script.properties.slidingdoor_opensoundfile.defaultpath = "Sound"

If a string is used then the editor can create an interface widget depending on that value, and plugins can add new interface types. If a widget for any given interface type is not found, then the editor can default to the default widget for the Lua data type (boolean, number, string, or object, depending on the default value).

The engine can automatically load these at startup so that the above values are fed into a variable for that script, i.e. so that self.enabled equals the value given above.

I have also considered using an XML or JSON file associated with the script for this. The editor doesn't actually run any entity scripts while running, which is nice for stability. I guess a JSON file for this would look something like this:

[{
	"name" : "slidingdoor_openstate",
	"label" : "Open State",
	"value" : "false",
	"interface" : "checkbox"
},
{
	"name" : "slidingdoor_speed",
	"label" : "Sound",
	"value" : "200",
	"interface" : "float",
	"min" : "0"
},
{
	"name" : "slidingdoor_opensoundfile",
	"label" : "Open Sound",
	"value" : "",
	"interface" : "path",
	"defaultpath" : "Sound",
	"filter" : "Wav File (*wav):wav|Sound"
}];

I will have to think about this some more. Do you have any ideas of a custom interface type you would want to add to the editor via a plugin? It's always easier to deal with realistic scenarios instead of theoretical cases.

Share this comment


Link to comment

Using text for the interface is a good idea. I think you may have the system properties and custom interface thing mostly solved.

Nope, everything that I can think of is hypothetical atm. The only thing that I would do is place every interface into a  separate container and stack those. That way a custom interface will know that it needs to fit in the containers bounds.

Currently I'm just thinking ahead to potentially more advanced input. Such as a curves/path widget as seen in the  Emitter properties, Or even a calendar widget to input dates for days easter-eggs show up. Nothing actually needed right now, but would be useful to someone, somewhere,someday.

Share this comment


Link to comment

Okay...

{
	"name" : "date",
	"label" : "Date",
	"value" : "01/01/1968",
	"interface" : "calendar",
},

Now I need to think about how the editor would load this and run scripts that evaluate it, and create the widget...

Share this comment


Link to comment

Does it need to be in the Script namespace? Can you add a separate EditorWidget namespace or something?. This should allow you to create a plugin manager. And then you can have the plugins tell the editor about itself ie:

EditorWidget.name = "Calender Widget"
EditorWidget.about = "Lets player Select a date from a calendar"
EditorWidget.author = "hacker Man"
EditorWidget.version = "1.0"

The only issue with this is if the game needs to use a feature from the plugin. For example, check if the day is a Tuesday. Would the editor script be available to the game at runtime?  

Share this comment


Link to comment

Well, the value would just be a string. So however the game interprets that is fine.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Create Your Account

Sign in

Already have an account? Sign in here.

Sign In Now
×