Beat saber-ish game

Example data

https://virtualcast.jp/products/2e733969d3ecdd31feb5e06c876470c440e77922c5b2d1b050609f811ac2761d

main.lua
local scene = 0     -- 0:ready 1:game 2:end
 
local HIDE_POSITION = Vector3.__new(-1000,-1000,-1000)
 
-- ready
local BtnStart = vci.assets.GetTransform("BtnStart")
local Title = vci.assets.GetTransform("Title")
 
local BtnStartPos = BtnStart.GetLocalPosition()
local TitlePos = Title.GetLocalPosition()
 
-- end
local BtnTitle = vci.assets.GetTransform("BtnTitle")
local Result = vci.assets.GetTransform("Result")
local saber_numbers_100 = vci.assets.GetTransform("saber_numbers_100")
local saber_numbers_10 = vci.assets.GetTransform("saber_numbers_10")
local saber_numbers_1 = vci.assets.GetTransform("saber_numbers_1")
local saber_numbers_percent = vci.assets.GetTransform("saber_numbers_percent")
 
local BtnTitlePos = BtnTitle.GetLocalPosition()
local ResultPos = Result.GetLocalPosition()
local saber_numbers_100Pos = saber_numbers_100.GetLocalPosition()
local saber_numbers_10Pos = saber_numbers_10.GetLocalPosition()
local saber_numbers_1Pos = saber_numbers_1.GetLocalPosition()
local saber_numbers_percentPos = saber_numbers_percent.GetLocalPosition()
 
-- game
local saber = vci.assets.GetTransform("saber")
local notes = {}
local current_note = 0
local current_stage_note_index = 1 --index of stage_note currently showing
local show_notes = {}
local notesDieList = {}
local notesColorAlphaList = {}
local score = 0
local noteTimer = vci.me.Time.TotalSeconds
for i = 1,9 do
    notes[i] = vci.assets.GetTransform("note"..tostring(i))
    notesDieList[i] = 0
    notesColorAlphaList[i] = 1
end
local green = Color.__new(0,1,0,0.5)
local red = Color.__new(1,0,0,0.5)
local invisible = Color.__new(0,0,0,0)
local noteAppearPos = Vector3.__new(0,2,30)
local noteAppearPosList = {}
noteAppearPosList[1] = Vector3.__new(-0.5,0,0)
noteAppearPosList[2] = Vector3.__new(0,0,0)
noteAppearPosList[3] = Vector3.__new(0.5,0,0)
local noteSpeed = Vector3.__new(0,0,-0.1)
 
-- List of time spans to the next note (second)
local stage1_notes = {
    1, 1, 0.5, 1, 1,
    1.5, 1.5, 0.5, 0.5, 0.5,
    1.5, 1.5, 0.5, 0.5, 0.5,
    1.5, 1.5, 0.5, 0.5, 0.5,
    1.5, 1.5, 0.5, 0.5, 0.5
}
local current_notes = {}
current_notes = {unpack(stage1_notes)}
 
-- Hide the objects of specified scene number
function hideSceneObject(sceneNumber)
    if sceneNumber == 0 then
        -- 0:ready
        BtnStart.SetLocalPosition(HIDE_POSITION)
        Title.SetLocalPosition(HIDE_POSITION)
    elseif sceneNumber == 1 then
        -- 1:game
        for i = 1,9 do
            vci.assets.GetTransform("note"..tostring(i)).SetLocalPosition(noteAppearPos + noteAppearPosList[math.random(1,3)])
            vci.assets._ALL_SetMaterialColorFromName("note"..tostring(i), invisible)
        end
    elseif sceneNumber == 2 then
        -- 2:end
        BtnTitle.SetLocalPosition(HIDE_POSITION)
        Result.SetLocalPosition(HIDE_POSITION)
        saber_numbers_100.SetLocalPosition(HIDE_POSITION)
        saber_numbers_10.SetLocalPosition(HIDE_POSITION)
        saber_numbers_1.SetLocalPosition(HIDE_POSITION)
        saber_numbers_percent.SetLocalPosition(HIDE_POSITION)
    end
end
 
-- Change textures
function SetTextureOffset( _count, _materialName )
    local offset = Vector2.zero
 
    -- y shift
    local Yshift = math.floor(_count / 4)
    offset.y = -(1/4) * Yshift
 
    -- x shift
    local Xshift = _count % 4
    offset.x = (1/4) * Xshift
 
    vci.assets._ALL_SetMaterialTextureOffsetFromName(_materialName, offset)
end
 
