Skip to content

Custom States

Mily_0 edited this page Jun 14, 2026 · 2 revisions

placeholder feito por uma IA feito há uns meses ENQUANTO nn finalizo a versão boa!!!

caso queira ver, ela estava assim:

https://github.com/0mily/ViroViroIce-Engine/wiki/custom-state-novo-sla-porra#como-fazer-custom-states


Custom States Wiki

Guia de referencia para criar, abrir, sobrescrever e dirigir CustomState e CustomSubstate por Lua ou HScript na ViroViroIce.

Este documento foi montado a partir do codigo atual. Quando uma coisa depende de PlayState ou de um state compilado especifico, ela fica marcada como tal.


Regra de ouro

Existem dois fluxos diferentes:

  • CustomState puro: uma tela vazia criada por script. Use para galerias, ferramentas, telas extras e menus totalmente proprios.
  • State compilado com script por cima: um state Haxe existente, como MainMenuState, continua criando a tela base. O script so muda comportamento, layout, input e objetos.

No codigo atual, pack.json -> states aponta primeiro para um CustomState. Se a intencao for rodar o script por cima de um state compilado, o script precisa de forkState/baseState apontando para esse state.


Onde colocar

Estados:

mods/MeuMod/data/states/NomeDoState.lua
mods/MeuMod/data/states/NomeDoState.hx

Substates:

mods/MeuMod/data/substates/NomeDoSubstate.lua
mods/MeuMod/data/substates/NomeDoSubstate.hx

Caminhos antigos aceitos:

mods/MeuMod/data/scripts/states/NomeDoState.lua
mods/MeuMod/data/scripts/substates/NomeDoSubstate.lua

O CustomState usa states/ como pasta interna. O CustomSubstate usa substates/.


Abrindo States

openCustomState(name, data)

Lua:

openCustomState('GalleryState')
openCustomState('GalleryState', {from = 'main-menu'})

Abre sempre um CustomState com o nome informado. O segundo argumento vai para o campo data do state.

Dentro do Lua do state, leia com:

local data = getProperty('data')

No HScript do state, data tambem e um campo do state:

trace(data);

switchState(name, args)

Lua:

switchState('MainMenuState')
switchState('states.MainMenuState')
switchState('options.OptionsState')
switchState('GalleryState')

O resolvedor tenta, nesta ordem:

  1. classe exatamente com o nome passado;
  2. states.Nome;
  3. options.Nome;
  4. se nada existir, cria CustomState.

args e usado como argumentos do construtor quando o alvo e uma classe Haxe. Para um CustomState criado por fallback, args nao vira data. Se precisar passar dados para um CustomState, use openCustomState(name, data) ou HScript com new CustomState(name, data).

loadState(name, args)

Parecido com switchState, mas usa MusicBeatState.loadState, pulando a transicao normal de entrada/saida.

reloadState(name, args)

Sem nome, recarrega o state atual:

reloadState()

Com nome, resolve e troca para o state informado:

reloadState('GalleryState')

resetState()

Recarrega o state atual pelo fluxo de MusicBeatState.resetState():

resetState()

HScript

MusicBeatState.switchState(new CustomState('GalleryState'));
MusicBeatState.switchState(new CustomState('GalleryState', {from: 'options'}));
MusicBeatState.loadState(new CustomState('GalleryState'));

pack.json E Aliases

Exemplo:

{
  "states": {
    "MainMenuState": "myMainMenu",
    "TitleState": "myTitle"
  }
}

Quando a engine tenta abrir MainMenuState, ela ve o alias e cria CustomState('myMainMenu').

Se myMainMenu for um state totalmente novo, nao precisa de mais nada.

Se myMainMenu deve rodar por cima do MainMenuState compilado, coloque no topo do script:

forkState = 'MainMenuState'

function onCreatePost()
    debugPrint('Agora estou rodando por cima do MainMenuState.')
end

Ou em HScript:

var forkState = 'MainMenuState';

function onCreatePost() {
    debugPrint('Agora estou rodando por cima do MainMenuState.');
}

Como o fork e resolvido

Antes de carregar o state de verdade, CustomState faz uma leitura de sondagem do script para descobrir o alvo base.

Ordem no Lua:

  1. variavel forkState;
  2. variavel baseState;
  3. funcao getState();
  4. funcao getBaseState().

Depois, se nada foi encontrado em Lua, a mesma ordem e tentada em HScript.

O retorno precisa ser uma string nao vazia. Se o nome normalizado for igual ao nome do proprio CustomState, o fork e ignorado. Se o alvo tambem resolver para outro CustomState, o fork e ignorado.

Durante a sondagem, o topo do arquivo roda. Evite efeitos colaterais no topo do script; coloque criacao de sprites, sons e input dentro de onCreate ou onCreatePost.


Como Scripts Sao Carregados

CustomState define multiScript = false. Na pratica, ele tenta carregar no maximo um caminho resolvido por linguagem.

Fluxo de ScriptedState:

  1. inicia HScript, se existir;
  2. registra a API Lua;
  3. chama onRegisterLuaAPI nos HScript carregados;
  4. inicia Lua, se existir.

Isso significa que .hx pode registrar callbacks ou funcoes antes do .lua rodar.

Se existirem Nome.hx e Nome.lua no caminho resolvido, os dois podem rodar: HScript primeiro, Lua depois.

Se nenhum script carregar, o CustomState abre um ErrorState. Nesse erro:

  • ACCEPT tenta recarregar o CustomState;
  • BACK volta para MainMenuState.

Ciclo De Vida

Callbacks genericos de states e substates scriptados:

Callback Quando roda Pode parar algo
onCreate() logo depois do script ser carregado nao
onCreatePost() depois do create() do state terminar nao
onUpdate(elapsed) antes do update base Function_Stop pula o update base daquele frame
onUpdatePost(elapsed) depois do update base nao
onDraw() antes do draw base Function_Stop pula o draw base e o onDrawPost
onDrawPost() depois do draw base nao
onStepHit(step) quando muda o step nao
onBeatHit(beat) quando muda o beat nao
onSectionHit(section) quando muda a section nao
onOpenSubState(subState) antes de abrir substate Function_Stop bloqueia
onClose() antes de close() em um substate/state scriptado Function_Stop bloqueia
onDestroy() antes de destruir scripts nao
onUpdatePresence(rpcDetails, rpcState) antes de atualizar Discord RPC Function_Stop bloqueia

