Skip to content

Tutorial 1–5

BlackTower edited this page Jul 28, 2022 · 2 revisions

In this tutorial, we will be implementing a score counter that increments when an enemy is killed and a trigger to respawn enemies.

This tutorial will cover:

1. Creating Functions

So far we've used the LogMessage() and CreateUnit() DCEI functions but we can create our own functions too. This lets us create re-usable pieces of code with customizable behavior (you can read more about functions here).

Let's write a function that spawns an enemy unit from just one "point" parameter (Lua supports passing arrays as parameters). In Lua, parameter names are arbitrary and untyped. We called ours "point" but we could call it whatever we wanted. Descriptive names are generally recommended.

Write the following function right below the -- TRIGGERS section.

-- TRIGGERS
function SpawnEnemyUnitAtPoint(point)
    DCEI.CreateUnit(-1, -1, enemy_unit_name, point[1], point[2])
end

If you SAVE and hit play, nothing new will happen. This is because functions must be called to be executed. Let's refactor our for loop to use our new function. At this point your complete trigger file should look like this:

-- VARIABLES
local hero_unit_name = DCEI.Unit("Hero IceMage")
local enemy_unit_name = DCEI.Unit("Standard MeleeUnit")
local enemy_points = {
    {20, 14},
    {21, 16},
    {20, 18}
}

-- TRIGGERS
function SpawnEnemyUnitAtPoint(point)
    DCEI.CreateUnit(-1, -1, enemy_unit_name, point[1], point[2])
end

DCEI.LogMessage("Hello Lua!")

DCEI.CreateUnit(1, 1, hero_unit_name, 16, 16)

for i = 1, 3, 1 do
    local point = enemy_points[i]
    SpawnEnemyUnitAtPoint(point)
end

Note that the function must be defined above where it is called in the script. This is because Lua executes line by line from the top down. If you tried to declare your function after the for loop, you'll get a script error.

2. Organizing Your Script With Comments

As you've seen so far, we've been separating our Variables and Triggers in separate sections. As your project grows, keeping your game logic organized is an important consideration for keeping development running smoothly.

I like to split my trigger files into four main sections: libraries, global variables, triggers, and initialization. We can use comments to split our trigger files into multiple sections that are easier to keep track of.

-- LIBRARIES

-- VARIABLES

-- TRIGGERS

-- INITIALIZATION

To write comments, start a line with -- Comment text. Commented lines aren't run by the game and are used to make the script more understandable. Block or multiline comments can be created with --[[ ]]:

--[[
    Block comment text that
    can span multiple lines
]]

Comments can also be used to add notes about a specific piece of code or even temporarily disable/enable code for testing. For practice, let's comment out the DCEI.LogMessage("Hello Lua!") since we don't need it anymore. If done correctly it should change color.

-- DCEI.LogMessage("Hello Lua!")

PRO TIP: The Windows VS Code keyboard shortcut for toggle line comment is CTRL+/

We can delete our "Hello Lua" log message now.

PRO TIP: The Windows VS Code keyboard shortcut for move line up/down is ALT+↑/ALT+↓

Lastly, let's clean up our floating CreateUnit and For loop, since it's generally bad practice to have code floating around. Move these floating lines into a new function called OnMapStart(), so that all of the starting units are created when this function is called. Then call this function under Initialization. Note that functions being called inside other functions (such as SpawnEnemyUnitAtPoint inside of OnMapStart()) do not need to be defined in order.

image 1

3. Hooking Up Trigger Events

Trigger events in Lua function similarly to trigger events in StarCraft II or Warcraft III, but the way you create them is a little different. You can find a complete list of currently supported trigger events here. To create a trigger that fires when any unit dies, you'd write this:

-- TRIGGERS
function OnUnitDied()
    DCEI.LogMessage("> Some unit died")
end

-- INITIALIZATION
DCEI.TriggerAddUnitDiedEvent(DCEI.UnitAny, OnUnitDied)

Note that in the trigger event, we're passing a function as a parameter, and that the () and parameters of OnUnitDied aren't used. Writing DCEI.TriggerAddUnitDiedEvent(DCEI.UnitAny, OnUnitDied()) will not work.

Now that we know about trigger events, lets create a score counter that increments whenever an enemy is defeated. To get started, create an OnUnitDied function and trigger event, as well as a global variable called score with a default value of 0.

image 2

Next we'll check if the unit that died is an enemy unit by comparing the unit type name of the dying unit with our enemy_unit_name variable. We can get the unit that died with DCEI.TriggeringUnit and a unit type name with DCEI.GetUnitType. Then we can compare the two with an if statement (similar to conditions in SC2/War3) and use a log message to check that our trigger is working correctly.

-- TRIGGERS
function OnUnitDied()
    local u = DCEI.TriggeringUnit
    local name = DCEI.GetUnitType(u)

    if name == enemy_unit_name then
        DCEI.LogMessage("> an enemy unit died!")
    end
end

To hookup our score counter, let's write a new function called UpdateScore() using a new function called DCEI.ShowObjectiveText(). In Lua we use .. to concatenate (or combine) strings. Lua automatically converts numbers into strings.

-- TRIGGERS
function UpdateScore()
    DCEI.ShowObjectiveText("Score: " .. score)
end

Next let's update our OnUnitDied function to increment our score variable and update the score display. While we're at it, we might as well add UpdateScore() to OnMapStart() so we can show our initial score value (of 0) when the game starts.

image 3

If everything is working correctly, you should have a score counter in-game that updates whenever an enemy is destroyed.

score counter

4. Respawning Enemies With Timers

Lastly we'll respawn enemies a few seconds after they die using the TriggerAddTimerEventElapsed() event and the SpawnEnemyUnitAtPoint() function we wrote earlier.

We can get the point of the dying unit with GetUnitPosition2D() like so:

local u = DCEI.TriggeringUnit
local p = DCEI.GetUnitPosition2D(u)

Note that GetUnitPosition2D() returns a table, rather than an array so we have to convert it to a two dimensional array for it to work in our SpawnEnemyUnitAtPoint() function. We can accomplish this like so:

local u = DCEI.TriggeringUnit
local p = DCEI.GetUnitPosition2D(u)
local point = {p.x, p.y}

If we look at the Trigger Event API's we'll see that TriggerAddTimerEventElapsed() takes a trigger (aka function) and float for the duration. As we learned earlier, DCEI.TriggerAddTimerEventElapsed(SpawnEnemyUnitAtPoint(point), duration) will not work.

What we can do, is wrap SpawnEnemyUnitAtPoint(point) in its own function without parameters like so:

DCEI.TriggerAddTimerEventElapsed(
    function()
        SpawnEnemyUnitAtPoint(point)
    end,
    2
)

It may look weird at first, but you'll get used to this syntax in no-time as it's very commonly used. Note that the duration is set to 2, so enemies should respawn 2 seconds after dying.

Altogether your function should look like this:

image 4

If everything has gone according to plan, you should have endlessly respawning enemies for your hero to thrash!


Tutorial Map: Tutorial 1-5 More Triggers

Next Up: Exploring the Workshop Series

Clone this wiki locally