Character Editor (#141)
* Character Editor * Update Character.hx * Updated more stuff * Update CharacterEditor.hx * Update DeleteAnimScreen.hx * Fixed indices * Update UpdateAnimScreen.hx * Update Character.hx * Update Character.hx * . * MoOOOre stuff --------- Co-authored-by: Ali <75845687+execoded@users.noreply.github.com> Co-authored-by: NeeEoo <NeeEoo@users.noreply.github.com>
This commit is contained in:
@@ -120,6 +120,10 @@ class Paths
|
||||
return getPath('songs/$song/charts/$difficulty.json', null);
|
||||
}
|
||||
|
||||
static public function character(character:String):String {
|
||||
return getPath('data/characters/$character.xml', null);
|
||||
}
|
||||
|
||||
inline static public function font(key:String)
|
||||
{
|
||||
return getPath('fonts/$key');
|
||||
|
||||
@@ -16,7 +16,7 @@ class EditorPicker extends MusicBeatSubstate {
|
||||
{
|
||||
name: "Character Editor",
|
||||
iconID: 1,
|
||||
state: null
|
||||
state: funkin.editors.character.CharacterSelection
|
||||
},
|
||||
{
|
||||
name: "Stage Editor",
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import funkin.game.Character;
|
||||
|
||||
class AddAnimScreen extends UISubstateWindow
|
||||
{
|
||||
public var char:Character;
|
||||
public var ghostChar:Character;
|
||||
|
||||
public var addButton:UIButton;
|
||||
public var cancelButton:UIButton;
|
||||
public var editor:CharacterEditor;
|
||||
|
||||
public var animName:UITextBox;
|
||||
public var animPrefix:UITextBox;
|
||||
public var fps:UITextBox;
|
||||
public var loop:UICheckbox;
|
||||
public var indices:UITextBox;
|
||||
|
||||
public function new(char:Character, ghostChar, editor:CharacterEditor)
|
||||
{
|
||||
super();
|
||||
this.char = char;
|
||||
this.ghostChar = ghostChar;
|
||||
}
|
||||
|
||||
public override function create()
|
||||
{
|
||||
winTitle = "Add new Animation";
|
||||
winWidth = 960;
|
||||
|
||||
super.create();
|
||||
|
||||
// Animation Properties
|
||||
var y_ofs = 60;
|
||||
var y_text_ofs = 20;
|
||||
|
||||
animName = new UITextBox(windowSpr.x + 50, windowSpr.y + y_ofs, "");
|
||||
add(animName);
|
||||
add(new UIText(animName.x, animName.y - y_text_ofs, -1, "Animation Name: "));
|
||||
|
||||
animPrefix = new UITextBox(animName.x, animName.y + y_ofs, "");
|
||||
add(animPrefix);
|
||||
add(new UIText(animPrefix.x, animPrefix.y - y_text_ofs, -1, "Animation on XML/Atlas file: "));
|
||||
|
||||
fps = new UITextBox(animPrefix.x, animPrefix.y + y_ofs, "24");
|
||||
add(fps);
|
||||
add(new UIText(fps.x, fps.y - y_text_ofs, -1, "Framerate: "));
|
||||
|
||||
loop = new UICheckbox(fps.x, fps.y + y_ofs, "Looped", false);
|
||||
add(loop);
|
||||
|
||||
indices = new UITextBox(loop.x, loop.y + y_ofs, "");
|
||||
add(indices);
|
||||
add(new UIText(indices.x, indices.y - y_text_ofs, -1, "Indices (0, 1, 2, 3): "));
|
||||
|
||||
// Add / Cancel Buttons
|
||||
addButton = new UIButton(windowSpr.x + 1, windowSpr.y + 31, "", function()
|
||||
{
|
||||
newAnimation(animName.label.text, animPrefix.label.text, Std.parseInt(fps.label.text), loop.checked, indices.label.text);
|
||||
close();
|
||||
});
|
||||
addButton.bWidth = addButton.bHeight = 30;
|
||||
add(addButton);
|
||||
|
||||
var addButtonIcon = new FlxSprite(addButton.x, addButton.y, Paths.image('editors/charter/add-button'));
|
||||
addButtonIcon.x += (30 - addButtonIcon.width) / 2;
|
||||
addButtonIcon.y += (30 - addButtonIcon.height) / 2;
|
||||
add(addButtonIcon);
|
||||
|
||||
cancelButton = new UIButton(addButton.x - 10, addButton.y, "Cancel", function()
|
||||
{
|
||||
close();
|
||||
});
|
||||
cancelButton.x -= cancelButton.bWidth;
|
||||
add(cancelButton);
|
||||
}
|
||||
|
||||
public function newAnimation(name:String, anim:String, ?fps:Int = 24, ?looped:Bool = false, ?indices:String)
|
||||
{
|
||||
var animType:XMLAnimType = NONE;
|
||||
|
||||
var animData:AnimData = {
|
||||
name: name,
|
||||
anim: anim,
|
||||
fps: fps,
|
||||
loop: looped,
|
||||
animType: animType,
|
||||
indices: [],
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
if (indices != "" && indices != null)
|
||||
{
|
||||
var indicesSplit = indices.split(",");
|
||||
|
||||
for (indice in indicesSplit)
|
||||
{
|
||||
var i = Std.parseInt(indice.trim());
|
||||
if (i != null)
|
||||
animData.indices.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
XMLUtil.addAnimToSprite(char, animData);
|
||||
XMLUtil.addAnimToSprite(ghostChar, animData);
|
||||
char.characterData.animations.push(animData);
|
||||
ghostChar.characterData.animations.push(animData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import haxe.xml.Printer;
|
||||
import sys.io.File;
|
||||
import sys.FileSystem;
|
||||
import flixel.group.FlxGroup;
|
||||
import haxe.xml.Access;
|
||||
import haxe.Exception;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.game.Character.CharacterData;
|
||||
|
||||
class CharacterConfig extends FlxTypedGroup<FlxBasic>
|
||||
{
|
||||
public static var DEFAULT_CHARACTER = "bf";
|
||||
|
||||
public static function loadCharacterData(character:String):CharacterData
|
||||
{
|
||||
var xml:Access;
|
||||
|
||||
var charData:CharacterData = {
|
||||
offsetY: 0,
|
||||
offsetX: 0,
|
||||
camOffsetY: 0,
|
||||
camOffsetX: 0,
|
||||
quickDancer: false,
|
||||
icon: character,
|
||||
iconColor: FlxColor.fromRGB(94, 94, 94),
|
||||
scale: 1,
|
||||
isPlayer: false,
|
||||
isGF: false,
|
||||
holdTime: 4,
|
||||
flipX: false,
|
||||
antialiasing: true,
|
||||
sprite: "bf",
|
||||
name: character,
|
||||
animations: []
|
||||
}
|
||||
|
||||
var xmlPath = Paths.xml('characters/$character');
|
||||
|
||||
var plainXML = Assets.getText(xmlPath);
|
||||
try
|
||||
{
|
||||
var charXML = Xml.parse(plainXML).firstElement();
|
||||
if (charXML == null)
|
||||
throw new Exception("Missing \"character\" node in XML.");
|
||||
xml = new Access(charXML);
|
||||
}
|
||||
|
||||
charData = xmlToData(charData, xml);
|
||||
|
||||
return charData;
|
||||
}
|
||||
|
||||
public static function xmlToData(charData:CharacterData, xml:Access):CharacterData
|
||||
{
|
||||
if (xml.has.isPlayer)
|
||||
charData.isPlayer = (xml.att.isPlayer == "true");
|
||||
if (xml.has.isGF)
|
||||
charData.isGF = (xml.att.isGF == "true");
|
||||
if (xml.has.x)
|
||||
charData.offsetX = Std.parseFloat(xml.att.x);
|
||||
if (xml.has.y)
|
||||
charData.offsetY = Std.parseFloat(xml.att.y);
|
||||
if (xml.has.gameOverChar)
|
||||
charData.gameOverChar = xml.att.gameOverChar;
|
||||
if (xml.has.camx)
|
||||
charData.camOffsetX = Std.parseFloat(xml.att.camx);
|
||||
if (xml.has.camy)
|
||||
charData.camOffsetY = Std.parseFloat(xml.att.camy);
|
||||
if (xml.has.holdTime)
|
||||
charData.holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4);
|
||||
if (xml.has.flipX)
|
||||
charData.flipX = (xml.att.flipX == "true");
|
||||
if (xml.has.icon)
|
||||
charData.icon = xml.att.icon;
|
||||
if (xml.has.iconColor)
|
||||
charData.iconColor = stringToRGB(xml.att.iconColor);
|
||||
if (xml.has.scale)
|
||||
{
|
||||
var scale = Std.parseFloat(xml.att.scale).getDefault(1);
|
||||
charData.scale = scale;
|
||||
}
|
||||
if (xml.has.antialiasing)
|
||||
charData.antialiasing = (xml.att.antialiasing == "true");
|
||||
if (xml.has.sprite)
|
||||
charData.sprite = xml.att.sprite;
|
||||
else
|
||||
charData.sprite = charData.name;
|
||||
|
||||
for (anim in xml.nodes.anim)
|
||||
{
|
||||
var animType:XMLAnimType = NONE;
|
||||
var animloop = false;
|
||||
if (anim.has.loop)
|
||||
animloop = (anim.att.loop == "true");
|
||||
charData.animations.push(XMLUtil.extractAnimFromXML(anim, animType, animloop));
|
||||
}
|
||||
return charData;
|
||||
}
|
||||
|
||||
public static function dataToXml(charData:CharacterData):Access
|
||||
{
|
||||
var root = Xml.createElement("character");
|
||||
root.set("x", Std.string(Math.floor(charData.offsetX)));
|
||||
root.set("y", Std.string(Math.floor(charData.offsetY)));
|
||||
root.set("gameOverChar", Std.string(charData.gameOverChar));
|
||||
root.set("camx", Std.string(Math.floor(charData.camOffsetX)));
|
||||
root.set("camy", Std.string(Math.floor(charData.camOffsetY)));
|
||||
root.set("holdTime", Std.string(charData.holdTime));
|
||||
root.set("flipX", Std.string(charData.flipX));
|
||||
root.set("icon", charData.icon);
|
||||
root.set("iconColor", Std.string(charData.iconColor));
|
||||
root.set("scale", Std.string(charData.scale));
|
||||
root.set("antialiasing", Std.string(charData.antialiasing));
|
||||
root.set("sprite", Std.string(charData.sprite));
|
||||
root.set("isPlayer", Std.string(charData.isPlayer));
|
||||
root.set("isGF", Std.string(charData.isGF));
|
||||
|
||||
for (anim in charData.animations)
|
||||
{
|
||||
var child:Xml = Xml.createElement('anim');
|
||||
child.set("name", Std.string(anim.name));
|
||||
child.set("anim", Std.string(anim.anim));
|
||||
child.set("loop", Std.string(anim.loop));
|
||||
child.set("fps", Std.string(anim.fps));
|
||||
child.set("x", Std.string(anim.x));
|
||||
child.set("y", Std.string(anim.y));
|
||||
child.set("indices", Std.string(anim.indices));
|
||||
root.addChild(child);
|
||||
}
|
||||
var xml = new Access(Xml.parse('${root.toString()}'));
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static function stringToRGB(rgbColor:String):FlxColor
|
||||
{
|
||||
var colorSplit = rgbColor.split(",");
|
||||
if (colorSplit[0] != null && colorSplit[1] != null && colorSplit[2] != null)
|
||||
return FlxColor.fromRGB(Std.parseInt(colorSplit[0]), Std.parseInt(colorSplit[1]), Std.parseInt(colorSplit[2]));
|
||||
else
|
||||
return FlxColor.fromRGB(90,90,90);
|
||||
}
|
||||
|
||||
public static function rgbToString(color:FlxColor):String
|
||||
return '${color.red}, ${color.green}, ${color.blue}';
|
||||
/**
|
||||
* @return Saves the character to the specific song folder path.
|
||||
*/
|
||||
public static function save(character:String, xml:String):String
|
||||
{
|
||||
var charPath = '';
|
||||
var data = '$xml';
|
||||
#if sys
|
||||
var charPath = '${character}';
|
||||
|
||||
File.saveContent(charPath, data);
|
||||
|
||||
// idk how null reacts to it so better be sure
|
||||
#end
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static function generateCharXML(xml:Xml):String
|
||||
{
|
||||
return '<!DOCTYPE codename-engine-character>\n${Printer.print(xml, true)}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import funkin.backend.system.framerate.Framerate;
|
||||
import funkin.backend.FlxAnimate;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
|
||||
import funkin.game.Character;
|
||||
import flixel.system.debug.interaction.tools.Pointer.GraphicCursorCross;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class CharacterEditor extends UIState
|
||||
{
|
||||
public var blockedInputs = [];
|
||||
public var blockedStuff = [];
|
||||
|
||||
public var curChar:String;
|
||||
|
||||
public var char:Character;
|
||||
public var ghostChar:Character;
|
||||
public var charLayer:FlxTypedSpriteGroup<Character> = new FlxTypedSpriteGroup<Character>(0, 0);
|
||||
public var animAtlas:FlxAnimate;
|
||||
|
||||
public var cameraPointer:FlxSprite;
|
||||
|
||||
public var camFollow:FlxObject;
|
||||
public var editorCamera:FlxCamera;
|
||||
public var uiCamera:FlxCamera;
|
||||
|
||||
public var topMenu:Array<UIContextMenuOption>;
|
||||
public var topMenuSpr:UITopMenu;
|
||||
|
||||
public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>();
|
||||
public var stage:FlxTypedSpriteGroup<FunkinSprite> = new FlxTypedSpriteGroup<FunkinSprite>(0, 0);
|
||||
|
||||
public var curAnim:Int = 0;
|
||||
|
||||
public var playingAnim:UIText;
|
||||
public var movingCamText:UIText;
|
||||
|
||||
public var camZoom(default, set):Float = 1;
|
||||
|
||||
public function new(charName:String)
|
||||
{
|
||||
super();
|
||||
this.curChar = charName;
|
||||
}
|
||||
|
||||
override function create()
|
||||
{
|
||||
super.create();
|
||||
|
||||
// Camera setup
|
||||
camFollow = new FlxObject(0, 0, 2, 2);
|
||||
camFollow.screenCenter();
|
||||
add(camFollow);
|
||||
editorCamera = FlxG.camera;
|
||||
uiCamera = new FlxCamera();
|
||||
uiCamera.bgColor = 0;
|
||||
FlxG.cameras.add(uiCamera);
|
||||
FlxG.camera.follow(camFollow);
|
||||
FlxCamera.defaultCameras = [editorCamera];
|
||||
|
||||
// Add Stage and Centered Cross
|
||||
add(stage);
|
||||
add(ofs_cross);
|
||||
loadCross();
|
||||
|
||||
// Indicators
|
||||
var pointer:FlxGraphic = FlxGraphic.fromClass(GraphicCursorCross);
|
||||
cameraPointer = new FlxSprite().loadGraphic(pointer);
|
||||
cameraPointer.setGraphicSize(40, 40);
|
||||
cameraPointer.updateHitbox();
|
||||
cameraPointer.color = FlxColor.WHITE;
|
||||
|
||||
// Character
|
||||
add(charLayer);
|
||||
add(cameraPointer);
|
||||
loadChar(!CharacterConfig.loadCharacterData(curChar).isPlayer);
|
||||
|
||||
// Reload Stage
|
||||
reloadStage();
|
||||
|
||||
// UI
|
||||
loadUI();
|
||||
|
||||
playingAnim = new UIText((FlxG.width / 2), 30, -1, 'Animation:\n${char.characterData.animations[curAnim].name}', 24);
|
||||
playingAnim.cameras = [uiCamera];
|
||||
|
||||
movingCamText = new UIText((FlxG.width / 4) - 310, (FlxG.height / 2), -1,
|
||||
"Press the left mouse button anywhere on this stage to set the character's camera position.", 24);
|
||||
movingCamText.cameras = [uiCamera];
|
||||
movingCamText.visible = false;
|
||||
|
||||
// Top Menu
|
||||
add(playingAnim);
|
||||
add(movingCamText);
|
||||
add(topMenuSpr);
|
||||
add(uiGroup);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
// Control
|
||||
controlCamera(elapsed);
|
||||
controlCharacter();
|
||||
changeAnim();
|
||||
moveCharPos();
|
||||
detectCamClick();
|
||||
|
||||
ghostChar.setPosition(char.x, char.y);
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
public function searchAnim(name:String, ?array:Array<AnimData>):AnimData
|
||||
{
|
||||
if (array == null)
|
||||
array = char.characterData.animations;
|
||||
for (i in 0...array.length - 1)
|
||||
if (array[i].name == name)
|
||||
return array[i];
|
||||
return array[0];
|
||||
}
|
||||
|
||||
public function getAnimIndex(name:String, ?array:Array<AnimData>):Int
|
||||
{
|
||||
if (array == null)
|
||||
array = char.characterData.animations;
|
||||
for (i in 0...array.length - 1)
|
||||
if (array[i].name == name)
|
||||
return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function resortAnims(animations:Array<AnimData>)
|
||||
{
|
||||
if (getAnimIndex("idle") != 0)
|
||||
{
|
||||
var oldanims = animations;
|
||||
animations.clear();
|
||||
animations.push(searchAnim("idle", oldanims));
|
||||
for (anim in oldanims)
|
||||
if (anim != searchAnim("idle", oldanims))
|
||||
animations.push(anim);
|
||||
}
|
||||
}
|
||||
|
||||
public function reloadChar(isOpponent:Bool, charData:CharacterData)
|
||||
{
|
||||
var i:Int = charLayer.members.length - 1;
|
||||
while (i >= 0)
|
||||
{
|
||||
var memb:Character = charLayer.members[i];
|
||||
if (memb != null)
|
||||
{
|
||||
memb.kill();
|
||||
charLayer.remove(memb);
|
||||
memb.destroy();
|
||||
}
|
||||
--i;
|
||||
}
|
||||
charLayer.clear();
|
||||
ghostChar = new Character(0, 0, curChar, !isOpponent);
|
||||
ghostChar.characterData = charData;
|
||||
ghostChar.debugMode = true;
|
||||
ghostChar.alpha = 0.6;
|
||||
|
||||
char = new Character(0, 0, curChar, !isOpponent);
|
||||
char.characterData = charData;
|
||||
|
||||
var charData = char.characterData;
|
||||
resortAnims(charData.animations);
|
||||
var availableAnim = searchAnim("idle");
|
||||
curAnim = getAnimIndex("idle");
|
||||
ghostChar.characterData.animations = char.characterData.animations;
|
||||
char.playAnim(availableAnim.name, true);
|
||||
ghostChar.playAnim(availableAnim.name, true);
|
||||
|
||||
char.debugMode = true;
|
||||
char.setPosition(char.characterData.offsetX + OFFSET_X + 100, char.characterData.offsetY);
|
||||
|
||||
charLayer.add(ghostChar);
|
||||
charLayer.add(char);
|
||||
|
||||
animAtlas = (char.animateAtlas != null ? char.animateAtlas : null);
|
||||
|
||||
updateCharPos(charData.offsetX, charData.offsetY);
|
||||
updatePointerPos();
|
||||
mixDataToChar(char, charData);
|
||||
mixDataToChar(ghostChar, charData);
|
||||
}
|
||||
|
||||
function mixDataToChar(target:Character, data:CharacterData)
|
||||
{
|
||||
var sprite = (target.xml.has.sprite ? target.xml.att.sprite : target.curCharacter);
|
||||
if (data.sprite != sprite)
|
||||
target.loadSprite(Paths.image('characters/${data.sprite}'));
|
||||
target.icon = data.icon;
|
||||
target.flipX = !!data.flipX;
|
||||
target.antialiasing = data.antialiasing;
|
||||
target.isPlayer = data.isPlayer;
|
||||
target.playerOffsets = data.isPlayer;
|
||||
target.isGF = data.isGF;
|
||||
target.scale.set(data.scale, data.scale);
|
||||
target.holdTime = data.holdTime;
|
||||
target.fixChar(true);
|
||||
target.updateHitbox();
|
||||
}
|
||||
|
||||
function loadChar(isOpponent:Bool)
|
||||
{
|
||||
var i:Int = charLayer.members.length - 1;
|
||||
while (i >= 0)
|
||||
{
|
||||
var memb:Character = charLayer.members[i];
|
||||
if (memb != null)
|
||||
{
|
||||
memb.kill();
|
||||
charLayer.remove(memb);
|
||||
memb.destroy();
|
||||
}
|
||||
--i;
|
||||
}
|
||||
charLayer.clear();
|
||||
ghostChar = new Character(0, 0, curChar, !isOpponent);
|
||||
ghostChar.debugMode = true;
|
||||
ghostChar.alpha = 0.6;
|
||||
|
||||
char = new Character(0, 0, curChar, !isOpponent);
|
||||
char.debugMode = true;
|
||||
char.setPosition(char.characterData.offsetX + OFFSET_X + 100, char.characterData.offsetY);
|
||||
|
||||
charLayer.add(ghostChar);
|
||||
charLayer.add(char);
|
||||
|
||||
var charData = char.characterData;
|
||||
animAtlas = (char.animateAtlas != null ? char.animateAtlas : null);
|
||||
updateCharPos(charData.offsetX, charData.offsetY);
|
||||
resortAnims(char.characterData.animations);
|
||||
curAnim = getAnimIndex("idle");
|
||||
char.playAnim(charData.animations[getAnimIndex("idle")].name, true);
|
||||
ghostChar.characterData.animations = char.characterData.animations;
|
||||
|
||||
char.updateHitbox();
|
||||
ghostChar.updateHitbox();
|
||||
updatePointerPos();
|
||||
}
|
||||
|
||||
function loadUI()
|
||||
{
|
||||
topMenu = [
|
||||
{
|
||||
label: "File",
|
||||
childs: [
|
||||
{
|
||||
label: "New"
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Save",
|
||||
keybind: [CONTROL, S],
|
||||
onSelect: _file_save,
|
||||
},
|
||||
{
|
||||
label: "Save As...",
|
||||
keybind: [CONTROL, SHIFT, S],
|
||||
onSelect: _file_saveas,
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Exit",
|
||||
keybind: [ESCAPE],
|
||||
onSelect: _file_exit
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Edit",
|
||||
childs: [
|
||||
{
|
||||
label: "Camera Position",
|
||||
onSelect: moveCameraPos
|
||||
},
|
||||
{
|
||||
label: "Properties",
|
||||
onSelect: edit_properties_window,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Animation",
|
||||
childs: [
|
||||
{
|
||||
label: "New Animation",
|
||||
keybind: [CONTROL, N],
|
||||
onSelect: add_anim_window,
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Update Animation",
|
||||
keybind: [CONTROL, U],
|
||||
onSelect: update_anim_window,
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Remove Animation",
|
||||
keybind: [CONTROL, D],
|
||||
onSelect: remove_anim_window,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Help",
|
||||
childs: [
|
||||
{
|
||||
label: "Controls",
|
||||
onSelect: help_controls_window
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
topMenuSpr = new UITopMenu(topMenu);
|
||||
topMenuSpr.cameras = uiGroup.cameras = [uiCamera];
|
||||
|
||||
Framerate.fpsCounter.alpha = 0.4;
|
||||
Framerate.memoryCounter.alpha = 0.4;
|
||||
Framerate.codenameBuildField.alpha = 0.4;
|
||||
}
|
||||
|
||||
var ofs_cross:FlxSpriteGroup = new FlxSpriteGroup(0, 0);
|
||||
|
||||
public function loadCross()
|
||||
{
|
||||
var i:Int = ofs_cross.members.length - 1;
|
||||
while (i >= 0)
|
||||
{
|
||||
var memb:FlxSprite = ofs_cross.members[i];
|
||||
if (memb != null)
|
||||
{
|
||||
memb.kill();
|
||||
ofs_cross.remove(memb);
|
||||
memb.destroy();
|
||||
}
|
||||
--i;
|
||||
}
|
||||
ofs_cross.clear();
|
||||
|
||||
var horizontal_cross = new FlxSprite(0, 0).makeGraphic(2000, 5);
|
||||
var vertical_cross = new FlxSprite(0, 0).makeGraphic(5, 2000);
|
||||
horizontal_cross.y += (vertical_cross.height / 2);
|
||||
vertical_cross.x += (horizontal_cross.width / 2);
|
||||
ofs_cross.add(horizontal_cross);
|
||||
ofs_cross.add(vertical_cross);
|
||||
horizontal_cross.scrollFactor.set(1, 1);
|
||||
vertical_cross.scrollFactor.set(1, 1);
|
||||
// ofs_cross.screenCenter();
|
||||
ofs_cross.y = -200;
|
||||
ofs_cross.scrollFactor.set(1, 1);
|
||||
ofs_cross.alpha = 0.6;
|
||||
}
|
||||
|
||||
var OFFSET_X:Float = 150;
|
||||
|
||||
public function reloadStage()
|
||||
{
|
||||
var i:Int = stage.members.length - 1;
|
||||
while (i >= 0)
|
||||
{
|
||||
var memb:FunkinSprite = stage.members[i];
|
||||
if (memb != null)
|
||||
{
|
||||
memb.kill();
|
||||
stage.remove(memb);
|
||||
memb.destroy();
|
||||
}
|
||||
--i;
|
||||
}
|
||||
stage.clear();
|
||||
var playerXDifference = 0;
|
||||
if (char.isPlayer)
|
||||
playerXDifference = 670;
|
||||
|
||||
var bg = new FunkinSprite(-600 + OFFSET_X - playerXDifference, -200, Paths.image("stages/default/stageback"));
|
||||
stage.add(bg);
|
||||
bg.scrollFactor.set(0.9, 0.9);
|
||||
|
||||
var stageFront = new FunkinSprite(-600 + OFFSET_X - playerXDifference, 600, Paths.image("stages/default/stagefront"));
|
||||
stageFront.setGraphicSize(Std.int(stageFront.width * 1.1));
|
||||
stageFront.updateHitbox();
|
||||
stage.add(stageFront);
|
||||
stageFront.scrollFactor.set(0.9, 0.9);
|
||||
|
||||
stage.setPosition(0, 0);
|
||||
}
|
||||
|
||||
function hasAtlas():Bool
|
||||
return (animAtlas != null);
|
||||
|
||||
var movingCam:Bool = false;
|
||||
var movingCamDefaultPos:FlxPoint = null;
|
||||
var movingCamDefaultPosCam:FlxPoint = null;
|
||||
|
||||
public function moveCameraPos(_)
|
||||
{
|
||||
movingCam = true;
|
||||
movingCamText.visible = true;
|
||||
updatePointerPos();
|
||||
}
|
||||
|
||||
public function detectCamClick()
|
||||
{
|
||||
var charData = char.characterData;
|
||||
|
||||
if (movingCam)
|
||||
{
|
||||
if (FlxG.mouse.justPressed)
|
||||
{
|
||||
var midx = char.getMidpoint().x;
|
||||
var midy = char.getMidpoint().y;
|
||||
|
||||
var pos = FlxG.mouse.getWorldPosition(editorCamera);
|
||||
var placedPos:FlxPoint = new FlxPoint((!char.isPlayer ? pos.x + -(midx) : pos.x + -(midx)), -(pos.y - midy));
|
||||
|
||||
movingCam = false;
|
||||
movingCamText.visible = false;
|
||||
charData.camOffsetX = placedPos.x;
|
||||
charData.camOffsetY = placedPos.y;
|
||||
updatePointerPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePointerPos()
|
||||
{
|
||||
var x:Float = char.getMidpoint().x;
|
||||
var y:Float = char.getMidpoint().y;
|
||||
/*if (!char.isPlayer)
|
||||
{ */
|
||||
x += char.characterData.camOffsetX;
|
||||
/*}
|
||||
else
|
||||
{
|
||||
x -= char.characterData.camOffsetX;
|
||||
}*/
|
||||
y -= char.characterData.camOffsetY;
|
||||
|
||||
x -= cameraPointer.width / 2;
|
||||
y -= cameraPointer.height / 2;
|
||||
cameraPointer.setPosition(x, y);
|
||||
}
|
||||
|
||||
var movingOffset:Bool = false;
|
||||
var dragX:Float = -1;
|
||||
var dragY:Float = -1;
|
||||
|
||||
public function moveCharPos()
|
||||
{
|
||||
var charSprite = (hasAtlas() ? char.animateAtlas : char);
|
||||
var charData = char.characterData;
|
||||
if (FlxG.mouse.justPressed && FlxG.mouse.overlaps(charSprite))
|
||||
{
|
||||
var pos = FlxG.mouse.getWorldPosition(FlxG.camera);
|
||||
dragX = charData.offsetX - pos.x;
|
||||
dragY = charData.offsetY - pos.y;
|
||||
movingOffset = true;
|
||||
}
|
||||
else if (!FlxG.mouse.overlaps(charSprite))
|
||||
movingOffset = false;
|
||||
|
||||
if (movingOffset && FlxG.mouse.pressed)
|
||||
{
|
||||
var pos = FlxG.mouse.getWorldPosition(editorCamera);
|
||||
updateCharPos((pos.x + dragX), (pos.y + dragY));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateCharPos(x:Float, y:Float)
|
||||
{
|
||||
char.characterData.offsetX = x;
|
||||
char.characterData.offsetY = y;
|
||||
char.x = x;
|
||||
char.y = y;
|
||||
updatePointerPos();
|
||||
}
|
||||
|
||||
public function reloadGhost()
|
||||
{
|
||||
ghostChar.loadSprite(char.characterData.sprite);
|
||||
for (i in 0...char.characterData.animations.length - 1)
|
||||
{
|
||||
XMLUtil.addAnimToSprite(ghostChar, char.characterData.animations[i]);
|
||||
}
|
||||
|
||||
char.alpha = 0.85;
|
||||
ghostChar.visible = true;
|
||||
/*if (ghostDropDown.selectedLabel == '')
|
||||
{
|
||||
ghostChar.visible = false;
|
||||
char.alpha = 1;
|
||||
}*/
|
||||
ghostChar.color = 0xFF666688;
|
||||
ghostChar.antialiasing = char.antialiasing;
|
||||
ghostChar.playAnim(char.animation.curAnim.name, true);
|
||||
}
|
||||
|
||||
function controlCharacter()
|
||||
{
|
||||
var controlArray:Array<Bool> = [
|
||||
FlxG.keys.justPressed.LEFT,
|
||||
FlxG.keys.justPressed.RIGHT,
|
||||
FlxG.keys.justPressed.UP,
|
||||
FlxG.keys.justPressed.DOWN
|
||||
];
|
||||
|
||||
for (i in 0...controlArray.length)
|
||||
{
|
||||
if (controlArray[i])
|
||||
{
|
||||
var holdShift = FlxG.keys.pressed.SHIFT;
|
||||
var multiplier = 1;
|
||||
if (holdShift)
|
||||
multiplier = 10;
|
||||
|
||||
var negaMult:Int = 1;
|
||||
if (i % 2 == 1)
|
||||
negaMult = -1;
|
||||
|
||||
// For Player Offsets
|
||||
if (i == 1 && char.playerOffsets)
|
||||
negaMult = -1;
|
||||
if (i == 0 && char.playerOffsets)
|
||||
negaMult = 1;
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
char.characterData.animations[curAnim].y += negaMult * multiplier;
|
||||
ghostChar.characterData.animations[curAnim].y += negaMult * multiplier;
|
||||
}
|
||||
else
|
||||
{
|
||||
char.characterData.animations[curAnim].x += negaMult * multiplier;
|
||||
ghostChar.characterData.animations[curAnim].x += negaMult * multiplier;
|
||||
}
|
||||
|
||||
char.animOffsets.set(char.characterData.animations[curAnim].name,
|
||||
new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y));
|
||||
ghostChar.animOffsets.set(char.characterData.animations[curAnim].name,
|
||||
new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y));
|
||||
|
||||
char.addOffset(char.characterData.animations[curAnim].anim, char.characterData.animations[curAnim].x,
|
||||
char.characterData.animations[curAnim].y);
|
||||
ghostChar.addOffset(char.characterData.animations[curAnim].anim, char.characterData.animations[curAnim].x,
|
||||
char.characterData.animations[curAnim].y);
|
||||
/*ghostChar.animOffsets.set(char.animation.curAnim.name,
|
||||
new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y)); */
|
||||
|
||||
char.playAnim(char.characterData.animations[curAnim].name, false);
|
||||
if (ghostChar.animation.curAnim != null
|
||||
&& char.animation.curAnim != null
|
||||
&& char.animation.curAnim.name == ghostChar.animation.curAnim.name)
|
||||
{
|
||||
ghostChar.playAnim(char.animation.curAnim.name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function controlCamera(elapsed:Float)
|
||||
{
|
||||
var ofs:Float = 500 * elapsed;
|
||||
var shift:Bool = (FlxG.keys.pressed.SHIFT);
|
||||
var cam_up:Bool = (FlxG.keys.pressed.W);
|
||||
var cam_down:Bool = (FlxG.keys.pressed.S);
|
||||
var cam_left:Bool = (FlxG.keys.pressed.A);
|
||||
var cam_right:Bool = (FlxG.keys.pressed.D);
|
||||
var cam_zoomers:Array<Bool> = [FlxG.keys.pressed.I, FlxG.keys.pressed.O];
|
||||
|
||||
if (shift)
|
||||
ofs *= 4;
|
||||
|
||||
if (cam_up)
|
||||
camFollow.y -= ofs;
|
||||
if (cam_down)
|
||||
camFollow.y += ofs;
|
||||
if (cam_left)
|
||||
camFollow.x -= ofs;
|
||||
if (cam_right)
|
||||
camFollow.x += ofs;
|
||||
|
||||
var zoom_ofs = FlxG.camera.zoom * elapsed;
|
||||
if (cam_zoomers[0] && camZoom < 3)
|
||||
{
|
||||
camZoom += zoom_ofs;
|
||||
if (camZoom > 3)
|
||||
camZoom = 3;
|
||||
}
|
||||
if (cam_zoomers[1] && camZoom > 0.1)
|
||||
{
|
||||
camZoom -= zoom_ofs;
|
||||
if (camZoom < 0.1)
|
||||
camZoom = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
function changeAnim()
|
||||
{
|
||||
var space = (FlxG.keys.justPressed.SPACE);
|
||||
var forward = (FlxG.keys.justPressed.L);
|
||||
var backwards = (FlxG.keys.justPressed.K);
|
||||
|
||||
var changelimit = char.characterData.animations.length;
|
||||
|
||||
if (forward || backwards)
|
||||
{
|
||||
if (forward)
|
||||
curAnim += 1;
|
||||
else if (backwards)
|
||||
curAnim -= 1;
|
||||
|
||||
if (curAnim >= changelimit)
|
||||
curAnim = 0;
|
||||
else if (curAnim < 0)
|
||||
curAnim = changelimit - 1;
|
||||
|
||||
char.playAnim(char.characterData.animations[curAnim].name, false);
|
||||
playingAnim.text = 'Animation:\n${char.characterData.animations[curAnim].name}';
|
||||
}
|
||||
|
||||
if (space)
|
||||
char.playAnim(char.characterData.animations[curAnim].name, true);
|
||||
|
||||
char.updateHitbox();
|
||||
ghostChar.updateHitbox();
|
||||
}
|
||||
|
||||
// Animation Windows
|
||||
function add_anim_window(_)
|
||||
openSubState(new AddAnimScreen(char, ghostChar, this));
|
||||
|
||||
function update_anim_window(_)
|
||||
openSubState(new SelectAnimUpdate(char, ghostChar, this));
|
||||
|
||||
function remove_anim_window(_)
|
||||
openSubState(new DeleteAnimScreen(char, ghostChar, this));
|
||||
|
||||
function help_controls_window(_)
|
||||
openSubState(new HelpControls());
|
||||
|
||||
function _file_exit(_)
|
||||
{
|
||||
FlxG.switchState(new CharacterSelection());
|
||||
}
|
||||
|
||||
override function destroy() {
|
||||
Framerate.fpsCounter.alpha = 1;
|
||||
Framerate.memoryCounter.alpha = 1;
|
||||
Framerate.codenameBuildField.alpha = 1;
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
function edit_properties_window(_)
|
||||
FlxG.state.openSubState(new CharacterProperties(char, ghostChar, this));
|
||||
|
||||
function _file_save(_)
|
||||
{
|
||||
#if sys
|
||||
var assetPath = Paths.character(char.curCharacter);
|
||||
var path = Assets.getPath(assetPath);
|
||||
saveTo(path);
|
||||
return;
|
||||
#end
|
||||
_file_saveas(_);
|
||||
}
|
||||
|
||||
function _file_saveas(_)
|
||||
{
|
||||
var xml = CharacterConfig.dataToXml(char.characterData);
|
||||
openSubState(new SaveSubstate(CharacterConfig.generateCharXML(xml.x), {
|
||||
defaultSaveFile: '${char.curCharacter}.xml'
|
||||
}));
|
||||
}
|
||||
|
||||
#if sys
|
||||
function saveTo(path:String)
|
||||
{
|
||||
var xml = CharacterConfig.dataToXml(char.characterData);
|
||||
CharacterConfig.save(path, CharacterConfig.generateCharXML(xml.x));
|
||||
}
|
||||
#end
|
||||
|
||||
function set_camZoom(value:Float):Float
|
||||
{
|
||||
editorCamera.zoom = value;
|
||||
camZoom = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import funkin.game.Character;
|
||||
|
||||
class CharacterProperties extends UISubstateWindow
|
||||
{
|
||||
public var char:Character;
|
||||
public var ghostChar:Character;
|
||||
public var charData:CharacterData;
|
||||
|
||||
public var applyButton:UIButton;
|
||||
public var cancelButton:UIButton;
|
||||
public var editor:CharacterEditor;
|
||||
|
||||
public var curAnimData:AnimData;
|
||||
|
||||
public var spriteFile:UITextBox;
|
||||
public var gameOverChar:UITextBox;
|
||||
public var healthIcon:UITextBox;
|
||||
public var iconColor:UITextBox;
|
||||
public var flipX:UICheckbox;
|
||||
public var isPlayer:UICheckbox;
|
||||
public var isGF:UICheckbox;
|
||||
public var antialiasing:UICheckbox;
|
||||
public var scale:UITextBox;
|
||||
public var holdTime:UITextBox;
|
||||
|
||||
public function new(char:Character, ghostChar, editor:CharacterEditor)
|
||||
{
|
||||
super();
|
||||
this.char = char;
|
||||
this.ghostChar = ghostChar;
|
||||
this.charData = char.characterData;
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
public override function create()
|
||||
{
|
||||
winTitle = 'Character Properties';
|
||||
winWidth = 960;
|
||||
|
||||
super.create();
|
||||
|
||||
// Offsets
|
||||
var y_ofs = 60;
|
||||
var x_ofs = 60;
|
||||
var y_text_ofs = 20;
|
||||
|
||||
// Character Properties
|
||||
spriteFile = new UITextBox(windowSpr.x + 50, windowSpr.y + 10 + y_ofs, charData.sprite);
|
||||
add(spriteFile);
|
||||
add(new UIText(spriteFile.x, spriteFile.y - y_text_ofs, -1, "Image/Atlas file: "));
|
||||
|
||||
gameOverChar = new UITextBox(spriteFile.x + spriteFile.bWidth + x_ofs, spriteFile.y, charData.gameOverChar);
|
||||
add(gameOverChar);
|
||||
add(new UIText(gameOverChar.x, gameOverChar.y - y_text_ofs, -1, "Game Over character: "));
|
||||
|
||||
healthIcon = new UITextBox(spriteFile.x, spriteFile.y + y_ofs, charData.icon);
|
||||
add(healthIcon);
|
||||
add(new UIText(healthIcon.x, healthIcon.y - y_text_ofs, -1, "Health Icon: "));
|
||||
|
||||
iconColor = new UITextBox(healthIcon.x + healthIcon.bWidth + x_ofs, healthIcon.y, CharacterConfig.rgbToString(charData.iconColor));
|
||||
add(iconColor);
|
||||
add(new UIText(iconColor.x, iconColor.y - y_text_ofs, -1, "Icon color (RGB): "));
|
||||
|
||||
flipX = new UICheckbox(healthIcon.x, healthIcon.y + y_ofs, "Flip X", charData.flipX);
|
||||
add(flipX);
|
||||
|
||||
antialiasing = new UICheckbox(flipX.x + flipX.field.width + x_ofs, flipX.y, "Antialiasing", charData.antialiasing);
|
||||
add(antialiasing);
|
||||
|
||||
isPlayer = new UICheckbox(flipX.x, flipX.y + y_ofs, "Playable Character", charData.isPlayer);
|
||||
add(isPlayer);
|
||||
|
||||
isGF = new UICheckbox(isPlayer.x + isPlayer.field.width + x_ofs, isPlayer.y, "GF Character", charData.isGF);
|
||||
add(isGF);
|
||||
|
||||
scale = new UITextBox(isPlayer.x, isPlayer.y + y_ofs, Std.string(charData.scale));
|
||||
add(scale);
|
||||
add(new UIText(scale.x, scale.y - y_text_ofs, -1, "Scale: "));
|
||||
|
||||
holdTime = new UITextBox(scale.x + scale.bWidth + y_ofs, scale.y, Std.string(charData.holdTime));
|
||||
add(holdTime);
|
||||
add(new UIText(holdTime.x, holdTime.y - y_text_ofs, -1, "Hold time: "));
|
||||
|
||||
// Apply / Cancel buttons
|
||||
applyButton = new UIButton(windowSpr.x - x_ofs - 70, windowSpr.y, "Apply", function()
|
||||
{
|
||||
updateProperties(spriteFile.label.text, healthIcon.label.text, CharacterConfig.stringToRGB(healthIcon.label.text), flipX.checked,
|
||||
antialiasing.checked, isPlayer.checked, isGF.checked, Std.parseFloat(scale.label.text), Std.parseFloat(holdTime.label.text), gameOverChar.label.text);
|
||||
});
|
||||
add(applyButton);
|
||||
|
||||
cancelButton = new UIButton(applyButton.x, applyButton.y + y_ofs, "Cancel", function()
|
||||
{
|
||||
close();
|
||||
});
|
||||
add(cancelButton);
|
||||
}
|
||||
|
||||
public function updateProperties(sprite:String, healthIcon:String, iconColor:FlxColor, flipX:Bool, antialiasing:Bool, isPlayer:Bool, isGF:Bool,
|
||||
scale:Float, holdTime:Float, gameOverChar:String)
|
||||
{
|
||||
// Character Data
|
||||
charData.sprite = sprite;
|
||||
charData.icon = healthIcon;
|
||||
charData.iconColor = iconColor;
|
||||
charData.flipX = flipX;
|
||||
charData.antialiasing = antialiasing;
|
||||
charData.isPlayer = isPlayer;
|
||||
charData.isGF = isGF;
|
||||
charData.scale = scale;
|
||||
charData.holdTime = holdTime;
|
||||
charData.gameOverChar = gameOverChar;
|
||||
|
||||
editor.reloadChar(!char.isPlayer, charData);
|
||||
editor.reloadStage();
|
||||
editor.loadCross();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.game.Character;
|
||||
import funkin.backend.chart.Chart;
|
||||
import funkin.game.Character.CharacterData;
|
||||
import funkin.options.type.TextOption;
|
||||
import funkin.options.type.IconOption;
|
||||
import funkin.options.OptionsScreen;
|
||||
import sys.FileSystem;
|
||||
|
||||
class CharacterSelection extends EditorTreeMenu
|
||||
{
|
||||
public var characterList:CharactersList;
|
||||
|
||||
public override function create()
|
||||
{
|
||||
bgType = "charter";
|
||||
|
||||
super.create();
|
||||
|
||||
characterList = CharactersList.get();
|
||||
|
||||
main = new OptionsScreen("Character Editor", "Select a character to edit", [
|
||||
for (s in characterList.characters)
|
||||
new IconOption(s.name, "Press ACCEPT to edit this character.", s.icon, function()
|
||||
{
|
||||
FlxG.switchState(new CharacterEditor(s.name));
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
public override function onMenuChange() {
|
||||
super.onMenuChange();
|
||||
if (optionsTree.members.length > 1) { // selected a song
|
||||
// small flashbang
|
||||
var color:FlxColor = characterList.characters[main.curSelected].iconColor.getDefault(0xFFFFFFFF);
|
||||
|
||||
bg.colorTransform.redOffset = 0.25 * color.red;
|
||||
bg.colorTransform.greenOffset = 0.25 * color.green;
|
||||
bg.colorTransform.blueOffset = 0.25 * color.blue;
|
||||
bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25);
|
||||
bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
|
||||
bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CharactersList
|
||||
{
|
||||
public var characters:Array<CharacterData> = [];
|
||||
|
||||
public function new()
|
||||
{
|
||||
}
|
||||
|
||||
public function getCharactersFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource)
|
||||
{
|
||||
var charactersFound:Array<String> = [];
|
||||
|
||||
charactersFound = Paths.getFolderContent('data/characters', false, source);
|
||||
|
||||
if (charactersFound.length > 0)
|
||||
{
|
||||
for (s in charactersFound)
|
||||
if (s.endsWith(".xml"))
|
||||
characters.push(CharacterConfig.loadCharacterData(s.replace(".xml", "")));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get()
|
||||
{
|
||||
var characterList = new CharactersList();
|
||||
|
||||
if (characterList.getCharactersFromSource(MODS))
|
||||
characterList.getCharactersFromSource(SOURCE);
|
||||
|
||||
return characterList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import flixel.math.FlxRandom;
|
||||
import cpp.Random;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import funkin.game.Character;
|
||||
|
||||
class DeleteAnimScreen extends UISubstateWindow
|
||||
{
|
||||
public var char:Character;
|
||||
public var ghostChar:Character;
|
||||
|
||||
public var editor:CharacterEditor;
|
||||
|
||||
public var animButtons:FlxTypedSpriteGroup<UIButton> = new FlxTypedSpriteGroup<UIButton>(365, -10);
|
||||
|
||||
public var closeButton:UIButton;
|
||||
|
||||
public function new(char:Character, ghostChar:Character, editor:CharacterEditor)
|
||||
{
|
||||
super();
|
||||
this.char = char;
|
||||
this.ghostChar = ghostChar;
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
public override function create()
|
||||
{
|
||||
winTitle = "Delete Animation";
|
||||
winWidth = 480;
|
||||
winHeight = 700;
|
||||
|
||||
super.create();
|
||||
|
||||
add(animButtons);
|
||||
|
||||
var y_adder = 60;
|
||||
for (anim in char.characterData.animations)
|
||||
{
|
||||
var newbutton = new UIButton(animButtons.x - 550, animButtons.y + y_adder, anim.name, function()
|
||||
{
|
||||
removeAnimation(anim);
|
||||
close();
|
||||
});
|
||||
animButtons.add(newbutton);
|
||||
y_adder += 60;
|
||||
}
|
||||
|
||||
closeButton = new UIButton(windowSpr.x - 120, windowSpr.y + 20, "Close", function() {
|
||||
close();
|
||||
});
|
||||
add(closeButton);
|
||||
}
|
||||
|
||||
public function removeAnimation(anim:AnimData)
|
||||
{
|
||||
var refreshAnim:Bool = false;
|
||||
if (char.animation.curAnim != null && anim.name == char.animation.curAnim.name)
|
||||
refreshAnim = true;
|
||||
|
||||
if (char.animation.getByName(anim.name) != null)
|
||||
char.animation.remove(anim.name);
|
||||
if (char.animOffsets.exists(anim.name))
|
||||
char.animOffsets.remove(anim.name);
|
||||
char.characterData.animations.remove(anim);
|
||||
|
||||
if (refreshAnim && char.characterData.animations.length > 0)
|
||||
char.playAnim(char.characterData.animations[0].name, true);
|
||||
|
||||
editor.reloadGhost();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
class HelpControls extends UISubstateWindow
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public override function create()
|
||||
{
|
||||
winTitle = 'Help: Controls';
|
||||
winWidth = 960;
|
||||
|
||||
super.create();
|
||||
|
||||
var helpText = new UIText(windowSpr.x + 25, windowSpr.y + 240, -1, "Left/Right/Up/Down Arrow: To change the offset of the current\nplaying animation.\n
|
||||
Left mouse button on the character: Change character's position.", 24);
|
||||
add(helpText);
|
||||
|
||||
var closeButton = new UIButton(helpText.x, helpText.y + 160, "Close", function()
|
||||
{
|
||||
close();
|
||||
});
|
||||
add(closeButton);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import flixel.math.FlxRandom;
|
||||
import cpp.Random;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import funkin.game.Character;
|
||||
|
||||
class SelectAnimUpdate extends UISubstateWindow
|
||||
{
|
||||
public var cancelButton:UIButton;
|
||||
|
||||
public var char:Character;
|
||||
public var ghostChar:Character;
|
||||
|
||||
public var editor:CharacterEditor;
|
||||
|
||||
public var animButtons:FlxTypedSpriteGroup<UIButton> = new FlxTypedSpriteGroup<UIButton>(365, -10);
|
||||
|
||||
public function new(char:Character, ghostChar:Character, editor:CharacterEditor)
|
||||
{
|
||||
super();
|
||||
this.char = char;
|
||||
this.ghostChar = ghostChar;
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
public override function create()
|
||||
{
|
||||
winTitle = "Select an animation to update:";
|
||||
winWidth = 480;
|
||||
winHeight = 700;
|
||||
|
||||
super.create();
|
||||
|
||||
add(animButtons);
|
||||
|
||||
var y_ofs = 60;
|
||||
|
||||
for (anim in char.characterData.animations)
|
||||
{
|
||||
var newbutton = new UIButton(animButtons.x - 550, animButtons.y + y_ofs, anim.name, function()
|
||||
{
|
||||
open_update_window(anim);
|
||||
});
|
||||
animButtons.add(newbutton);
|
||||
y_ofs += 60;
|
||||
}
|
||||
|
||||
cancelButton = new UIButton(windowSpr.x - 10, windowSpr.y + 20, "Cancel", function()
|
||||
{
|
||||
close();
|
||||
});
|
||||
cancelButton.x -= cancelButton.bWidth;
|
||||
add(cancelButton);
|
||||
}
|
||||
|
||||
public function open_update_window(anim:AnimData)
|
||||
{
|
||||
openSubState(new UpdateAnimScreen(this.char, this.ghostChar, anim, this.editor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import funkin.game.Character;
|
||||
|
||||
class UpdateAnimScreen extends UISubstateWindow
|
||||
{
|
||||
public var char:Character;
|
||||
public var ghostChar:Character;
|
||||
|
||||
public var updateButton:UIButton;
|
||||
public var cancelButton:UIButton;
|
||||
public var editor:CharacterEditor;
|
||||
|
||||
public var curAnimData:AnimData;
|
||||
|
||||
public var animName:UITextBox;
|
||||
public var animPrefix:UITextBox;
|
||||
public var fps:UITextBox;
|
||||
public var loop:UICheckbox;
|
||||
public var indices:UITextBox;
|
||||
|
||||
public function new(char:Character, ghostChar, animData:AnimData, editor:CharacterEditor)
|
||||
{
|
||||
super();
|
||||
this.char = char;
|
||||
this.ghostChar = ghostChar;
|
||||
this.curAnimData = animData;
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
public override function create()
|
||||
{
|
||||
winTitle = 'Update Animation: ${curAnimData.name}';
|
||||
winWidth = 960;
|
||||
|
||||
super.create();
|
||||
|
||||
// Animation Properties
|
||||
var y_ofs = 60;
|
||||
var y_text_ofs = 20;
|
||||
|
||||
animName = new UITextBox(windowSpr.x + 50, windowSpr.y + y_ofs, curAnimData.name);
|
||||
add(animName);
|
||||
add(new UIText(animName.x, animName.y - y_text_ofs, -1, "Animation Name: "));
|
||||
|
||||
animPrefix = new UITextBox(animName.x, animName.y + y_ofs, curAnimData.anim);
|
||||
add(animPrefix);
|
||||
add(new UIText(animPrefix.x, animPrefix.y - y_text_ofs, -1, "Animation on XML/Atlas file: "));
|
||||
|
||||
fps = new UITextBox(animPrefix.x, animPrefix.y + y_ofs, Std.string(curAnimData.fps));
|
||||
add(fps);
|
||||
add(new UIText(fps.x, fps.y - y_text_ofs, -1, "Framerate: "));
|
||||
|
||||
loop = new UICheckbox(fps.x, fps.y + y_ofs, "Looped", curAnimData.loop);
|
||||
add(loop);
|
||||
|
||||
indices = new UITextBox(loop.x, loop.y + y_ofs, Std.string(curAnimData.indices).replace("[", "").replace("]", ""));
|
||||
add(indices);
|
||||
add(new UIText(indices.x, indices.y - y_text_ofs, -1, "Indices (0, 1, 2, 3): "));
|
||||
|
||||
// Add / Cancel Buttons
|
||||
updateButton = new UIButton(windowSpr.x - 10, windowSpr.y + 31, "Update", function()
|
||||
{
|
||||
updateAnimation(animName.label.text, animPrefix.label.text, Std.parseInt(fps.label.text), loop.checked, indices.label.text);
|
||||
close();
|
||||
});
|
||||
updateButton.x -= updateButton.bWidth;
|
||||
add(updateButton);
|
||||
|
||||
cancelButton = new UIButton(updateButton.x, updateButton.y + y_ofs, "Cancel", function()
|
||||
{
|
||||
close();
|
||||
});
|
||||
add(cancelButton);
|
||||
}
|
||||
|
||||
public function updateAnimation(name:String, ?anim:String, ?fps:Int = 24, ?looped:Bool = false, ?indices:String)
|
||||
{
|
||||
var animType:XMLAnimType = NONE;
|
||||
|
||||
var selectedAnim:AnimData = null;
|
||||
var selectedAnimIndex = 0;
|
||||
|
||||
for (i in 0...char.characterData.animations.length)
|
||||
{
|
||||
if (char.characterData.animations[i].name == name)
|
||||
{
|
||||
selectedAnim = char.characterData.animations[i];
|
||||
selectedAnimIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedAnim == null)
|
||||
return;
|
||||
|
||||
var animData:AnimData = {
|
||||
name: name,
|
||||
anim: anim,
|
||||
fps: fps,
|
||||
loop: looped,
|
||||
animType: animType,
|
||||
indices: [],
|
||||
x: selectedAnim.x,
|
||||
y: selectedAnim.y
|
||||
};
|
||||
|
||||
if (indices != "" && indices != null)
|
||||
{
|
||||
var indicesSplit = indices.split(",");
|
||||
|
||||
for (indice in indicesSplit)
|
||||
{
|
||||
var i = Std.parseInt(indice.trim());
|
||||
if (i != null)
|
||||
animData.indices.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
XMLUtil.addAnimToSprite(char, animData);
|
||||
char.characterData.animations[selectedAnimIndex] = animData;
|
||||
char.playAnim(name, true);
|
||||
}
|
||||
}
|
||||
+137
-57
@@ -1,29 +1,49 @@
|
||||
package funkin.game;
|
||||
|
||||
import funkin.editors.character.CharacterConfig;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.backend.FunkinSprite;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.system.interfaces.IBeatReceiver;
|
||||
import funkin.backend.system.interfaces.IOffsetCompatible;
|
||||
import funkin.backend.utils.XMLUtil;
|
||||
|
||||
import flixel.animation.FlxBaseAnimation;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.math.FlxRect;
|
||||
|
||||
import haxe.xml.Access;
|
||||
import haxe.Exception;
|
||||
import haxe.io.Path;
|
||||
import funkin.backend.system.Conductor;
|
||||
|
||||
import openfl.geom.ColorTransform;
|
||||
|
||||
import funkin.backend.scripting.DummyScript;
|
||||
import funkin.backend.scripting.Script;
|
||||
import funkin.backend.scripting.events.*;
|
||||
import funkin.backend.scripting.events.PlayAnimEvent.PlayAnimContext;
|
||||
|
||||
using StringTools;
|
||||
|
||||
typedef CharacterData =
|
||||
{
|
||||
var offsetX:Float;
|
||||
var offsetY:Float;
|
||||
var camOffsetX:Float;
|
||||
var camOffsetY:Float;
|
||||
var quickDancer:Bool;
|
||||
var holdTime:Float;
|
||||
var isPlayer:Bool;
|
||||
var flipX:Bool;
|
||||
var antialiasing:Bool;
|
||||
var sprite:String;
|
||||
var name:String;
|
||||
var animations:Array<AnimData>;
|
||||
@:optional var isGF:Bool;
|
||||
@:optional var icon:String;
|
||||
@:optional var iconColor:FlxColor;
|
||||
@:optional var scale:Float;
|
||||
@:optional var gameOverChar:String;
|
||||
}
|
||||
|
||||
@:allow(funkin.desktop.editors.CharacterEditor)
|
||||
@:allow(funkin.game.StrumLine)
|
||||
@:allow(funkin.game.PlayState)
|
||||
@@ -33,9 +53,11 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
|
||||
private var __stunnedTime:Float = 0;
|
||||
private var __lockAnimThisFrame:Bool = false;
|
||||
|
||||
public var stunned(default, set):Bool = false;
|
||||
|
||||
private function set_stunned(b:Bool) {
|
||||
private function set_stunned(b:Bool)
|
||||
{
|
||||
__stunnedTime = 0;
|
||||
return stunned = b;
|
||||
}
|
||||
@@ -60,26 +82,35 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
public var script:Script;
|
||||
public var xml:Access;
|
||||
|
||||
public var characterData:CharacterData;
|
||||
|
||||
public var shadowFrame:CharacterShadowFrame;
|
||||
|
||||
|
||||
public inline function getCameraPosition() {
|
||||
public inline function getCameraPosition()
|
||||
{
|
||||
var midpoint = getMidpoint();
|
||||
var event = EventManager.get(PointEvent).recycle(
|
||||
midpoint.x + (isPlayer ? -100 : 150) + globalOffset.x + cameraOffset.x,
|
||||
midpoint.y - 100 + globalOffset.y + cameraOffset.y);
|
||||
var event = EventManager.get(PointEvent)
|
||||
.recycle(midpoint.x
|
||||
+ (isPlayer ? -100 : 150)
|
||||
+ globalOffset.x
|
||||
+ cameraOffset.x, midpoint.y
|
||||
- 100
|
||||
+ globalOffset.y
|
||||
+ cameraOffset.y);
|
||||
script.call("onGetCamPos", [event]);
|
||||
midpoint.put();
|
||||
// this event cannot be cancelled
|
||||
return new FlxPoint(event.x, event.y);
|
||||
}
|
||||
|
||||
public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, Force:Bool = true, Reversed:Bool = false, Frame:Int = 0) {
|
||||
public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, Force:Bool = true, Reversed:Bool = false, Frame:Int = 0)
|
||||
{
|
||||
var anims = ["singLEFT", "singDOWN", "singUP", "singRIGHT"];
|
||||
|
||||
var event = EventManager.get(DirectionAnimEvent).recycle('${anims[direction]}$suffix', direction, suffix, Context, Reversed, Frame, Force);
|
||||
script.call("onPlaySingAnim", [event]);
|
||||
if (!event.cancelled) playAnim(event.animName, event.force, Context, event.reversed, event.frame);
|
||||
if (!event.cancelled)
|
||||
playAnim(event.animName, event.force, Context, event.reversed, event.frame);
|
||||
}
|
||||
|
||||
public function new(x:Float, y:Float, ?character:String = "bf", isPlayer:Bool = false, switchAnims:Bool = true)
|
||||
@@ -92,53 +123,76 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
|
||||
antialiasing = true;
|
||||
|
||||
while(true) {
|
||||
while (true)
|
||||
{
|
||||
switch (curCharacter)
|
||||
{
|
||||
// case 'your-char': // To hardcode characters
|
||||
default:
|
||||
// load xml
|
||||
var xmlPath = Paths.xml('characters/$curCharacter');
|
||||
if (!Assets.exists(xmlPath)) {
|
||||
if (!Assets.exists(xmlPath))
|
||||
{
|
||||
curCharacter = "bf";
|
||||
continue;
|
||||
}
|
||||
|
||||
var plainXML = Assets.getText(xmlPath);
|
||||
try {
|
||||
try
|
||||
{
|
||||
var charXML = Xml.parse(plainXML).firstElement();
|
||||
if (charXML == null) throw new Exception("Missing \"character\" node in XML.");
|
||||
if (charXML == null)
|
||||
throw new Exception("Missing \"character\" node in XML.");
|
||||
xml = new Access(charXML);
|
||||
} catch(e) {
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
Logs.trace('Error while loading character ${curCharacter}: ${e}', ERROR);
|
||||
curCharacter = "bf";
|
||||
continue;
|
||||
}
|
||||
var sprite:String = curCharacter;
|
||||
|
||||
if (xml.has.isPlayer) playerOffsets = (xml.att.isPlayer == "true");
|
||||
if (xml.has.isGF) isGF = (xml.att.isGF == "true");
|
||||
if (xml.has.x) globalOffset.x = Std.parseFloat(xml.att.x);
|
||||
if (xml.has.y) globalOffset.y = Std.parseFloat(xml.att.y);
|
||||
if (xml.has.gameOverChar) gameOverCharacter = xml.att.gameOverChar;
|
||||
if (xml.has.camx) cameraOffset.x = Std.parseFloat(xml.att.camx);
|
||||
if (xml.has.camy) cameraOffset.y = Std.parseFloat(xml.att.camy);
|
||||
if (xml.has.holdTime) holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4);
|
||||
if (xml.has.flipX) flipX = (xml.att.flipX == "true");
|
||||
if (xml.has.icon) icon = xml.att.icon;
|
||||
if (xml.has.scale) {
|
||||
if (xml.has.isPlayer)
|
||||
playerOffsets = (xml.att.isPlayer == "true");
|
||||
if (xml.has.isGF)
|
||||
isGF = (xml.att.isGF == "true");
|
||||
if (xml.has.x)
|
||||
globalOffset.x = Std.parseFloat(xml.att.x);
|
||||
if (xml.has.y)
|
||||
globalOffset.y = Std.parseFloat(xml.att.y);
|
||||
if (xml.has.gameOverChar)
|
||||
gameOverCharacter = xml.att.gameOverChar;
|
||||
if (xml.has.camx)
|
||||
cameraOffset.x = Std.parseFloat(xml.att.camx);
|
||||
if (xml.has.camy)
|
||||
cameraOffset.y = Std.parseFloat(xml.att.camy);
|
||||
if (xml.has.holdTime)
|
||||
holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4);
|
||||
if (xml.has.flipX)
|
||||
flipX = (xml.att.flipX == "true");
|
||||
if (xml.has.icon)
|
||||
icon = xml.att.icon;
|
||||
if (xml.has.scale)
|
||||
{
|
||||
var scale = Std.parseFloat(xml.att.scale).getDefault(1);
|
||||
this.scale.set(scale, scale);
|
||||
updateHitbox();
|
||||
}
|
||||
if (xml.has.antialiasing) antialiasing = (xml.att.antialiasing == "true");
|
||||
if (xml.has.sprite) sprite = xml.att.sprite;
|
||||
if (xml.has.antialiasing)
|
||||
antialiasing = (xml.att.antialiasing == "true");
|
||||
if (xml.has.sprite)
|
||||
sprite = xml.att.sprite;
|
||||
|
||||
loadSprite(Paths.image('characters/$sprite'));
|
||||
for(anim in xml.nodes.anim) {
|
||||
|
||||
for (anim in xml.nodes.anim)
|
||||
{
|
||||
XMLUtil.addXMLAnimation(this, anim);
|
||||
}
|
||||
|
||||
characterData = CharacterConfig.loadCharacterData(character);
|
||||
|
||||
// Loads the script and calls it's "create" function
|
||||
script = Script.create(Paths.script(Path.withoutExtension(xmlPath), null, true));
|
||||
script.setParent(this);
|
||||
@@ -147,7 +201,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (script == null) script = new DummyScript(curCharacter);
|
||||
if (script == null)
|
||||
script = new DummyScript(curCharacter);
|
||||
|
||||
/**
|
||||
NON CONVERTED CHARACTERS - DO NOT REMOVE
|
||||
@@ -170,7 +225,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
// antialiasing = false;
|
||||
// flipX = true;
|
||||
|
||||
fixChar(true);
|
||||
|
||||
dance();
|
||||
script.call("postCreate");
|
||||
}
|
||||
|
||||
public function fixChar(switchAnims:Bool = false)
|
||||
{
|
||||
isDanceLeftDanceRight = (hasAnimation("danceLeft") && hasAnimation("danceRight"));
|
||||
|
||||
// alternative to xor operator
|
||||
@@ -189,10 +251,9 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
switchOffset('singLEFT', 'singRIGHT');
|
||||
switchOffset('singLEFTmiss', 'singRIGHTmiss');
|
||||
}
|
||||
if (isPlayer) flipX = !flipX;
|
||||
if (isPlayer)
|
||||
flipX = !flipX;
|
||||
__baseFlipped = flipX;
|
||||
dance();
|
||||
script.call("postCreate");
|
||||
}
|
||||
|
||||
var __baseFlipped:Bool = false;
|
||||
@@ -202,7 +263,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
{
|
||||
super.update(elapsed);
|
||||
script.call("update", [elapsed]);
|
||||
if (stunned) {
|
||||
if (stunned)
|
||||
{
|
||||
__stunnedTime += elapsed;
|
||||
if (__stunnedTime > 5 / 60)
|
||||
stunned = false;
|
||||
@@ -221,22 +283,27 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
{
|
||||
var event = EventManager.get(DanceEvent).recycle(danced);
|
||||
script.call("onDance", [event]);
|
||||
if (event.cancelled) return;
|
||||
if (event.cancelled)
|
||||
return;
|
||||
|
||||
switch (curCharacter)
|
||||
{
|
||||
// hardcode custom dance animations here
|
||||
default:
|
||||
if (isDanceLeftDanceRight) {
|
||||
if (isDanceLeftDanceRight)
|
||||
{
|
||||
playAnim((danced = !danced) ? 'danceLeft' : 'danceRight', DANCE);
|
||||
} else
|
||||
}
|
||||
else
|
||||
playAnim('idle', DANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function tryDance() {
|
||||
switch(lastAnimContext) {
|
||||
public function tryDance()
|
||||
{
|
||||
switch (lastAnimContext)
|
||||
{
|
||||
case SING | MISS:
|
||||
if (lastHit + (Conductor.stepCrochet * holdTime) < Conductor.songPosition)
|
||||
dance();
|
||||
@@ -261,24 +328,30 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
*/
|
||||
public var danceInterval:Int = 1;
|
||||
|
||||
public override function beatHit(curBeat:Int) {
|
||||
public override function beatHit(curBeat:Int)
|
||||
{
|
||||
script.call("beatHit", [curBeat]);
|
||||
if (danceInterval < 1)
|
||||
danceInterval = 1;
|
||||
|
||||
if (danceOnBeat && curBeat % danceInterval == 0 && !__lockAnimThisFrame) {
|
||||
if (danceOnBeat && curBeat % danceInterval == 0 && !__lockAnimThisFrame)
|
||||
{
|
||||
tryDance();
|
||||
}
|
||||
}
|
||||
|
||||
public override function stepHit(curStep:Int) {
|
||||
public override function stepHit(curStep:Int)
|
||||
{
|
||||
script.call("stepHit", [curStep]);
|
||||
// nothing
|
||||
}
|
||||
|
||||
var __reverseDrawProcedure:Bool = false;
|
||||
public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect {
|
||||
if (__reverseDrawProcedure) {
|
||||
|
||||
public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect
|
||||
{
|
||||
if (__reverseDrawProcedure)
|
||||
{
|
||||
scale.x *= -1;
|
||||
var bounds = super.getScreenBounds(newRect, camera);
|
||||
scale.x *= -1;
|
||||
@@ -289,8 +362,11 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
|
||||
var __drawingShadowFrame = false;
|
||||
var __oldColorTransform = new ColorTransform();
|
||||
public override function draw() {
|
||||
if (!__drawingShadowFrame && shadowFrame != null) {
|
||||
|
||||
public override function draw()
|
||||
{
|
||||
if (!__drawingShadowFrame && shadowFrame != null)
|
||||
{
|
||||
__drawingShadowFrame = true;
|
||||
|
||||
var oldFrame = _frame;
|
||||
@@ -322,7 +398,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
__drawingShadowFrame = false;
|
||||
}
|
||||
|
||||
if ((isPlayer != playerOffsets) != (flipX != __baseFlipped)) {
|
||||
if ((isPlayer != playerOffsets) != (flipX != __baseFlipped))
|
||||
{
|
||||
__reverseDrawProcedure = true;
|
||||
|
||||
flipX = !flipX;
|
||||
@@ -332,7 +409,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
scale.x *= -1;
|
||||
|
||||
__reverseDrawProcedure = false;
|
||||
} else
|
||||
}
|
||||
else
|
||||
super.draw();
|
||||
}
|
||||
|
||||
@@ -340,7 +418,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
{
|
||||
var event = EventManager.get(PlayAnimEvent).recycle(AnimName, Force, Reversed, Frame, Context);
|
||||
script.call("onPlayAnim", [event]);
|
||||
if (event.cancelled) return;
|
||||
if (event.cancelled)
|
||||
return;
|
||||
|
||||
super.playAnim(event.animName, event.force, event.context, event.reverse, event.startingFrame);
|
||||
|
||||
@@ -349,16 +428,16 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
lastHit = Conductor.songPosition;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override function destroy() {
|
||||
public override function destroy()
|
||||
{
|
||||
super.destroy();
|
||||
|
||||
cameraOffset.put();
|
||||
globalOffset.put();
|
||||
}
|
||||
|
||||
public inline function getIcon() {
|
||||
public inline function getIcon()
|
||||
{
|
||||
return (icon != null) ? icon : curCharacter;
|
||||
}
|
||||
|
||||
@@ -397,7 +476,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
|
||||
}
|
||||
}
|
||||
|
||||
typedef CharacterShadowFrame = {
|
||||
typedef CharacterShadowFrame =
|
||||
{
|
||||
var anim:String;
|
||||
var frame:FlxFrame;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user