Big List of Defold Pro Tips!

Hi, I’m new to defold. I just want to ask how should I go about using the render script you outlined above but having it work on a non-square resolution.

So far, I modified the set_projection call from

render.set_projection(vmath.matrix4_orthographic(0, RESOLUTION, 0, RESOLUTION, -1, 1))

to

render.set_projection(vmath.matrix4_orthographic(0, 160, 0, 284, -1, 1))

160x284 is my target render resolution (similar resolution to Downwell).

It seems to be working but the rendered image is “squished”, forcing it to be square. What should I modify to allow it to scale properly?

After much tinkering, I was able to find a solution. The solution was already there right in front of me: fixed_fit_projection.

So I just used that instead of the default call to stretch_projection. However there was still 1 problem, since the resolution that I wanted was 160x284, it means I have to set the display resolution to 160x284 in game.project to get the intended effect. That means the window looks very small.

I was able to get around that problem by replacing all calls to render.get_width() and render.get_height() with the base resolution that I want:

local WIDTH = 160
local HEIGHT = 284

--
-- projection that centers content with maintained aspect ratio and optional zoom
--
local function fixed_projection(near, far, zoom)
  local projected_width = render.get_window_width() / (zoom or 1)
  local projected_height = render.get_window_height() / (zoom or 1)
  local xoffset = -(projected_width - WIDTH) / 2
  local yoffset = -(projected_height - HEIGHT) / 2
  return vmath.matrix4_orthographic(xoffset, xoffset + projected_width, yoffset, yoffset + projected_height, near, far)
end
--
-- projection that centers and fits content with maintained aspect ratio
--
local function fixed_fit_projection(near, far)
  local width = WIDTH
  local height = HEIGHT
  local window_width = render.get_window_width()
  local window_height = render.get_window_height()
  local zoom = math.min(window_width / width, window_height / height)
  return fixed_projection(near, far, zoom)
end

So now, I can set the display resolution to anything in game.project and it will just fit the contents to the window.

I hope this helps other people looking for solutions to the same problem.

4 Likes

Thank you for sharing your solution. Nowadays most Defold devs add a dependency to RenderCam or Defold-Orthographic and call it a day. Those extension do more or less what you did but nicely abstracted away and with more added benefits such as camera effects, camera follow, zoom and so on.

8 Likes

Thank you for the advice! I’ll look into both RenderCam and Defold-Ortho. It seems that my current setup only works for games that just use the default camera (ie. no camera).

3 Likes

How cost effective is the hash function?

Would it make sense to create a hashed module that contains all actions for the game and load them at startup or load them in the init function of each game object/gui script?

Hash is fast, but it’s still more efficient to pre-hash everything. See this thread for other ways to do it Is calling hash() every time inefficient?

1 Like

Also, it’s good to keep all the pre-hashed values in variables so that you don’t accidentally misspell something.

3 Likes

Just little insight that maybe some newcomer didnt notice, in GUI when you want to sort like where the render first (Node), it must in order from the top to bottom list. To make sure you wanna move it, just press “alt + up” or “alt + down” to reorder the nodes…

1 Like

The above tip for rearranging nodes is good for simpler UIs. For more complex GUI scenes you’re likely to want to use hierarchies and layers.

How to fixed uncaught unexpected data size
Fixing uncaught unexpected data size in html build.
This error happened when you make a build in windows and them commit that html build it git. When git commit it replace line endings. So file size can be changes and runtime can’t load it.
To fix it you need:
1)add .gitattributes file to project root
2)in that file add “* binary”. It said to git that all files is binary add it will not change file endings. example

* binary

3)commit that file
4)fixed current endings https://help.github.com/articles/dealing-with-line-endings/

10 Likes

Line break
There is a very useful property for text node in gui:

image

and for regural game object’s label:

image

that allows to divide the text and flow it over several lines without additional coding. I’ve discovered it right now, so I’m posting it for the others overlookers :smiley: It is of course described in the documentation :stuck_out_tongue:

6 Likes

Module with prehashed message ids
As many of you know, it is a good practice to pre hash message ids, so for a generic solution prepare a module (e.g. msgs.lua) that just returns one table with key indexed hashes of message ids, like this for example:

return {
-- SYSTEM MESSAGES:
CONTACT = hash("contact_point_response"),	-- defold generated message, sent on one point collision of kinematic and dynamic objects
COLLISION = hash("collision_response"),		-- defold generated message, sent on collision
TRIGGER = hash("trigger_response"),				-- defold generated message, sent on trigger collision
ANIM_DONE = hash("animation_done"),				-- defold generated message, sent when a sprite animation is done
SET_PARENT = hash("set_parent"),					-- defold message to change parent for a game object
RAY_CAST = hash("ray_cast_response"),			-- defold message sent as a response to a raycast
WIN_RESIZE = hash("window_resized"),			-- defold message sent on window resize event
ENABLE = hash("enable"),									-- enable game object or componenet
DISABLE = hash("disable"),								-- disable game object or component

-- EXAMPLE MESSAGES:
TOUCH = hash("touch"),							-- popular input for mobile control
ANIM = hash("animate"),							-- play an animation (anim)
STOP_ANIM = hash("stop_animation"),	-- stop any animation
FLIP = hash("flip"),								-- flip a sprite
REGISTER = hash("register"),				-- register as a subsriber to receive all messages from a publisher
UNREGISTER = hash("unregister")			-- deregister form subscription list

}

and then use it after requiring globally once or locally per script, for example:

msgs = require "main.msgs"

like this:

if message_id == msgs.ANIM_DONE then ... end

Don’t name your module msg (it is used by Defold, e.g. msg.url()). Don’t send DISABLE message and then try to do something with a component/go - I spend whole day on finding bug, where I send DISABLE message because I tried to make something BEFORE deleting the object and was wondering why it is not perfomed and object disappers :confused: ANIM_DONE message is only sent after a animation that is not looped - so never wait for a looped animation to be done - use another variable to lock animation and unlock it if needed. WIN_RESIZE is useful when you want to manage different windowed appplication or mobile with horizontal and vertical GUI - you have to manage changes on your own and on this event it is possible. If you have any other useful messages or ideas to unify message system, share it :wink:

16 Likes

Shallow copying

Again, I’ve bumped into an unusual problem, that when realized could allow to make better programs in Lua. :smiley: I’ve created a table - event, in a Lua module - events and a script with such update function:

function update(self, dt)
	for key,state in pairs(events.event) do
		print(key, self.events[key], state)
		if self.events[key] ~= state then
			print("Event changed!", key, state)
			self.events[key] = state
			events.post(key, state)
		end
	end
end

I simply wanted to check if anyone, having access to that module, changed one of the entries. If so, I would like to inform subscribers that should be notified about the change - events.post(key, state)

The problem I had was, that self.events[key] was always the same as events.event[key] (== state if loop). Why? I copied the events.event table to self.events in init:

self.events = events.event

And that was a mistake. I didn’t need another variable having this events.event table, but I needed a copy of it, so I can detect the difference and then change it inside if statement.

I found Lua’s shallowcopy and deepcopy implementation
and it solved my problem! :smiley:

self.events = shallowcopy(events.event)
4 Likes

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