Files
CodenameEngine/source/funkin/editors/character/CharacterEditor.hx
T

698 lines
17 KiB
Haxe

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, false);
FlxG.camera.follow(camFollow);
// 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>)
{
// TODO: add support for danceLeft danceRight
if (getAnimIndex("idle") != 0)
{
var oldanims = animations.copy();
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 x = -800;
var horizontal_cross = new FlxSprite(x, -70).makeSolid(2000, 5);
var vertical_cross = new FlxSprite(x, 0).makeSolid(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.characterData.animations[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
];
var holdShift = FlxG.keys.pressed.SHIFT;
var holdAlt = FlxG.keys.pressed.ALT;
for (i in 0...controlArray.length)
{
if (controlArray[i])
{
var multiplier:Float = 1;
if (holdShift)
multiplier = 10;
if (holdAlt)
multiplier = 0.1;
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 (char.getAnimName() != null && char.getAnimName() == ghostChar.getAnimName())
{
ghostChar.playAnim(char.getAnimName(), 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);
if (forward || backwards)
{
var changeLimit = char.characterData.animations.length;
curAnim = FlxMath.wrap(curAnim + (forward ? 1 : -1), 0, changeLimit-1);
trace(curAnim);
trace(char.characterData.animations[curAnim]);
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;
return camZoom = value;
}
}