Difference between revisions of "Tips for mod coders"

From Legends of Aria Admin and Modding Wiki
Jump to: navigation, search
(Creating section for custom animations)
(Triggering custom Unity Animations from Lua)
Line 129: Line 129:
 
== Triggering custom Unity Animations from Lua ==
 
== Triggering custom Unity Animations from Lua ==
 
The problem is pretty similar to the one above (about audio) and the solution/trick is pretty similar too.<BR>
 
The problem is pretty similar to the one above (about audio) and the solution/trick is pretty similar too.<BR>
We're also going to use a script/Unity module provided by CS in the LoA toolkit, in this particular case "Triggered Trap Object", to just trigger a 1 way animatio (e.g. door opening/closing)/<BR>
+
We're also going to use a script/Unity module provided by CS in the LoA toolkit, in this particular case "Triggered Trap Object", to just trigger a 1 way animation (e.g. door opening/closing)/<BR>
 
Other modules can also work, e.g. "Toggle Switch Object", once you understand the concept).<BR>
 
Other modules can also work, e.g. "Toggle Switch Object", once you understand the concept).<BR>
 
<BR>
 
<BR>

Revision as of 20:54, 19 September 2018

Constants, attributes and ObjVars

One thing to consider is the life-cycle of LoA Lua "objects":

  • When server initially starts (with no backups, etc), the module is loaded, you will have an event handler like here under in your code to process the basic initialization of your object.
RegisterEventHandler(EventType.ModuleAttached,"template_name", function ( ... ) Blah Blah end)
  • When server starts with a backup, you will have to recover some of the information needed for the good-running of your object. You will have an event handler like below.
RegisterEventHandler(EventType.LoadedFromBackup,"", function ( ... ) Blah Blah end)

If you haven't saved some of the information needed as ObjVars before a backup, that information will be lost after a restart of the server.
Thus, one thing you better think about, considering the life-cycle of objects, is choosing carefully how object "parameters" are stored in your objects, either as:

  • constants: Those values should not change during the life of your object.
    • Technically in Lua, there is no strict "const", i.e. Immutability, paradigm.
    • The best way to approach it, pragmatically and simply, is to UPPERCASE those constants names at the top of your Lua file to clearly identify them
  • attributes: those are parameters with mutability in time or context dependent value changes
    • You need to clearly identify is such attributes needs to be "transferred" between 2 server executions, or not.
    • If the value of such attributes needs to be recovered, you need to use the "ObjVars" method here under
    • If not, i.e. the value is temporary, you can just create the variable in the Lua code
  • ObjVars: Those are parameters (with a key, i.e. "Name", and a value) that will be saved in the backups and can be recovered after a server restart.
    • The SetObjVar and GetObjvar functions are to be used for that purpose
    • Do not forget to save, i.e. SetObjVar, any value that is modified during the lifetime of your object, esp. tables/arrays.

Looking for objects

In your scripts, you will certainly be needing to find objects. Even though CS provides plenty of functions to search, it might be difficult to find a particular object, esp. when using default templates provided by CS (and thus not modifiable).
The best option, not to have to create a specific template for such object, is to use "Custom Object Variables".
You can either add such variables directly from the SeedObjects.xml file of your module, or do it from Unity.

From Unity:

  1. Load your module, and its Seed Objects (cf. modding guide). Leave the "Seed Objects Editor" window opened.
  2. Select the given Seed Object in your hierarchy or Scene view
  3. in the "Seed Object Editor", press "+" under "Custom Object Variables". This will create a new entry.
  4. You can choose a "Name" for that object, which is going to be the "ObjVar" name in Lua
  5. You can define a type and value, which is going to be the value returned when you call GetObjVar("Name")


The XML that will be generated will look like this in SeedObjects.xml
Note that the "<Name>" tag isn't of use, the Lua GetName() function isn't related to that.

<DynamicObject>
            <Name>Door</Name>
            <Id>12</Id>
            <ObjectCreationParams>tudor_door_dark -20.809 1.714 206.889 0 90 0 1 1.09 1.315</ObjectCreationParams>
             <ObjVarOverrides>
                <StringVariable Name="DoorId">DoorTeam</StringVariable>
            </ObjVarOverrides>
</DynamicObject>

