1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-09-11 20:57:20 +00:00

Added onAdd event so GF character can fix position in Tutorial

This commit is contained in:
EliteMasterEric 2023-05-26 17:10:08 -04:00
parent 6ab391d57a
commit 872f7c93e8
11 changed files with 240 additions and 58 deletions

View file

@ -1104,17 +1104,17 @@ class ChartingState extends MusicBeatState
// leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
// rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
leftIcon.playAnimation(p1Muted ? LOSING : IDLE);
rightIcon.playAnimation(p2Muted ? LOSING : IDLE);
//
// leftIcon.playAnimation(p1Muted ? LOSING : IDLE);
// rightIcon.playAnimation(p2Muted ? LOSING : IDLE);
}
else
{
leftIcon.characterId = (_song.player2);
rightIcon.characterId = (_song.player1);
leftIcon.playAnimation(p2Muted ? LOSING : IDLE);
rightIcon.playAnimation(p1Muted ? LOSING : IDLE);
// leftIcon.playAnimation(p2Muted ? LOSING : IDLE);
// rightIcon.playAnimation(p1Muted ? LOSING : IDLE);
// leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
// rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;

View file

@ -30,6 +30,18 @@ interface IStateChangingScriptedClass extends IScriptedClass
public function onSubstateCloseEnd(event:SubStateScriptEvent):Void;
}
/**
* Defines a set of callbacks available to scripted classes which can be added to the current state.
* Generally requires the class to be an instance of FlxBasic.
*/
interface IStateStageProp extends IScriptedClass
{
/**
* Called when the relevant element is added to the game state.
*/
public function onAdd(event:ScriptEvent):Void;
}
/**
* Defines a set of callbacks available to scripted classes which represent notes.
*/

View file

