Text shadow effects

I’ve tried several approaches to getting text shadows, but none of them are working. I’m trying achieve text shadow effect like this: 46%20PM

The methods I’ve experimented with were based off this this thread How font cache works.

An easy way to do this offset type would be to create two text nodes (or more), and parent one to the other. Then make one the darker color, and then update all text nodes to whatever text you want.

3 Likes

I was trying to be clever thinking the only way was to use a shader (premature optimization…). Not sure why I never considered this simple solution. Thanks a bunch @Pkeod!

3 Likes

For those interested, heres a function I’ve made for this. It caches the generated shadow nodes so you can apply it to the same node with low performance cost.

-- should replace with an lru cache
local appliedNodes = {}

local function addShadow(parentPos, shadow, color, offset)	
	gui.set_color(shadow, color)
	local offsetPos = parentPos + offset
	gui.set_position(shadow, offsetPos)
	return shadow
end

local function setShadow(parent, textColor, layer, shadows)
	local root = parent
	local rootPos = gui.get_position(root)
	local id = gui.get_id(root)	

	local previouslyApplied = appliedNodes[id] ~= nil
	local t1 = nil
	if not previouslyApplied then
		-- remove color from parent since we'll be creating child nodes for the actual visual result
		gui.set_color(root, vmath.vector4(0))
		
        -- new text to proxy the root
		t1 = gui.clone(root)
		gui.set_inherit_alpha(t1, false)
		gui.set_parent(t1, root)
		gui.move_above(t1, root)
	end
	-- store generated gui nodes so we can reference them for future updates
	appliedNodes[id] = appliedNodes[id] or {
		t1 = t1,
		shadowNodes = {}
	}
	local shadowNodes = appliedNodes[id].shadowNodes
	t1 = appliedNodes[id].t1

	gui.set_layer(t1, layer)
	gui.set_color(t1, textColor)
	
	if shadows == nil then
		return
	end
	for i=1, #shadows do
		local color, offset = unpack(shadows[i])

		local previouslyRendered = shadowNodes[i] ~= nil
		local shadow = shadowNodes[i] or gui.clone(t1)
		shadowNodes[i] = shadow

		if not previouslyRendered then
			gui.set_inherit_alpha(shadow, false)
			gui.set_parent(shadow, root)
			gui.move_above(t1, shadow)
		end
		gui.set_layer(shadow, layer)
		addShadow(rootPos, shadow, color, offset)
	end
end

function init(self)
	self.r = 0
	self.dir = 1
end

function update(self, dt)
	-- change direction
	if self.r >= 1 then
		self.dir = -1
	elseif self.r <= 0 then
		self.dir = 1
	end
	
    -- change color over time
	self.r = self.r + (dt * self.dir)
	
	local node = gui.get_node("text")
	local textColor = vmath.vector4(self.r,1,1,1)
	local layer = "text"
	local shadowColor = vmath.vector4(self.r,.3,.3,1)
	local shadows = {
		-- south shadow
		{
			shadowColor,
			-- 2px down
			vmath.vector3(0, - 2, 1)
		},

		-- east shadow
		{
			shadowColor,
			-- 2px right
			vmath.vector3(2, 0, 1)
		},
	}
	setShadow(node, textColor, layer, shadows)
end
5 Likes