No Lua, onOpenSubState recebe o nome do substate. No HScript, recebe o objeto do substate.

Constantes de retorno:

Function_Continue
Function_Stop
Function_StopLua
Function_StopHScript
Function_StopAll

Variaveis E Valores Padrao

Todo Lua recebe:

Nome Valor
Function_StopLua, Function_StopHScript, Function_StopAll, Function_Stop, Function_Continue constantes de controle
luaDebugMode false por padrao
luaDeprecatedWarnings true por padrao
version versao Psych/Mint exposta pelo menu
modVersion versao do fork
screenWidth, screenHeight tamanho da tela
buildTarget alvo da build
modFolder pasta do mod do script, quando detectada
scriptName caminho/nome do script
currentModDirectory mod atual
curStep, curBeat, curSection valores ritmicos inteiros
curDecStep, curDecBeat, curDecSection valores ritmicos decimais
DEF_nome defines de compilacao espelhados para Lua

Todo ScriptedState cria cameras padrao:

Nome Uso
camMain camera principal criada por PsychCamera
camGame alias da camera principal
camOther camera transparente extra
camHUD em state scriptado, aponta para camOther

Essas cameras ficam no mapa de variaveis do state. Em Lua, use:

setObjectCamera('sprite', 'hud')
setProperty('camGame.zoom', 1.2)

Nomes aceitos por funcoes de camera:

game, camGame, main, camMain, hud, camHUD, other, camOther

Variaveis que sao de PlayState

As variaveis abaixo aparecem em scripts de gameplay porque sao setadas por PlayState.implementLua. Elas nao sao garantidas em um CustomState puro:

mechanics, modchart, pixelRender, allowMiku, customScore, extra, stageUI
downscroll, middlescroll, framerate, ghostTapping, hideHud, antialiasing
timeBarType, scoreZoom, cameraZoomOnBeat, flashingLights, noteOffset
healthBarAlpha, noResetButton, lowQuality, shadersEnabled
noteSkin, noteSkinPostfix, splashSkin, splashSkinPostfix, splashAlpha

Tambem sao de gameplay: score, misses, hits, combo, health, nomes de personagem, strums, musica atual, dificuldade e semana.


Variaveis Do State

Lua:

setVar('tag', value)
local value = getVar('tag')

setVar grava no extraData do state atual. Objetos criados por Lua tambem sao guardados ali, entao getProperty('tag.x') funciona para tags salvas.

HScript:

setVar('tag', value);
var value = getVar('tag');
hasVar('tag');
removeVar('tag');

HScript tambem expoe:

game          // parentState
global        // mapa extraData do state
globalStatic  // mapa static de HScript
this          // instancia HScript

Como a macro de extraData existe nos objetos, variaveis criadas com obj.setVar('nome', valor) podem ser lidas como campos no HScript quando o interpretador resolver aquele objeto.


Funcoes De Script

Disponiveis em Lua dentro de states scriptados:

Funcao Uso
callScript(luaFile, funcName, args) chama funcao de outro Lua rodando
isRunning(scriptFile) verifica Lua ou HScript ativo
getRunningScripts() lista scripts Lua ativos
addLuaScript(luaFile, ignoreAlreadyRunning) inicia outro Lua
addHScript(scriptFile, ignoreAlreadyRunning) inicia outro HScript
removeLuaScript(luaFile) para um Lua
removeHScript(scriptFile) destroi um HScript
setOnScripts(varName, value, ignoreSelf, exclusions) seta variavel em Lua e HScript
setOnLuas(varName, value, ignoreSelf, exclusions) seta variavel em Lua
setOnHScript(varName, value, ignoreSelf, exclusions) seta variavel em HScript
callOnScripts(funcName, args, ignoreStops, ignoreSelf, exclusions, excludeValues) chama Lua e HScript
callOnLuas(...) chama so Lua
callOnHScript(...) chama so HScript
close() fecha o script Lua atual

luaFile/scriptFile pode vir com ou sem extensao. A busca passa por Paths.getPath, e tambem aceita caminho absoluto/existente.


Reflexao E Instancias

Essas funcoes deixam um CustomState montar telas sem precisar de classe Haxe propria:

Funcao Uso
getProperty(path, allowMaps) le campo do state/objeto
setProperty(path, value, allowMaps, allowInstances) seta campo
getPropertyFromClass(className, path, allowMaps) le campo static/classe
setPropertyFromClass(className, path, value, allowMaps, allowInstances) seta campo static/classe
getPropertyFromGroup(group, index, path, allowMaps) le membro de array/grupo
setPropertyFromGroup(group, index, path, value, allowMaps, allowInstances) seta membro
callMethod(path, args) chama metodo no state atual
callMethodFromClass(className, method, args) chama metodo static/classe
createInstance(tag, className, args) cria instancia Haxe e salva em tag
instanceArg(tag, className) passa uma instancia salva como argumento
addLuaSprite(tag, inFront) adiciona objeto salvo ao state
addInstance(tag, inFront) alias de addLuaSprite
addToGroup(group, tag, index) adiciona objeto a grupo/array
removeFromGroup(group, index, tag, destroy) remove de grupo/array
openSubstate(className, args) abre substate Haxe compilado

createInstance nao aceita tag com ponto ou colchete.


Sprites, Textos E Sons

Funcoes comuns para montar tela:

Grupo Funcoes
sprites makeLuaSprite, makeAnimatedLuaSprite, makeGraphic, loadGraphic, loadFrames, loadMultipleFrames
animacao addAnimationByPrefix, addAnimation, addAnimationByIndices, playAnim, addOffset, characterDance
tamanho/posicao screenCenter, setScrollFactor, setGraphicSize, scaleObject, updateHitbox
ordem getObjectOrder, setObjectOrder, removeLuaSprite
camera/blend setObjectCamera, setBlendMode
colisoes/pixel objectsOverlap, getPixelColor
texto makeLuaText, addLuaText, removeLuaText, setTextString, setTextSize, setTextWidth, setTextHeight, setTextAutoSize, setTextBorder, setTextColor, setTextFont, setTextItalic, setTextAlignment, getTextString, getTextSize, getTextFont, getTextWidth
som playMusic, playSound, stopSound, pauseSound, resumeSound, soundFadeIn, soundFadeOut, soundFadeCancel, getSoundVolume, setSoundVolume, getSoundTime, setSoundTime, getSoundPitch, setSoundPitch

