1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-07-12 22:28:03 +00:00

Additional fixes for health icons and camera positioning.

This commit is contained in:
Eric Myllyoja 2022-03-26 22:18:26 -04:00
parent 3634346072
commit 4b7744f4e3
13 changed files with 353 additions and 110 deletions

View file

@ -1,14 +1,10 @@
package funkin; package funkin;
import funkin.util.Constants; import flixel.addons.transition.FlxTransitionableState;
import funkin.modding.events.ScriptEvent.UpdateScriptEvent; import flixel.effects.FlxFlicker;
import funkin.modding.module.ModuleHandler;
import funkin.NGio;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.FlxState; import flixel.FlxState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.effects.FlxFlicker;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.input.touch.FlxTouch; import flixel.input.touch.FlxTouch;
@ -18,14 +14,19 @@ import flixel.tweens.FlxTween;
import flixel.ui.FlxButton; import flixel.ui.FlxButton;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import lime.app.Application; import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
import openfl.filters.ShaderFilter; import funkin.modding.module.ModuleHandler;
import funkin.NGio;
import funkin.shaderslmfao.ScreenWipeShader; import funkin.shaderslmfao.ScreenWipeShader;
import funkin.ui.AtlasMenuList; import funkin.ui.AtlasMenuList;
import funkin.ui.MenuList; import funkin.ui.MenuList;
import funkin.ui.OptionsState; import funkin.ui.OptionsState;
import funkin.ui.PreferencesMenu; import funkin.ui.PreferencesMenu;
import funkin.ui.Prompt; import funkin.ui.Prompt;
import funkin.util.Constants;
import funkin.util.WindowUtil;
import lime.app.Application;
import openfl.filters.ShaderFilter;
using StringTools; using StringTools;
@ -185,17 +186,7 @@ class MainMenuState extends MusicBeatState
#if CAN_OPEN_LINKS #if CAN_OPEN_LINKS
function selectDonate() function selectDonate()
{ {
#if linux WindowUtil.openURL(Constants.URL_KICKSTARTER);
// Sys.command('/usr/bin/xdg-open', ["https://ninja-muffin24.itch.io/funkin", "&"]);
Sys.command('/usr/bin/xdg-open', [
"https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/",
"&"
]);
#else
// FlxG.openURL('https://ninja-muffin24.itch.io/funkin');
FlxG.openURL('https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/');
#end
} }
#end #end

View file

@ -95,10 +95,14 @@ class MusicBeatState extends FlxUIState
function debug_refreshModules() function debug_refreshModules()
{ {
// Forcibly clear scripts so that scripts can be edited.
ModuleHandler.clearModuleCache(); ModuleHandler.clearModuleCache();
// Forcibly reload scripts so that scripted stages can be edited.
polymod.hscript.PolymodScriptClass.clearScriptClasses(); polymod.hscript.PolymodScriptClass.clearScriptClasses();
// Forcibly reload Polymod so it finds any new files.
polymod.Polymod.reload();
// Reload scripted classes so stages and modules will update.
polymod.hscript.PolymodScriptClass.registerAllScriptClasses(); polymod.hscript.PolymodScriptClass.registerAllScriptClasses();
// Reload the stages in cache. This might cause a lag spike but who cares this is a debug utility. // Reload the stages in cache. This might cause a lag spike but who cares this is a debug utility.
@ -106,7 +110,7 @@ class MusicBeatState extends FlxUIState
CharacterDataParser.loadCharacterCache(); CharacterDataParser.loadCharacterCache();
ModuleHandler.loadModuleCache(); ModuleHandler.loadModuleCache();
// Create a new instance of the current state class. // Restart the current state, so old data is cleared.
FlxG.resetState(); FlxG.resetState();
} }

View file

