Documentations of TECHMANIA projects, hosted in a repo so they can be localized.
Applies to version: 2.3
This page explains how to make skins for TECHMANIA.
There are currently 4 types of skins in TECHMANIA:
<TECHMANIA folder>/Skins/Note/<skin name>
.<TECHMANIA folder>/Skins/VFX/<skin name>
.<TECHMANIA folder>/Skins/Combo/<skin name>
.<TECHMANIA folder>/Skins/Game UI/<skin name>
.Each skin is a folder containing a number of sprite sheets and a skin.json
file, both of which will be explained later.
All skins are based on sprite sheets so it’s important to understand them. The term may have different meanings in other fields, but in the context of TECHMANIA skins, a sprite sheet is a series of sprites, tiled horizontally into one image, where each sprite corresponds to one frame of an animation. The sprites must be identical in size, and tiled tightly, with optional paddings inside each tile, but without any space between tiles.
(Example sprite sheets in this page are taken from https://www.gamedesigning.org/animation/sprites/)
Not all tiles in a sprite sheet must contain a sprite. It’s okay to leave a few empty at the beginning and/or end of the sprite sheet.
For each sprite sheet, you need to write an accompanying JSON object to describe it to TECHMANIA, which looks like:
{
"filename": <filename>,
"rows": <rows of tiles>,
"columns": <columns of tiles>,
"firstIndex": <index of the first tile that contains a sprite>,
"lastIndex": <index of the last tile that contains a sprite>,
"padding": <amount of padding in pixels>,
"bilinearFilter": <true or false>,
"scale": <number, skin-specific>,
"speed": <number, skin-specific>,
"additiveShader": <true or false, skin-specific>,
"flipWhenScanningLeft", <true or false, skin-specific>
}
Tiles are indexed in row-major order, and start from 0. For example, the JSON object that describes the 2nd sprite sheet above may look like:
{
"filename": "sprite sheet.png",
"rows": 3,
"columns": 4,
"firstIndex": 1,
"lastIndex": 10
}
In this example, the first row contains tiles #0, #1, #2 and #3, the second row contains #4, #5, #6 and #7, and the third row contains #8, #9, #10 and #11. The tiles containing sprites are #1 to #10.
By default, TECHMANIA applies bilinear filter when scaling sprites to different sizes. This makes the scaled image smoother, but may produce artifacts at the edges when sprites are not transparent at all 4 edges, because the filter will read pixels from slightly outside the sprites’ borders, or in other words, up to 1 pixel into neighboring sprites. There are 2 ways to remove artifacts:
"bilinearFilter": false
in the sprite sheet. This prevents artifacts by forcing the game to read whole pixels instead of interpolating between neighboring pixels; as a side effect, this also causes scaled sprites to appear pixelated.It is recommended that you try padding first, and if the result looks weird, fall back to turning off bilinear filter.
All fields other than filename
are optional, and will take the following default values if you omit them:
rows
: 1columns
: 1firstIndex
: 0lastIndex
: 0padding
: 0bilinearFilter
: truescale
: 1speed
: 1additiveShader
: falseflipWhenScanningLeft
: falseThis means if you write a filename and nothing else, TECHMANIA will load the entire file as one sprite.
scale
, speed
, additiveShader
and flipWhenScanningLeft
are only supported by specific items in specific skins. In the skins that support them:
scale
determines the in-game size of sprites, in multiples of a lane’s height. Different skins define this parameter slightly differently, which will be discussed in detail in their corresponding sections.speed
determines the speed of the animation, in multiples of 60 frames per second. For example, "speed": 0.5
means the animation plays at 30 frames per second.additiveShader
determines whether the sprites are rendered with an additive shader. This will cause the colors of the sprites to be added to the layers below them, instead of replace the layers below them.flipWhenScanningLeft
determines whether the sprites are flipped (usually horizontally) when on a right-to-left scan. For skin items that do not support this field, it’s behavior on a right-to-left scan is usually hardcoded.The skin.json
file in a note skin follows the following format:
{
"version": "1",
"author": <author>,
"basic": <basic note>,
"chainHead": <chain head>,
"chainNode": <chain node>,
"chainPath": <chain path>,
"dragHead": <drag head>,
"dragCurve": <drag curve>,
"holdHead": <hold head>,
"holdTrail": <hold trail>,
"holdTrailEnd": <hold trail end>,
"holdOngoingTrail": <hold ongoing trail>,
"repeatHead": <repeat head>,
"repeat": <repeat>,
"repeatHoldTrail": <repeat hold trail>,
"repeatHoldTrailEnd": <repeat hold trail end>,
"repeatPath": <repeat path>,
"repeatPathEnd": <repeat path end>
}
Each value is a sprite sheet. Refer to the Terminology page for the meaning of most fields.
Item | Supports scale |
Supports speed |
Supports additiveShader |
Supports flipWhenScanningLeft |
---|---|---|---|---|
basic |
Yes *square | Yes | ||
chainHead |
Yes *square | Yes | ||
chainNode |
Yes *square | Yes | ||
chainPath |
Yes *1-D | Yes | ||
dragHead |
Yes *square | Yes | ||
dragCurve |
Yes *1-D | Yes | ||
holdHead |
Yes *square | Yes | ||
holdTrail |
Yes *1-D | |||
holdTrailEnd |
||||
holdOngoingTrail |
Yes *1-D | |||
repeatHead |
Yes *square | Yes | ||
repeat |
Yes *square | Yes | ||
repeatHoldTrail |
Yes *1-D | |||
repeatHoldTrailEnd |
||||
repeatPath |
Yes *1-D | |||
repeatPathEnd |
scale * lane height
. This is only visual, and has no effect on hitbox sizes.scale
. The size in the perpendicular dimension will be scaled to scale * lane height
.dragCurve
sprites are stretched in a special way: the left half is stretched to form the curve’s body, and the right half is left intact to form the curve’s end.
holdTrailEnd
, repeatHoldTrailEnd
and repeatPathEnd
are images attached to the end of their respective trails/paths, typically used for shadows and glows. They will be automatically scaled so that their height match the trail/path, while keeping the aspect ratio of the sprites unchanged.
holdOngoingTrail
is the trail drawn over the portion of a hold note that the scanline has passed.
All animations will play at a speed of one cycle per beat, and the 1st sprites in each sprite sheet will be the ones shown at whole beats.
For chainHead
, chainNode
and dragHead
, TECHMANIA assumes the sprites point to the right, and from there will rotate them to point in the direction of the next chain node or the curve. The last node in a chain will point to the same direction as the second last node.
Each chainPath
connecting two chain head/nodes will be rotated to point from the later to the earlier one. This can be counterintuitive, but is a result of how chain notes are rendered.
All note heads, plus chainNode
, chainPath
, dragCurve
and repeat
, support flipWhenScanningLeft
.
Hold trails, trail ends, repeat paths and repeat path ends do not support flipWhenScanningLeft
. They are hardcoded to always horizontally flip on right-to-left scans.
The skin.json
file in a VFX skin follows the following format:
{
"version": "1",
"author": <author>,
"feverOverlay": <fever overlay>,
"basicMax": [<layer 0>, <layer 1>, ..., <layer n>],
"basicCool": [<layer 0>, <layer 1>, ..., <layer n>],
"basicGood": [<layer 0>, <layer 1>, ..., <layer n>],
"dragOngoing": [<layer 0>, <layer 1>, ..., <layer n>],
"dragComplete": [<layer 0>, <layer 1>, ..., <layer n>],
"holdOngoingHead": [<layer 0>, <layer 1>, ..., <layer n>],
"holdOngoingTrail": [<layer 0>, <layer 1>, ..., <layer n>],
"holdComplete": [<layer 0>, <layer 1>, ..., <layer n>],
"repeatHead": [<layer 0>, <layer 1>, ..., <layer n>],
"repeatNote": [<layer 0>, <layer 1>, ..., <layer n>],
"repeatHoldOngoingHead": [<layer 0>, <layer 1>, ..., <layer n>],
"repeatHoldOngoingTrail": [<layer 0>, <layer 1>, ..., <layer n>],
"repeatHoldComplete": [<layer 0>, <layer 1>, ..., <layer n>]
}
In a VFX skin, each effect (except for Fever overlay) is drawn in multiple layers, each layer being one sprite sheet. The layers will be drawn in the order of their definition, so the last layer in the list will show up at the top. You cannot omit the []
even when there is only 0 or 1 layers in an effect.
Item | Explanation | Looping | Supports scale |
Supports speed |
Supports additiveShader |
Supports flipWhenScanningLeft |
---|---|---|---|---|---|---|
feverOverlay |
Overlay when Fever is active | Yes | Yes | Yes | Yes | |
basicMax |
Max on basic/chain notes | Yes | Yes | Yes | ||
basicCool |
Cool on basic/chain notes | Yes | Yes | Yes | ||
basicGood |
Good on basic/chain notes | Yes | Yes | Yes | ||
dragOngoing |
VFX on drag heads | Yes | Yes | Yes | Yes | |
dragComplete |
VFX after drags are complete | Yes | Yes | Yes | ||
holdOngoingHead |
VFX on hold heads | Yes | Yes | Yes | Yes | |
holdOngoingTrail |
VFX on hold trails, following scanline | Yes | Yes | Yes | Yes | |
holdComplete |
VFX after holds are complete | Yes | Yes | Yes | ||
repeatHead |
VFX on repeat heads | Yes | Yes | Yes | ||
repeatNote |
VFX on repeat notes | Yes | Yes | Yes | ||
repeatHoldOngoingHead |
VFX on heads when playing repeat holds | Yes | Yes | Yes | Yes | |
repeatHoldOngoingTrail |
VFX on trails when playing repeat holds | Yes | Yes | Yes | Yes | |
repeatHoldComplete |
VFX after repeat holds are complete | Yes | Yes | Yes |
All sprites will be scaled so that their heights are equal to scale * lane height
, while keeping the aspect ratios unchanged.
Besides feverOverlay
, all VFX are never flipped, whether on a left-to-right or right-to-left scan.
The skin.json
file in a combo skin follows the following format:
{
"version": "1",
"author": <author>,
"distanceToNote": <distance to note>,
"height": <height>,
"spaceBetweenJudgementAndCombo": <space between judgement and combo>,
"feverMaxJudgement": <Fever MAX judgement>,
"rainbowMaxJudgement": <Rainbow MAX judgement>,
"maxJudgement": <MAX judgement>,
"coolJudgement": <COOL judgement>,
"goodJudgement": <GOOD judgement>,
"missJudgement": <MISS judgement>,
"breakJudgement": <BREAK judgement>,
"feverMaxDigits": [<digit 0>, <digit 1>, ..., <digit 9>],
"rainbowMaxDigits": [<digit 0>, <digit 1>, ..., <digit 9>],
"maxDigits": [<digit 0>, <digit 1>, ..., <digit 9>],
"coolDigits": [<digit 0>, <digit 1>, ..., <digit 9>],
"goodDigits": [<digit 0>, <digit 1>, ..., <digit 9>],
"animationCurves": [<curve 0>, <curve 1>, ...]
}
Each judgement and digit is a sprite sheet.
Item | Supports scale |
Supports speed |
Supports additiveShader |
Supports flipWhenScanningLeft |
---|---|---|---|---|
feverMaxJudgement |
Yes | |||
rainbowMaxJudgement |
Yes | |||
maxJudgement |
Yes | |||
coolJudgement |
Yes | |||
goodJudgement |
Yes | |||
missJudgement |
Yes | |||
breakJudgement |
Yes | |||
feverMaxDigits |
Yes | |||
rainbowMaxDigits |
Yes | |||
maxDigits |
Yes | |||
coolDigits |
Yes | |||
goodDigits |
Yes |
The combo text contains 3 parts: the judgement, some horizontal space, and up to 4 digits that show the current combo. If the judgement is MISS or BREAK, the space and digits are not shown; otherwise, the digits are taken from the list that matches the judgement.
While each judgement and combo digit may play animations of their own, you can also define an animation for the combo text as a whole.
Combo text scales differently from other note and VFX skins in that it’s not dependent on lane height. Instead, the combo text’s position and size is controlled by distanceToNote
, height
and spaceBetweenJudgementAndCombo
:
All 3 numbers are in integer pixels. As a reference, the game window’s height is always 1080 pixels regardless of resolution. All sprites in the combo text will be scaled so that their heights are equal to height
, while keeping their aspect ratios unchanged.
Explained in a separate page: Combo text animation
The animation is optional. If you omit the "animationCurves"
field altogether, TECHMANIA will play a default, built-in animation on the combo text.
The skin.json
file in a game UI skin follows the following format:
{
"version": "1",
"author": <author>,
"scanline": <scanline>,
"autoPlayScanline": <scanline in auto play mode>,
"scanCountdownBackground": <scan countdown background>,
"scanCountdownNumbers": <scan countdown numbers>,
"scanCountdownCoversFiveEighthScans": <true or false>,
"touchClickFeedback": <touch/click feedback>,
"touchClickFeedbackSize": <touch/click feedback size in pixels>,
"approachOverlay": <approach overlay>
}
Each value, except for scanCountdownCoversFiveEighthScans
and touchClickFeedbackSize
, is a sprite sheet.
Item | Looping | Supports scale |
Supports speed |
Supports additiveShader |
Supports flipWhenScanningLeft |
---|---|---|---|---|---|
scanline |
Yes | ||||
autoPlayScanline |
Yes | ||||
scanCountdownBackground |
Temporary no* | ||||
scanCountdownNumbers |
Temporary no* | ||||
touchClickFeedback |
Yes | Yes | Temporary no* | ||
approachOverlay |
Yes |
* Since 2.0 TECHMANIA’s UI is built on UI Toolkit, which does not support shaders. Once supported is added, we will re-enable additiveShader
on these elements.
Scanline sprites are scaled so their heights are equal to the scan height, while keeping their aspect ratios unchanged. If they include animations, they play once per scan.
TECHMANIA assumes the scanline sprites are moving to the right, and will horizontally flip the sprites if a scan is to the left.
Scan countdown sprite sheets appear on the starting side of each scan, a set amount of time before it starts.
If scanCountdownCoversFiveEighthScans
is true
, the countdown starts 5/8 scans before the scan starts.
If scanCountdownCoversFiveEighthScans
is false
, the countdown starts 3 beats before the scan starts, with exceptions on low-BPS patterns:
The sprites are scaled so their heights are equal to the scan height, while keeping their aspect ratios unchanged. TECHMANIA assumes the scanCountdownBackground
sprites appear on the left side of a scan (for scans that flow to the right), and will horizontally flip the sprites if a scan is to the left. scanCountdownNumbers
is considered adirectional, and will not be flipped.
Touch/click feedback appears on each touch point (in Touch patterns) or the mouse cursor when the mouse button is held (in KM patterns). The sprites are scaled to a square whose side length is equal to touchClickFeedbackSize
pixels. As a reference, the game window’s height is always 1080 pixels regardless of resolution.
Approach overlay appears on each basic, chain head, drag head, hold head and repeat head note, 1/2 scans before its correct time, and lasts for 1/2 scans. The sprites are scaled to a square whose side length is equal to scale * lane height
. TECHMANIA will horizontally flip the sprites for notes on a right-to-left scan.
By default, TECHMANIA only loads skins when:
In the select skin menu, there’s an option to also reload the skins each time you load a pattern. This may be useful for making and testing new skins, but it increases load time, so remember to turn it off after you complete your skin.
Keep the following in mind when writing JSON:
filename
fields are strings).[]
or {}
, write commas after each value except for the last one.