makeLuaSprite, makeAnimatedLuaSprite, textos e widgets removem pontos do tag. Evite tags com ..

Em CustomState puro, quando usar addLuaSprite/addInstance, passar true em inFront adiciona direto no topo. O valor false tenta inserir antes de grupos de personagem quando existe PlayState.


Tweens, Timers E Cores

Funcao Uso
startTween(tag, vars, values, duration, options) tween generico
doTweenX/Y/Angle/Alpha/Zoom/Color tweens antigos por propriedade
cancelTween(tag) cancela tween salvo
runTimer(tag, time, loops) cria timer
cancelTimer(tag) cancela timer
FlxColor(color) cor por string
getColorFromName(color) alias
getColorFromString(color) alias
getColorFromHex(hex) hex sem #

Callbacks relacionados:

function onTweenCompleted(tag, vars) end
function onTimerCompleted(tag, loops, loopsLeft) end
function onSoundFinished(tag) end

startTween pode receber callbacks por options.onUpdate, options.onStart e options.onComplete, chamando funcoes Lua globais pelo nome.


Transicoes, Musica E Camera Helpers

Grupo Funcoes
state/musica resetState, loadSong, loadWeek, changeTransStickers
precache precacheImage, precacheSound, precacheMusic
tempo getSongPosition
camera scroll setCameraScroll, addCameraScroll, getCameraScrollX, getCameraScrollY
camera efeitos cameraShake, cameraFlash, cameraFade
posicoes getMidpointX, getMidpointY, getGraphicMidpointX, getGraphicMidpointY, getScreenPositionX, getScreenPositionY

Funcoes de gameplay como addScore, setHealth, triggerEvent, startCountdown, endSong, exitSong, noteTweenX e semelhantes sao registradas por implementGame, entao dependem de PlayState.instance. Nao trate essas funcoes como API segura de um CustomState puro.


Input, Mouse E Arquivos

Input:

keyboardJustPressed, keyboardPressed, keyboardReleased
keyJustPressed, keyPressed, keyReleased
mouseClicked, mousePressed, mouseReleased, getMouseX, getMouseY
anyGamepadJustPressed, anyGamepadPressed, anyGamepadReleased
gamepadAnalogX, gamepadAnalogY, gamepadJustPressed, gamepadPressed, gamepadReleased

Cursor:

setCustomCursor, reloadCustomCursor, resetCustomCursor

Save e arquivos:

initSaveData, flushSaveData, getDataFromSave, setDataFromSave, eraseSaveData
checkFileExists, saveFile, deleteFile, getTextFromFile, directoryFileList, getPath

Strings e random:

stringStartsWith, stringEndsWith, stringTrim, stringSplit
getRandomInt, getRandomFloat, getRandomBool

Cameras E Backdrops

Funcoes locais de ScriptedState:

addCamera(tag, bgColor, x, y, width, height, zoom, front)
setMainCamera(tag)
removeCamera(tag, destroy)

addCamera salva a camera em tag. setMainCamera troca o draw target principal para uma camera criada. removeCamera nao remove a camera que esta como principal.

Grid/backdrop:

addGridBackdrop(tag, cellWidth, cellHeight, width, height, velocityX, velocityY, color1, color2, alpha, x, y, repeatAxes)
setBackdropVelocity(tag, velocityX, velocityY)
removeBackdrop(tag, destroy)

repeatAxes aceita:

xy, x, horizontal, y, vertical, none

Runtime Shaders

Disponivel via callbacks locais de Lua quando shaders/runtime shaders existem na build:

initLuaShader
setSpriteShader, removeSpriteShader
setCameraShader, removeCameraShader
setWindowShader, removeWindowShader
doTweenShader
getShaderBool, getShaderBoolArray
getShaderInt, getShaderIntArray
getShaderFloat, getShaderFloatArray
setShaderBool, setShaderBoolArray
setShaderInt, setShaderIntArray
setShaderFloat, setShaderFloatArray
setShaderSampler2D, setShaderValue

As funcoes respeitam ClientPrefs.data.shaders e retornam false em plataformas sem suporte.


Outras APIs Globais

Dependendo dos defines da build, mais funcoes podem ser registradas:

Area Funcoes
achievements getAchievementScore, setAchievementScore, addAchievementScore, unlockAchievement, isAchievementUnlocked, achievementExists
Discord changeDiscordPresence, changeDiscordClientID
traducoes getTranslationPhrase, getFileTranslation
FlxAnimate makeFlxAnimateSprite, loadAnimateAtlas, addAnimationBySymbol, addAnimationBySymbolIndices

Aliases antigos ainda existem para compatibilidade:

addAnimationByIndicesLoop, objectPlayAnimation, characterPlayAnim
luaSpriteMakeGraphic, luaSpriteAddAnimationByPrefix, luaSpriteAddAnimationByIndices
luaSpritePlayAnimation, setLuaSpriteCamera, setLuaSpriteScrollFactor
scaleLuaSprite, getPropertyLuaSprite, setPropertyLuaSprite
musicFadeIn, musicFadeOut, updateHitboxFromGroup

Eles emitem aviso de deprecacao e devem ser trocados pelas funcoes novas indicadas no proprio aviso.


UI Widgets Lua

Widgets usam as classes PsychUI* e salvam o objeto pelo tag.

Box

makeLuaBox(tag, tabs, defSelect, width, height, x, y)
addLuaBox(tag, inFront)
addToBox(tag, box, tab)

Exemplo:

makeLuaBox('box', {'Main', 'Config'}, 'Main', 320, 180, 40, 40)
addLuaSprite('box', true)

addToBox adiciona qualquer objeto salvo no menu da aba.

Label

createLabel(spr, txt, box, tab)

Cria um FlxText em spr.x, spr.y - 15, largura 250, tamanho 9. Se box e tab forem informados, adiciona dentro da aba. Senao, adiciona no state alvo.

Button

makeLuaButton(tag, label, scaleX, scaleY, x, y)
addLuaButton(tag, inFront)
addButtonToBox(tag, box, tab)

Callback:

function onButtonPressed(tag) end

