Big List of Defold Pro Tips!

I just discovered your book! I’m currently writing some documentation in french about Defold and I’m glad that other people are trying to share their knowledge as well! Good job!

4 Likes

In your book, you said that the builtins folder is read only. Is this still the case as of today?

1 Like

Yes. And any dependency for that matter is read-only :slight_smile:

3 Likes

Perfect. I’ll add it to my doc then!

3 Likes

Yeah!!. It took me a long while to understand that userdata values are referenced by ‘=’ operator and not equated :wink:

3 Likes

Parent-child relation in particle fx

Did you know you can apply different modifiers to specified emitters by making a child-parent relation? :wink:

image

In that case Radial and Vortex modifiers won’t affects particles from emitter1.

9 Likes

Dynamic instance of watch - helper function

If you need to start a timer in a dynamically changing environment and you need to ensure, that you didn’t started any or overwrite the current ticking or cancel some countdown you can make use of this little wrapper module I wrote:

local WD = {}

local id = 0

function WD.create(delay, cb, repeat_flag)
	local instance = {
		delay = delay,
		cb = cb,
		repeat_flag = repeat_flag or false
	}
	id = id + 1
	instance.id = id
	instance.stop = function()
		if instance.handle and instance.handle ~= timer.INVALID_TIMER_HANDLE then
			timer.cancel(instance.handle)
			instance.handle = nil
			print("Stopped watch ", instance.id)
		else
			print("Stopping watch FAILED", instance.id, instance.handle)
		end
	end
	instance.start = function()
		instance.stop()
		instance.handle = timer.delay(instance.delay, instance.repeat_flag, instance.cb)
		assert(instance.handle ~= timer.INVALID_TIMER_HANDLE, "Starting watch FAILED - id:"..instance.id)
	end

	print("Created Watch with delay", delay)
	return instance
end

return WD

Example:

local watch = require "path.to.watch"

local zoom_to_normal = watch.create(2, function() msg.post(HERO_CAM, m.ZOOM, {zoom = m.NORMAL}) end)

-- when input released try to start a timer, which will change the zoom after 2 sec:
zoom_to_normal.start()

-- when input is pressed cancel the current countdown (if any):
zoom_to_normal.stop()

I’m using it to because even if I trigger some countdown in one state, the player could quickly change it (move further, so you’re leaving idle state), so I needed to ensure the callback won’t be called when I’m in different state - thus the .stop() method is useful.

5 Likes

Defold recently dropped support of Internet Explorer 11 for HTML5 builds. If you want to run your game on IE11 (but without sounds) then add this code to your engine_template.html right after the <body> tag.

Tested on Defold 1.2.167.

Changelog

2020-04-16 - Added “resume” implementation for AudioContext for Edge ≤18, Chrome <41, Firefox <40 browsers.

6 Likes

Typical random seed value of os.time() is not the best choice because between subsequent launches the seed value changes too little, resulting in similar random values.

There are better alternatives. The first is to get the seed value from a memory address of a temporary Lua table.

math.randomseed(tonumber(hash_to_hex(hash(tostring({}))):sub(4, 8), 16))

The second is to use the fractional part of socket.gettime()

math.randomseed(100000 * (socket.gettime() % 1))
20 Likes

Lua gamedev libraries

There are great libraries of gamedev focused Lua functions:

Lume:

Knife:

9 Likes

6 posts were split to a new topic: Defold and IE11 incompatibility

Defold Github repos

If you want to find people who already contributed to Defold and made a lot of libraries and extensions, here’s the github topic:

12 Likes

small tip:
upper and lower cases don’t work in Lua with cyrillic characters, so instead of :upper of Lua to use the method utf8.upper from extension defold-utf8 made by @d954mas (same with lower case)

6 Likes

Weighted random.

Usage:

local items_and_weights = {
  weapon = 2,
  armour = 1,
  health = 3,
  money = 7
}

local loot_items = weighted_random(items_and_weights, 2)
--> { 'money', 'health' }

local loot_item = weighted_random(items_and_weights)
--> 'weapon'

Code:

