Jump to content
  • entries
    940
  • comments
    5,894
  • views
    863,945

Using Multiple Entity Scripts in Turbo Game Engine


Josh

10,716 views

 Share

During development of Leadwerks Game Engine, there was some debate on whether we should allow multiple scripts per entity or just associate a single script with an entity. My first iteration of the scripting system actually used multiple scripts, but after using it to develop the Darkness Awaits example I saw a lot of problems with this. Each script used a different classname to store its variables and functions in, so you ended up with code like this:

function Script:HurtEnemy(amount)
	if self.enemy ~= nil then
		if self.enemy.healthmanager ~= nil then
			if type(self.enemy.healthmanager.TakeDamage)=="function" then
				self.enemy.healthmanager.TakeDamage(amount)
			end
		end
	end
end

I felt this hurt script interoperability because you had to have a bunch of prefixes like healthmanager, ammomanager, etc. I settled on using a single script, which I still feel was the better choice between these two options:

function Script:HurtEnemy(amount)
	if self.enemy ~= nil then
		if type(self.enemy.TakeDamage)=="function" then
			self.enemy.TakeDamage(amount)
		end
	end
end

Scripting in Turbo Game Engine is a bit different. First of all, all values and functions are attached to the entity itself, so there is no "script" table. When you access the "self" variable in a script function you are using the entity object itself. Here is a simple script that makes an entity spin around its Y axis:

function Entity:Update()
    self:Turn(0,0.1,0)
end

Through some magic that is only possible due to the extreme flexibility of Lua, I have managed to devise a system for multiple script attachments that makes sense. There is no "component" or "script" objects itself, adding a script to an entity just executes some code that attached values and functions to an entity. Adding a script to an entity can be done in C++ as follows:

model->AttachScript("Scripts/Objects/spin.lua");

Or in Lua itself:

model:AttachScript("Scripts/Objects/spin.lua");

Note there is no concept of "removing" a script, because a script just executes a bit of code that adds values and functions to the entity.

Let's say we have two scripts named "makeHealth100 and "makeHealth75".

MakeHealth100.lua

Entity.health=100

MakeHealth75.lua

Entity.health=75

Now if you were to run the code below, which attaches the two scripts, the health value would first be set to 100, and then the second script would set the same value to 75, resulting in the number 75 being printed out:

model->AttachScript("Scripts/Objects/MakeHealth100.lua");
model->AttachScript("Scripts/Objects/MakeHealth75.lua");
Print(entity->GetNumber("health"));

Simple enough, right? The key point here is that with multiple scripts, variables are shared between scripts. If one scripts sets a variable to a value that conflicts with another script, the two scripts won't work as expected. However, it also means that two scripts can easily share values to work together and create new functionality, like this health regeneration script that could be added to work with any other scripts that treat the value "health" as a number.

HealthRegen.lua

Entity.healthregendelay = 1000

function Entity:Start()
	self.healthregenupdatetime = CurrentTime()
end

function Entity:Update()
	if self.health > 0 then
		if CurrentTime() - self.healthregenupdatetime > self.healthregendelay then
			self.health = self.health + 1
			self.health = Min(self.health,100)
		end
	end
end

What about functions? Won't adding a script to an entity overwrite any functions it already has attached to it? If I treated functions the same way, then each entity could only have one function for each name, and there would be very little point in having multiple scripts! That's why I implemented a special system that copies any added functions into an internal table. If two functions with the same name are declared in two different scripts, they will both be copied into an internal table and executed. For example, you can add both scripts below to an entity to make it both spin and make the color pulse:

Spin.lua

function Entity:Update()
	self:Turn(0,0.1,0)
end

Pulse.lua

function Entity:Update()
	local i = Sin(CurrentTime()) * 0.5 + 0.5
	self:SetColor(i,i,i)
end

When the engine calls the Update() function, both copies of the function will be called, in the order they were added.

But wait, there's more.

The engine will add each function into an internal table, but it also creates a dummy function that iterates through the table and executes each copy of the function. This means when you call functions in Lua, the same multi-execution feature will be available. Let's consider a theoretical bullet script that causes damage when the bullet collides with something:

function Entity:Collision(entity,position,normal,speed)
	if type(entity.TakeDamage) == "function" then
		entity:TakeDamage(20)
	end
end

If you have two (or more) different TakeDamage functions on different scripts attached to that entity, all of them would get called, in order.

What if a function returns a value, like below?:

function Entity:Update()
	if self.target ~= nil then
		if self.target:GetHealth() <= 0 then
			self.target = nil --stop chasing if dead
		end
	end
end

If multiple functions are attached that return values, then all the return values are returned.

2k6qco.jpg.47118c97ce0e02ce7f1451470dffb7c7.jpg

To grab multiple returned values, you can set up multiple variables like this:

function foo()
	return 1,2,3
end

a, b, c = foo()
print(a) --1
print(b) --2
print(c) --3

But a more practical usage would be to create a table from the returned values like so:

function foo()
	return 1,2,3
end

t = { foo() }
print(t[1]) --1
print(t[2]) --2
print(t[3]) --3

How could this be used? Let's say you had a script that was used to visually debug AI scripts. It did this by checking to see what an entity's target enemy was, by calling a GetTarget() function, and then creating a sprite and aligning it to make a line going from the AI entity to its target it was attacking:

function Entity:UpdateDisplay()
	local target = self:GetTarget()
	self.sprite = CreateSprite()
	local p1 = self.entity:GetPosition() 
  	local p2 = target:GetPosition()
	self.sprite:SetPosition((p1 +  p2) * 0.5)
	self.sprite:AlignToVector(p2 - p1)
	self.sprite:SetSize(0.1,(p2-p1):Length())
end

Now let's imagine we had a tank with a main gun as well as an anti-aircraft gun that would ward off attacks from above, like this beauty I found on Turbosquid:

01.jpg7aadaf3f-2e0c-4a62-8154-f8b5da25f02bOriginal.thumb.jpg.3528aef8665ae145a2a5001d0054799e.jpg

Let's imagine we have two different scripts we attach to the tank. One handles AI for driving and shooting the main turret, while the other just manages the little machine gun. Both the scripts have a GetTarget() function, as the tank may be attacking two different enemies at once.

We can easily modify our AI debugging script to handle multiple returned values as follows:

function Entity:UpdateDisplay()
	local targets = { self:GetTarget() } --all returned values get put into a table
	for n,target in ipairs(targets) do
		local sprite = CreateSprite()
		self.sprites.insert(sprite)
		local p1 = self.entity:GetPosition() 
	  	local p2 = target:GetPosition()
		sprite:SetPosition((p1 +  p2) * 0.5)
		sprite:AlignToVector(p2 - p1)
		sprite:SetSize(0.1,(p2-p1):Length())
	end
end

However, any scripts that are not set up to account for multiple returned values from a function will simply use the first returned value, and proceed as normal.

This system supports both easy mix and match behavior with multiple scripts, but keeps the script code simple and easy to use. Scripts have easy interoperability by default, but if you want to make your function and variable names unique to the script it is easy to do so.

Let me know if you have any other ideas for scripting in Turbo Game Engine.

  • Like 1
  • Upvote 1
 Share

51 Comments


Recommended Comments



Hi can u guys help me...im playing shadowgun legends game....can u guys do a script damage n heatlh...if can email me in kalamzul26@gmail.com

Thank you

Link to comment

Guest
Add a comment...

×   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.

×
×
  • Create New...