Link Search Menu Expand Document

Basic skinning guide

In this guide we’ll create a basic arrow skin with judgements and other ui elements for 4key mode that you can customize however you like.
Note: this guide doesn’t use 100%-custom elements or moddedgame folder, it lists only things provided by default.

Table of contents
  1. Basic skin structure
  2. Step 0: Importing functions
  3. Step 1: Noteskin
    1. Declaring noteskin
    2. Adding and ordering inputs in the skin
    3. Adding columns
    4. Adding textures
    5. Using note images
      1. Short notes
      2. Long notes
    6. Measure line
  4. Step 2: Playfield
    1. Drawing notes and receptors
    2. Guidelines (or lane separators)
    3. Base elements
    4. Judgements
  5. Step 3: “Exporting” and troubleshooting
  6. Closing thoughts

Basic skin structure

  • Skins should contain skin in the filename and have extension .lua and should go in their respective skin folder, for example skins/example-skin.
    • Preferred naming scheme is <filename>.skin.lua
    • If you want to add more than one keymode to your skin, you’ll have to create another skin file(s) in the same folder.
  • Skin’s textures can be located anywhere in the skin folder.
  • Things are layered on the screen in the order of their appearance in the skin file.
  • To make it all work, skin needs 4 things: necessary functions, noteskin, playfield and return noteskin in the end of the file.

Step 0: Importing functions

Just add those right in the beginning of the file and don’t question anything, without them it’ll never work.

local NoteSkinVsrg = require("sphere.models.NoteSkinModel.NoteSkinVsrg")
local BasePlayfield = require("sphere.models.NoteSkinModel.BasePlayfield")

Step 1: Noteskin

Declaring noteskin

First step in making a skin is, of course, filling out required skin metadata.
Copy this code and edit as you need, explanations below

local noteskin = NoteSkinVsrg:new({
--	path = ...,
	name = "example 4k arrow",
	inputMode = "4key",
	range = {-1, 1},
	unit = 480,
	hitposition = 460,
})

path

Path to the skin file, not necessary to add

name

Name of the skin in noteskins menu
Examples: JustVldKsh's 4k arrow or circle

inputMode

Input mode this skin file is for, can contain key, pedal and scratch
Examples: 4key, 7key1scratch, 5key1pedal1scratch

range

Render distance (in screens) for notes below and above hitposition
Default:{-1, 1}

unit

Height of the screen (going down)
Default: 480

hitposition

Horizontal position of the note receptors. 0 is the top, + is down and - is up

Adding and ordering inputs in the skin

To be able to actually use the skin, you need to set inputs the skin accepts. Amount of possible inputs should be the same as the set input mode and be separated by commas, but they can be ordered in any way. Considering that the skin we’re making is for 4key mode, next line in the skin will be noteskin:setInput({"key1", "key2", "key3", "key4"}). Examples for other modes:

