Sonolus Wiki

06. Setting up Development Server

For rest of the guide, we will be creating a one-lane-tap-only rhythm game engine, as well as a level to go along with it, as a real world example.

In this chapter, we will go over setting up development server using Sonolus.js.

Required Resources

The primary purpose of Sonolus.js is for developing engines, so its development server requires Engine Configuration and Engine Data, as well as Level Data for a test level.

Engine Configuration

Engine configuration contains:

  • Options: provide a way for players to adjust engine specific settings.
  • UI: allows player to adjust visibility and animation of various gameplay UI elements.

We don't need any options for now:

const options = defineOptions({})
const options = defineOptions({})

For UI we can use some placeholder values and adjust in the future:

const ui: EngineConfigurationUI = {
    primaryMetric: 'arcade',
    secondaryMetric: 'life',
    menuVisibility: {
        alpha: 1,
        scale: 1,
    },
    judgmentVisibility: {
        alpha: 1,
        scale: 1,
    },
    comboVisibility: {
        alpha: 1,
        scale: 1,
    },
    primaryMetricVisibility: {
        alpha: 1,
        scale: 1,
    },
    secondaryMetricVisibility: {
        alpha: 1,
        scale: 1,
    },
    judgmentAnimation: {
        scale: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
        alpha: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
    },
    comboAnimation: {
        scale: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
        alpha: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
    },
    judgmentErrorStyle: 'none',
    judgmentErrorPlacement: 'both',
    judgmentErrorMin: 0,
}
const ui = {
    primaryMetric: 'arcade',
    secondaryMetric: 'life',
    menuVisibility: {
        alpha: 1,
        scale: 1,
    },
    judgmentVisibility: {
        alpha: 1,
        scale: 1,
    },
    comboVisibility: {
        alpha: 1,
        scale: 1,
    },
    primaryMetricVisibility: {
        alpha: 1,
        scale: 1,
    },
    secondaryMetricVisibility: {
        alpha: 1,
        scale: 1,
    },
    judgmentAnimation: {
        scale: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
        alpha: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
    },
    comboAnimation: {
        scale: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
        alpha: {
            from: 1,
            to: 1,
            duration: 0,
            ease: 'Linear',
        },
    },
    judgmentErrorStyle: 'none',
    judgmentErrorPlacement: 'both',
    judgmentErrorMin: 0,
}

Finally putting them together is as simple as:

const engineConfiguration = {
    options,
    ui,
}
const engineConfiguration = {
    options,
    ui,
}

Engine Data

Engine data contains:

  • Buckets: provides a way for players to visualize their judgments on different types of notes.
  • Archetypes: abstractions of entities that share common behaviors and data.
  • Scripts: abstractions of archetype behaviors.

We will skip buckets for now:

const buckets = defineBuckets({})
const buckets = defineBuckets({})

For now, we have only an initialization script which simply sets up UI Menu in preprocess so we can exit:

const scripts = defineScripts({
    initialization: () => ({
        preprocess: UIMenu.set(
            Subtract(0.05, ScreenAspectRatio),
            0.95,
            0,
            1,
            0.15,
            0.15,
            0,
            1,
            HorizontalAlign.Center,
            true
        ),
    }),
})
const scripts = defineScripts({
    initialization: () => ({
        preprocess: UIMenu.set(
            Subtract(0.05, ScreenAspectRatio),
            0.95,
            0,
            1,
            0.15,
            0.15,
            0,
            1,
            HorizontalAlign.Center,
            true
        ),
    }),
})

And an initialization archetype using initialization script:

const archetypes = defineArchetypes({
    initialization: scripts.initializationIndex,
})
const archetypes = defineArchetypes({
    initialization: scripts.initializationIndex,
})

Finally putting them together:

const engineData = {
    buckets,
    archetypes,
    scripts,
}
const engineData = {
    buckets,
    archetypes,
    scripts,
}

Level Data

Level data contains:

  • Entities: a list of entities to be spawned in the level.

For now, we have only one entity of initialization archetype:

const levelData = {
    entities: [
        {
            archetype: archetypes.initializationIndex,
        },
    ],
}
const levelData = {
    entities: [
        {
            archetype: archetypes.initializationIndex,
        },
    ],
}

Building

With these information, we can call build to build them into formats Sonolus can understand:

const buildOutput = build({
    engine: {
        configuration: engineConfiguration,
        data: engineData,
    },

    level: {
        data: levelData,
    },
})
const buildOutput = build({
    engine: {
        configuration: engineConfiguration,
        data: engineData,
    },

    level: {
        data: levelData,
    },
})

Serving

Finally we can serve it with a development server by calling serve:

serve(buildOutput)
serve(buildOutput)

Try connecting to one of the addresses listed using Sonolus app and if it works, we are ready to develop our engine!