@ -32,6 +32,14 @@ class ScriptEvent
*/
public static inline final DESTROY:ScriptEventType = "DESTROY";
/**
* Called when the relevent object is added to the game state.
* This assumes all data is loaded and ready to go.
*
* This event is not cancelable.
*/
public static inline final ADDED:ScriptEventType = 'ADDED';
/**
* Called during the update function.
* This is called every frame, so be careful!

View file

@ -34,6 +34,17 @@ class ScriptEventDispatcher
return;
}
if (Std.isOfType(target, IStateStageProp))
{
var t:IStateStageProp = cast(target, IStateStageProp);
switch (event.type)
{
case ScriptEvent.ADDED:
t.onAdd(cast event);
return;
}
}
if (Std.isOfType(target, IPlayStateScriptedClass))
{
var t = cast(target, IPlayStateScriptedClass);

View file

@ -81,7 +81,7 @@ class AnimateAtlasCharacter extends BaseCharacter
super.onCreate(event);
}
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reverse:Bool = false):Void
{
if ((!canPlayOtherAnims && !ignoreOther)) return;

View file

@ -21,7 +21,12 @@ class BaseCharacter extends Bopper
/**
* Whether the player is an active character (Boyfriend) or not.
*/
public var characterType:CharacterType = OTHER;
public var characterType(default, set):CharacterType = OTHER;
function set_characterType(value:CharacterType):CharacterType
{
return this.characterType = value;
}
/**
* Tracks how long, in seconds, the character has been playing the current `sing` animation.
@ -30,8 +35,18 @@ class BaseCharacter extends Bopper
*/
public var holdTimer:Float = 0;
/**
* Set to true when the character dead. Part of the handling for death animations.
*/
public var isDead:Bool = false;
public var debugMode:Bool = false;
/**
* Set to true when the character being used in a special way.
* This includes the Chart Editor and the Animation Editor.
*
* Used by scripts to ensure that they don't try to run code to interact with the stage when the stage doesn't actually exist.
*/
public var debug:Bool = false;
/**
* This character plays a given animation when hitting these specific combo numbers.
@ -44,7 +59,7 @@ class BaseCharacter extends Bopper
public var dropNoteCounts(default, null):Array<Int>;
final _data:CharacterData;
final singTimeCrochet:Float;
final singTimeSec:Float;
/**
* The offset between the corner of the sprite and the origin of the sprite (at the character's feet).
@ -102,14 +117,14 @@ class BaseCharacter extends Bopper
*/
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
override function set_animOffsets(value:Array<Float>)
override function set_animOffsets(value:Array<Float>):Array<Float>
{
if (animOffsets == null) value = [0, 0];
if (animOffsets == value) return value;
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
// Make sure animOffets are halved when scale is 0.5.
var xDiff = (animOffsets[0] * this.scale.x) - value[0];
var yDiff = (animOffsets[1] * this.scale.y) - value[1];
var xDiff = (animOffsets[0] * this.scale.x / (this.isPixel ? 6 : 1)) - value[0];
var yDiff = (animOffsets[1] * this.scale.y / (this.isPixel ? 6 : 1)) - value[1];
// Call the super function so that camera focus point is not affected.
super.set_x(this.x + xDiff);
@ -164,7 +179,7 @@ class BaseCharacter extends Bopper
{
this.characterName = _data.name;
this.name = _data.name;
this.singTimeCrochet = _data.singTime;
this.singTimeSec = _data.singTime;
this.globalOffsets = _data.offsets;
this.flipX = _data.flipX;
}
@ -229,7 +244,7 @@ class BaseCharacter extends Bopper
* Set the sprite scale to the appropriate value.
* @param scale
*/
function setScale(scale:Null<Float>):Void
public function setScale(scale:Null<Float>):Void
{
if (scale == null) scale = 1.0;
@ -257,7 +272,7 @@ class BaseCharacter extends Bopper
super.onCreate(event);
// Make sure we are playing the idle animation...
this.dance();
this.dance(true);
// ...then update the hitbox so that this.width and this.height are correct.
this.updateHitbox();
// Without the above code, width and height (and therefore character position)
@ -291,7 +306,9 @@ class BaseCharacter extends Bopper
if (PlayState.instance.iconP1 == null)
{
trace('[WARN] Player 1 health icon not found!');
return;
}
PlayState.instance.iconP1.isPixel = _data.healthIcon?.isPixel ?? false;
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];
@ -303,12 +320,14 @@ class BaseCharacter extends Bopper
if (PlayState.instance.iconP2 == null)
{
trace('[WARN] Player 2 health icon not found!');
return;
}
PlayState.instance.iconP2.isPixel = _data.healthIcon?.isPixel ?? false;
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];
PlayState.instance.iconP1.flipX = _data.healthIcon.flipX;
PlayState.instance.iconP2.flipX = _data.healthIcon.flipX;
}
}
@ -328,13 +347,16 @@ class BaseCharacter extends Bopper
return;
}
// This logic turns the idle animation into a "lead-in" animation.
if (hasAnimation('idle-hold') && getCurrentAnimation() == 'idle' && isAnimationFinished()) playAnimation('idle-hold');
// If there is an animation, and another animation with the same name + "-hold" exists,
// the second animation will play (and be looped if configured to do so) after the first animation finishes.
// This is good for characters that need to hold a pose while maintaining an animation, like the parents (this keeps their eyes flickering)
// and Darnell (this keeps the flame on his lighter flickering).
// Works for idle, singLEFT/RIGHT/UP/DOWN, alt singing animations, and anything else really.
if (hasAnimation('singLEFT-hold') && getCurrentAnimation() == 'singLEFT' && isAnimationFinished()) playAnimation('singLEFT-hold');
if (hasAnimation('singDOWN-hold') && getCurrentAnimation() == 'singDOWN' && isAnimationFinished()) playAnimation('singDOWN-hold');
if (hasAnimation('singUP-hold') && getCurrentAnimation() == 'singUP' && isAnimationFinished()) playAnimation('singUP-hold');
if (hasAnimation('singRIGHT-hold') && getCurrentAnimation() == 'singRIGHT' && isAnimationFinished()) playAnimation('singRIGHT-hold');
if (!getCurrentAnimation().endsWith('-hold') && hasAnimation(getCurrentAnimation() + '-hold') && isAnimationFinished())
{
playAnimation(getCurrentAnimation() + '-hold');
}
// Handle character note hold time.
if (getCurrentAnimation().startsWith('sing'))
@ -345,18 +367,18 @@ class BaseCharacter extends Bopper
// This lets you add frames to the end of the sing animation to ease back into the idle!
holdTimer += event.elapsed;
var singTimeMs:Float = singTimeCrochet * (Conductor.crochet * 0.001); // x beats, to ms.
var singTimeSec:Float = singTimeSec * (Conductor.crochet * 0.001); // x beats, to ms.
if (getCurrentAnimation().endsWith('miss')) singTimeMs *= 2; // makes it feel more awkward when you miss
if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss
// Without this check here, the player character would only play the `sing` animation
// for one beat, as opposed to holding it as long as the player is holding the button.
var shouldStopSinging:Bool = (this.characterType == BF) ? !isHoldingNote() : true;
FlxG.watch.addQuick('singTimeMs-${characterId}', singTimeMs);
if (holdTimer > singTimeMs && shouldStopSinging)
FlxG.watch.addQuick('singTimeSec-${characterId}', singTimeSec);
if (holdTimer > singTimeSec && shouldStopSinging)
{
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation');
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
holdTimer = 0;
dance(true);
}
@ -370,7 +392,7 @@ class BaseCharacter extends Bopper
}
/**
* Since no `onBeatHit` or `dance` calls happen in GameOverSubstate,
* Since no `onBeatHit` or `dance` calls happen in GameOverSubState,
* this regularly gets called instead.
*
* @param force Force the deathLoop animation to play, even if `firstDeath` is still playing.
@ -386,7 +408,7 @@ class BaseCharacter extends Bopper
override function dance(force:Bool = false):Void
{
// Prevent default dancing behavior.
if (debugMode || isDead) return;
if (isDead) return;
if (!force)
{

View file

@ -189,7 +189,7 @@ class CharacterDataParser
* @param charId The character ID to fetch.
* @return The character instance, or null if the character was not found.
*/
public static function fetchCharacter(charId:String):Null<BaseCharacter>
public static function fetchCharacter(charId:String, ?debug:Bool = false):Null<BaseCharacter>
{
if (charId == null || charId == '' || !characterCache.exists(charId))
{
@ -246,7 +246,9 @@ class CharacterDataParser
return null;
}
trace('Successfully instantiated character: ${charId}');
trace('Successfully instantiated character (${debug ? 'debug' : 'stable'}): ${charId}');
char.debug = debug;
// Call onCreate only in the fetchCharacter() function, not at application initialization.
ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE));
@ -419,6 +421,7 @@ class CharacterDataParser
id: null,
scale: null,
flipX: null,
isPixel: null,
offsets: null
};
}
@ -458,6 +461,11 @@ class CharacterDataParser
input.isPixel = DEFAULT_ISPIXEL;
}
if (input.healthIcon.isPixel == null)
{
input.healthIcon.isPixel = input.isPixel;
}
if (input.danceEvery == null)
{
input.danceEvery = DEFAULT_DANCEEVERY;
@ -674,6 +682,12 @@ typedef HealthIconData =
*/
var flipX:Null<Bool>;
/**
* Multiply scale by 6 and disable antialiasing
* @default false
*/
var isPixel:Null<Bool>;
/**
* The offset of the health icon, in pixels.
* @default [0, 25]

View file

@ -179,14 +179,14 @@ class MultiSparrowCharacter extends BaseCharacter
trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
}
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reverse:Bool = false):Void
{
// Make sure we ignore other animations if we're currently playing a forced one,
// unless we're forcing a new animation.
if (!this.canPlayOtherAnims && !ignoreOther) return;
loadFramesByAnimName(name);
super.playAnimation(name, restart, ignoreOther);
super.playAnimation(name, restart, ignoreOther, reverse);
}
override function set_frames(value:FlxFramesCollection):FlxFramesCollection

View file

@ -42,6 +42,18 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
*/
public var idleSuffix(default, set):String = '';
/**
* If this bopper is rendered with pixel art,
* disable anti-aliasing and render at 6x scale.
*/
public var isPixel(default, set):Bool = false;
function set_isPixel(value:Bool):Bool
{
if (isPixel == value) return value;
return isPixel = value;
}
/**
* Whether this bopper should bop every beat. By default it's true, but when used
* for characters/players, it should be false so it doesn't cut off their animations!!!!!
@ -80,7 +92,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
function set_animOffsets(value:Array<Float>):Array<Float>
{
if (animOffsets == null) animOffsets = [0, 0];
if (animOffsets == value) return value;
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
var xDiff = animOffsets[0] - value[0];
var yDiff = animOffsets[1] - value[1];
@ -213,7 +225,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
* @param name
*/
function correctAnimationName(name:String)
function correctAnimationName(name:String):String
{
// If the animation exists, we're good.
if (hasAnimation(name)) return name;
@ -248,16 +260,16 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
* @param name The name of the animation to play.
* @param restart Whether to restart the animation if it is already playing.
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
* @param reversed If true, play the animation backwards, from the last frame to the first.
*/
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reversed:Bool = false):Void
{
if (!canPlayOtherAnims && !ignoreOther) return;
var correctName = correctAnimationName(name);
if (correctName == null) return;
this.animation.paused = false;
this.animation.play(correctName, restart, false, 0);
this.animation.play(correctName, restart, reversed, 0);
if (ignoreOther)
{
@ -291,10 +303,10 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
}, 1);
}
function applyAnimationOffsets(name:String)
function applyAnimationOffsets(name:String):Void
{
var offsets = animationOffsets.get(name);
if (offsets != null)
if (offsets != null && !(offsets[0] == 0 && offsets[1] == 0))
{
this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
}
@ -325,14 +337,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
return this.animation.curAnim.name;
}
public function onScriptEvent(event:ScriptEvent) {}
public function onCreate(event:ScriptEvent) {}
public function onDestroy(event:ScriptEvent) {}
public function onUpdate(event:UpdateScriptEvent) {}
public function onPause(event:PauseScriptEvent) {}
public function onResume(event:ScriptEvent) {}