noteskin:setInput({"key1", "key2", "key3", "key4", "key5", "key6", "key7"})`
noteskin:setInput({"scratch1", "key1", "key2", "key3", "key4", "key5"})`
noteskin:setInput({"key1", "key2", "key3", "key4", "key5", "key6", "key7", "scratch1"})`

Adding columns

This step is necessary so the game knows where to draw the conveyor with notes and receptors.

noteskin:setColumns({
	offset = 0,
	align = "center",
	width = {64, 64, 64, 64},
	space = {32, 0, 0, 0, 32}
})
  • offset - horizontal shift from the default position of the conveyor. - is left and + is right.
  • align - horizontal align of the conveyor, can be left, center and right.
  • width - width of each note, number of values should be equal to amount of inputs.
  • space - space between notes and both sides of the conveyor, number of values should be equal to amount of inputs + 1 (so if there are 4 inputs then there should be 5 values and so on)
  • measure line will use full width of the conveyor (sum of all widths and spaces)

Adding textures

This step can be skipped if you want to waste your time by typing out paths to the textures further in the skin.
Note: some things (like judgements) can’t be used if added here.
As previously said, textures can be located anywhere in the skin folder (feel free to draw your own textures or if you can’t then just grab from another one), so for everything to be clean we’ll make following folder structure:

+-- judges
|   |-- bad.png
|   |-- good.png
|   |-- great.png
|   |-- marv.png
|   |-- miss.png
|   +-- perf.png
|
|-- notes
|   |-- body.png (ln body)
|   |-- down.png
|   |-- left.png
|   |-- right.png
|   |-- tail.png (ln end)
|   +-- up.png
|
|-- receptors
|   |-- down0.png (released)
|   |-- down1.png (pressed)
|   +-- other receptors with same name pattern
|
|-- 4key.skin.lua
|
+-- pixel.png (mandatory 1x1 white image)

To use the images in the skin, copy this:

noteskin:setTextures({
--mandatory 1x1 white image
	{pixel = "pixel.png"},
--notes
	{body = "notes/body.png"},
	{tail = "notes/tail.png"},
	{left = "notes/left.png"},
	{down = "notes/down.png"},
	{up = "notes/up.png"},
	{right = "notes/right.png"},
})

--if you have images with black non-transparent background then you can add next code
--[[
noteskin:setBlendModes({
    image = {"add", "alphamultiply"}
})
]]

--automatic image-variable assign
noteskin:setImagesAuto()

--manual image-variable assign
--[[
noteskin:setImages({
	pixel = {"pixel"},
	body = {"body"},
	tail = {"tail"},
	left = {"left"},
	down = {"down"},
	up = {"up"},
	right = {"right"},
})
]]

Using note images

In this section you’ll set note images using textures set above
Note: image, head, body and tail all can be both tables or strings if you want to reuse the same image

Short notes

noteskin:setShortNote({
	image = {"left", "down", "up", "right"},
	h = 64,
})
  • image - image(s) used as notes
  • h - height of notes

Long notes

noteskin:setLongNote({
	head = {"left", "down", "up", "right"},
	body = "body",
	tail = "tail",
	h = 64,
})
  • head - start of LN
  • body - body
  • tail - end
  • h - texture height

Measure line

Can be added but not mandatory unlike elements above, removing it is the same as using NML (NoMeasureLine) mod

noteskin:addMeasureLine({
	h = 4,
	color = {0.5, 0.5, 0.5, 1},
	image = "pixel"
})
  • color - in 0-1 range, order is red green blue alpha

Step 2: Playfield

Time to draw the playfield itself starting with creating it first:

local playfield = BasePlayfield:new({
	noteskin = noteskin
})

From now on things will get layered by the order of appearance in the file.

Drawing notes and receptors

Here we’ll draw notes and receptors on the screen itself. If you don’t need receptors (for example if you use bottom of the screen instead) then don’t add them.
You also can edit camera perspective in this step if you know how to.

playfield:enableCamera()
--notes
playfield:addNotes()

--receptors
playfield:addKeyImages({
	h = 64,
	padding = noteskin.unit-noteskin.hitposition,
	pressed = {"receptors/left1.png", "receptors/down1.png", "receptors/up1.png", "receptors/right1.png"},
	released = {"receptors/left0.png", "receptors/down0.png", "receptors/up0.png", "receptors/right0.png"},
})
playfield:disableCamera()
  • playfield:addNotes adds notes and addKeyImages adds receptors
  • h - is obviously receptor height
  • padding is tied to hitposition and unit in this example so it moves together with it, 0 is at bottom
  • pressed - images when keys are pressed/held
  • released - images when keys are released

Guidelines (or lane separators)

By adding these it’s possible to visually divide the lanes in case if it’s more comfortable for you to see on which lane where goes which note

playfield:addGuidelines({
	y = {0, 0, 0, 0, 0},
	w = {0, 0, 0, 0, 0},
	h = {480, 480, 480, 480, 480},
	image = "pixel.png",
	color = {1, 1, 1, 0.5},
})
  • y - top of the guideline image
  • w - width (0 - no separation)
  • h - height (going down)
  • image and color are the usual

Base elements

Base elements can be added (but not mandatory) with playfield:addBaseElements()
If you need to add only specific elements then you can use playfield addBaseElements("progress", "hp", "score") instead. There are 6 base elements: progress, hp, score, accuracy, combo and hit error.

If you need to customize one or multiple base elements then specify other non-modified elements and use BasePlayfield.lua as reference.

Judgements

It’s also possible to add judgements to the skin, which we’ll do here.
In this example we’ll use osu!mania OD 10 timing windows.
Note: timing windows are used in seconds, so if the window is 16ms then it’ll be 0.016 seconds, mirrored on both positive and negative side.

playfield:addDeltaTimeJudgement({
	x = 0, y = 540, ox = 0.5, oy = 0.5,
	rate = 2,
	transform = playfield:newLaneCenterTransform(1080),
	judgements = {
		"judges/miss.png",
		-0.121,
		"judges/bad.png",
		-0.097,
		"judges/good.png",
		-0.067,
		"judges/great.png",
		-0.034,
		"judges/perf.png",
		-0.016,
		"judges/marv.png",
		0.016,
		"judges/perf.png",
		0.034,
		"judges/great.png",
		0.067,
		"judges/good.png",
		0.097,
		"judges/bad.png",
		0.121,
		"judges/miss.png"
	}
})
  • x, y, ox, oy - judgement location, change only x and y
  • rate - image scale
  • transform - love2d object transformations, more info on that
  • judgements - array of alternating judgement images and timings, center is 0 and image between specified timings is used. Note: if you use custom timings.lua then different judgement might appear as worst, for example bad instead of miss, tweak the skin as needed. Also default soundsphere miss window is 160ms.

Step 3: “Exporting” and troubleshooting

To “export” the skin so the game can actually read the information from it, add return noteskin in the end and save the file. Now you can try the skin ingame and if it didn’t crashed and works as intended then congrats, you did everything correctly.

But in case it did crashed, you can try few things:

  • Make sure you did actually save the file
  • Look if you forgot a comma somewhere
  • If game tells if table x should have y values, then add/remove values in the corresponding table
  • if you still don’t have pixel.png in your skin, it can be found in other soundsphere skins or in the resources folder in the game’s root.
  • If you can’t figure out the problem on your own, feel free to ask for help in #help or #skins channels of soundsphere Discord.

Closing thoughts

After following this guide you should end up with a relatively basic soundsphere skin that you can modify as you wish.
If you think you can improve this guide in any way, feel free to contribute on Github
Note: had to leave out adding BGA to the skin from the guide because of page build errors, for that just look at the bundled skins’ code.