Input Text

makeLuaInputText(tag, input, size, width, x, y)
addLuaInputText(tag, inFront)
addInputTextToBox(tag, box, tab)
getInputTextString(tag)
isInputTextOnFocus()

Slider

makeLuaSlider(tag, label, min, max, defValue, width, x, y)
addLuaSlider(tag, inFront)
addSliderToBox(tag, box, tab)

Callback:

function onSliderChanged(tag, value) end

CheckBox

makeLuaCheckBox(tag, label, checked, hitbox, x, y)
addLuaCheckBox(tag, inFront)
addCheckBoxToBox(tag, box, tab)

Callback:

function onCheckBoxChecked(tag, checked) end

Numeric Stepper

makeLuaNumericStepper(tag, step, decimals, min, max, defValue, wid, x, y, isPercent)
addLuaNumericStepper(tag, inFront)
addNumericStepperToBox(tag, box, tab)

Callback:

function onNumericStepperChange(tag, value) end

Custom Substates

Lua:

openCustomSubstate(name, pauseGame, data)
closeCustomSubstate()
insertToCustomSubstate(tag, pos)

Dentro do script do substate:

closeSubstate()

HScript:

CustomSubstate.openCustomSubstate('ConfirmBox', true, {reason: 'quit'});
CustomSubstate.closeCustomSubstate();

pauseGame = true faz:

  • tenta setar paused = true no state atual;
  • persistentDraw = true;
  • persistentUpdate = false.

O substate usa a ultima camera da lista de cameras.

Callbacks no parent

Quando um CustomSubstate abre, o state pai recebe:

function onCustomSubstateCreate(name) end
function onCustomSubstateCreatePost(name) end
function onCustomSubstateUpdate(name, elapsed) end
function onCustomSubstateUpdatePost(name, elapsed) end
function onCustomSubstateDestroy(name) end

Tambem sao setados no parent:

customSubstateName
customSubstate    // HScript recebe o objeto; Lua recebe o nome via customSubstateName

Se nao existir arquivo de script para o substate, mas o state pai tiver algum desses callbacks, o substate ainda e considerado valido.

Se nao existir script nem callback pai, o substate imprime aviso em amarelo e fecha.


HScript Em Custom States

Imports/valores comuns predefinidos:

Type, File, FileSystem, FlxG, FlxMath, FlxSprite, FlxText, FlxCamera
PsychCamera, FlxTimer, FlxTween, FlxEase, FlxColor
PlayState, Paths, Conductor, ClientPrefs, CustomCursor
FunkinLua, Character, Alphabet, Note
CustomState, CustomSubstate, MusicBeatState, MusicBeatSubstate
controls, version, modVersion, modFolder, buildTarget
Function_Stop, Function_Continue, Function_StopLua, Function_StopHScript, Function_StopAll

HScript tambem recebe helpers de shader quando suportado:

FlxRuntimeShader, ErrorHandledRuntimeShader, CodenameRuntimeShader, CustomShader
addCameraShader, removeCameraShader, addWindowShader, removeWindowShader, ShaderFilter

Funcoes HScript disponiveis:

setVar(name, value);
getVar(name);
hasVar(name);
removeVar(name);
debugPrint(text, color);
getModSetting(saveTag, modName);
createGlobalCallback(name, func);
createCallback(name, func, funk);
addHaxeLibrary(libName, libPackage);

Lua pode executar HScript inline:

runHaxeCode([[
    function hello(name:String) {
        trace('hi ' + name);
        return name.length;
    }
]])

local size = runHaxeFunction('hello', {'Mily'})

runHaxeFunction so funciona depois de runHaxeCode inicializar o modulo Haxe daquele Lua.


States Compilados Com Extras

Se voce usa forkState para rodar por cima de um menu/state compilado, entram as APIs locais daquele state. Essas funcoes nao existem em um CustomState puro.

MainMenuState

Variaveis:

curSelected, curColumn, selectedItemName, menuItemNames, menuItemsGroup
bg, magenta, camFollow, EngineVerTxt, FNFVerTxt
storymode, freeplay, mods, options, credits, awards

Callbacks:

onInputUpdate(elapsed)
onBack()
onHighlighted(itemName, index, column)
onHighlightedPost(itemName, index, column)
onSelectItem(index)              -- Lua
onSelectItemPost(index)          -- Lua
onSelected(itemName, index, column)
onAccept(index)                  -- Lua

No HScript, alguns callbacks recebem o objeto MenuItem em vez de so nome/index quando o codigo usa callOnScriptsExt.

Funcoes Lua:

addItemMenu(item, imagePath, fps, column, insertAt)
removeItemMenu(item)
setItemOrder(orderArray)
hasBeatenSong(songName)
hasBeatenWeek(weekName)
changeMainMenuSelection(change, column)
acceptMainMenuSelection()

StoryMenuState

Variaveis:

curWeek, curDifficulty, selectedWeek, loadedWeeks
weekTextGroup, weekCharactersGroup, bgYellow, bgSprite

Callbacks:

onInputUpdate, onBack, onSelected, onAccept
onChangeDifficulty, onChangeDifficultyPost
onHighlighted, onHighlightedPost
onSelectItem, onSelectItemPost

Funcoes Lua:

changeStoryWeek(change)
changeStoryDifficulty(change)
acceptStoryWeek()
isStoryWeekLocked(name)
hasBeatenWeek(name)

FreeplayState

Variaveis:

curSelected, curDifficulty, selectedSong, songsList
scoreText, diffText, bg, bottomText

Callbacks:

onInputUpdate, onBack
onMusicPlayer, onMusicPlayerPost
onSelected, onAccept
onChangeDifficulty, onChangeDifficultyPost
onHighlighted, onHighlightedPost
onSelectItem, onSelectItemPost

Funcoes Lua:

changeFreeplaySelection(change, playSound)
changeFreeplayDifficulty(change)
setFreeplayBottomText(text)

OptionsState

Variaveis:

curSelected, selectedOption, optionsList, optionsGroup, bg

Callbacks:

onInputUpdate, onBack, onSelected, onAccept
onHighlighted, onHighlightedPost
onSelectItem

Funcoes Lua:

addOptionMenu(label, stateName, insertAt)
removeOptionMenu(label)
setOptionOrder(orderArray)
changeOptionsSelection(change)
acceptOptionsSelection()

