ActorScroller & ScreenSelectMaster - RhythmLunatic/stepmania GitHub Wiki
ScreenSelectMaster is a built in screen that's used to display menus such as the title screen, play mode, and style select.
There is also ActorScroller, a similar lua based Actor that behaves mostly the same.
ScreenSelectMaster
This is much easier to implement than ActorScroller but doesn't give you full control.
Pros:
- Input handling already works
- Easier and usually quicker to implement.
- GainFocusCommand and LoseFocusCommand works for any actor in the scroller.
Cons:
- Can only have one.
- Limited to executing gamecommands when you press start. (This can kind of be worked around, see ceveats section)
To get started, simply define a new screen then set fallback as ScreenSelectMaster. Here is a basic scroller you can copy and paste:
[ScreenSelectModeCustom]
Fallback="ScreenSelectMaster"
PrevScreen="ScreenTitleMenu"
NextScreen=Branch.StartGame()
#Change these to your own choices! If you need help, check the gamecommand wiki page or look at other themes.
DefaultChoice="Game"
ChoiceNames="Game,Freestyle,Customize"
ChoiceGame="applydefaultoptions;text,GAME MODE;setenv,GameMode,arcade;screen,"..Branch.StartGame()
ChoiceFreestyle="applydefaultoptions;text,FREE STYLE;setenv,GameMode,freestyle;screen,ScreenSelectPlayMode"
ChoiceCustomize="screen,ScreenCustomize;text,CUSTOMIZE"
ShowScroller=true
WrapScroller=true
LoopScroller=true
ScrollerSecondsPerItem=0.2
ScrollerX=SCREEN_CENTER_X
ScrollerY=SCREEN_CENTER_Y
ScrollerTransform=function(self,offsetFromCenter,itemIndex,numItems) \
self:x(offsetFromCenter * SCREEN_WIDTH); \
end;
ShowIcon=false
UseIconMetrics=false
PerChoiceScrollElement=false
PerChoiceIconElement=false
There's a lot of stuff here, so scroll down to the Glossary and come back to find out what it all means. Or keep reading. Either way is fine.
Now that you've copypasted that into your metrics.ini (probably), you can add graphics for the scroller to display.
The name of the Screen is ScreenSelectModeCustom and PerChoiceScrollElement is false, so all the scroller items will be loaded from Graphics/ScreenSelectModeCustom scroller.
Of course you don't want the same item to be loaded for every choice, so here is the standard trick everyone uses:
- Create a folder named "ScreenSelectModeCustom scroller" in Graphics.
- Inside the folder's default lua, put this in:
local gc = Var("GameCommand");
return Def.ActorFrame{
LoadActor(gc:GetName());
}
- Create lua files or graphics that match the name of the choice and put them inside the ScreenSelectModeCustom scroller folder (where ScreenSelectModeCustom is the name of your screen, of course). Ex. Freestyle.lua, Customize.lua, Game.lua.
- I don't know where else to put this so I'm putting it here. All children actors of a ScreenSelectMaster derived screen can use GainFocusCommand and LoseFocusCommand. These play when you select/deselect the item in the scroller, of course.
- If you would like icons to go along with the scroller such as a description for each choice, set ShowIcon=true. Then it will load from "Graphics/ScreenSelectModeCustom icon choice" where ScreenSelectModeCustom is replaced by the name of your screen. Note that icons do not change position when you scroll.
scroller choice actor example
return Def.ActorFrame{
LoadActor("Cool_Backing_Icon");
--Text
LoadFont("Common Normal")..{
--[[
Warning: This is not localization safe.
If you want to translate it into other languages, use
Text=THEME:GetString("ScreenTitleMenu",gc:GetText());
instead and set the text in metrics to match
]]
Text=gc:GetText();
InitCommand=cmd(diffusealpha,0;shadowcolor,Color("Black");shadowlength,1);
OnCommand=cmd(addx,-300;sleep,index/8;decelerate,.2;addx,200;);
GainFocusCommand=cmd(stoptweening;linear,.1;addx,5;diffuse,Color("HoloBlue"));
LoseFocusCommand=cmd(stoptweening;linear,.1;addx,-5;diffuse,Color("White"));
};
}
Commands Glossary
All commands can also be used inside metrics if UseIconMetrics=true
in metrics.
Command Name | When it's activated |
---|---|
GainFocus | Run when the icon gains focus |
LoseFocus | Run when the icon loses focus |
SwitchToPageX | Run when pages are switched (For MAX~EXTREME style themes) |
LostSelectedLoseFocus | ??? |
Change | ??? |
InitialSelection | Only played when DOUBLE_PRESS_TO_SELECT is true. Maybe plays on first press? |
OffFocused OffUnfocused | Plays when the screen activates its 'off' state. |
Metrics Glossary
Metrics values | type | What they do |
---|---|---|
WrapCursor | bool | Allows you the cursor to loop back to the first item after reaching the end. |
ScrollerSecondsPerItem | float | The time it takes for your scroller to move from one selection to another. If set to 0, the scroller will not move. |
ScrollerNumItemsToDraw | int | Number of items to draw. duh. |
ScrollerTransform | Transform Function | The scroller transform function. |
ScrollerSubdivisions | float | Normally the scroller will only calculate the next point for the actor to scroll towards in the TransformFunction. Setting this to a value above 1 will add a point for it to tween towards before stopping at the end point. (In English: you can make circular strollers with it.) |
ShowIcon | bool | If the scroller should load additional actors from Graphics/ScreenName Icon Choice (For example, ScreenSelectPlayMode icon Choice). Unlike scroll choice, icons do not change position when you scroll. Also read the description for PerChoiceIconElement. |
ShowCursor | bool | If the scroller should load additional actors from Graphics/ScreenName CursorPX (For example, ScreenSelectDifficulty CursorP1 and ScreenSelectDifficulty CursorP2). This seems to be for the difficulty select in MAX~EXTREME style themes. |
ShowScroller | bool | If the scroller should load actors from Graphics/ScreenName Scroll Choice (For example, ScreenSelectPlayMode Scroll Choice). Setting this to false would defeat the purpose of your scroller... Also read the description for PerChoiceIconElement. |
WrapScroller | bool | Changes the appearance of the WrapCursor metric if it is set to true. What this option does specifically is unknown, but having WrapScroller and LoopScroller on will make the cursor loop forever instead of jumping back to the start with WrapCursor enabled. |
LoopScroller | bool | Changes the appearance of the WrapCursor metric if it is set to true. What this option does specifically is unknown, but having WrapScroller and LoopScroller on will make the cursor loop forever instead of jumping back to the start with WrapCursor enabled. |
ScrollerX ScrollerY | float | Take a wild guess. |
PerChoiceIconElement | bool | If this is true, instead of loading 'Graphics/ScreenName Icon Choice' for every choice, it will instead load 'Graphics/ScreenName Icon ChoiceNAME_OF_CHOICE', where NAME_OF_CHOICE is the name of the choice defined in ChoiceNames of your metrics. I really recommend keeping this false since it's unnecessary and makes your theme very messy. |
PerChoiceScrollElement | bool | Same as above except for 'Graphics/ScreenName Scroll Choice'. I really recommend keeping this false since it's unnecessary and makes your theme very messy. |
SharedSelection | bool | If there are one or two cursors. I think ShowCursor has to be true for this to work. |
ChoiceNames | string | A string containing the choices you want in your scroller. For example: "Game,Freestyle,Customize" |
ChoiceXXXX | gamecommand | Where XXXX is a choice you defined with ChoiceNames. The command set here will be executed when pressing start. For example, ChoiceFreestyle="applydefaultoptions;text,FREE STYLE;setenv,GameMode,freestyle;screen,ScreenSelectPlayMode" |
IconChoicePosFunction | function | Unknown purpose. Seems to be a function that returns a table of positions when given the number of choices. Perhaps used to position differently depending on number of elements in the scroller (Like IIDX' mode select screens?) |
There are other metrics values available but it's unlikely you'll need them unless you're an advanced themer or trying to do something from the MAX~EXTREME era.
Old metrics you probably don't need | type | what they do |
---|---|---|
DoSwitchAnyways | bool | ??? |
AllowRepeatingInput | bool | ??? |
OverrideSleepAfterTweenOffSeconds | bool | Unknown purpose, false by default. |
SleepAfterTweenOffSeconds | float | Unknown purpose. 0 by default. |
NumCodes | int | Unknown Purpose |
OptionOrderUp, OptionOrderDown, OptionOrderLeft, OptionOrderRight, OptionOrderAuto | ??? | ??? |
PreSwitchPageSeconds | float | Controls the animation for multi page scrollers (DDR EXTREME style) |
PostSwitchPageSeconds | float | Controls the animation for multi page scrollers (DDR EXTREME style) |
NumChoicesOnPage1 | int | This is for the old DDRMAX~EXTREME style difficulty select where there were two pages. |
UseIconMetrics | bool | Just... Don't ever touch this. Don't use it. It's insane. |
CursorPXOffsetXFromIcon CursorPXOffsetYFromIcon | float | Where PX is P1, P2... You need to define it for all the players. |
DisabledColor | color | I have no idea what this does. |
DoublePressToSelect | bool | Take a wild guess. |
IconChoiceXSwitchToPage1Command IconChoiceXSwitchToPage2Command IconChoiceXOnCommand IconChoiceXOffCommand IconChoiceXOffFocusedCommand IconChoiceXOffUnfocusedCommand | Command (You know, like cmd()) | Where X is the icon index. The same as using commands in a lua scroller. |
IdleCommentSeconds | float | For the announcer. |
IdleTimeoutSeconds | float | ??? |
UpdateOnMessage | MessageCommand? | Possibly subscribes to a broadcast? |
Caveats when using ScreenSelectMaster scrollers
Scroller icons draw below scroller choices
There is a fix for this but it's not easy. Disable icons in metrics. Then move all your icons to the overlay of your screen in BGAnimations, for example ScreenSelectStyle overlay.
Then inside your default.lua (ex. BGAnimations/ScreenSelectStyle overlay/default.lua) do:
local t = Def.ActorFrame{}
--This should be declared outside of an ActorFrame, InitCommand, OnCommand, etc. Right below the 'local t' declaration is a good place to put it.
local ChoiceNames = split(",",THEME:GetMetric(Var("LoadingScreen"),"ChoiceNames"))
--Insert this for loop wherever you want it to display, if you want it to display on top of everything then put this at the end.
for i,text in ipairs(ChoiceNames) do
t[#t+1] = LoadActor("Choice_"..text)..{ --Ex. Choice_Easy, Choice_Medium, Choice_Hard
InitCommand=cmd(Center);
MenuSelectionChangedMessageCommand=function(self)
if SCREENMAN:GetTopScreen():GetSelectionIndex(GAMESTATE:GetMasterPlayerNumber())+1 == i then
--Do whatever you would do in GainFocusCommand here
self:visible(true)
else
--Do whatever you would do in LoseFocusCommand here
self:visible(false)
end;
end;
};
end;
return t;
can't execute lua
Add an OffCommand to your screen's overlay layer:
t[#t+1] = Def.ActorFrame{
OffCommand=function(self)
--This assumes you already have the variable 'local ChoiceNames', if you don't then copy paste it from the above example
local choice = ChoiceNames[SCREENMAN:GetTopScreen():GetSelectionIndex(GAMESTATE:GetMasterPlayerNumber())+1]
if choice == "Easy" then --Replace Easy with the choice you defined in metrics, of course.
--Do something here...
elseif choice == "Arcade" then
--Do something here...
end
end
}
ActorScroller
Using ActorScroller instead of ScreenSelectMaster provides some benefits (along with some drawbacks).
Pros:
- Can have as many scrollers in one screen as you want. For example you can show a popup actorscroller asking if you want to play the tutorial when they select a choice.
- Can jump more than one value at a time.
- Can execute lua. In fact, you can do whatever you want when start, up, down, etc is pressed since the input handler is unrelated to the ActorScroller.
Cons:
- Must implement your own input handler (Don't worry, the example tells you how)
- Must reimplement GainFocus and LoseFocus (Don't worry, the example tells you how)
- For tweens to work, actors must be wrapped in an ActorFrame.
- Scroller items don't know their own index. Can be somewhat dealt with by assigning them an index in a for loop.
There is a Basic example in the Docs folder of StepMania to get you started.
Additionally all the constructors for an ActorScroller are documented in the wiki.
I won't explain the constructors or the example since you should read it yourself, but I'll provide an example with input handling and GainFocus/LoseFocus taken care of.
Step 1: Add input handling in metrics
This example will use the 'simple' input handling of CodeMessageCommand. Paste the CodeNames section in your custom screen and edit it accordingly. For this example the custom screen is called "ScreenSelectChapter".
[ScreenSelectChapter]
#Your standard stuff that every screen has. Since this is a custom screen, Fallback="ScreenWithMenuElements".
Fallback="ScreenWithMenuElements"
PrevScreen="ScreenTitleMenu"
NextScreen="ScreenSelectDifficulty"
#This is the part you want to copy and paste.
CodeNames="Left,Right,Down,Up,Select,Start,Back"
CodeLeft="Left"
CodeRight="Right"
CodeDown="Down"
CodeUp="Up"
CodeSelect="Select"
CodeStart="Start"
CodeBack="Back"
Step 2: Adding the scroller
Now declare your scroller somewhere in a screen layer. This example uses "ScreenSelectChapter overlay".
--The thing you want to be displayed in the ActorScroller. It has to be a table because I said so.
local scrollerItemTable = {"Item 1", "Item 2", "Item 3", "Item 4"}
--Declare your ActorScroller.
local as = Def.ActorScroller{
--Like I said, the example and the wiki tells you everything you need to know about these constructors.
NumItemsToDraw=0, --DON'T LEAVE THIS AT 0!!!! You can use NumItemsToDraw=(#scrollerItemTable)*2 if you want.
SecondsPerItem=.3, --If you change this to 0, the scroller will not move.
TransformFunction=function(self,offset,itemIndex,numItems)
self:y(80*offset);
end,
-- Scroller commands. This is an example InitCommand.
InitCommand=cmd(rotationz,15;xy,325,SCREEN_CENTER_Y;SetFastCatchup,true),
--This is the input handler for the scroller.
CodeMessageCommand=function(self, param)
if param.Name == "Up" or param.Name == "Left" then
if self:GetDestinationItem() > 0 then
self:SetDestinationItem(self:GetDestinationItem()-1);
SOUND:PlayOnce(THEME:GetPathS("Common", "value"), true);
end;
elseif param.Name == "Down" or param.Name == "Right" then
if self:GetDestinationItem() < self:GetNumItems()-1 then
self:SetDestinationItem(self:GetDestinationItem()+1);
SOUND:PlayOnce(THEME:GetPathS("Common", "value"), true);
end;
elseif param.Name == "Start" then
SCREENMAN:GetTopScreen():StartTransitioningScreen("SM_GoToNextScreen");
elseif param.Name == "Back" then
SCREENMAN:GetTopScreen():StartTransitioningScreen("SM_GoToPrevScreen");
end;
--GainFocus and LoseFocus aren't part of ActorScroller, they're part of ScreenSelectMaster. So we'll reimplement them.
for i=1,self:GetNumItems() do
-- C++ is 0-indexed while lua is 1-indexed. So 0 is the minimum for an ActorScroller even though lua items start at 1.
if self:GetDestinationItem() == i-1 then
self:GetChild(i):playcommand("GainFocus")
else
self:GetChild(i):playcommand("LoseFocus")
end;
end;
end
}
Now below your scroller declaration, use a for loop to add the children to the scroller:
for i,v in ipairs(scrollerItemTable) do
--Need to wrap in an ActorFrame for GainFocus & LoseFocus to work properly.
as[#as+1] = Def.ActorFrame{
Name=i; --Do not forget this! You need to give the items an index for GainFocus and LoseFocus to work!
LoadFont("Common Normal")..{
Text=v;
GainFocusCommand=cmd(stoptweening;linear,.5;diffuse,Color("Red"));
LoseFocusCommand=cmd(stoptweening;linear,.5;diffuse,Color("White"));
};
};
end;
Finally, add the actorscroller back to the main ActorFrame...
t[#t+1] = as;