mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-14 19:25:16 -05:00
First implementation of credits state.
This commit is contained in:
parent
99ea7bbe84
commit
1e888658f7
10 changed files with 445 additions and 13 deletions
|
@ -175,6 +175,7 @@
|
|||
<haxedef name="haxeui_focus_out_on_click" />
|
||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
||||
<haxedef name="haxeui_dont_impose_base_class" />
|
||||
<haxedef name="HARDCODED_CREDITS" />
|
||||
|
||||
<!-- Skip the Intro -->
|
||||
<section if="debug">
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 5f1726f1b0c11fc747b7473708cf4e5f28be05f1
|
||||
Subproject commit edccf04217c49c730b11c80736e2b2d98a25ee95
|
|
@ -325,12 +325,3 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
contents:String
|
||||
};
|
||||
|
|
10
source/funkin/data/JsonFile.hx
Normal file
10
source/funkin/data/JsonFile.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.data;
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
contents:String
|
||||
};
|
|
@ -427,7 +427,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return ScriptedSong.listScriptClasses();
|
||||
}
|
||||
|
||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
|
@ -438,7 +438,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return {fileName: entryFilePath, contents: rawJson};
|
||||
}
|
||||
|
||||
function loadMusicDataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadMusicDataFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
||||
|
@ -456,7 +456,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return openfl.Assets.exists(entryFilePath);
|
||||
}
|
||||
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
|
|
26
source/funkin/ui/credits/CreditsData.hx
Normal file
26
source/funkin/ui/credits/CreditsData.hx
Normal file
|
@ -0,0 +1,26 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
/**
|
||||
* The members of the Funkin' Crew, organized by their roles.
|
||||
*/
|
||||
typedef CreditsData =
|
||||
{
|
||||
var roles:Array<CreditsDataRole>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The members of a specific role on the Funkin' Crew.
|
||||
*/
|
||||
typedef CreditsDataRole =
|
||||
{
|
||||
var roleName:String;
|
||||
var members:Array<CreditsDataMember>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A member of a specific person on the Funkin' Crew.
|
||||
*/
|
||||
typedef CreditsDataMember =
|
||||
{
|
||||
var fullName:String;
|
||||
}
|
130
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
130
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
|
@ -0,0 +1,130 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
import funkin.data.JsonFile;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:nullSafety
|
||||
class CreditsDataHandler
|
||||
{
|
||||
public static final BACKER_PUBLIC_URL:String = 'https://funkin.me/backers';
|
||||
|
||||
#if HARDCODED_CREDITS
|
||||
static final CREDITS_DATA_PATH:String = "assets/exclude/data/credits.json";
|
||||
#else
|
||||
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
|
||||
#end
|
||||
|
||||
public static function debugPrint(data:Null<CreditsData>):Void
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
trace('CreditsData(NULL)');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var roleCount = data.roles.length;
|
||||
var memberCount = 0;
|
||||
for (role in data.roles)
|
||||
{
|
||||
memberCount += role.members.length;
|
||||
}
|
||||
|
||||
trace('CreditsData($roleCount roles with $memberCount members)');
|
||||
}
|
||||
|
||||
/**
|
||||
* If for some reason the full credits won't load,
|
||||
* use this hardcoded data for the original Funkin' Crew.
|
||||
*
|
||||
* @return `CreditsData`
|
||||
*/
|
||||
public static inline function getFallback():CreditsData
|
||||
{
|
||||
return {
|
||||
roles: [
|
||||
{
|
||||
roleName: 'Founders',
|
||||
members: [
|
||||
{fullName: 'ninjamuffin99'},
|
||||
{fullName: 'PhantomArcade'},
|
||||
{fullName: 'KawaiSprite'},
|
||||
{fullName: 'evilsk8r'},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
public static function fetchBackerEntries():Array<String>
|
||||
{
|
||||
// TODO: Replace this with a web request.
|
||||
// We can't just grab the current Kickstarter data and include it in builds,
|
||||
// because we don't want to deadname people who haven't logged into the portal yet.
|
||||
// It can be async and paginated for performance!
|
||||
return ['See the list of backers at $BACKER_PUBLIC_URL.'];
|
||||
}
|
||||
|
||||
#if HARDCODED_CREDITS
|
||||
/**
|
||||
* The data for the credits.
|
||||
* Hardcoded into game via a macro at compile time.
|
||||
*/
|
||||
public static final CREDITS_DATA:Null<CreditsData> = #if macro null #else CreditsDataMacro.loadCreditsData() #end;
|
||||
#else
|
||||
|
||||
/**
|
||||
* The data for the credits.
|
||||
* Loaded dynamically from the game folder when needed.
|
||||
* Nullable because data may fail to parse.
|
||||
*/
|
||||
public static var CREDITS_DATA(get, default):Null<CreditsData> = null;
|
||||
|
||||
static function get_CREDITS_DATA():Null<CreditsData>
|
||||
{
|
||||
if (CREDITS_DATA == null) CREDITS_DATA = parseCreditsData(fetchCreditsData());
|
||||
|
||||
return CREDITS_DATA;
|
||||
}
|
||||
|
||||
static function fetchCreditsData():funkin.data.JsonFile
|
||||
{
|
||||
var rawJson:String = openfl.Assets.getText(CREDITS_DATA_PATH).trim();
|
||||
|
||||
return {
|
||||
fileName: CREDITS_DATA_PATH,
|
||||
contents: rawJson
|
||||
};
|
||||
}
|
||||
|
||||
static function parseCreditsData(file:JsonFile):Null<CreditsData>
|
||||
{
|
||||
#if !macro
|
||||
if (file.contents == null) return null;
|
||||
|
||||
var parser = new json2object.JsonParser<CreditsData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
trace('[CREDITS] Parsing credits data from ${CREDITS_DATA_PATH}');
|
||||
parser.fromJson(file.contents, file.fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, file.fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
static function printErrors(errors:Array<json2object.Error>, id:String = ''):Void
|
||||
{
|
||||
trace('[CREDITS] Failed to parse credits data: ${id}');
|
||||
|
||||
for (error in errors)
|
||||
funkin.data.DataError.printError(error);
|
||||
}
|
||||
#end
|
||||
}
|
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
|
@ -0,0 +1,67 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
#end
|
||||
|
||||
@:access(funkin.ui.credits.CreditsDataHandler)
|
||||
class CreditsDataMacro
|
||||
{
|
||||
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
|
||||
{
|
||||
#if !display
|
||||
trace('Hardcoding credits data...');
|
||||
var json = CreditsDataMacro.fetchJSON();
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
Context.info('[WARN] Could not fetch JSON data for credits.', Context.currentPos());
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
}
|
||||
|
||||
var creditsData = CreditsDataMacro.parseJSON(json);
|
||||
|
||||
if (creditsData == null)
|
||||
{
|
||||
Context.info('[WARN] Could not parse JSON data for credits.', Context.currentPos());
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
}
|
||||
|
||||
CreditsDataHandler.debugPrint(creditsData);
|
||||
return macro $v{creditsData};
|
||||
// return macro $v{null};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case we return
|
||||
// a minimal value to keep code completion fast.
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
#end
|
||||
}
|
||||
|
||||
#if macro
|
||||
static function fetchJSON():Null<String>
|
||||
{
|
||||
return sys.io.File.getContent(CreditsDataHandler.CREDITS_DATA_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the JSON data for the credits.
|
||||
*
|
||||
* @param json The string data to parse.
|
||||
* @return The parsed data.
|
||||
*/
|
||||
static function parseJSON(json:String):Null<CreditsData>
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Use something with better validation but that still works at macro time.
|
||||
return haxe.Json.parse(json);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace('[ERROR] Failed to parse JSON data for credits.');
|
||||
trace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
200
source/funkin/ui/credits/CreditsState.hx
Normal file
200
source/funkin/ui/credits/CreditsState.hx
Normal file
|
@ -0,0 +1,200 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
|
||||
/**
|
||||
* The state used to display the credits scroll.
|
||||
* AAA studios often fail to credit properly, and we're better than them!
|
||||
*/
|
||||
class CreditsState extends MusicBeatState
|
||||
{
|
||||
/**
|
||||
* The height the credits should start at.
|
||||
* Make this an instanced variable so it gets set by the constructor.
|
||||
*/
|
||||
final STARTING_HEIGHT = FlxG.height;
|
||||
|
||||
/**
|
||||
* The padding on each side of the screen.
|
||||
*/
|
||||
static final SCREEN_PAD = 24;
|
||||
|
||||
/**
|
||||
* The width of the screen the credits should maximally fill up.
|
||||
* Make this an instanced variable so it gets set by the constructor.
|
||||
*/
|
||||
final FULL_WIDTH = FlxG.width - (SCREEN_PAD * 2);
|
||||
|
||||
/**
|
||||
* The font to use to display the text.
|
||||
* To use a font from the `assets` folder, use `Paths.font(...)`.
|
||||
* Choose something that will render Unicode properly.
|
||||
*/
|
||||
static final CREDITS_FONT = 'Arial';
|
||||
|
||||
/**
|
||||
* The size of the font.
|
||||
*/
|
||||
static final CREDITS_FONT_SIZE = 48;
|
||||
|
||||
static final CREDITS_HEADER_FONT_SIZE = 72;
|
||||
|
||||
/**
|
||||
* The color of the text itself.
|
||||
*/
|
||||
static final CREDITS_FONT_COLOR = FlxColor.WHITE;
|
||||
|
||||
/**
|
||||
* The color of the text's outline.
|
||||
*/
|
||||
static final CREDITS_FONT_STROKE_COLOR = FlxColor.BLACK;
|
||||
|
||||
/**
|
||||
* The speed the credits scroll at, in pixels per second.
|
||||
*/
|
||||
static final CREDITS_SCROLL_BASE_SPEED = 25.0;
|
||||
|
||||
/**
|
||||
* The speed the credits scroll at while the button is held, in pixels per second.
|
||||
*/
|
||||
static final CREDITS_SCROLL_FAST_SPEED = CREDITS_SCROLL_BASE_SPEED * 4.0;
|
||||
|
||||
/**
|
||||
* The actual sprites and text used to display the credits.
|
||||
*/
|
||||
var creditsGroup:FlxSpriteGroup;
|
||||
|
||||
var scrollPaused:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
// Background
|
||||
var bg = new FlxSprite(Paths.image('menuDesat'));
|
||||
bg.scrollFactor.x = 0;
|
||||
bg.scrollFactor.y = 0;
|
||||
bg.setGraphicSize(Std.int(FlxG.width));
|
||||
bg.updateHitbox();
|
||||
bg.x = 0;
|
||||
bg.y = 0;
|
||||
bg.visible = true;
|
||||
bg.color = 0xFFB57EDC; // Lavender
|
||||
add(bg);
|
||||
|
||||
// TODO: Once we need to display Kickstarter backers,
|
||||
// make this use a recycled pool so we don't kill peformance.
|
||||
creditsGroup = new FlxSpriteGroup();
|
||||
creditsGroup.x = SCREEN_PAD;
|
||||
creditsGroup.y = STARTING_HEIGHT;
|
||||
|
||||
buildCreditsGroup();
|
||||
|
||||
add(creditsGroup);
|
||||
|
||||
// Music
|
||||
FunkinSound.playMusic('freeplayRandom',
|
||||
{
|
||||
startingVolume: 0.0,
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
loop: true
|
||||
});
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
|
||||
function buildCreditsGroup():Void
|
||||
{
|
||||
var y = 0;
|
||||
|
||||
for (role in CreditsDataHandler.CREDITS_DATA.roles)
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(role.roleName, y, true, CreditsSide.Center));
|
||||
y += CREDITS_HEADER_FONT_SIZE;
|
||||
|
||||
for (member in role.members)
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(member.fullName, y, false, CreditsSide.Center));
|
||||
y += CREDITS_FONT_SIZE;
|
||||
}
|
||||
|
||||
// Padding between each role.
|
||||
y += CREDITS_FONT_SIZE * 2;
|
||||
}
|
||||
}
|
||||
|
||||
function buildCreditsLine(text:String, yPos:Float, header:Bool, side:CreditsSide = CreditsSide.Center):FlxText
|
||||
{
|
||||
// CreditsSide.Center: Full screen width
|
||||
// CreditsSide.Left: Left half of screen
|
||||
// CreditsSide.Right: Right half of screen
|
||||
var xPos = (side == CreditsSide.Right) ? (FULL_WIDTH / 2) : 0;
|
||||
var width = (side == CreditsSide.Center) ? FULL_WIDTH : (FULL_WIDTH / 2);
|
||||
var size = header ? CREDITS_HEADER_FONT_SIZE : CREDITS_FONT_SIZE;
|
||||
|
||||
var creditsLine:FlxText = new FlxText(xPos, yPos, width, text);
|
||||
creditsLine.setFormat(CREDITS_FONT, size, CREDITS_FONT_COLOR, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, CREDITS_FONT_STROKE_COLOR, true);
|
||||
|
||||
return creditsLine;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (!scrollPaused)
|
||||
{
|
||||
// TODO: Replace with whatever the special note button is.
|
||||
if (controls.ACCEPT || FlxG.keys.pressed.SPACE)
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_FAST_SPEED * elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_BASE_SPEED * elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.BACK || hasEnded())
|
||||
{
|
||||
exit();
|
||||
}
|
||||
else if (controls.PAUSE)
|
||||
{
|
||||
scrollPaused = !scrollPaused;
|
||||
}
|
||||
}
|
||||
|
||||
function hasEnded():Bool
|
||||
{
|
||||
return creditsGroup.y < -creditsGroup.height;
|
||||
}
|
||||
|
||||
function exit():Void
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.mainmenu.MainMenuState());
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
enum CreditsSide
|
||||
{
|
||||
Left;
|
||||
Center;
|
||||
Right;
|
||||
}
|
|
@ -117,6 +117,10 @@ class MainMenuState extends MusicBeatState
|
|||
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||
});
|
||||
|
||||
createMenuItem('options', 'mainmenu/options', function() {
|
||||
startExitState(() -> new funkin.ui.credits.CreditsState());
|
||||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
var spacing = 160;
|
||||
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||
|
@ -125,6 +129,9 @@ class MainMenuState extends MusicBeatState
|
|||
var menuItem = menuItems.members[i];
|
||||
menuItem.x = FlxG.width / 2;
|
||||
menuItem.y = top + spacing * i;
|
||||
menuItem.scrollFactor.x = 0.0;
|
||||
// This one affects how much the menu items move when you scroll between them.
|
||||
menuItem.scrollFactor.y = 0.4;
|
||||
}
|
||||
|
||||
resetCamStuff();
|
||||
|
|
Loading…
Reference in a new issue