local function shallow_copy(orig) -- or your own shallow copying function
  local copy = { }

  for key, value in pairs(orig) do
    copy[key] = value
  end

  return copy
end

local function random(x, y) -- or your own random function
  math.randomseed(os.clock() * 10000000)
  _ = math.random()
  _ = math.random()
  _ = math.random()
  
  if x and y then
    return math.random(x, y)
  else
    return math.random()
  end
end

local function weighted_random(weights, results_count)
  local results_count = results_count or 1
  local weights = shallow_copy(weights)
  local results = { }

  for _ = 1, results_count do
    local pool = { }
    
    for key, weight in pairs(weights) do
      for _ = 1, weight do
        table.insert(pool, key)
      end
    end
  
    local index = math.random(1, #pool)
    local result = pool[index]
    table.insert(results, result)
    weights[result] = nil
  end
  
  return #results > 1 and results or results[1]
end
8 Likes

You can also require lume.lua for similar method:

9 Likes

When working with sprite cursor animations it can be useful to know what frame is showing by range in normalized cursor values.

Code:

local function frames_to_cursor_range(total_frames)
	local frame = 1 ; local max = total_frames ; local min = 0.0
	for i = frame, max, 1 do
		local range_min = ((frame - 1) - min) / (max - min) + 0.001
		local range_max = (frame - min) / (max - min)
		if i == 1 then
		range_min = 0.000
		end
		print("Frame_"..i.." Range( start:"..string.format("%.3f",range_min).." , end:"..string.format("%.3f",range_max),")")
		frame = frame + 1 
	end
end

Usage:

12 total frames of sprite animation would print.

frames_to_cursor_range(12)

12Framestocursorrange

9 Likes

As pre-hashing is highly recommended, instead of defining local variables for hashed values, you can utilize an amazingly clever solution by @sergey.lerg:

local hashed = require('hashed')
print(hashed.hello_world)
print(hashed.any_compatible_string)
print(hashed['any string with any characters'])

If you require it in one script, you can use the same values everywhere, so it is very convenient for example for animations or messages:

In one.script:

msg.post("#two", hashed.my_message, message)

in two.script:

if message_id == hashed.my_message then
   --do stuff
end
12 Likes

Regarding optimizations (but rather micro-optimizations) it is good to localize as much as possible. I saw in some Lua libraries localized versions of used globals and this is pretty clever, especially when it comes to some commonly used ones or ones used in update() functions!

local table_insert = table.insert
local table_remove = table.remove

or even only table:

local table = table
local pairs, ipairs, print = pairs, ipairs, print

for i,v in pairs(some_table) do
   table.insert(i)
   print(v)
end

etc.
So when you actually overwrite the global with your local, you don’t need to change each call in your code then :wink:

4 Likes

I like one idea from Monarch for logging handling, so when you have disabled debugging, the log function looks like this:

local function log(...) end

But when you enable debug, by calling monarch.debug(), you change the function to:

function M.debug()
    log = print
end

So you basically only then assign any function to log()

This way, anywhere in the module you write:

log("something to print")

You print it only after enabling debug, otherwise it is just a very quick, almost neglectable call to empty function :heart_eyes:

3 Likes

Usually we use several fonts in our games to nicely display different numerical information (damage value, speed dial and so on). And Defold includes all ASCII characters in fonts. However, for this case, it doesn’t make sense to store unused glyphs in the fonts . Especially if it’s a web game where the download size matters.

We deal with this by using FontForge, leaving only the necessary glyphs in the font. And, to simplify the task, I made a small online tool - https://jsbin.com/molahek

So, how to keep only the necessary glyphs in the font:

  1. Open the tool, insert text or character list. The tool will leave only unique characters and generate an FF script.
  2. Download FontForge and install it.
  3. Open a font.
  4. Run the generated FF script, i.e. click “File” / “Execute Script”, then paste the script, select “FF”, click “OK”.
  5. Save the font, click “File” / “Generate Fonts”.

The difference in file size will be approximately like that:

Tip: do this only at the final stages of the project development.

25 Likes