@ -46,7 +46,7 @@ class StoryMenuState extends MusicBeatState
['mom', 'bf', 'gf'], ['mom', 'bf', 'gf'],
['parents-christmas', 'bf', 'gf'], ['parents-christmas', 'bf', 'gf'],
['senpai', 'bf', 'gf'], ['senpai', 'bf', 'gf'],
['tankman', 'bf', 'gf'] ['tankman', 'bf', 'gf'],
]; ];
var weekNames:Array<String> = [ var weekNames:Array<String> = [

View file

@ -27,7 +27,7 @@ typedef AnimationData =
* Offset the character's position by this amount when playing this animation. * Offset the character's position by this amount when playing this animation.
* @default [0, 0] * @default [0, 0]
*/ */
var offsets:Null<Array<Int>>; var offsets:Null<Array<Float>>;
/** /**
* Whether the animation should loop when it finishes. * Whether the animation should loop when it finishes.

View file

@ -1,7 +1,9 @@
package funkin.play; package funkin.play;
import funkin.play.character.CharacterData.CharacterDataParser; import flixel.math.FlxMath;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.math.FlxPoint;
import funkin.play.character.CharacterData.CharacterDataParser;
import openfl.utils.Assets; import openfl.utils.Assets;
/** /**
@ -31,10 +33,25 @@ class HealthIcon extends FlxSprite
/** /**
* Whether this health icon should automatically update its state based on the character's health. * Whether this health icon should automatically update its state based on the character's health.
* You can set this to false if you want to manually forc * Note that turning this off means you have to manually do the following:
* - Bumping the icon on the beat.
* - Switching between winning/losing/idle animations.
* - Repositioning the icon as health changes.
*/ */
public var autoUpdate:Bool = true; public var autoUpdate:Bool = true;
/**
* Since the `scale` of the sprite dynamically changes over time,
* this value allows you to set a relative scale for the icon.
* @default 1x scale
*/
public var size:FlxPoint = new FlxPoint(1, 1);
/**
* Apply the "bump" animation once every X steps.
*/
public var bumpEvery:Int = 4;
/** /**
* The player the health icon is attached to. * The player the health icon is attached to.
*/ */
@ -46,6 +63,11 @@ class HealthIcon extends FlxSprite
*/ */
var isPixel:Bool = false; var isPixel:Bool = false;
/**
* Whether this is a legacy icon or not.
*/
var isLegacyStyle:Bool = false;
/** /**
* At this amount of health, play the Winning animation instead of the idle. * At this amount of health, play the Winning animation instead of the idle.
*/ */
@ -127,10 +149,57 @@ class HealthIcon extends FlxSprite
switch (playerId) switch (playerId)
{ {
case 0: // Boyfriend case 0: // Boyfriend
// Update the animation based on the current state.
updateHealthIcon(PlayState.instance.health); updateHealthIcon(PlayState.instance.health);
// Update the position to match the health bar.
this.x = PlayState.instance.healthBar.x
+ (PlayState.instance.healthBar.width * (FlxMath.remapToRange(PlayState.instance.healthBar.value, 0, 2, 100, 0) * 0.01));
case 1: // Dad case 1: // Dad
// Update the animation based on the current state.
updateHealthIcon(MAXIMUM_HEALTH - PlayState.instance.health); updateHealthIcon(MAXIMUM_HEALTH - PlayState.instance.health);
// Update the position to match the health bar.
this.x = PlayState.instance.healthBar.x
+ (PlayState.instance.healthBar.width * (FlxMath.remapToRange(PlayState.instance.healthBar.value, 0, 2, 100, 0) * 0.01))
- (this.width);
} }
// Lerp the health icon back to its normal size,
// while maintaining aspect ratio.
if (this.width > this.height)
{
// Apply linear interpolation while accounting for frame rate.
var targetSize = Std.int(CoolUtil.coolLerp(this.width, 150 * this.size.x, 0.15));
setGraphicSize(targetSize, 0);
}
else
{
var targetSize = Std.int(CoolUtil.coolLerp(this.height, 150 * this.size.y, 0.15));
setGraphicSize(0, targetSize);
}
this.updateHitbox();
}
}
public function onStepHit(curStep:Int)
{
if (curStep % bumpEvery == 0 && isLegacyStyle)
{
// Make the health icons bump (the update function causes them to lerp back down).
if (this.width > this.height)
{
var targetSize = Std.int(CoolUtil.coolLerp(this.width + 30, 150, 0.15));
setGraphicSize(targetSize, 0);
}
else
{
var targetSize = Std.int(CoolUtil.coolLerp(this.height + 30, 150, 0.15));
setGraphicSize(0, targetSize);
}
this.updateHitbox();
} }
} }
@ -169,6 +238,10 @@ class HealthIcon extends FlxSprite
case FROM_LOSING | FROM_WINNING: case FROM_LOSING | FROM_WINNING:
if (isAnimationFinished()) if (isAnimationFinished())
playAnimation(IDLE); playAnimation(IDLE);
case "":
playAnimation(IDLE);
default:
playAnimation(IDLE);
} }
} }
@ -200,7 +273,7 @@ class HealthIcon extends FlxSprite
function correctCharacterId(charId:String):String function correctCharacterId(charId:String):String
{ {
if (!Assets.exists(Paths.image('icons/icon-' + charId))) if (!Assets.exists(Paths.image('icons/icon-$charId')))
{ {
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!'); FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
return "face"; return "face";
@ -211,7 +284,7 @@ class HealthIcon extends FlxSprite
function isNewSpritesheet(charId:String):Bool function isNewSpritesheet(charId:String):Bool
{ {
return Assets.exists(Paths.xml('icons/icon-' + characterId)); return Assets.exists(Paths.file('images/icons/icon-$characterId.xml'));
} }
function fetchIsPixel(charId:String):Bool function fetchIsPixel(charId:String):Bool
@ -235,7 +308,9 @@ class HealthIcon extends FlxSprite
isPixel = fetchIsPixel(charId); isPixel = fetchIsPixel(charId);
if (isNewSpritesheet(charId)) isLegacyStyle = !isNewSpritesheet(charId);
if (!isLegacyStyle)
{ {
frames = Paths.getSparrowAtlas('icons/icon-$charId'); frames = Paths.getSparrowAtlas('icons/icon-$charId');
@ -290,11 +365,17 @@ class HealthIcon extends FlxSprite
{ {
// Attempt to play the animation // Attempt to play the animation
if (hasAnimation(name)) if (hasAnimation(name))
{
this.animation.play(name, restart, false, 0); this.animation.play(name, restart, false, 0);
return;
}
// Play the fallback animation if the requested animation was not found // Play the fallback animation if the requested animation was not found
if (fallback != null && hasAnimation(fallback)) if (fallback != null && hasAnimation(fallback))
{
this.animation.play(fallback, restart, false, 0); this.animation.play(fallback, restart, false, 0);
return;
}
// If we don't have an animation, we're done. // If we don't have an animation, we're done.
} }

View file

@ -185,7 +185,7 @@ class PlayState extends MusicBeatState implements IHook
* The bar which displays the player's health. * The bar which displays the player's health.
* Dynamically updated based on the value of `healthLerp` (which is based on `health`). * Dynamically updated based on the value of `healthLerp` (which is based on `health`).
*/ */
private var healthBar:FlxBar; public var healthBar:FlxBar;
/** /**
* The background image used for the health bar. * The background image used for the health bar.
@ -193,6 +193,16 @@ class PlayState extends MusicBeatState implements IHook
*/ */
public var healthBarBG:FlxSprite; public var healthBarBG:FlxSprite;
/**
* The health icon representing the player.
*/
public var iconP1:HealthIcon;
/**
* The health icon representing the opponent.
*/
public var iconP2:HealthIcon;
/** /**
* The sprite group containing active player's strumline notes. * The sprite group containing active player's strumline notes.
*/ */
@ -247,8 +257,6 @@ class PlayState extends MusicBeatState implements IHook
private var combo:Int = 0; private var combo:Int = 0;
private var generatedMusic:Bool = false; private var generatedMusic:Bool = false;
private var startingSong:Bool = false; private var startingSong:Bool = false;
private var iconP1:HealthIcon;
private var iconP2:HealthIcon;
var dialogue:Array<String>; var dialogue:Array<String>;
var talking:Bool = true; var talking:Bool = true;
@ -275,8 +283,9 @@ class PlayState extends MusicBeatState implements IHook
instance = this; instance = this;
// TEMP: For testing // Displays the camera follow point as a sprite for debug purposes.
cameraFollowPoint.makeGraphic(8, 8, 0xFFFF00FF); // TODO: Put this on a toggle?
cameraFollowPoint.makeGraphic(8, 8, 0xFF00FF00);
cameraFollowPoint.zIndex = 1000000; cameraFollowPoint.zIndex = 1000000;
// Reduce physics accuracy (who cares!!!) to improve animation quality. // Reduce physics accuracy (who cares!!!) to improve animation quality.
@ -326,6 +335,18 @@ class PlayState extends MusicBeatState implements IHook
// Once the song is loaded, we can continue and initialize the stage. // Once the song is loaded, we can continue and initialize the stage.
var healthBarYPos:Float = PreferencesMenu.getPref('downscroll') ? FlxG.height * 0.1 : FlxG.height * 0.9;
healthBarBG = new FlxSprite(0, healthBarYPos).loadGraphic(Paths.image('healthBar'));
healthBarBG.screenCenter(X);
healthBarBG.scrollFactor.set(0, 0);
add(healthBarBG);
healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this,
'healthLerp', 0, 2);
healthBar.scrollFactor.set();
healthBar.createFilledBar(Constants.HEALTH_BAR_RED, Constants.HEALTH_BAR_GREEN);
add(healthBar);
initStage(); initStage();
initCharacters(); initCharacters();
#if discord_rpc #if discord_rpc
@ -357,31 +378,11 @@ class PlayState extends MusicBeatState implements IHook
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height); FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
var healthBarYPos:Float = PreferencesMenu.getPref('downscroll') ? FlxG.height * 0.1 : FlxG.height * 0.9;
healthBarBG = new FlxSprite(0, healthBarYPos).loadGraphic(Paths.image('healthBar'));
healthBarBG.screenCenter(X);
healthBarBG.scrollFactor.set(0, 0);
add(healthBarBG);
healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this,
'healthLerp', 0, 2);
healthBar.scrollFactor.set();
healthBar.createFilledBar(Constants.HEALTH_BAR_RED, Constants.HEALTH_BAR_GREEN);
add(healthBar);
scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, "", 20); scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, "", 20);
scoreText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); scoreText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
scoreText.scrollFactor.set(); scoreText.scrollFactor.set();
add(scoreText); add(scoreText);
iconP1 = new HealthIcon(currentSong.player1, 0);
iconP1.y = healthBar.y - (iconP1.height / 2);
add(iconP1);
iconP2 = new HealthIcon(currentSong.player2, 1);
iconP2.y = healthBar.y - (iconP2.height / 2);
add(iconP2);
// Attach the groups to the HUD camera so they are rendered independent of the stage. // Attach the groups to the HUD camera so they are rendered independent of the stage.
grpNoteSplashes.cameras = [camHUD]; grpNoteSplashes.cameras = [camHUD];
activeNotes.cameras = [camHUD]; activeNotes.cameras = [camHUD];
@ -475,6 +476,9 @@ class PlayState extends MusicBeatState implements IHook
currentStageId = 'schoolEvil'; currentStageId = 'schoolEvil';
case 'guns' | 'stress' | 'ugh': case 'guns' | 'stress' | 'ugh':
currentStageId = 'tankmanBattlefield'; currentStageId = 'tankmanBattlefield';
case 'experimental-phase' | 'perfection':
// SERIOUSLY REVAMP THE CHART FORMAT ALREADY
currentStageId = "breakout";
default: default:
currentStageId = "mainStage"; currentStageId = "mainStage";
} }
@ -484,6 +488,14 @@ class PlayState extends MusicBeatState implements IHook
function initCharacters() function initCharacters()
{ {
iconP1 = new HealthIcon(currentSong.player1, 0);
iconP1.y = healthBar.y - (iconP1.height / 2);
add(iconP1);
iconP2 = new HealthIcon(currentSong.player2, 1);
iconP2.y = healthBar.y - (iconP2.height / 2);
add(iconP2);
// //
// GIRLFRIEND // GIRLFRIEND
// //
@ -501,6 +513,9 @@ class PlayState extends MusicBeatState implements IHook
gfVersion = 'gf-pixel'; gfVersion = 'gf-pixel';
case 'tankmanBattlefield': case 'tankmanBattlefield':
gfVersion = 'gf-tankmen'; gfVersion = 'gf-tankmen';
case 'breakout':
// SERIOUSLY PUT THIS SHIT IN THE CHART
gfVersion = '';
} }
if (currentSong.player1 == "pico") if (currentSong.player1 == "pico")
@ -585,8 +600,8 @@ class PlayState extends MusicBeatState implements IHook
{ {
// We're using Eric's stage handler. // We're using Eric's stage handler.
// Characters get added to the stage, not the main scene. // Characters get added to the stage, not the main scene.
currentStage.addCharacter(boyfriend, BF);
currentStage.addCharacter(girlfriend, GF); currentStage.addCharacter(girlfriend, GF);
currentStage.addCharacter(boyfriend, BF);
currentStage.addCharacter(dad, DAD); currentStage.addCharacter(dad, DAD);
// Redo z-indexes. // Redo z-indexes.
@ -1048,17 +1063,6 @@ class PlayState extends MusicBeatState implements IHook
if (FlxG.keys.justPressed.NINE) if (FlxG.keys.justPressed.NINE)
iconP1.toggleOldIcon(); iconP1.toggleOldIcon();
iconP1.setGraphicSize(Std.int(CoolUtil.coolLerp(iconP1.width, 150, 0.15)));
iconP2.setGraphicSize(Std.int(CoolUtil.coolLerp(iconP2.width, 150, 0.15)));
iconP1.updateHitbox();
iconP2.updateHitbox();
var iconOffset:Int = 26;
iconP1.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.value, 0, 2, 100, 0) * 0.01) - iconOffset);
iconP2.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.value, 0, 2, 100, 0) * 0.01)) - (iconP2.width - iconOffset);
if (health > 2) if (health > 2)
health = 2; health = 2;
@ -1293,7 +1297,7 @@ class PlayState extends MusicBeatState implements IHook
function killCombo():Void function killCombo():Void
{ {
// Girlfriend gets sad if you combo break after hitting 5 notes. // Girlfriend gets sad if you combo break after hitting 5 notes.
if (currentStage.getGirlfriend() != null) if (currentStage != null && currentStage.getGirlfriend() != null)
if (combo > 5 && currentStage.getGirlfriend().hasAnimation('sad')) if (combo > 5 && currentStage.getGirlfriend().hasAnimation('sad'))
currentStage.getGirlfriend().playAnimation('sad'); currentStage.getGirlfriend().playAnimation('sad');
@ -1727,6 +1731,9 @@ class PlayState extends MusicBeatState implements IHook
resyncVocals(); resyncVocals();
} }
iconP1.onStepHit(curStep);
iconP2.onStepHit(curStep);
dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep)); dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep));
} }
@ -1765,12 +1772,6 @@ class PlayState extends MusicBeatState implements IHook
} }
} }
// Make the health icons bump (the update function causes them to lerp back down).
iconP1.setGraphicSize(Std.int(iconP1.width + 30));
iconP2.setGraphicSize(Std.int(iconP2.width + 30));
iconP1.updateHitbox();
iconP2.updateHitbox();
// Make the characters dance on the beat // Make the characters dance on the beat
danceOnBeat(); danceOnBeat();

