Unity to Defold Survival Guide

Hi all,

I’m a Unity Refugee. We previously work exclusively on Unity 2D mobile games for the past 8 years.

We like Defold’s engineering philosophy, but we’re also finding it somewhat challenging to pick up. I’d love for other devs to share the concepts that threw you off and things that has helped.

I’ll start with 2 things that helped a bunch:

Starting tutorial
This is the end to end tutorial that I feel helped me the most.

I felt like the Defold tutorials on the website, doesn’t flow very well for me.

Coroutines

I’ll start with Coroutines. I had a really hard time with lua coroutines, trying to do something simple like this:

IEnumerator MyCoroutine()
{
	for(int i = 0; i < 10; i++)
	{
		yield return WaitForSeconds(0.1);
		Debug.Log("We did it!");
	}
}

Until we discovered ludobits
ludobits.flow allows very similar

local flow = require "ludobits.m.flow"

flow(function()
    for i = 1,10 do
		flow.delay(0.5)
		print("We did it")
    end
end)

Let’s save each other’s sanity! What other good tips have helped you with Defold?

6 Likes

I am not sure I understand completely the code you wrote. Never used coroutines with Defold. I would use the timer for simple tasks:

timer.delay(1.0, false, function() print("We did it!) end)

But maybe my suggestion is completely off…

Ciao!

1 Like

Thanks! Added the “for loop” for clarity.

In a loop context, why can’t a repeating or recursive function be used? These methods would be more lua-native and simple to work with in integrating other things than relying on lua coroutines which are rarely used because they are rarely needed.
Two possible methods:

--recursive method
function doThingNTimes(x)
> local n = x
> local function doThing()
> > print("thing done")
> > if n > 0 then timer.delay(0.5, false, doThing) else n = n - 1 end
> end
end
--timer cancel method


local n = 10
function doThing()
> --does thing
> if n > 0 then timer.cancel(t) else n = n - 1 end
end
t = timer.delay(0.5, true, doThing)

I know it is painful to change your habits especially after them working out for so long but if you take the time to learn the different flows and approaches in lua and Defold, it will be worth it and pay off in easier to read and easier to debug code!

1 Like

Lua coroutines are really useful and I would recommend that developers use them to sync up async operations such as http requests, animations, pathfinding and many more things.

https://www.lua.org/pil/9.1.html

7 Likes

Do you have some examples of using coroutines? I still can’t get my head around those things…

Here’s an example of how to create a patrol pattern for a game object with nice synchronous looking code:

https://defold.com/codepad/#?c=#cp_sprite&s1=DYewxghsAEBmCuA7MAXAliR0C2IBuApgBQS5IoA00AJvAE4TqYCUAUAJCiQxgjQC80XnRDx0iAgDo6SRGkQBzIm3YQAzmoJ0URXlQBEATVE54alEKgwUACzRq4SVBiywR2aAHc0t+dAhCICJi8gT6KlxQ0Ch8ggogkgoEKAD6AA4gaj4uytAA1P5kiCgckTC0DExYghWMLtBB0ACMpeBR1ATAEACeAtAADBwAtEPQ5hDa/lgQcth1mBzxkjNocyjE+pL6BhlZVdvQSwAKADIAgoYAQmcAwgDSKQDyAHI3AKIpAGKPAEoA6mcfgARKgxKhLN5nADKAElngBxFJwx4AVQAKgBFFGAtFUWpVPGdHpUBDIKrKDjQKnQEbQOgENTwbAEaI2FnCUTiFmeNlYWwslZreo2dRweT2NnUDjsDkhCTSBlM4i8FQERDUFS0tIQMws/lCej04qBYJc6DkNAwemM5lSmVBTmhSTdNCdagUtVS1hlRxk+qeCA+IgdLrdCJtHixE2O+UyRByRQU1QaLQ6PTQIwmbBmCzcax2Byk5yYODuLw+OzTaNysIqdDMuiSEM9YNE7okqCaElOckqe2mp3WpW6ECq9UqWVc52u4Dutie1iLn1FqrQbUoETACnUrx2YB6mQs6ggVg79i03CEaAAJn6/TXaAAHp0HHQ0AobBY/NexgReOq1FPal2EvYg8DWGxJEIVAggAZiIW9+ioJCBmYKhrzYM9aQDHw4EaEVgFgfxf3/KUzxwnR+kkABWTDgIvfAWSaO8H2fYAHGPTwsD8JoSMwMjgNAohwMYSDoJiOh4JQoZmJQ/o0OaOiqXYCiiCo2igOUhir0Q1iX2gfdYBKM8hJE2woL/CT4KGRDkOQhSMM06AVMDSiaKU5ztKYli0iffT4DSJyQMY4SIIsmDJLUqhZPs6KPJcoN1I8hcF1YFd6nFHRNEIzVRjjVkWXXTdfWLLB9HkLIOn0YjJ1CVpuECPpavlMB6UYYgipAYAJwdGsFRtZVR1YBcgA===

This bit which looks like nice synchronous code is powered by a coroutine which pauses execution of the while true do loop for each function it calls (move, wait, move, wait and so on):

local function patrol()
    while true do
    	-- move 200 pixels right in 2 seconds
    	move(vmath.vector3(200, 0, 0), 2)
    	-- wait for half a second
    	wait(0.5)
    	-- move 100 pixels down in 1 second
    	move(vmath.vector3(0, -100, 0), 1)
    	wait(0.5)
    	-- move 200 pixels left
    	move(vmath.vector3(-200, 0, 0), 2)
    	wait(0.5)
    	-- move 100 pixels up
    	move(vmath.vector3(0, 100, 0), 1)
    	wait(0.5)
    end
end
6 Likes

Thanks for all the ideas @Griffin and @britzl.

I wanted to provide additional context that I consider myself more of a “Technical Games Designer” than a software engineer. So what I’m saying might not feel right to a software engineer.

I have a tendency to think about how the next engineer or non-techncial designer will try to use what I write.

I definitely understand this:

In a loop context, why can’t a repeating or recursive function be used?

They’re functionally the same.

We use NodeJS for our backend, and started in the early days where we used a lot of callbacks.

The suggested pattern using Recursive does work, but from my experience in NodeJS, it also tends to very deep callback or staggered callbacks? (Hard to understand the flow, difficult to debug)

The Unity C# or the ludobits.flow approach tends to lead to more readable code. Which IMO leads to less bugs on bigger project.

local function patrol()
    while true do
    	-- move 200 pixels right in 2 seconds
    	move(vmath.vector3(200, 0, 0), 2)
    	-- wait for half a second
    	wait(0.5)
    	-- move 100 pixels down in 1 second
    	move(vmath.vector3(0, -100, 0), 1)
    	wait(0.5)
    	-- move 200 pixels left
    	move(vmath.vector3(-200, 0, 0), 2)
    	wait(0.5)
    	-- move 100 pixels up
    	move(vmath.vector3(0, 100, 0), 1)
    	wait(0.5)
    end
end

Similarly, for this code snippet, I worry that the true asynchronous nature being hidden in the “wait” function results in code that is easy to mis-use for the next programmer, reducing maintainability of the project.

Just wanted to say that I greatly appreciate the responses here. I’m hyper-aware that there WILL be pain coming from a different engine. And we are prepared to adapt.

I don’t yet feel I have a grasp of all the tools at our disposal to make a successful mid-sized project.

If others had pain points coming from Unity please do share!

2 Likes

I’m not sure I understand what you mean by this?

I’ll try to explain. But again I’m not a programmer in the most traditional sense. So please pardon me if I screw this up.

In Unity C# this is what the wait function will look like

IEnumerator Wait(float sec)
{
	yield return WaitForSeconds(sec);
}

In TypeScript it’ll look like this:

async function wait(sec: number)
{
	return new Promise(resolve => {
		setTimeout(function(){
			resolve(null)
		})
	})
}

By looking at the function signatures alone:

IEnumerator Wait(float sec)
async function wait(sec: number)

We’re guiding the users to the intended usage pattern.

In the lua version:

local function wait(delay)

We simply don’t know what this might do unless ask the user (e.g. non-technical designer OR technical artist) to go deeper.

It’s not an issue with Defold, but rather the way lua language is designed.

From a technical game design perspective, I think the “ludobits.flow” approach is likely going to be more user friendly for a project that might involve less technical folks.

1 Like

You mean the fact that it is an asynchronous function?

IEnumerator Wait(float sec)

If I saw this function I would not really be able to know that it was async, although I would guess that it is the case since I am not passing a callback function and the return value does not (at least to me) tell me anything of value either.

async function wait(sec: number)

This would at least tell me that it is an async function.

IEnumerator Wait(float sec)

I think the IEnumerator pattern is a very Unity C# specific pattern. 90% of the time when you see an IEnumerator function, in Unity, the usage pattern is:

// Unity C#
monobehavior.StartCoroutine(Wait(0.5f));

// TS
wait(0.5).then(() => {})

or in another coroutine / IEnumerator function, we’d:

// Unity C#
yield return Wait(0.5f);

// TS
await wait(0.5);

Thanks for taking the time to understand.

All I’m saying is that coming from Unity C# and Cocos Creator TS, the ludobits.flow pattern feels more intuitive.

2 Likes

For me the big “aha moment” was when I understood the philosophy of Defold: barebones game engine with extra stuff available as extensions. I was evaluating Defold based on built-in features and was surprised that they are minimalistic. Assets/extensions are first class citizens in Defold. Perhaps it would make sense to have examples of Druid and other popular assets on main Defold website so that they are more trustworthy? Dunno.

8 Likes

That’s exactly what I was looking for!

I’ve been searching for an engine like this for quite some time. I guess I came across Defold a few times, but I didn’t pay enough attention

Coming from Unity, I fantasized about really small game sizes, you know, only using what you need, why would I have idk 10-15 whatever extra mbs in build for stuff that I don’t use because maybe in my game I don’t use physics or whatever

6 Likes

Perhaps yes. We could possible link to Druid and similar very popular assets from the Defold manuals, and perhaps also more prominently highlight them on the website somehow.