CreditsState

Variaveis:

curSelected, selectedCredit, creditsList, bg, descText

Callbacks:

onInputUpdate, onSelected, onAccept, onBack
onHighlighted, onHighlightedPost

Funcoes Lua:

changeCreditsSelection(change)
setCreditDescription(text)

AchievementsMenuState

Variaveis:

curSelected, selectedAchievement, achievementsList, bg, progressText

Callbacks:

onInputUpdate, onBack, onHighlighted, onHighlightedPost

Funcao Lua:

changeAchievementSelection(change)

TitleState

Variaveis:

logoBl, gfDance, titleText, ngSpr, credGroup, textGroup
curWacky, titleTextPools, introActions

Callbacks:

onInputUpdate, onAccept, onIntroBeat(beat)

Funcoes Lua:

skipTitleIntro()
setTitleIntroActions(actions)

LoadingState

Funcoes Lua:

getLoaded()
getLoadMax()
addBehindBar(tag)

Exemplo: CustomState Puro

Arquivo:

mods/MeuMod/data/states/GalleryState.lua

Codigo:

function onCreate()
    setPropertyFromClass('flixel.FlxG', 'mouse.visible', true)

    makeLuaSprite('bg', 'menuDesat')
    screenCenter('bg')
    addLuaSprite('bg', true)

    makeLuaText('title', 'Gallery', 0, 0, 40)
    screenCenter('title', 'x')
    addLuaText('title')
end

function onUpdate(elapsed)
    if keyboardJustPressed('ESCAPE') then
        switchState('MainMenuState')
    end
end

Abrir:

openCustomState('GalleryState')

Exemplo: Alias Que Forka MainMenuState

pack.json:

{
  "states": {
    "MainMenuState": "myMainMenu"
  }
}

mods/MeuMod/data/states/myMainMenu.lua:

forkState = 'MainMenuState'

function onCreatePost()
    debugPrint('Menu vanilla carregado, script custom por cima.')
end

function onInputUpdate(elapsed)
    if keyboardJustPressed('M') then
        openCustomState('GalleryState')
        return Function_Stop
    end
end

Sem forkState, esse alias abriria myMainMenu como CustomState vazio.


Exemplo: CustomSubstate

Parent:

function onUpdate(elapsed)
    if keyboardJustPressed('ENTER') then
        openCustomSubstate('ConfirmBox', true, {source = 'gallery'})
    end
end

function onCustomSubstateDestroy(name)
    debugPrint(name .. ' fechou.')
end

Substate:

mods/MeuMod/data/substates/ConfirmBox.lua
function onCreate()
    makeLuaSprite('shade')
    makeGraphic('shade', screenWidth, screenHeight, 'AA000000')
    addLuaSprite('shade', true)

    makeLuaText('msg', 'Confirm?', 0, 0, 300)
    screenCenter('msg')
    addLuaText('msg')
end

function onUpdate(elapsed)
    if keyboardJustPressed('ESCAPE') then
        closeSubstate()
    end
end

Checklist

Para um state novo:

  1. crie data/states/Nome.lua ou .hx;
  2. abra com openCustomState('Nome');
  3. crie camera/sprites/textos/input no script;
  4. use switchState('MainMenuState') ou outro state para sair.

Para sobrescrever menu compilado:

  1. crie alias em pack.json;
  2. crie data/states/Alias.lua ou .hx;
  3. coloque forkState = 'MainMenuState' ou o state alvo;
  4. use callbacks do state compilado, como onInputUpdate, onHighlightedPost e onAccept.

Para substate:

  1. crie data/substates/Nome.lua ou .hx;
  2. abra com openCustomSubstate('Nome', pauseGame, data);
  3. feche com closeSubstate() dentro dele ou closeCustomSubstate() fora.

Troubleshooting

Abriu tela vazia

Voce abriu um CustomState puro. Isso e normal quando nao existe classe Haxe com aquele nome e o script nao criou nada.

Alias de menu nao manteve o menu vanilla

No codigo atual, alias em pack.json vira CustomState(alias). Coloque forkState = 'MainMenuState' no topo do script para rodar sobre o menu compilado.

Dados nao chegaram no CustomState

switchState('MeuState', args) nao passa args como data para CustomState. Use openCustomState('MeuState', data).

Variavel stageUI/mechanics nao existe

Essas variaveis sao de PlayState. Em CustomState puro, leia direto de ClientPrefs por HScript ou exponha por conta propria.

Substate fecha sozinho

Nao encontrou script do substate e o parent tambem nao tinha callbacks onCustomSubstate....

Input duplica

Quando interceptar comportamento vanilla em menus forkados, retorne Function_Stop em callbacks de input/accept/selection que suportam bloqueio.


antigo q o chatgpt tb fez mas com umas coisa estranha

State Scripting Wiki

Guia completo para editar states existentes e criar states novos via .lua ou .hx.

Nota: a referencia mais nova e focada em Custom States esta em CUSTOM_STATES_WIKI.md. Ela documenta o fluxo atual de pack.json -> states, forkState/baseState, CustomState e CustomSubstate diretamente pelo codigo.

Este documento cobre:

  • como a engine procura scripts de state
  • como sobrescrever states existentes com pack.json
  • como criar um state novo do zero
  • callbacks genéricos
  • callbacks e APIs extras dos menus
  • formato do titleState.xml
  • dicas de performance e troubleshooting

1. Visão geral

Agora a engine tem dois jeitos diferentes de trabalhar com states scriptados:

A. Editar um state compilado já existente

Exemplo:

  • MainMenuState
  • TitleState
  • StoryMenuState
  • FreeplayState
  • CreditsState
  • OptionsState
  • AchievementsMenuState

Nesse modo, o state Haxe continua existindo normalmente, criando sprites, grupos, câmera, lógica-base e layout-base.

O seu script roda por cima dele, podendo:

  • interceptar input
  • mudar seleção
  • reposicionar itens
  • trocar textos
  • trocar sprites
  • bloquear comportamentos padrão com Function_Stop

Esse é o melhor jeito para menus.

B. Criar um custom state do zero

Exemplo:

  • switchState('MyCustomState')
  • openCustomState('MyCustomState')

Se não existir uma classe Haxe chamada states.MyCustomState, a engine abre um CustomState.

