Merge branch 'notehold-styles' into rewrite/master

This commit is contained in:
Cameron Taylor
2025-03-21 18:58:29 -04:00
8 changed files with 252 additions and 81 deletions
+1 -1
Submodule assets updated: 26b7f54a06...51f7f0df72
@@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<!-- TODO: Include NoteSplash and NoteStyle changes made to accomodate week 6 note splash/hold stuff -->
## [1.1.0]
### Added
- Added several new `assets`:
@@ -290,4 +290,33 @@ typedef NoteStyleData_HoldNoteCover =
@:optional
@:default(true)
var enabled:Bool;
@:optional
var left:NoteStyleData_HoldNoteCoverDirectionData;
@:optional
var down:NoteStyleData_HoldNoteCoverDirectionData;
@:optional
var up:NoteStyleData_HoldNoteCoverDirectionData;
@:optional
var right:NoteStyleData_HoldNoteCoverDirectionData;
};
typedef NoteStyleData_HoldNoteCoverDirectionData =
{
/**
* Optionally specify an asset path to use for this specific animation.
* @:default The assetPath of the main holdNoteCover asset
*/
@:optional
var assetPath:String;
@:optional
var start:UnnamedAnimationData;
@:optional
var hold:UnnamedAnimationData;
@:optional
var end:UnnamedAnimationData;
}
+9 -38
View File
@@ -4,66 +4,37 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.util.assets.FlxAnimationUtil;
import flixel.FlxSprite;
import funkin.play.notes.notestyle.NoteStyle;
class NoteHoldCover extends FlxTypedSpriteGroup<FlxSprite>
{
static final FRAMERATE_DEFAULT:Int = 24;
static var glowFrames:FlxFramesCollection;
public var holdNote:SustainTrail;
var glow:FlxSprite;
public var glow:FlxSprite;
var sparks:FlxSprite;
public function new()
public function new(noteStyle:NoteStyle)
{
super(0, 0);
setup();
}
public static function preloadFrames():Void
{
glowFrames = null;
for (direction in Strumline.DIRECTIONS)
{
var directionName = direction.colorName.toTitleCase();
var atlas:FlxFramesCollection = Paths.getSparrowAtlas('holdCover${directionName}');
atlas.parent.persist = true;
if (glowFrames != null)
{
glowFrames = FlxAnimationUtil.combineFramesCollections(glowFrames, atlas);
}
else
{
glowFrames = atlas;
}
}
setupHoldNoteCover(noteStyle);
}
/**
* Add ALL the animations to this sprite. We will recycle and reuse the FlxSprite multiple times.
*/
function setup():Void
function setupHoldNoteCover(noteStyle:NoteStyle):Void
{
glow = new FlxSprite();
add(glow);
if (glowFrames == null) preloadFrames();
glow.frames = glowFrames;
for (direction in Strumline.DIRECTIONS)
{
var directionName = direction.colorName.toTitleCase();
// TODO: null check here like how NoteSplash does
noteStyle.buildHoldCoverSprite(this);
glow.animation.addByPrefix('holdCoverStart$directionName', 'holdCoverStart${directionName}0', FRAMERATE_DEFAULT, false, false, false);
glow.animation.addByPrefix('holdCover$directionName', 'holdCover${directionName}0', FRAMERATE_DEFAULT, true, false, false);
glow.animation.addByPrefix('holdCoverEnd$directionName', 'holdCoverEnd${directionName}0', FRAMERATE_DEFAULT, false, false, false);
}
glow.animation.finishCallback = this.onAnimationFinished;
glow.animation.onFinish.add(this.onAnimationFinished);
if (glow.animation.getAnimationList().length < 3 * 4)
{
+2 -7
View File
@@ -12,19 +12,13 @@ class NoteSplash extends FlxSprite
static var frameCollection:FlxFramesCollection;
public static function preloadFrames():Void
{
frameCollection = Paths.getSparrowAtlas('noteSplashes');
frameCollection.parent.persist = true;
}
public function new(noteStyle:NoteStyle)
{
super(0, 0);
setupSplashGraphic(noteStyle);
this.animation.finishCallback = this.onAnimationFinished;
this.animation.onFinish.add(this.onAnimationFinished);
}
/**
@@ -55,6 +49,7 @@ class NoteSplash extends FlxSprite
}
// splashUP0, splashUP1, splashRIGHT0, etc.
// the animations are processed via `NoteStyle.fetchSplashAnimationData()` in this format
this.playAnimation('splash${direction.nameUpper}${variant}');
if (animation.curAnim == null) return;
+7 -5
View File
@@ -779,10 +779,10 @@ class Strumline extends FlxSpriteGroup
splash.x = this.x;
splash.x += getXPos(direction);
splash.x += INITIAL_OFFSET;
splash.x += noteStyle.getSplashOffsets()[0] * splash.scale.x;
splash.y = this.y;
splash.y -= INITIAL_OFFSET;
splash.y += 0;
splash.x += noteStyle.getSplashOffsets()[0] * splash.scale.x;
splash.y += noteStyle.getSplashOffsets()[1] * splash.scale.y;
}
}
@@ -806,12 +806,14 @@ class Strumline extends FlxSpriteGroup
cover.x += getXPos(holdNote.noteDirection);
cover.x += STRUMLINE_SIZE / 2;
cover.x -= cover.width / 2;
cover.x += -12; // Manual tweaking because fuck.
cover.x += noteStyle.getHoldCoverOffsets()[0] * cover.scale.x;
cover.x += -12; // hardcoded adjustment, because we are evil.
cover.y = this.y;
cover.y += INITIAL_OFFSET;
cover.y += STRUMLINE_SIZE / 2;
cover.y += -96; // Manual tweaking because fuck.
cover.y += noteStyle.getHoldCoverOffsets()[1] * cover.scale.y;
cover.y += -96; // hardcoded adjustment, because we are evil.
}
}
@@ -916,7 +918,7 @@ class Strumline extends FlxSpriteGroup
if (noteHoldCovers.length < noteHoldCovers.maxSize)
{
// Create a new note hold cover.
result = new NoteHoldCover();
result = new NoteHoldCover(noteStyle);
this.noteHoldCovers.add(result);
}
else
+194 -28
View File
@@ -2,6 +2,7 @@ package funkin.play.notes.notestyle;
import funkin.play.Countdown;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.data.animation.AnimationData;
import funkin.data.IRegistryEntry;
import funkin.graphics.FunkinSprite;
@@ -99,6 +100,15 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
target.updateHitbox();
}
function getNoteAssetLibrary():Null<String>
{
// library:path
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return null;
return parts[0];
}
var noteFrames:Null<FlxAtlasFrames> = null;
function buildNoteFrames(force:Bool = false):Null<FlxAtlasFrames>
@@ -123,7 +133,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
if (noteFrames != null && !force) return noteFrames;
noteFrames = Paths.getSparrowAtlas(noteAssetPath, getNoteAssetLibrary());
noteFrames = Paths.getSparrowAtlas(noteAssetPath, getAssetLibrary(getNoteAssetPath(true)));
if (noteFrames == null)
{
@@ -149,15 +159,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return parts[1];
}
function getNoteAssetLibrary():Null<String>
{
// library:path
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return null;
return parts[0];
}
function buildNoteAnimations(target:NoteSprite):Void
{
var leftData:Null<AnimationData> = fetchNoteAnimationData(LEFT);
@@ -232,7 +233,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
// TODO: Add support for multi-Sparrow.
// Will be less annoying after this is merged: https://github.com/HaxeFlixel/flixel/pull/2772
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath() ?? '', getStrumlineAssetLibrary());
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath() ?? '', getAssetLibrary(getStrumlineAssetPath(true)));
if (atlas == null)
{
@@ -258,14 +259,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return parts[1];
}
function getStrumlineAssetLibrary():Null<String>
{
// library:path
var parts = getStrumlineAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length <= 1) return null;
return parts[0];
}
public function applyStrumlineAnimations(target:StrumlineNote, dir:NoteDirection):Void
{
FlxAnimationUtil.addAtlasAnimations(target, getStrumlineAnimationData(dir));
@@ -921,7 +914,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
if (splashFrames != null && !force) return splashFrames;
splashFrames = Paths.getSparrowAtlas(splashAssetPath, getSplashAssetLibrary());
splashFrames = Paths.getSparrowAtlas(splashAssetPath, getAssetLibrary(getSplashAssetPath(true)));
splashFrames.parent.persist = true;
if (splashFrames == null)
{
@@ -945,14 +938,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return parts[1];
}
function getSplashAssetLibrary():Null<String>
{
var parts = getSplashAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return null;
return parts[0];
}
function buildSplashAnimations(target:NoteSplash):Void
{
final addSplashAnim:NoteDirection->Void = function(dir:NoteDirection) {
@@ -1007,8 +992,189 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return _data?.assets?.noteSplash?.data?.framerateVariance ?? fallback?.getSplashFramerateVariance() ?? 2;
}
public function buildHoldCoverSprite(target:NoteHoldCover):Void
{
// NoteHoldCover has "glow" and "sparks". Right now it only implements "glow"
// but "sparks" I believe is meant to be used for the ending of the hold note
var glowAtlas:Null<FlxFramesCollection> = buildHoldCoverFrames(false);
if (glowAtlas == null)
{
throw 'Could not load spritesheet for note style: $id';
}
target.glow.frames = glowAtlas;
target.antialiasing = !(_data.assets.holdNoteCover?.isPixel ?? false);
target.glow.antialiasing = !(_data.assets.holdNoteCover?.isPixel ?? false);
target.scale.set(_data.assets.holdNoteCover?.scale ?? 1.0, _data.assets.holdNoteCover?.scale ?? 1.0);
target.updateHitbox();
target.glow.updateHitbox();
buildHoldCoverAnimations(target);
}
// we use FlxFramesCollection here so we can combine em into one atlas later
var holdCoverFrames:Null<FlxFramesCollection> = null;
function buildHoldCoverFrames(force:Bool = false):Null<FlxFramesCollection>
{
if (holdCoverFrames != null && !force) return holdCoverFrames;
for (direction in Strumline.DIRECTIONS)
{
// We make a FlxFramesCollection here so we can combine em into one atlas later
var atlas:Null<FlxFramesCollection> = buildHoldCoverFrameForDirection(direction);
if (holdCoverFrames == null) holdCoverFrames = atlas;
else if (atlas != null) holdCoverFrames = FlxAnimationUtil.combineFramesCollections(holdCoverFrames, atlas);
}
return holdCoverFrames;
}
function buildHoldCoverFrameForDirection(direction:NoteDirection):Null<FlxFramesCollection>
{
var directionData = switch (direction)
{
case LEFT: _data.assets?.holdNoteCover?.data?.left;
case DOWN: _data.assets?.holdNoteCover?.data?.down;
case UP: _data.assets?.holdNoteCover?.data?.up;
case RIGHT: _data.assets?.holdNoteCover?.data?.right;
};
if (directionData == null) return null;
var holdCoverAssetPath:Null<String> = getHoldCoverDirectionAssetPath(direction) ?? fallback?.getHoldCoverDirectionAssetPath(direction);
if (holdCoverAssetPath == null)
{
FlxG.log.warn('Hold Note Cover asset path not found: ${id}');
return null;
}
if (!FunkinSprite.isTextureCached(Paths.image(holdCoverAssetPath)))
{
FlxG.log.warn('Hold Note Cover texture not cached: ${holdCoverAssetPath}');
}
var atlas:FlxFramesCollection = Paths.getSparrowAtlas(holdCoverAssetPath, getAssetLibrary(getHoldCoverDirectionAssetPath(direction, true)));
if (atlas == null) return null;
atlas.parent.persist = true;
return atlas;
}
function buildHoldCoverAnimations(target:NoteHoldCover):Void
{
for (direction in Strumline.DIRECTIONS)
{
var animData:Null<Array<AnimationData>> = fetchHoldCoverAnimationData(direction);
if (animData != null)
{
// this entry is the base animation that plays ("holdCover$noteColor" animation), which we want to loop
animData[1].looped = true;
FlxAnimationUtil.addAtlasAnimations(target.glow, animData);
}
}
}
function fetchHoldCoverAnimationData(dir:NoteDirection):Null<Array<AnimationData>>
{
var noteColor:String = dir.colorName.toTitleCase();
var result:Array<Null<AnimationData>> = switch (dir)
{
case LEFT: [
_data.assets?.holdNoteCover?.data?.left?.start?.toNamed('holdCoverStart$noteColor'),
_data.assets?.holdNoteCover?.data?.left?.hold?.toNamed('holdCover$noteColor'),
_data.assets?.holdNoteCover?.data?.left?.end?.toNamed('holdCoverEnd$noteColor'),
];
case DOWN: [
_data.assets?.holdNoteCover?.data?.down?.start?.toNamed('holdCoverStart$noteColor'),
_data.assets?.holdNoteCover?.data?.down?.hold?.toNamed('holdCover$noteColor'),
_data.assets?.holdNoteCover?.data?.down?.end?.toNamed('holdCoverEnd$noteColor'),
];
case UP: [
_data.assets?.holdNoteCover?.data?.up?.start?.toNamed('holdCoverStart$noteColor'),
_data.assets?.holdNoteCover?.data?.up?.hold?.toNamed('holdCover$noteColor'),
_data.assets?.holdNoteCover?.data?.up?.end?.toNamed('holdCoverEnd$noteColor'),
];
case RIGHT: [
_data.assets?.holdNoteCover?.data?.right?.start?.toNamed('holdCoverStart$noteColor'),
_data.assets?.holdNoteCover?.data?.right?.hold?.toNamed('holdCover$noteColor'),
_data.assets?.holdNoteCover?.data?.right?.end?.toNamed('holdCoverEnd$noteColor'),
];
default: [];
};
// New variable so we can change the type.
var filteredResult:Array<AnimationData> = thx.Arrays.filterNull(result);
if (filteredResult.length == 0) return fallback?.fetchHoldCoverAnimationData(dir) ?? [];
return filteredResult;
}
// fallbacks:
// assetPath for a direction -> root assetpath -> fallback assetpath for direction -> fallback root assetpath
// `direction` here is required, however it only is used for fallback purposes
function getHoldCoverRootAssetPath(direction:NoteDirection, raw:Bool = false):Null<String>
{
if (raw)
{
var rawPath:Null<String> = _data?.assets?.holdNoteCover?.assetPath;
// remember, if we need a fallback for our *root* asset path,
// we fallback and look for the *direction* asset path first
if (rawPath == null) return fallback?.getHoldCoverDirectionAssetPath(direction, true);
return rawPath;
}
var parts:Array<String> = getHoldCoverRootAssetPath(direction, true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return getHoldCoverRootAssetPath(direction, true);
return parts[1];
}
function getHoldCoverDirectionAssetPath(direction:NoteDirection, raw:Bool = false):Null<String>
{
if (raw)
{
var rawPath:Null<String> = switch (direction)
{
case LEFT: _data?.assets?.holdNoteCover?.data?.left?.assetPath;
case DOWN: _data?.assets?.holdNoteCover?.data?.down?.assetPath;
case UP: _data?.assets?.holdNoteCover?.data?.up?.assetPath;
case RIGHT: _data?.assets?.holdNoteCover?.data?.right?.assetPath;
};
// we don't want to fallback directly just yet, we want to check our root assetpath first
if (rawPath == null) return getHoldCoverRootAssetPath(direction, true);
return rawPath;
}
var parts:Array<String> = getHoldCoverDirectionAssetPath(direction, true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length == 0) return null;
if (parts.length == 1) return getHoldCoverDirectionAssetPath(direction, true);
return parts[1];
}
public function getHoldCoverOffsets():Array<Float>
{
return _data?.assets?.holdNoteCover?.offsets ?? fallback?.getHoldCoverOffsets() ?? [0.0, 0.0];
}
public function destroy():Void {}
/**
* Returns a string of the library name for the given asset id
* `default:assets/images/awesome.png` returns `default`
* If you pass a asset path with no library name (no `:` aka LIBRARY_SEPARATOR) it will return null
* @param id The asset id to get the library name from
* @return Null<String> The library name, or null if no library name is present
*/
function getAssetLibrary(?id:String):Null<String>
{
var parts:Array<String> = id?.split(Constants.LIBRARY_SEPARATOR) ?? [];
if (parts.length <= 1) return null;
return parts[0];
}
public function toString():String
{
return 'NoteStyle($id)';
@@ -9,7 +9,7 @@ class FlxAnimationUtil
/**
* Properly adds an animation to a sprite based on the provided animation data.
*/
public static function addAtlasAnimation(target:FlxSprite, anim:AnimationData)
public static function addAtlasAnimation(target:FlxSprite, anim:AnimationData):Void
{
var frameRate = anim.frameRate == null ? 24 : anim.frameRate;
var looped = anim.looped == null ? false : anim.looped;
@@ -33,7 +33,7 @@ class FlxAnimationUtil
/**
* Properly adds multiple animations to a sprite based on the provided animation data.
*/
public static function addAtlasAnimations(target:FlxSprite, animations:Array<AnimationData>)
public static function addAtlasAnimations(target:FlxSprite, animations:Array<AnimationData>):Void
{
for (anim in animations)
{
@@ -41,6 +41,12 @@ class FlxAnimationUtil
}
}
/**
* Combine two FlxFramesCollection objects into one.
* @param a The first FlxFramesCollection
* @param b The second FlxFramesCollection
* @return FlxFramesCollection The combined FlxFramesCollection
*/
public static function combineFramesCollections(a:FlxFramesCollection, b:FlxFramesCollection):FlxFramesCollection
{
var result:FlxFramesCollection = new FlxFramesCollection(null, ATLAS, null);