If you want to manually add it, you can edit the SeedObjects.xml file directly (making sure you don't have Unity running and seedobjects loaded) by adding the consistent <ObjVarOverrides> as shown above.

Note: this will just manually add that objvar to the object on creation when you resetworld or start with no backup

To find that object from your Lua script, you can now easily search for the custom object variable:

     local myDoor = FindObject(SearchObjVar("DoorId","DoorTeam"))

Custom Mod Music

While CS doesn't provide yet a way to easily add your custom music in the game, you can manually add it using trickery.
Indeed, custom C# code cannot be attached to custom objects libraries, so we're stuck for now with using existing scripts that handle existing game animations and have the game engine think our custom objects are LoA prefabs.
The technique below applies to a single AudioSource playing at a time ... you can create variations when you understand the concept.
In my example, I was trying to play different music clips depending on a game status
NOTE: The technique only works with Permanent Objects (instantiated within Unity's Hierarchy), not Seed Objects (created dynamically within LoA)


In Unity:

  • You first need a custom objects library (cf. the handbook)
  • Under your custom library folder in the "Project" view, create a "Music" folder
  • Import the music clips you will want to use within that folder (Note: Unity recognizes MP3 and WAV)
  • Create a new "GameObject" in your scene's Hierarchy (I will call it "AudioManager")
    • It should be located at the center of your map for global scene audio, or localized where you want the music to originate from
    • "Add Component" and search for "Client Object".
  • Create an AudioSource objects under the "AudioManager"
    • Disable the object by clicking on the checkbox (uncheck it) on the left of its name at the top of the Inspector.
    • Make sure "Play On Awake"and "Loop" are checked
    • Under the "3D Sound Settings", use "Linear Rolloff" for "Volume Rolloff".
    • If you want music for the whole map, set "Max Distance" to well over your map size and "Min Distance" just 1 unit less than the max. This will ensure the volume isn't decreased wherever players are.
    • Set the "AudioClip" to one of the audio clips you imported
    • You can copy/paste that object for every clip you want to use, just change "AudioClip" for each of them

NOTE 2: This technique won't allow the player to control the volume from the game settings. Be careful with the volume of the AudioClip, adjust it as low as neeeded for the music to be there and not to end up with hearing problems ;-)

  • Back to the AudioManager Object, select it and "Add Component", search for "Alternate Mesh State Handler"
    • Set "Size" to the number of clips you imported PLUS 1 ==> Element 0 shall remain empty "None (GameObject)" (Required to start playing the first music when game starts, don't ask me .. didn't work otherwise)
    • Set each of the entries with one of the AudioSource objects you created before
  • Create a prefab from the AudioManager in your custom objects library (cf. handbook), build the library and build the MapData (cf. handbook)

Note: You will certainly have to delete your client cache (see troubleshooting in handbook)

In your module:

  • You need to add the information in ObjectTagDefinitions.xml for the game to think your object is of a specific type to enable interaction between Lua and Unity/C#
    • In this case, we use "Rock" since it uses the "Alternate Mesh State Handler" script internally.
    • Under is an example from my own code:
<ObjectTags>
	<ObjectTag>
		<Name>AudioManager</Name>
                <SharedStateEntry name="ResourceSourceId" type="string" value="Rock" restriction="readonly"/>
                <SharedStateEntry name="CTF_Audio" type="bool" value="true"/>
                <VisualState id="1" name="Waiting" collisionEnabled="false"/>
                <VisualState id="2" name="Countdown" collisionEnabled="false"/>
                <VisualState id="3" name="InGame" collisionEnabled="false"/>
		<CustomAssetBundle>FastPaceLibrary</CustomAssetBundle>
		<ClientId>5</ClientId>
	</ObjectTag>
</ObjectTags>
    • "ResourceSourceId" seems to be how the game engine assigns scripts. Using "Rock" here since we want to switch between objects (other scripts allow triggering animations)
    • "ClientId" must match the client ID from your library (like for seed objects), check in Unity. Make sure you put your consistent "CustomAssetBundle" as well
    • I used a SharedStateEntry named "CTF_Audio", only to easily find the permanent object (make sure it is unique for each object you're searching) .. it's not recognized as usable by the game engine:
	local searcher = PermanentObjSearchSharedStateEntry("CTF_Audio",true)
	local foundObjs = FindPermanentObjects(searcher)
	if (foundObjs ~= nil) then
		for i, foundObj in pairs(foundObjs) do
			audioSource = foundObj
		end
	end
    • In this example, there are 3 Visual States for the 3 audio clips we have imported in Unity.
    • the "id" corresponds to the entry number of the AudioManager we set earlier. entry "0" being empty, we don't use it ("0" is the default from Unity)
    • the "name" is what is used to trigger the change from Lua (here under "mode" is one of the 3 names, i.e. "Waiting", "Countdown" or "InGame"):
	if (audioSource ~= nil) then
		audioSource:SetVisualState(mode)
	end

Triggering custom Unity Animations from Lua

The problem is pretty similar to the one above (about audio) and the solution/trick is pretty similar too.
We're also going to use a script/Unity module provided by CS in the LoA toolkit, in this particular case "Triggered Trap Object", to just trigger a 1 way animation (e.g. door opening/closing)/
Other modules can also work, e.g. "Toggle Switch Object", once you understand the concept).

In Unity:

  • You first need a custom objects library (cf. the handbook)
  • Under your custom library folder in the "Project" view, create a folder for the new animated object you want to create.
    • This is important since animations need to have strict naming and will conflict if you try to create several animated object within your library.
  • Create a new "GameObject" in your scene's Hierarchy (I will call it "ParentObject")
    • This is an object with no geometry. You can add the "Client Object" and "Triggered Trap Object" (or another animation script/module provided by CS) modules
  • Under that "ParentObject", you can add your "ChildObject" animated prefab (either a model of your own or imported). This is the object containing geometry.
    • The "Animation" module must be attached to this "ChildObject".
    • Currently CS animation scripts only allow 2 states, i.e. 'true' or 'false' for "IsTriggered" (or "IsActivated" for switch, cf. the C# code for the module)
    • The 2 states need to match with 2 animations named "Trigger" and "Reset" (or "Activate" and "Reset" for switch, cf. the C# code for the module)
    • If you already have your animations (e.g. to open and close a door), you can rename them. If you don't have the animations, select the "ChildObject" in the hierarchy and in Unity's "Window" menu, select "Animation"
    • Creating animation can be pretty simple, by selecting the attributes you want to animate and create key-frames. You can "preview" your animation directly.
  • Once animations are available, you can assign them (if not automatically added), to the "ChildObject" 'Animation' module "Animations" (you should thus have 2 named "Reset" and "Trigger").
    • It is preferable to assign the default "Animation" to "Reset" and check "Play Automatically", to ensure your object is reloaded in it initial animation state, e.g. door closed.
  • Back to the "ParentObject", you can now assign the "ChildObject" as "Trap Animator" in the "Triggered Trap Object" Script
  • Create a prefab from the "ParentObject" in your custom objects library (cf. handbook), build the library and build the MapData (cf. handbook)
    • If your object is supposed to be dynamically created (e.g. SeedObject or script), make sure you delete or "Disable" the instance in your hierarchy before saving the scene.

Note: You will certainly have to delete your client cache (see troubleshooting in handbook)
In your module:

  • You need to add the information in ObjectTagDefinitions.xml for the game to enable interaction between Lua and Unity/C#
    • Under is an example from my own code:
<ObjectTags>
    <ObjectTag>
        <Name>MyFlagObject</Name>
        <SharedStateEntry name="IsTriggered" type="bool" value="false"/>
	<CustomAssetBundle>FastPaceLibrary</CustomAssetBundle>
        <ClientId>1</ClientId>
    </ObjectTag>
</ObjectTags>
    • The <Name> doesn't really matter
    • The SharedStateEntry must match the requirement for the module/script you used, i.e. "IsTriggered" for traps or "IsActivated" for switches
    • "ClientId" must match the client ID from your library (like for seed objects), check in Unity. Make sure you put your consistent "CustomAssetBundle" as well
    • For dynamic objects, you are also supposed to create a template for them, in this case "ctf_flag.xml" (plus optionally a Lua script for its own behavior):
<ObjectTemplate>
	<ClientId>1</ClientId>
	<Name>MyFlagObject</Name>
	<CustomAssetBundle>FastPaceLibrary</CustomAssetBundle>
	<ScriptEngineComponent>
		<LuaModule Name="ctf_flag"/>
	</ScriptEngineComponent>
</ObjectTemplate>
  • Now you can easily control the animation from your Lua code:
RegisterEventHandler(EventType.Message,"Activate",
	function ()
		if not(this:GetSharedObjectProperty("IsTriggered")) then
			this:SetSharedObjectProperty("IsTriggered",true)
			CallFunctionDelayed(TimeSpan.FromSeconds(0.3),function ( ... )
				this:SetSharedObjectProperty("IsTriggered",false)
			end)
		end
	end)