[From Gideros] Generate spinning rays dynamically

#1

Hey everyone!
This is my first post on the forum after messing around with Defold for the first time after 6 years with Gideros.

First impressions? Great! But… a lot different. I spent the last 3 days following tutorials and trying to understand how the new engine works. I think that I’ve got a rough idea on how things work (at least the basic basics), my objective is to port one of my unfinished projects from Gideros to Defold.

Obviously I can’t just copy and paste all the code from one engine to another and I’m starting slowly by implementing small and simple objects that will be included in the final game. I hope to learn and work on the project at the same time.

Now, coming to the topic’s title. The objective is to create something like that:

Nothing fancy, just some shapes rotating clockwise and counterclockwise. The way I did in Gideros was to create a class GraphicRays and then generate N shapes with some simple math (not the most efficient nor best way, but it works nicely). Then a timer would take care of the rotation.

for i=1, self.rays*2 do
		if(i%2==0)then
			local current1Degrees=math.rad(i*self.degreesPerRay/2);
			local current2Degrees=math.rad((i+1)*self.degreesPerRay/2);
			local val1X=self.radius*math.cos(current1Degrees)
			local val1Y=self.radius*math.sin(current1Degrees)
			local val2X=self.radius*math.cos(current2Degrees)
			local val2Y=self.radius*math.sin(current2Degrees)
			
			local tempShape=Shape.new()
			tempShape:setFillStyle(Shape.SOLID, self.color)
			tempShape:beginPath()
			tempShape:moveTo(0,0)
			tempShape:lineTo(val1X, val1Y)
			tempShape:lineTo(val2X, val2Y)
			tempShape:closePath()
			tempShape:endPath()

			self.shapes[i/2]=tempShape
			
			self.raysContainer:addChild(self.shapes[i/2])
		end
	end

In Defold I attempted the following “implementations” (from the “worst” to the ideally best):

  • Use a pre-rendered image of the rays and just rotate it in a script. (Works but it’s not memory efficient and the number of rays can’t be changed)
  • Use a pre-rendered image of one slice of the rays, use a factory to copy the sprite to multiple angles and then rotate all the sprites (kind of works, more efficient but the width of the rays can’t be changed without stretching the single ray)
  • Use generated ray as I did in Gideros (couldn’t find a way to draw the shapes dynamically, the only solution I found is to message the renderer with draw_line but people said that it should be used for debugging only: How to draw rectangles, circles)

What I’m trying to achieve:

  • The number of rays should be decided when the object is initialized.
  • The rays rotation speed should be changed on the fly (easy, done with messages).
  • The rays color should be editable (easy? Rotate the hue of the sprites).

What approach would you choose to solve this “problem”? I’m still in the Gideros-way of thinking and I really want to open my mind the new possibilities Defold offers.

Thank you,
Crypto

0 Likes

#2

I think you’re on the right track. Defold as an engine is really optimised for sprite handling.
I would use your method n. 2, but as a fourth method you could try the draw pixel extension, to build your rays:

1 Like

#3

You could do this with a fragment shader on a quad too.

0 Likes

#4

On your example it seems like it is a “on screen effect” meaning you can do this in gui.

You can do this with a pie node by setting the “Pie Fill Angle”.

Gui scene setup

Create a box node and name it root, set the size to (0, 0, 0)
Create a pie node under root and name it prototype, set the so it covers the screen
You might also want to change he Blend Mode

Script

Calulate the Pie Fill Angle by dividing how many segments you want with 360.
Then for every other segment create a clone, set the Pie Fill Angle and rotate it.
You can then simply rotate the root.

5 Likes

#5

Yeah. I’d do it in a fragment shader as well. It’s probably the easiest solution, it doesn’t use memory at all and it’s GPU-accelerated. The math shouldn’t really be that hard to do either.

1 Like

#6

Thanks to everyone for the suggestions. I’m currently trying @Jerakin’s solution and it seems to work pretty well, the rays can be generated dynamically.

local function generate_rays(node_prot, n_rays, alpha)
	local rays_deg=360/(n_rays*2)

	gui.set_fill_angle(node_prot, rays_deg)
	gui.set_perimeter_vertices(node_prot, 4)

	local color=gui.get_color(node_prot)
	color.w=alpha
	gui.set_color(node_prot, color)

	for i=1,n_rays-1 do
		local node=gui.clone(node_prot)
		gui.set_rotation(node, vmath.vector3(0, 0, i*2*rays_deg))
	end
