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:
Ali
2023-06-11 19:40:09 +02:00
committed by GitHub
co-authored by Ali NeeEoo
parent 753098c9e2
commit f992b4f65d
12 changed files with 1621 additions and 58 deletions
+4
View File
@@ -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');
+1 -1
View File
@@ -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
View File
@@ -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;
}
}