-- Score setup
function setupScore( _score )
    if _score >= 100 then
        SetTextureOffset(1, "saber_numbers_100")
        SetTextureOffset(0, "saber_numbers_10")
        SetTextureOffset(0, "saber_numbers_1")
    elseif _score >= 10 then
        SetTextureOffset(15, "saber_numbers_100")
        local str10 = string.sub(tostring(_score), 1,1)
        SetTextureOffset(tonumber(str10), "saber_numbers_10")
        local str1 = string.sub(tostring(_score), 2)
        SetTextureOffset(tonumber(str1), "saber_numbers_1")
    else
        local str1 = string.sub(tostring(_score), 1)
        SetTextureOffset(15, "saber_numbers_100")
        SetTextureOffset(15, "saber_numbers_10")
        SetTextureOffset(tonumber(str1), "saber_numbers_1")
    end
    SetTextureOffset(10, "saber_numbers_percent")
end
 
-- Scene setup
function setupScene( sceneNumber )
    if sceneNumber == 0 then
        -- 0:ready
        hideSceneObject( 1 )
        hideSceneObject( 2 )
 
        BtnStart.SetLocalPosition(BtnStartPos)
        Title.SetLocalPosition(TitlePos)
    elseif sceneNumber == 1 then
        -- 1:game
        score = 0
        current_notes = {unpack(stage1_notes)}
        current_stage_note_index = 1
        hideSceneObject( 0 )
        hideSceneObject( 2 )
    elseif sceneNumber == 2 then
        -- 2:end
        hideSceneObject( 0 )
        hideSceneObject( 1 )
 
        local resultScore = math.floor(score / #current_notes * 100)
        print("resultScore is "..tostring(resultScore))
        setupScore(resultScore)
 
        BtnTitle.SetLocalPosition(BtnTitlePos)
        Result.SetLocalPosition(ResultPos)
        saber_numbers_100.SetLocalPosition(saber_numbers_100Pos)
        saber_numbers_10.SetLocalPosition(saber_numbers_10Pos)
        saber_numbers_1.SetLocalPosition(saber_numbers_1Pos)
        saber_numbers_percent.SetLocalPosition(saber_numbers_percentPos)
    end
    scene = sceneNumber
end
 
 
function updateAll()
    if scene == 1 then
        if current_stage_note_index <= #current_notes then
            if vci.me.Time.TotalSeconds - noteTimer > current_notes[current_stage_note_index] then
                --print(current_stage_note_index)
 
                noteTimer = vci.me.Time.TotalSeconds -- refresh noteTimer
                --
                if current_note < 9 then
                    current_note = current_note + 1
                else
                    current_note = 1
                end
                table.insert(show_notes, current_note) -- add to the end of show_notes
                --
                local noteLength = #show_notes
                notes[show_notes[noteLength]].SetLocalPosition(noteAppearPos + noteAppearPosList[math.random(1,3)])
                vci.assets._ALL_SetMaterialColorFromName("note"..tostring(show_notes[noteLength]), green)
                --
                current_stage_note_index = current_stage_note_index + 1
            end
        end
 
        if #show_notes > 0 then
            for key, value in pairs(show_notes) do
                if notesDieList[value] < 1 then
                    notes[value].SetLocalPosition(notes[value].GetLocalPosition() + noteSpeed)
                else
                    -- Cut
                    notesColorAlphaList[value] = notesColorAlphaList[value] - 0.05
                    if notesColorAlphaList[value] > 0 then
                        -- Fade-out processing after being cut
                        local color = red * Color.__new(1,1,1,notesColorAlphaList[value])
                        vci.assets._ALL_SetMaterialColorFromName("note"..tostring(value), color)
                    else
                        -- Rest
                        notes[value].SetLocalPosition(noteAppearPos + noteAppearPosList[math.random(1,3)])
                        vci.assets._ALL_SetMaterialColorFromName("note"..tostring(value), invisible)
                        notesColorAlphaList[value] = 1
                        notesDieList[value] = 0
 
                        -- Remove from show_notes
                        table.remove(show_notes, key) -- add to the end of show_notes
 
                        if #show_notes == 0 then
                            setupScene(2)
                        end
                    end
                end
 
                if notes[value].GetLocalPosition().z <= -3 then
                    -- Reset missed notes
                    notes[value].SetLocalPosition(noteAppearPos + noteAppearPosList[math.random(1,3)])
                    vci.assets._ALL_SetMaterialColorFromName("note"..tostring(value), invisible)
 
                    -- Remove from show_notes
                    table.remove(show_notes, key) -- add to the end of show_notes
 
                    if #show_notes == 0 then
                        setupScene(2)
                    end
                end
            end
        end
    end
end
 
function onTriggerEnter(item,hit)
    -- ready
    if item == "saber" and hit == "BtnStart" then
        setupScene(1)
    end
    -- game
    for i = 1,9 do
        local note_name = "note"..tostring(i)
        if item == "saber" and hit == note_name then
            -- Cut
            score = score + 1
            --print("score is "..tostring(score))
            notesDieList[i] = 1
            vci.assets._ALL_SetMaterialColorFromName(note_name, red)
 
            -- Play sound
            vci.assets._ALL_PlayAudioFromName("shot-struck1")
        end
    end
    -- end
    if item == "saber" and hit == "BtnTitle" then
        setupScene(0)
    end
end
 
setupScene(0)