Nesse caso, o state é praticamente vazio.

Você precisa criar tudo por script:

  • sprites
  • textos
  • input
  • câmera
  • troca de state

Esse modo é ótimo para:

  • telas extras
  • menus especiais
  • galerias
  • ferramentas
  • testes

Mas não é o ideal para “substituir” o menu principal visualmente se você ainda quer aproveitar a estrutura dele.


2. Onde colocar scripts

Caminhos novos

A engine agora procura scripts em:

  • data/states
  • data/substates

Exemplos:

  • mods/MyMod/data/states/MainMenuState.lua
  • mods/MyMod/data/states/tgtState.lua
  • mods/MyMod/data/substates/PauseSubState.lua

Compatibilidade com caminho antigo

Também continua aceitando:

  • data/scripts/states
  • data/scripts/substates

Então mods antigos não quebram.


3. Como a engine decide qual script carregar

Para states compilados que herdam de ScriptedState, a engine pega:

  1. o nome da classe do state
  2. verifica se existe um alias em pack.json
  3. se existir alias, abre um CustomState com o nome do alias
  4. se esse script declarar forkState/baseState, carrega o state compilado e usa o alias como script dele

Exemplo:

Se o state atual é MainMenuState, normalmente ela procuraria:

  • data/states/MainMenuState.lua
  • data/states/MainMenuState.hx

Mas se o pack.json tiver:

{
    "states": {
        "MainMenuState": "tgtState"
    }
}

ela abre CustomState('tgtState'). Esse CustomState vai procurar:

  • data/states/tgtState.lua
  • data/states/tgtState.hx

Importante:

  • sem forkState/baseState, isso vira um CustomState vazio/scriptado
  • com forkState = 'MainMenuState', o script roda por cima do MainMenuState compilado

Esse é o comportamento certo para menus.


4. pack.json e alias de state

Exemplo:

{
    "states": {
        "TitleState": "myTitle",
        "MainMenuState": "tgtState",
        "StoryMenuState": "myStory",
        "FreeplayState": "myFreeplay",
        "CreditsState": "myCredits",
        "OptionsState": "myOptions"
    }
}

O que isso faz

  • quando a engine abrir TitleState, ela abre CustomState('myTitle')
  • quando abrir MainMenuState, ela abre CustomState('tgtState')
  • e assim por diante
  • para manter o state compilado, o script alias deve declarar forkState/baseState

O que isso não faz

  • não muda o nome da classe Haxe
  • não remove automaticamente a lógica-base do state quando voce usa forkState
  • sem forkState, o alias nao usa a lógica-base do state compilado

5. Como criar um state novo do zero

Se você quiser um state 100% novo, sem classe Haxe própria:

Arquivo

mods/MyMod/data/states/MyGallery.lua

Abrir por Lua

switchState('MyGallery')

ou

openCustomState('MyGallery')

O que acontece

A engine não encontra uma classe states.MyGallery, então ela abre um CustomState.

Esse CustomState vai procurar:

  • data/states/MyGallery.lua
  • data/states/MyGallery.hx

Quando usar

Use isso quando você quer:

  • uma tela totalmente nova
  • uma ferramenta
  • uma galeria
  • um menu especial

Evite isso para menus vanilla se o objetivo for só trocar layout, porque editar o state compilado costuma ser mais estável e mais fácil.


6. Funções de troca de state

switchState(name, args)

Troca de state com transição.

Exemplos:

switchState('FreeplayState')
switchState('states.FreeplayState')
switchState('MyGallery')

loadState(name, args)

Troca de state sem a transição normal.

Exemplos:

loadState('TitleState')
loadState('MyGallery')

reloadState(name, args)

Recarrega um state.

Exemplos:

reloadState()
reloadState('MainMenuState')
reloadState('MyGallery')

Regra importante

Se o nome passado:

  • for uma classe Haxe válida, a engine usa a classe
  • senão, ela tenta abrir um CustomState com esse nome

7. Callbacks genéricos

Todos os states/substates scriptados podem usar os callbacks abaixo.

Criação

function onCreate()
end

function onCreatePost()
end

Update

function onUpdate(elapsed)
end

function onUpdatePost(elapsed)
end

Se onUpdate retornar Function_Stop, o update padrão do state é interrompido naquele frame.

Draw

function onDraw()
end

function onDrawPost()
end

Se onDraw retornar Function_Stop, o draw padrão é bloqueado.

Música/Conductor

function onStepHit(step)
end

function onBeatHit(beat)
end

function onSectionHit(section)
end

Fechamento

function onClose()
end

function onDestroy()
end

8. Variáveis genéricas úteis

Boa parte dessas já existe nos scripts de Psych/engine:

  • curStep
  • curBeat
  • curSection
  • curDecStep
  • curDecBeat
  • curDecSection
  • screenWidth
  • screenHeight
  • version
  • modVersion
  • scriptName
  • modFolder

Também continuam disponíveis utilitários como:

  • getProperty
  • setProperty
  • getPropertyFromGroup
  • setPropertyFromGroup
  • runHaxeCode
  • keyboardJustPressed
  • keyboardPressed
  • keyboardReleased
  • keyJustPressed
  • keyPressed
  • keyReleased
  • mouseClicked
  • mousePressed
  • mouseReleased
  • getMouseX
  • getMouseY

9. Inputs e Function_Stop

O jeito mais seguro de sobrescrever um menu é usar:

function onInputUpdate(elapsed)
    if keyboardJustPressed('LEFT') then
        -- sua lógica
        return Function_Stop
    end

    return Function_Continue
end

O que isso faz

  • você trata seu input primeiro
  • se retornar Function_Stop, a lógica padrão do menu não roda naquele frame

Dica prática

Use onInputUpdate para:

  • trocar navegação
  • trocar binds
  • bloquear mouse
  • criar layout próprio

Use onHighlightedPost para:

  • atualizar posição
  • mover câmera
  • aplicar tweens visuais

Use onSelected para:

  • zoom de confirmação
  • som extra
  • pré-animação antes da troca de state

10. Editando o MainMenuState

O MainMenuState foi preparado para script por cima da lógica-base.

Callbacks úteis

function onInputUpdate(elapsed)
end

function onBack()
end

function onHighlighted(itemName, index, column)
end