end

function init(self)
	generate_rays(gui.get_node("ray_prototype_0"), 12, 0.6)
	generate_rays(gui.get_node("ray_prototype_1"), 16, 0.2)

	local root=gui.get_node("root_0")
	gui.animate(root, gui.PROP_ROTATION, vmath.vector3(0, 0, -360), gui.EASING_LINEAR, 60, 0, nil, gui.PLAYBACK_LOOP_FORWARD) -- start animation

	local root=gui.get_node("root_1")
	gui.animate(root, gui.PROP_ROTATION, vmath.vector3(0, 0, 360), gui.EASING_LINEAR, 30, 0, nil, gui.PLAYBACK_LOOP_FORWARD) -- start animation
end

One problem that I’m currently facing is that (for what I understand) the GUIs are rendered in front of all the other elements. Following this thread I managed to make them render in the background, however now all the GUIs elements get drawn behind the other elements.

Is there a way to selectively render one GUI only in the background?

0 Likes

#7

There is a few ways and the best way depends on some factors.

Are you mixing game objects and gui? As in do you want to draw Game Object -> Gui -> Game Object?

If that is the case you wil have to change the render script. You can create a new material with a new tag, assign a render predicate and draw it in the correct place.

Do you want to draw this Gui scene behind another gui scene? Gui -> Gui -> Gui

gui.set_render_order is what you are looking for then

Are all your gui elements in the same scene?

You can then control which order they draw in with the heirachy (and layers)

3 Likes

#8

You need to use a DAE model of a single ray (triangle) and use factory.create() to spawn as many as you need. You can have several models with different ray width or use vertex shader to define the width at runtime.
I think this is the most effective and accurate way of doing it.

1 Like

#9

Thank you! I ended up following this suggestion and it works! Here are the steps on how to do it (in case someone in the future faces the same issue)

How to draw a specific GUI in the background behind the other elements

Steps:

  • Copy the gui material (from the builtins materials) and name it “gui_bg”.
  • Change the ‘name’ and the ‘tag’ from gui to gui_bg. (Don’t modify the vertex and fragment references).
  • Assign the material to the GUI that needs to be in the background.
  • In the render script add a couple of lines.
    • In the init function add:
...
self.gui_bg_pred = render.predicate({"gui_bg"})
...
  • In the update function, before the -- render tiles comment add:
-- render Background UI
--
render.set_view(vmath.matrix4())
render.set_projection(vmath.matrix4_orthographic(0, render.get_window_width(), 0, render.get_window_height(), -1, 1))

render.enable_state(render.STATE_STENCIL_TEST)
render.draw(self.gui_bg_pred)
render.draw(self.text_pred)
render.disable_state(render.STATE_STENCIL_TEST)
2 Likes

Can I change the render order of a node?
#10

I’m in a similar situation, welcome! I might have uncovered some Gideros <> Defold solutions, so feel free to ask. As for the rays… I’d just do it with bitmaps. :slight_smile: Or GUI, like someone suggested.

2 Likes

#11

Thanks a ton!

I was following your steps and just want to add a little thing in the final steps in case someone else needs to draw GUI in the background (most likely me in the future).

In the step where you add couple of lines to the render script, you properly wouldn’t able to edit the render script. What you could do instead is:

  • Copy the folder render to another place outside builtin folder
  • Edit the following files in your new render folder instead:
    default.render: change Script to the script within the new render folder.
    – Edit the script inside the new render folder as @Crypto said and save the results.
  • In game.project, change Render to the default.render in your new render folder.
3 Likes

#12

Good catch! :wink:

I thought it was obvious but it’s always better to add more than less information.

0 Likes

#13

It’s a little bit embarrassing since it’s so obvious but I followed your steps ~5 separate times in the past few months and still couldn’t get it working.

Not until today while I was reading the document about the render pipeline for the n-th times do I realize what I missed :joy:

Turn out I never change the Script property in the new default.render file before so it still refers to the old unchanged render.script file.

Therefore I added little more information here to prevent myself from making the same stupid mistake again in the future cuz I know I will certainly go back to this post to look for your answer!

2 Likes