Skip to content

Scripting

Alex Miyamoto edited this page Apr 23, 2018 · 2 revisions

Scripting in Usagi is done using LUA.

The scripts in Usagi are not designed to handle generic gameplay code, but specialized code for levels, bosses etc. They are intentionally entirely event driven to limit how much of the internal workings of the game is exposed to designers, reducing the potential for bugs to be introduced from the scripting side. Combined with customized AI behaviour trees they allow for a good amount of flexibility.

A script can be added to any entity in the game using the script component in the entity definition file, which could not be simpler:

Script:
  filename: Scripts/MyScript.lua

Scripts live in Data/Scripts

The system which handles an attached script is ExecuteScript. It exposes the Run and LateUpdate singals to scripts, as well any events the script is interested in.

function Run(dt)
  -- Run the script update code for this entity
end

function LateUpdate(dt)
  -- Run the late update script for this entity
end

These functions on their own aren't very useful (and indeed will probably not be major parts of your scripts), how is the script to know about the state of the game, and how do we make changes? Lets look at some of the health events.

message HealthChangedEvent
{
	option (nanopb_msgopt).lua_receive = true;
	option (usagi_msg).doc_jp = "ヘルスが変わった";

	required float fPrev = 1 [(usagi).doc_jp = "変更前の値"];
	required float fNew = 2 [(usagi).doc_jp = "変更後の値"];
}

message IncreaseHealthEvent
{
	option (nanopb_msgopt).lua_send = true;
	option (usagi_msg).doc_jp = "ヘルスを増やす";

	required float amount = 1;
}

Notice the options lua_send and lua_receive. These tell the us that we want to be able to send a health. If you look at the automatically generated FrameworkEvents.lua.cpp you will see a great amount of automatically generated code surrounding these two events.

The amount of automatically generated templated code means you should be careful to only tag events as and when you need them.

Now lets look at the OnEvent function for HealthChanged

function OnEvent.HealthChangedEvent(event)
  -- The health of this entity has changed
  -- We can access event.fPrev and event.fNew just as we would in C++
end

It's basically the same as the C++ version except you have no inputs or outputs, only the event its self.

Now lets look at some of the generated code for the IncreaseHealthEvent.

template<>
void ::usg::RegisterLuaEventTransmitters< ::usg::Events::IncreaseHealthEvent >(lua_State* L)
{
	bool bIsRegistered = LuaSerializer< ::usg::Events::IncreaseHealthEvent >::PushMetatable(L);
	ASSERT(bIsRegistered);
	lua_pushcfunction(L, LuaEvents< ::usg::Events::IncreaseHealthEvent >::Send);
	lua_setfield(L, -2, "Send");
	lua_pushcfunction(L, LuaEvents< ::usg::Events::IncreaseHealthEvent >::SendToEntity);
	lua_setfield(L, -2, "SendToEntity");
	lua_pushcfunction(L, LuaEvents< ::usg::Events::IncreaseHealthEvent >::SendOverNetwork);
	lua_setfield(L, -2, "SendOverNetwork");
	lua_pushcfunction(L, LuaEvents< ::usg::Events::IncreaseHealthEvent >::SendToNetworkEntity);
	lua_setfield(L, -2, "SendToNetworkEntity");
	lua_pop(L, 1);
}

To send a message from script you define one of the exposed events and then use one of the above functions (Send, SendToEntity, SendOverNetwork, SendToNetworkEntity)

function IncreaseHealth(_amount)
	-- This event is being sent out globally so every entity with a health component will get a boost
	local healthEvent = Events.IncreaseHealthEvent { amount = _amount }
	messageEvent:Send()
end

Sometimes complex types such as enums and messages need to be exposed to LUA, in such cases add the option lua_generate to the protocol buffer message, e.g.:

message NetworkUID
{
	option (nanopb_msgopt).lua_generate = true;
	required sint64 gameUID = 1;
}

Caution

Scripting was a last minute addition. Some features such as messaging specific entities were never utilised so there is a chance that undetected bugs remain.