SetUpdateFunction & Advanced Animations - RhythmLunatic/stepmania GitHub Wiki

Better known as "trigonometry class in action".

These two examples use SetUpdateFunction, which is a function that takes two arguments: The actorframe that called this function and the time between the last time it was called, which is the time between each frame rendered onscreen.

A very simple example looks this:

local function updateFunction(self,delta)
    local actor = self:GetChild("Text"); --Obtain the actor named 'Text' defined in the ActorFrame below.
    if actor:GetX() > SCREEN_WIDTH then --If it goes offscreen, reset it
        actor:x(0)
    else
        --Move the actor towards the right at the rate at which StepMania is currently updating the screen
        actor:x(actor:GetX()+delta*10)
    end;
end;

return Def.ActorFrame{
    InitCommand=function(self)
         self:SetUpdateFunction(updateFunction);
    end;
    LoadFont("Common Normal")..{
         Name="Text";
         Text="Hello World!";
    };
}

Wavy text

This makes wavy text that scrolls across the screen from right to left. It has two parts: The function that animates the text (named updateFunction) and the function that generates the actors to be animated.

It's fairly simple: It runs through each actor in the frame. For each actor it takes the x value and moves it left at a constant speed (175 in this case), then uses sin() to calculate the y value. It's equivalent to generating a sine wave every frame by plotting all the points, except in this case it's a constantly moving range of points.

Of course, text isn't the only thing you can use it for.

local function updateFunction(self,delta)
	for n,child in pairs(self:GetChildren()) do
		local x = child:GetX()-(delta*175)
		if x < -1000 then
			x=x+SCREEN_WIDTH+1200
		end;
		local y = math.sin(x*math.pi/(SCREEN_WIDTH/4))*30
		child:xy(x,y)
	end;
end;

local function getWavyTextPlusRainbow(s,x,y)
	local letters = Def.ActorFrame{
		OnCommand=function(self)
			self:SetUpdateFunction(updateFunction)
			self:y(y);
		end;
	};
	local spacing = 16;
	for i=1,#s do
		local c = s:sub(i,i)
		letters[i] = LoadFont("venacti/_venacti_outline 26px bold diffuse")..{
			Name=i;
			Text=c;
			InitCommand=cmd(x,x-(#s)*spacing/2+i*spacing;rainbow;effectoffset,i*.1;);
		};
	end;
	return letters;
end;

Usage example: t[#t+1]= getWavyTextPlusRainbow("Greetings from RhythmLunatic",SCREEN_CENTER_X,SCREEN_CENTER_Y);

Circular Tween

Knowing how the "Unit Circle" works (I hope you remember that from high school), you can create circular animations. This one is synced to the menu timer to create a circular dot that completes one rotation every second.

--[[
x = x offset
y = y offset
r = radius of circle
Change the second and third argument of scale to the expected range of i. In this case it is a range from 0 to 1 because it's tweening based on the seconds in the timer.

If you don't want to tween based on timer, make i=0, i=i+delta then stop tweening when you have enough of i (so 10 for 10 seconds)
]]
local timer;
local function updateFunction(self,delta)
	local x, y, r = 0, 0, 36.5
	local i = timer:GetSeconds() % 1 --Obtain the decimal only. i is in the range from 0 to 0.999999999(...).
	local angle = scale(i,0,1,1,360) * math.pi / 180
	local ptx, pty = x + r * math.cos( angle ), y + r * math.sin( angle )
	self:GetChild("light"):xy(ptx,pty)
	self:GetChild("Numbers"):settext(math.floor(timer:GetSeconds()))
end;

return Def.ActorFrame{
	--Condition=PREFSMAN:GetPreference("MenuTimer");
	InitCommand=cmd(xy,THEME:GetMetric(Var("LoadingScreen"),"TimerX"),THEME:GetMetric(Var("LoadingScreen"),"TimerY"));
	OnCommand=function(self)
		timer = SCREENMAN:GetTopScreen():GetChild("Timer");
		assert(timer,"This screen doesn't seem to have a timer, so the lua timer cannot be loaded.");
		self:SetUpdateFunction(updateFunction)
	end;
	Def.Sprite{
		Texture=THEME:GetPathG("_timer","ring");
	};
	Def.Sprite{
		Name="light";
		Texture=THEME:GetPathG("_timer","light");
		InitCommand=cmd(blend,Blend.Add);
	};
	Def.Sprite{
		Texture=THEME:GetPathG("_timer","remaining");
		InitCommand=cmd(y,-25);
	};
	LoadFont("Timer Numbers")..{
		Name="Numbers";
		Text="99";
	};
};