function onHighlightedPost(itemName, index, column)
end

function onSelected(itemName, index, column)
end

function onAccept(index)
end

function onSelectItem(index)
end

function onSelectItemPost(index)
end

Colunas

column pode ser:

  • 'left'
  • 'center'
  • 'right'

Variáveis extras expostas

  • curSelected
  • curColumn
  • selectedItemName
  • menuItemNames
  • menuItemsGroup
  • bg
  • magenta
  • camFollow
  • EngineVerTxt
  • FNFVerTxt
  • storymode
  • freeplay
  • mods
  • options
  • credits
  • awards

Funções extras

addItemMenu(item, imagePath, fps, column, insertAt)

Adiciona item ao menu.

Exemplo:

addItemMenu('gallery', 'mainmenu/menu_gallery', 24, 'center')

column pode ser:

  • 'center'
  • 'left'
  • 'right'

removeItemMenu(item)

Exemplo:

removeItemMenu('mods')

setItemOrder(orderArray)

Exemplo:

setItemOrder({
    'story_mode',
    'freeplay',
    'credits'
})

hasBeatenSong(songName)

Exemplo:

if hasBeatenSong('bopeebo') then
    debugPrint('ja passou')
end

hasBeatenWeek(weekName)

Exemplo:

if hasBeatenWeek('week1') then
    debugPrint('week1 completa')
end

changeMainMenuSelection(change, column)

Exemplo:

changeMainMenuSelection(1, 'center')
changeMainMenuSelection(0, 'left')

acceptMainMenuSelection()

Confirma o item atual.


11. Editando o StoryMenuState

Callbacks úteis

  • onInputUpdate(elapsed)
  • onBack()
  • onHighlighted(weekName, index)
  • onHighlightedPost(weekName, index)
  • onSelected(weekName, index)
  • onAccept(weekData, index)
  • onSelectItem(weekData, index)
  • onSelectItemPost(weekName, index)
  • onChangeDifficulty(diffName, diffIndex)
  • onChangeDifficultyPost(diffName, diffIndex)

Variáveis extras

  • curWeek
  • curDifficulty
  • selectedWeek
  • loadedWeeks
  • weekTextGroup
  • weekCharactersGroup
  • bgYellow
  • bgSprite

Funções extras

  • changeStoryWeek(change)
  • changeStoryDifficulty(change)
  • acceptStoryWeek()
  • isStoryWeekLocked(name)
  • hasBeatenWeek(name)

12. Editando o FreeplayState

Callbacks úteis

  • onInputUpdate(elapsed)
  • onBack()
  • onHighlighted(songName, index)
  • onHighlightedPost(songName, index)
  • onSelected(songName, index)
  • onAccept(index)
  • onSelectItem(songData, index)
  • onSelectItemPost(index) / onSelectItemPost(songData, index) no HScript
  • onChangeDifficulty(diffName, diffIndex)
  • onChangeDifficultyPost(diffName, diffIndex)
  • onMusicPlayer(isPlayAction, index)
  • onMusicPlayerPost(isPlayAction, index)

Variáveis extras

  • curSelected
  • curDifficulty
  • selectedSong
  • songsList
  • scoreText
  • diffText
  • bg
  • bottomText

Funções extras

  • changeFreeplaySelection(change, playSound)
  • changeFreeplayDifficulty(change)
  • setFreeplayBottomText(text)

13. Editando o OptionsState

Callbacks úteis

  • onInputUpdate(elapsed)
  • onBack()
  • onHighlighted(optionName, index)
  • onHighlightedPost(optionName, index)
  • onSelected(optionName, index)
  • onAccept(optionName, index)
  • onSelectItem(optionName, index)

Variáveis extras

  • curSelected
  • selectedOption
  • optionsList
  • optionsGroup
  • bg

Funções extras

  • addOptionMenu(label, stateName, insertAt)
  • removeOptionMenu(label)
  • setOptionOrder(orderArray)
  • changeOptionsSelection(change)
  • acceptOptionsSelection()

14. Editando o CreditsState

Callbacks úteis

  • onInputUpdate(elapsed)
  • onBack()
  • onHighlighted(name, index)
  • onHighlightedPost(name, index)
  • onSelected(name, index)
  • onAccept(creditData, index)

Variáveis extras

  • curSelected
  • selectedCredit
  • creditsList
  • bg
  • descText

Funções extras

  • changeCreditsSelection(change)
  • setCreditDescription(text)

15. Editando o AchievementsMenuState

Callbacks úteis

  • onInputUpdate(elapsed)
  • onBack()
  • onHighlighted(achievementName, index)
  • onHighlightedPost(achievementName, index)

Variáveis extras

  • curSelected
  • selectedAchievement
  • achievementsList
  • bg
  • progressText

Funções extras

  • changeAchievementSelection(change)

Observação:

Esse menu usa grid, não lista simples.


16. Editando o TitleState

Callbacks úteis

  • onInputUpdate(elapsed)
  • onAccept()
  • onIntroBeat(beat)

Variáveis extras

  • logoBl
  • gfDance
  • titleText
  • ngSpr
  • credGroup
  • textGroup
  • introActions

Funções extras

  • skipTitleIntro()
  • setTitleIntroActions(actions)

XML do Title

O TitleState agora aceita:

  • data/titleState.xml

Se o XML não existir, ele volta para:

  • images/gfDanceTitle.json

17. Formato do titleState.xml

Exemplo:

<?xml version="1.0" encoding="utf-8"?>
<titleState>
    <layout
        titlex="-150"
        titley="-100"
        startx="100"
        starty="576"
        gfx="512"
        gfy="40"
        bpm="102"
        image="gfDanceTitle"
        animation="gfDance"
        idle="false"
        backgroundSprite="menuDesat">

        <dance
            left="15,16,17,18,19,20,21,22,23,24,25,26,27,28,29"
            right="30,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14" />

        <background sprite="menuBG" />
    </layout>

    <intro>
        <beat index="0">
            <music song="freakyMenu" />
        </beat>

        <beat index="1">
            <create lines="ViroViroIce by" offset="40" />
        </beat>

        <beat index="3">
            <add text="mily_0" offset="40" />
            <add text="Shiho" offset="40" />
        </beat>

        <beat index="4">
            <clear />
        </beat>

        <beat index="7">
            <add text="newgrounds" offset="-40" />
            <newgrounds visible="true" />
        </beat>

        <beat index="8">
            <clear />
            <newgrounds visible="false" />
        </beat>

        <beat index="16">
            <skipIntro />
        </beat>
    </intro>