View file

@ -61,21 +61,21 @@ class BaseCharacter extends Bopper
*/ */
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0); public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
override function set_animOffset(value:Array<Float>) override function set_animOffsets(value:Array<Float>)
{ {
if (animOffset == null) if (animOffsets == null)
animOffset = [0, 0]; animOffsets = [0, 0];
if (animOffset == value) if (animOffsets == value)
return value; return value;
var xDiff = animOffset[0] - value[0]; var xDiff = animOffsets[0] - value[0];
var yDiff = animOffset[1] - value[1]; var yDiff = animOffsets[1] - value[1];
// Call the super function so that camera focus point is not affected. // Call the super function so that camera focus point is not affected.
super.set_x(this.x + xDiff); super.set_x(this.x + xDiff);
super.set_y(this.y + yDiff); super.set_y(this.y + yDiff);
return animOffset = value; return animOffsets = value;
} }
/** /**
@ -122,6 +122,7 @@ class BaseCharacter extends Bopper
{ {
this.characterName = _data.name; this.characterName = _data.name;
this.singTimeCrochet = _data.singTime; this.singTimeCrochet = _data.singTime;
this.globalOffsets = _data.offsets;
} }
} }
@ -139,12 +140,43 @@ class BaseCharacter extends Bopper
this.updateHitbox(); this.updateHitbox();
} }
/**
* The per-character camera offset.
*/
var characterCameraOffsets(get, null):Array<Float>;
function get_characterCameraOffsets():Array<Float>
{
return _data.cameraOffsets;
}
override function onCreate(event:ScriptEvent):Void override function onCreate(event:ScriptEvent):Void
{ {
this.cameraFocusPoint = new FlxPoint(this.x + this.width / 2 + _data.cameraOffset[0], this.y + this.height / 2 + _data.cameraOffset[0]); // Camera focus point
var charCenterX = this.x + this.width / 2;
var charCenterY = this.y + this.height / 2;
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
super.onCreate(event); super.onCreate(event);
} }
public function initHealthIcon(isOpponent:Bool):Void
{
if (!isOpponent)
{
PlayState.instance.iconP1.characterId = _data.healthIcon.id;
PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
PlayState.instance.iconP1.offset.y = _data.healthIcon.offsets[1];
}
else
{
PlayState.instance.iconP2.characterId = _data.healthIcon.id;
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];
PlayState.instance.iconP2.offset.y = _data.healthIcon.offsets[1];
}
}
public override function onUpdate(event:UpdateScriptEvent):Void public override function onUpdate(event:UpdateScriptEvent):Void
{ {
super.onUpdate(event); super.onUpdate(event);

View file

@ -290,11 +290,11 @@ class CharacterDataParser
static final DEFAULT_ISPIXEL:Bool = false; static final DEFAULT_ISPIXEL:Bool = false;
static final DEFAULT_LOOP:Bool = false; static final DEFAULT_LOOP:Bool = false;
static final DEFAULT_NAME:String = "Untitled Character"; static final DEFAULT_NAME:String = "Untitled Character";
static final DEFAULT_OFFSETS:Array<Int> = [0, 0]; static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
static final DEFAULT_HEALTHICON_OFFSETS:Array<Int> = [0, 25];
static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.SPARROW; static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.SPARROW;
static final DEFAULT_SCALE:Float = 1; static final DEFAULT_SCALE:Float = 1;
static final DEFAULT_SCROLL:Array<Float> = [0, 0]; static final DEFAULT_SCROLL:Array<Float> = [0, 0];
static final DEFAULT_CAMERAOFFSET:Array<Float> = [0, 0];
static final DEFAULT_STARTINGANIM:String = "idle"; static final DEFAULT_STARTINGANIM:String = "idle";
/** /**
@ -341,6 +341,40 @@ class CharacterDataParser
return null; return null;
} }
if (input.offsets == null)
{
input.offsets = DEFAULT_OFFSETS;
}
if (input.cameraOffsets == null)
{
input.cameraOffsets = DEFAULT_OFFSETS;
}
if (input.healthIcon == null)
{
input.healthIcon = {
id: null,
scale: null,
offsets: null
};
}
if (input.healthIcon.id == null)
{
input.healthIcon.id = id;
}
if (input.healthIcon.scale == null)
{
input.healthIcon.scale = DEFAULT_SCALE;
}
if (input.healthIcon.offsets == null)
{
input.healthIcon.offsets = DEFAULT_OFFSETS;
}
if (input.startingAnimation == null) if (input.startingAnimation == null)
{ {
input.startingAnimation = DEFAULT_STARTINGANIM; input.startingAnimation = DEFAULT_STARTINGANIM;
@ -351,11 +385,6 @@ class CharacterDataParser
input.scale = DEFAULT_SCALE; input.scale = DEFAULT_SCALE;
} }
if (input.cameraOffset == null)
{
input.cameraOffset = DEFAULT_CAMERAOFFSET;
}
if (input.isPixel == null) if (input.isPixel == null)
{ {
input.isPixel = DEFAULT_ISPIXEL; input.isPixel = DEFAULT_ISPIXEL;
@ -467,12 +496,23 @@ typedef CharacterData =
*/ */
var scale:Null<Float>; var scale:Null<Float>;
/**
* Optional data about the health icon for the character.
*/
var healthIcon:Null<HealthIconData>;
/**
* The global offset to the character's position, in pixels.
* @default [0, 0]
*/
var offsets:Null<Array<Float>>;
/** /**
* The amount to offset the camera by while focusing on this character. * The amount to offset the camera by while focusing on this character.
* Default value focuses on the character directly. * Default value focuses on the character directly.
* @default [0, 0] * @default [0, 0]
*/ */
var cameraOffset:Array<Float>; var cameraOffsets:Array<Float>;
/** /**
* Setting this to true disables anti-aliasing for the character. * Setting this to true disables anti-aliasing for the character.
@ -510,3 +550,23 @@ typedef CharacterData =
*/ */
var startingAnimation:Null<String>; var startingAnimation:Null<String>;
}; };
typedef HealthIconData =
{
/**
* The ID to use for the health icon.
* @default The character's ID
*/
var id:Null<String>;
/**
* The scale of the health icon.
*/
var scale:Null<Float>;
/**
* The offset of the health icon, in pixels.
* @default [0, 25]
*/
var offsets:Null<Array<Float>>;
}

