Repository navigation
Custom States
placeholder feito por uma IA feito há uns meses ENQUANTO nn finalizo a versão boa!!!
caso queira ver, ela estava assim:
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.
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.
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/.
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);Lua:
switchState('MainMenuState')
switchState('states.MainMenuState')
switchState('options.OptionsState')
switchState('GalleryState')O resolvedor tenta, nesta ordem:
- classe exatamente com o nome passado;
-
states.Nome; -
options.Nome; - 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).
Parecido com switchState, mas usa MusicBeatState.loadState, pulando a
transicao normal de entrada/saida.
Sem nome, recarrega o state atual:
reloadState()Com nome, resolve e troca para o state informado:
reloadState('GalleryState')Recarrega o state atual pelo fluxo de MusicBeatState.resetState():
resetState()MusicBeatState.switchState(new CustomState('GalleryState'));
MusicBeatState.switchState(new CustomState('GalleryState', {from: 'options'}));
MusicBeatState.loadState(new CustomState('GalleryState'));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.')
endOu em HScript:
var forkState = 'MainMenuState';
function onCreatePost() {
debugPrint('Agora estou rodando por cima do MainMenuState.');
}Antes de carregar o state de verdade, CustomState faz uma leitura de sondagem
do script para descobrir o alvo base.
Ordem no Lua:
- variavel
forkState; - variavel
baseState; - funcao
getState(); - 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.
CustomState define multiScript = false. Na pratica, ele tenta carregar no
maximo um caminho resolvido por linguagem.
Fluxo de ScriptedState:
- inicia HScript, se existir;
- registra a API Lua;
- chama
onRegisterLuaAPInos HScript carregados; - 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.
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_StopAllTodo 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
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.
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 HScriptComo 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.
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.
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.
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.
| 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) endstartTween pode receber callbacks por options.onUpdate, options.onStart e
options.onComplete, chamando funcoes Lua globais pelo nome.
| 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:
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
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
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.
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.
Widgets usam as classes PsychUI* e salvam o objeto pelo tag.
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.
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.
makeLuaButton(tag, label, scaleX, scaleY, x, y)
addLuaButton(tag, inFront)
addButtonToBox(tag, box, tab)Callback:
function onButtonPressed(tag) endmakeLuaInputText(tag, input, size, width, x, y)
addLuaInputText(tag, inFront)
addInputTextToBox(tag, box, tab)
getInputTextString(tag)
isInputTextOnFocus()makeLuaSlider(tag, label, min, max, defValue, width, x, y)
addLuaSlider(tag, inFront)
addSliderToBox(tag, box, tab)Callback:
function onSliderChanged(tag, value) endmakeLuaCheckBox(tag, label, checked, hitbox, x, y)
addLuaCheckBox(tag, inFront)
addCheckBoxToBox(tag, box, tab)Callback:
function onCheckBoxChecked(tag, checked) endmakeLuaNumericStepper(tag, step, decimals, min, max, defValue, wid, x, y, isPercent)
addLuaNumericStepper(tag, inFront)
addNumericStepperToBox(tag, box, tab)Callback:
function onNumericStepperChange(tag, value) endLua:
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 = trueno state atual; -
persistentDraw = true; -
persistentUpdate = false.
O substate usa a ultima camera da lista de cameras.
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) endTambem 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.
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.
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.
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) -- LuaNo 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()
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)
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)
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()
Variaveis:
curSelected, selectedCredit, creditsList, bg, descText
Callbacks:
onInputUpdate, onSelected, onAccept, onBack
onHighlighted, onHighlightedPost
Funcoes Lua:
changeCreditsSelection(change)
setCreditDescription(text)
Variaveis:
curSelected, selectedAchievement, achievementsList, bg, progressText
Callbacks:
onInputUpdate, onBack, onHighlighted, onHighlightedPost
Funcao Lua:
changeAchievementSelection(change)
Variaveis:
logoBl, gfDance, titleText, ngSpr, credGroup, textGroup
curWacky, titleTextPools, introActions
Callbacks:
onInputUpdate, onAccept, onIntroBeat(beat)
Funcoes Lua:
skipTitleIntro()
setTitleIntroActions(actions)
Funcoes Lua:
getLoaded()
getLoadMax()
addBehindBar(tag)
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
endAbrir:
openCustomState('GalleryState')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
endSem forkState, esse alias abriria myMainMenu como CustomState vazio.
Parent:
function onUpdate(elapsed)
if keyboardJustPressed('ENTER') then
openCustomSubstate('ConfirmBox', true, {source = 'gallery'})
end
end
function onCustomSubstateDestroy(name)
debugPrint(name .. ' fechou.')
endSubstate:
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
endPara um state novo:
- crie
data/states/Nome.luaou.hx; - abra com
openCustomState('Nome'); - crie camera/sprites/textos/input no script;
- use
switchState('MainMenuState')ou outro state para sair.
Para sobrescrever menu compilado:
- crie alias em
pack.json; - crie
data/states/Alias.luaou.hx; - coloque
forkState = 'MainMenuState'ou o state alvo; - use callbacks do state compilado, como
onInputUpdate,onHighlightedPosteonAccept.
Para substate:
- crie
data/substates/Nome.luaou.hx; - abra com
openCustomSubstate('Nome', pauseGame, data); - feche com
closeSubstate()dentro dele oucloseCustomSubstate()fora.
Voce abriu um CustomState puro. Isso e normal quando nao existe classe Haxe
com aquele nome e o script nao criou nada.
No codigo atual, alias em pack.json vira CustomState(alias). Coloque
forkState = 'MainMenuState' no topo do script para rodar sobre o menu
compilado.
switchState('MeuState', args) nao passa args como data para CustomState.
Use openCustomState('MeuState', data).
Essas variaveis sao de PlayState. Em CustomState puro, leia direto de
ClientPrefs por HScript ou exponha por conta propria.
Nao encontrou script do substate e o parent tambem nao tinha callbacks
onCustomSubstate....
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
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 depack.json -> states,forkState/baseState,CustomStateeCustomSubstatediretamente 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
Agora a engine tem dois jeitos diferentes de trabalhar com states scriptados:
Exemplo:
MainMenuStateTitleStateStoryMenuStateFreeplayStateCreditsStateOptionsStateAchievementsMenuState
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.
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.
A engine agora procura scripts em:
data/statesdata/substates
Exemplos:
mods/MyMod/data/states/MainMenuState.luamods/MyMod/data/states/tgtState.luamods/MyMod/data/substates/PauseSubState.lua
Também continua aceitando:
data/scripts/statesdata/scripts/substates
Então mods antigos não quebram.
Para states compilados que herdam de ScriptedState, a engine pega:
- o nome da classe do state
- verifica se existe um alias em
pack.json - se existir alias, abre um
CustomStatecom o nome do alias - 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.luadata/states/MainMenuState.hx
Mas se o pack.json tiver:
{
"states": {
"MainMenuState": "tgtState"
}
}ela abre CustomState('tgtState'). Esse CustomState vai procurar:
data/states/tgtState.luadata/states/tgtState.hx
Importante:
- sem
forkState/baseState, isso vira um CustomState vazio/scriptado - com
forkState = 'MainMenuState', o script roda por cima doMainMenuStatecompilado
Esse é o comportamento certo para menus.
Exemplo:
{
"states": {
"TitleState": "myTitle",
"MainMenuState": "tgtState",
"StoryMenuState": "myStory",
"FreeplayState": "myFreeplay",
"CreditsState": "myCredits",
"OptionsState": "myOptions"
}
}- quando a engine abrir
TitleState, ela abreCustomState('myTitle') - quando abrir
MainMenuState, ela abreCustomState('tgtState') - e assim por diante
- para manter o state compilado, o script alias deve declarar
forkState/baseState
- 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
Se você quiser um state 100% novo, sem classe Haxe própria:
mods/MyMod/data/states/MyGallery.lua
switchState('MyGallery')ou
openCustomState('MyGallery')A engine não encontra uma classe states.MyGallery, então ela abre um CustomState.
Esse CustomState vai procurar:
data/states/MyGallery.luadata/states/MyGallery.hx
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.
Troca de state com transição.
Exemplos:
switchState('FreeplayState')
switchState('states.FreeplayState')
switchState('MyGallery')Troca de state sem a transição normal.
Exemplos:
loadState('TitleState')
loadState('MyGallery')Recarrega um state.
Exemplos:
reloadState()
reloadState('MainMenuState')
reloadState('MyGallery')Se o nome passado:
- for uma classe Haxe válida, a engine usa a classe
- senão, ela tenta abrir um
CustomStatecom esse nome
Todos os states/substates scriptados podem usar os callbacks abaixo.
function onCreate()
end
function onCreatePost()
endfunction onUpdate(elapsed)
end
function onUpdatePost(elapsed)
endSe onUpdate retornar Function_Stop, o update padrão do state é interrompido naquele frame.
function onDraw()
end
function onDrawPost()
endSe onDraw retornar Function_Stop, o draw padrão é bloqueado.
function onStepHit(step)
end
function onBeatHit(beat)
end
function onSectionHit(section)
endfunction onClose()
end
function onDestroy()
endBoa parte dessas já existe nos scripts de Psych/engine:
curStepcurBeatcurSectioncurDecStepcurDecBeatcurDecSectionscreenWidthscreenHeightversionmodVersionscriptNamemodFolder
Também continuam disponíveis utilitários como:
getPropertysetPropertygetPropertyFromGroupsetPropertyFromGrouprunHaxeCodekeyboardJustPressedkeyboardPressedkeyboardReleasedkeyJustPressedkeyPressedkeyReleasedmouseClickedmousePressedmouseReleasedgetMouseXgetMouseY
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- você trata seu input primeiro
- se retornar
Function_Stop, a lógica padrão do menu não roda naquele frame
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
O MainMenuState foi preparado para script por cima da lógica-base.
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)
endcolumn pode ser:
'left''center''right'
curSelectedcurColumnselectedItemNamemenuItemNamesmenuItemsGroupbgmagentacamFollowEngineVerTxtFNFVerTxtstorymodefreeplaymodsoptionscreditsawards
Adiciona item ao menu.
Exemplo:
addItemMenu('gallery', 'mainmenu/menu_gallery', 24, 'center')column pode ser:
'center''left''right'
Exemplo:
removeItemMenu('mods')Exemplo:
setItemOrder({
'story_mode',
'freeplay',
'credits'
})Exemplo:
if hasBeatenSong('bopeebo') then
debugPrint('ja passou')
endExemplo:
if hasBeatenWeek('week1') then
debugPrint('week1 completa')
endExemplo:
changeMainMenuSelection(1, 'center')
changeMainMenuSelection(0, 'left')Confirma o item atual.
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)
curWeekcurDifficultyselectedWeekloadedWeeksweekTextGroupweekCharactersGroupbgYellowbgSprite
changeStoryWeek(change)changeStoryDifficulty(change)acceptStoryWeek()isStoryWeekLocked(name)hasBeatenWeek(name)
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)
curSelectedcurDifficultyselectedSongsongsListscoreTextdiffTextbgbottomText
changeFreeplaySelection(change, playSound)changeFreeplayDifficulty(change)setFreeplayBottomText(text)
onInputUpdate(elapsed)onBack()onHighlighted(optionName, index)onHighlightedPost(optionName, index)onSelected(optionName, index)onAccept(optionName, index)onSelectItem(optionName, index)
curSelectedselectedOptionoptionsListoptionsGroupbg
addOptionMenu(label, stateName, insertAt)removeOptionMenu(label)setOptionOrder(orderArray)changeOptionsSelection(change)acceptOptionsSelection()
onInputUpdate(elapsed)onBack()onHighlighted(name, index)onHighlightedPost(name, index)onSelected(name, index)onAccept(creditData, index)
curSelectedselectedCreditcreditsListbgdescText
changeCreditsSelection(change)setCreditDescription(text)
onInputUpdate(elapsed)onBack()onHighlighted(achievementName, index)onHighlightedPost(achievementName, index)
curSelectedselectedAchievementachievementsListbgprogressText
changeAchievementSelection(change)
Observação:
Esse menu usa grid, não lista simples.
onInputUpdate(elapsed)onAccept()onIntroBeat(beat)
logoBlgfDancetitleTextngSprcredGrouptextGroupintroActions
skipTitleIntro()setTitleIntroActions(actions)
O TitleState agora aceita:
data/titleState.xml
Se o XML não existir, ele volta para:
images/gfDanceTitle.json
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>Aceita:
titlextitleystartxstartygfxgfybpmimageanimationidlebackgroundSprite
Aceita:
leftright
São listas separadas por vírgula.
Aceita:
sprite
Cada <beat index="N"> roda ações naquele beat.
<music song="freakyMenu" /><create lines="linha1|linha2" offset="40" /><add text="texto" offset="40" /><clear /><newgrounds visible="true" /><skipIntro />
Use | para quebrar em várias linhas:
<create lines="Linha 1|Linha 2|Linha 3" />{
"states": {
"MainMenuState": "tgtState"
}
}mods/MyMod/data/states/tgtState.lua
No topo do arquivo:
forkState = 'MainMenuState'- o alias abre
CustomState('tgtState') -
forkStatemanda esse CustomState carregar oMainMenuStatecompilado - o script
tgtState.luavira o script doMainMenuState - então o script ainda tem acesso a:
menuItemsleftItemrightItemcamFollowbg- callbacks do menu
Isso é o jeito recomendado para carrossel, menu 3D e layouts alternativos do menu principal.
Sem forkState, tgtState vira um CustomState puro.
mods/MyMod/data/states/GalleryState.lua
switchState('GalleryState')function onCreate()
makeLuaSprite('bg', 'menuDesat', 0, 0)
addLuaSprite('bg')
end
function onUpdate(elapsed)
if keyboardJustPressed('ESCAPE') then
switchState('MainMenuState')
end
endEsse modo é “state vazio”.
Se você precisar de itens de menu vanilla, use alias por pack.json + forkState em vez de custom state puro.
Sim.
A lógica de pasta, alias e carregamento serve tanto para:
.lua.hx
Exemplos:
data/states/MainMenuState.luadata/states/MainMenuState.hxdata/states/tgtState.luadata/states/tgtState.hx
Se ambos existirem, os dois podem rodar, dependendo do fluxo do state.
Se um menu ficar travado ou pesado, normalmente é por um destes motivos:
Evite fazer dezenas de blocos Haxe por frame se der para usar:
setPropertysetPropertyFromGroup- callbacks só quando seleção muda
Se você tweena todos os itens em onUpdate, o menu pode ficar pesado.
Prefira:
onHighlightedPostonSelectedonCreatePost
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
Exemplo ruim:
function onUpdate(elapsed)
-- recalcula tudo sempre
endExemplo melhor:
function onHighlightedPost(item, index, column)
-- recalcula só quando a seleção muda
endCausa comum:
- tratar
MainMenuState -> tgtStatecomo se fosse custom state vazio
Jeito certo:
- usar
pack.jsoncomstates - colocar
tgtState.luaemdata/states - colocar
forkState = 'MainMenuState'no topo do script - deixar o script rodar sobre o state compilado depois do fork
Cheque:
- nome do arquivo
- maiúsculas/minúsculas
- pasta correta
- se o alias no
pack.jsonbate com o nome do script
Isso é normal se MeuScript não for um state compilado.
Você abriu um CustomState.
Se você queria editar um menu vanilla, use:
pack.json-
forkStateapontando para o menu vanilla
Se você queria um state novo do zero, então crie tudo no script.
Cheque:
- se
onInputUpdateretornaFunction_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
Cheque:
-
runHaxeCodeexcessivo - loops/tweens em
onUpdate - reordenação de itens todo frame
Use:
-
pack.json+ alias de state - script em
data/states -
forkState/baseStateapontando para o menu vanilla
Use:
switchState('MeuStateNovo')- script em
data/states/MeuStateNovo.lua
Prefira:
- callbacks do state
Function_StopsetProperty- APIs extras do menu
Evite depender demais de runHaxeCode por frame.
- criar alias em
pack.json - criar script em
data/states - declarar
forkState/baseState - usar
onInputUpdate - usar
onHighlightedPostpara layout - usar
onSelectedpara zoom/confirm
- criar script em
data/states - abrir com
switchState('Nome') - criar sprites e input manualmente
- usar
switchState('MainMenuState')para sair
-
pack.json -> statesabre umCustomStatecom o nome do alias -
forkState/baseStatefaz 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/statesedata/substatessão os caminhos principais -
titleState.xmlpermite 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
- Creating new characters
- Creating new songs
- Creating new stages
- Creating new levels
- Creating new stickers
- Creating new freeplay tabs
- Creating new mixes
- Creating new states
- on Songs
- on Characters
- on Notetypes
- on Events
- on Dialogues (DialoguePlus)
- on GameOver
- on Dialogue Portraits
- on Icons
- on StoryMenu
- on Pause Screen
- Globally