</titleState>

layout

Aceita:

  • titlex
  • titley
  • startx
  • starty
  • gfx
  • gfy
  • bpm
  • image
  • animation
  • idle
  • backgroundSprite

<dance />

Aceita:

  • left
  • right

São listas separadas por vírgula.

<background />

Aceita:

  • sprite

intro

Cada <beat index="N"> roda ações naquele beat.

Ações do intro

  • <music song="freakyMenu" />
  • <create lines="linha1|linha2" offset="40" />
  • <add text="texto" offset="40" />
  • <clear />
  • <newgrounds visible="true" />
  • <skipIntro />

Observação sobre lines

Use | para quebrar em várias linhas:

<create lines="Linha 1|Linha 2|Linha 3" />

18. Exemplo de override do MainMenuState

pack.json

{
    "states": {
        "MainMenuState": "tgtState"
    }
}

Arquivo

mods/MyMod/data/states/tgtState.lua

No topo do arquivo:

forkState = 'MainMenuState'

O que isso significa

  • o alias abre CustomState('tgtState')
  • forkState manda esse CustomState carregar o MainMenuState compilado
  • o script tgtState.lua vira o script do MainMenuState
  • então o script ainda tem acesso a:
    • menuItems
    • leftItem
    • rightItem
    • camFollow
    • bg
    • callbacks do menu

Isso é o jeito recomendado para carrossel, menu 3D e layouts alternativos do menu principal. Sem forkState, tgtState vira um CustomState puro.


19. Exemplo de custom state totalmente novo

Arquivo

mods/MyMod/data/states/GalleryState.lua

Abrir

switchState('GalleryState')

Exemplo mínimo

function onCreate()
    makeLuaSprite('bg', 'menuDesat', 0, 0)
    addLuaSprite('bg')
end

function onUpdate(elapsed)
    if keyboardJustPressed('ESCAPE') then
        switchState('MainMenuState')
    end
end

Esse modo é “state vazio”.

Se você precisar de itens de menu vanilla, use alias por pack.json + forkState em vez de custom state puro.


20. HScript funciona igual?

Sim.

A lógica de pasta, alias e carregamento serve tanto para:

  • .lua
  • .hx

Exemplos:

  • data/states/MainMenuState.lua
  • data/states/MainMenuState.hx
  • data/states/tgtState.lua
  • data/states/tgtState.hx

Se ambos existirem, os dois podem rodar, dependendo do fluxo do state.


21. Performance: como não travar menu

Se um menu ficar travado ou pesado, normalmente é por um destes motivos:

1. runHaxeCode todo frame para muita coisa

Evite fazer dezenas de blocos Haxe por frame se der para usar:

  • setProperty
  • setPropertyFromGroup
  • callbacks só quando seleção muda

2. Tween cancelado e recriado o tempo todo

Se você tweena todos os itens em onUpdate, o menu pode ficar pesado.

Prefira:

  • onHighlightedPost
  • onSelected
  • onCreatePost

3. Input duplicado

Se você cria sua própria navegação e não retorna Function_Stop, o input padrão e o seu rodam juntos.

Resultado:

  • pulo de seleção
  • movimento estranho
  • sensação de travado

4. Atualizar layout inteiro sem necessidade

Exemplo ruim:

function onUpdate(elapsed)
    -- recalcula tudo sempre
end

Exemplo melhor:

function onHighlightedPost(item, index, column)
    -- recalcula só quando a seleção muda
end

22. Troubleshooting

Tela preta ao sobrescrever menu

Causa comum:

  • tratar MainMenuState -> tgtState como se fosse custom state vazio

Jeito certo:

  • usar pack.json com states
  • colocar tgtState.lua em data/states
  • colocar forkState = 'MainMenuState' no topo do script
  • deixar o script rodar sobre o state compilado depois do fork

Script não carrega

Cheque:

  • nome do arquivo
  • maiúsculas/minúsculas
  • pasta correta
  • se o alias no pack.json bate com o nome do script

switchState('MeuScript') abre uma tela vazia

Isso é normal se MeuScript não for um state compilado.

Você abriu um CustomState.

Se você queria editar um menu vanilla, use:

  • pack.json
  • forkState apontando para o menu vanilla

Se você queria um state novo do zero, então crie tudo no script.

Input fica bugado

Cheque:

  • se onInputUpdate retorna Function_Stop
  • se você não está misturando input custom com input padrão
  • se não está chamando mudança de seleção em mais de um callback

Menu fica travado

Cheque:

  • runHaxeCode excessivo
  • loops/tweens em onUpdate
  • reordenação de itens todo frame

23. Recomendação prática

Quer só editar layout e comportamento de um menu vanilla?

Use:

  • pack.json + alias de state
  • script em data/states
  • forkState/baseState apontando para o menu vanilla

Quer uma tela totalmente nova?

Use:

  • switchState('MeuStateNovo')
  • script em data/states/MeuStateNovo.lua

Quer compatibilidade máxima

Prefira:

  • callbacks do state
  • Function_Stop
  • setProperty
  • APIs extras do menu

Evite depender demais de runHaxeCode por frame.


24. Checklist rápido

Override de menu existente

  1. criar alias em pack.json
  2. criar script em data/states
  3. declarar forkState/baseState
  4. usar onInputUpdate
  5. usar onHighlightedPost para layout
  6. usar onSelected para zoom/confirm

State novo

  1. criar script em data/states
  2. abrir com switchState('Nome')
  3. criar sprites e input manualmente
  4. usar switchState('MainMenuState') para sair

25. Resumo final

  • pack.json -> states abre um CustomState com o nome do alias
  • forkState/baseState faz esse alias rodar por cima de um state compilado
  • switchState('NomeSemClasse') abre um CustomState vazio
  • menus devem ser sobrescritos via alias + fork, não via custom state puro
  • data/states e data/substates são os caminhos principais
  • titleState.xml permite layout e intro do title por XML

Se você esquecer de tudo, lembra desta regra:

menu vanilla modificado = alias + forkState

state novo do zero = custom state

Clone this wiki locally