View file

@ -47,22 +47,27 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
return value; return value;
} }
private var animOffset(default, set):Array<Float> = [0, 0]; /**
* The offset of the character relative to the position specified by the stage.
*/
public var globalOffsets(default, null):Array<Float> = [0, 0];
function set_animOffset(value:Array<Float>) private var animOffsets(default, set):Array<Float> = [0, 0];
function set_animOffsets(value:Array<Float>)
{ {
if (animOffset == null) if (animOffsets == null)
animOffset = [0, 0]; animOffsets = [0, 0];
if (animOffset == value) if (animOffsets == value)
return value; return value;
var xDiff = animOffset[0] - value[0]; var xDiff = animOffsets[0] - value[0];
var yDiff = animOffset[1] - value[1]; var yDiff = animOffsets[1] - value[1];
this.x += xDiff; this.x += xDiff;
this.y += yDiff; this.y += yDiff;
return animOffset = value; return animOffsets = value;
} }
/** /**
@ -192,11 +197,11 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
var offsets = animationOffsets.get(name); var offsets = animationOffsets.get(name);
if (offsets != null) if (offsets != null)
{ {
this.animOffset = offsets; this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
} }
else else
{ {
this.animOffset = [0, 0]; this.animOffsets = globalOffsets;
} }
} }
@ -205,7 +210,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
return this.animation.finished; return this.animation.finished;
} }
public function setAnimationOffsets(name:String, xOffset:Int, yOffset:Int):Void public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void
{ {
animationOffsets.set(name, [xOffset, yOffset]); animationOffsets.set(name, [xOffset, yOffset]);
applyAnimationOffsets(name); applyAnimationOffsets(name);

View file

@ -241,33 +241,70 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
if (character == null) if (character == null)
return; return;
#if debug
// Temporary marker that shows where the character's location is relative to.
// Should display at the stage position of the character (before any offsets).
// TODO: Make this a toggle? It's useful to turn on from time to time.
var debugIcon:FlxSprite = new FlxSprite(0, 0);
debugIcon.makeGraphic(8, 8, 0xffff00ff);
debugIcon.zIndex = 1000000;
#end
// Apply position and z-index. // Apply position and z-index.
switch (charType) switch (charType)
{ {
case BF: case BF:
this.characters.set("bf", character); this.characters.set("bf", character);
character.zIndex = _data.characters.bf.zIndex; character.zIndex = _data.characters.bf.zIndex;
// Start with the per-stage character position.
// Subtracting the origin ensures characters are positioned relative to their feet. // Subtracting the origin ensures characters are positioned relative to their feet.
character.x = _data.characters.bf.position[0] - character.characterOrigin.x; // Subtracting the global offset allows positioning on a per-character basis.
character.y = _data.characters.bf.position[1] - character.characterOrigin.y; character.x = _data.characters.bf.position[0] - character.characterOrigin.x + character.globalOffsets[0];
character.y = _data.characters.bf.position[1] - character.characterOrigin.y + character.globalOffsets[1];
character.cameraFocusPoint.x += _data.characters.bf.cameraOffsets[0];
character.cameraFocusPoint.y += _data.characters.bf.cameraOffsets[1];
debugIcon.x = _data.characters.bf.position[0];
debugIcon.y = _data.characters.bf.position[1];
character.initHealthIcon(false);
case GF: case GF:
this.characters.set("gf", character); this.characters.set("gf", character);
character.zIndex = _data.characters.gf.zIndex; character.zIndex = _data.characters.gf.zIndex;
// Subtracting the origin ensures characters are positioned relative to their feet.
character.x = _data.characters.gf.position[0] - character.characterOrigin.x; character.x = _data.characters.gf.position[0] - character.characterOrigin.x + character.globalOffsets[0];
character.y = _data.characters.gf.position[1] - character.characterOrigin.y; character.y = _data.characters.gf.position[1] - character.characterOrigin.y + character.globalOffsets[1];
character.cameraFocusPoint.x += _data.characters.gf.cameraOffsets[0];
character.cameraFocusPoint.y += _data.characters.gf.cameraOffsets[1];
// Draw the debug icon at the character's feet.
debugIcon.x = _data.characters.gf.position[0];
debugIcon.y = _data.characters.gf.position[1];
case DAD: case DAD:
this.characters.set("dad", character); this.characters.set("dad", character);
character.zIndex = _data.characters.dad.zIndex; character.zIndex = _data.characters.dad.zIndex;
// Subtracting the origin ensures characters are positioned relative to their feet.
character.x = _data.characters.dad.position[0] - character.characterOrigin.x; character.x = _data.characters.dad.position[0] - character.characterOrigin.x + character.globalOffsets[0];
character.y = _data.characters.dad.position[1] - character.characterOrigin.y; character.y = _data.characters.dad.position[1] - character.characterOrigin.y + character.globalOffsets[1];
character.cameraFocusPoint.x += _data.characters.dad.cameraOffsets[0];
character.cameraFocusPoint.y += _data.characters.dad.cameraOffsets[1];
// Draw the debug icon at the character's feet.
debugIcon.x = _data.characters.dad.position[0];
debugIcon.y = _data.characters.dad.position[1];
character.initHealthIcon(true);
default: default:
this.characters.set(character.characterId, character); this.characters.set(character.characterId, character);
} }
// Add the character to the scene. // Add the character to the scene.
this.add(character); this.add(character);
this.add(debugIcon);
} }
public inline function getGirlfriendPosition():FlxPoint public inline function getGirlfriendPosition():FlxPoint

View file

@ -174,7 +174,7 @@ class StageDataParser
static final DEFAULT_DANCEEVERY:Int = 0; static final DEFAULT_DANCEEVERY:Int = 0;
static final DEFAULT_ISPIXEL:Bool = false; static final DEFAULT_ISPIXEL:Bool = false;
static final DEFAULT_NAME:String = "Untitled Stage"; static final DEFAULT_NAME:String = "Untitled Stage";
static final DEFAULT_OFFSETS:Array<Int> = [0, 0]; static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
static final DEFAULT_POSITION:Array<Float> = [0, 0]; static final DEFAULT_POSITION:Array<Float> = [0, 0];
static final DEFAULT_SCALE:Float = 1.0; static final DEFAULT_SCALE:Float = 1.0;
static final DEFAULT_SCROLL:Array<Float> = [0, 0]; static final DEFAULT_SCROLL:Array<Float> = [0, 0];
@ -183,6 +183,7 @@ class StageDataParser
static final DEFAULT_CHARACTER_DATA:StageDataCharacter = { static final DEFAULT_CHARACTER_DATA:StageDataCharacter = {
zIndex: DEFAULT_ZINDEX, zIndex: DEFAULT_ZINDEX,
position: DEFAULT_POSITION, position: DEFAULT_POSITION,
cameraOffsets: DEFAULT_OFFSETS,
} }
/** /**
@ -358,6 +359,10 @@ class StageDataParser
{ {
inputCharacter.position = [0, 0]; inputCharacter.position = [0, 0];
} }
if (inputCharacter.cameraOffsets == null || inputCharacter.cameraOffsets.length != 2)
{
inputCharacter.cameraOffsets = [0, 0];
}
} }
// All good! // All good!
@ -475,5 +480,11 @@ typedef StageDataCharacter =
/** /**
* The position to render the character at. * The position to render the character at.
*/ */
position:Array<Float> position:Array<Float>,
/**
* The camera offsets to apply when focusing on the character on this stage.
* @default [0, 0]
*/
cameraOffsets:Array<Float>,
}; };

View file

@ -31,4 +31,7 @@ class Constants
return 'v${Application.current.meta.get('version')}' + VERSION_SUFFIX; return 'v${Application.current.meta.get('version')}' + VERSION_SUFFIX;
} }
#end #end
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/";
public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin";
} }

View file

@ -0,0 +1,18 @@
package funkin.util;
class WindowUtil
{
public static function openURL(targetUrl:String)
{
#if CAN_OPEN_LINKS
#if linux
// Sys.command('/usr/bin/xdg-open', [, "&"]);
Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]);
#else
FlxG.openURL(targetUrl);
#end
#else
trace('Cannot open')
#end
}
}