View file

@ -77,7 +77,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
// Reset positions of characters.
if (getBoyfriend() != null)
{
getBoyfriend().resetCharacter(false);
getBoyfriend().resetCharacter(true);
}
else
{
@ -85,11 +85,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
}
if (getGirlfriend() != null)
{
getGirlfriend().resetCharacter(false);
getGirlfriend().resetCharacter(true);
}
if (getDad() != null)
{
getDad().resetCharacter(false);
getDad().resetCharacter(true);
}
// Reset positions of named props.
@ -286,6 +286,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
*/
override function preAdd(Sprite:FlxSprite):Void
{
if (Sprite == null) return;
var sprite:FlxSprite = cast Sprite;
sprite.x += x;
sprite.y += y;
@ -377,22 +378,36 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
// Add the character to the scene.
this.add(character);
ScriptEventDispatcher.callEvent(character, new ScriptEvent(ScriptEvent.ADDED, false));
#if debug
debugIconGroup.add(debugIcon);
debugIconGroup.add(debugIcon2);
#end
}
/**
* Get the position of the girlfriend character, as defined in the stage data.
* @return An FlxPoint position.
*/
public inline function getGirlfriendPosition():FlxPoint
{
return new FlxPoint(_data.characters.gf.position[0], _data.characters.gf.position[1]);
}
/**
* Get the position of the boyfriend character, as defined in the stage data.
* @return An FlxPoint position.
*/
public inline function getBoyfriendPosition():FlxPoint
{
return new FlxPoint(_data.characters.bf.position[0], _data.characters.bf.position[1]);
}
/**
* Get the position of the dad character, as defined in the stage data.
* @return An FlxPoint position.
*/
public inline function getDadPosition():FlxPoint
{
return new FlxPoint(_data.characters.dad.position[0], _data.characters.dad.position[1]);
@ -409,6 +424,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
/**
* Retrieve the Boyfriend character.
* @param pop If true, the character will be removed from the stage as well.
* @return The Boyfriend character.
*/
public function getBoyfriend(?pop:Bool = false):BaseCharacter
{
@ -428,14 +444,70 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
}
}
public function getGirlfriend():BaseCharacter
/**
* Retrieve the player/Boyfriend character.
* @param pop If true, the character will be removed from the stage as well.
* @return The player/Boyfriend character.
*/
public function getPlayer(?pop:Bool = false):BaseCharacter
{
return getCharacter('gf');
return getBoyfriend(pop);
}
public function getDad():BaseCharacter
/**
* Retrieve the Girlfriend character.
* @param pop If true, the character will be removed from the stage as well.
* @return The Girlfriend character.
*/
public function getGirlfriend(?pop:Bool = false):BaseCharacter
{
return getCharacter('dad');
if (pop)
{
var girlfriend:BaseCharacter = getCharacter('gf');
// Remove the character from the stage.
this.remove(girlfriend);
this.characters.remove('gf');
return girlfriend;
}
else
{
return getCharacter('gf');
}
}
/**
* Retrieve the Dad character.
* @param pop If true, the character will be removed from the stage as well.
* @return The Dad character.
*/
public function getDad(?pop:Bool = false):BaseCharacter
{
if (pop)
{
var dad:BaseCharacter = getCharacter('dad');
// Remove the character from the stage.
this.remove(dad);
this.characters.remove('dad');
return dad;
}
else
{
return getCharacter('dad');
}
}
/**
* Retrieve the opponent/Dad character.
* @param pop If true, the character will be removed from the stage as well.
* @return The opponent character.
*/
public function getOpponent(?pop:Bool = false):BaseCharacter
{
return getDad(pop);
}
/**
@ -448,6 +520,26 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
return this.namedProps.get(name);
}
/**
* Pause the animations of ALL sprites in this group.
*/
public function pause():Void
{
forEachAlive(function(prop:FlxSprite) {
if (prop.animation != null) prop.animation.pause();
});
}
/**
* Resume the animations of ALL sprites in this group.
*/
public function resume():Void
{
forEachAlive(function(prop:FlxSprite) {
if (prop.animation != null) prop.animation.resume();
});
}
/**
* Retrieve a list of all the asset paths required to load the stage.
* Override this in a scripted class to ensure that all necessary assets are loaded!

View file

@ -1,13 +1,32 @@
package funkin.play.stage;
import funkin.modding.events.ScriptEvent;
import flixel.FlxSprite;
import funkin.modding.IScriptedClass.IStateStageProp;
class StageProp extends FlxSprite
class StageProp extends FlxSprite implements IStateStageProp
{
public var name:String = "";
/**
* An internal name for this prop.
*/
public var name:String = '';
public function new()
{
super();
}
/**
* Called when this prop is added to the stage.
* @param event
*/
public function onAdd(event:ScriptEvent):Void {}
public function onScriptEvent(event:ScriptEvent) {}
public function onCreate(event:ScriptEvent) {}
public function onDestroy(event:ScriptEvent) {}
public function onUpdate(event:UpdateScriptEvent) {}
}