a buncha characcter editor stuff

This commit is contained in:
lunarcleint
2024-06-04 02:48:55 -07:00
parent 5dabed8214
commit 03d723e155
7 changed files with 245 additions and 652 deletions
@@ -1,10 +1,20 @@
package funkin.editors.character;
import flxanimate.animate.FlxSymbol;
import flxanimate.animate.FlxElement;
import flxanimate.animate.SymbolParameters;
import flxanimate.animate.FlxKeyFrame;
import flxanimate.animate.FlxTimeline;
import flixel.graphics.frames.FlxFrame;
import flxanimate.animate.FlxAnim.SymbolStuff;
import flixel.animation.FlxAnimation;
import openfl.geom.Rectangle;
import funkin.backend.utils.XMLUtil.AnimData;
import flixel.math.FlxPoint;
import flixel.util.FlxColor;
using StringTools;
class CharacterAnimButton extends UIButton {
public var anim:String = null;
public var data:AnimData = null;
@@ -32,6 +42,9 @@ class CharacterAnimButton extends UIButton {
public var labels:Map<UISprite, UIText> = [];
public var foldableButtons:Array<FlxSprite> = [];
public var closed:Bool = true;
public function new(x:Float, y:Float, animData:AnimData, parent:CharacterAnimsWindow) {
this.anim = animData.name;
this.data = animData;
@@ -51,40 +64,72 @@ class CharacterAnimButton extends UIButton {
frames = Paths.getFrames('editors/ui/inputbox');
field.fieldWidth = 0; framesOffset = 9;
field.size = 14;
// field.visible = false;
animationDisplayBG = new UISliceSprite(x+12, y+12+18+12, 128, 128, 'editors/ui/inputbox-small');
members.push(animationDisplayBG);
foldableButtons.push(animationDisplayBG);
nameTextBox = new UITextBox(animationDisplayBG.x+126+16, animationDisplayBG.y, animData.name, 116, 22, false, true);
nameTextBox.onChange = (newName:String) -> {this.changeName(newName);};
members.push(nameTextBox);
foldableButtons.push(nameTextBox);
addLabelOn(nameTextBox, "Name", 12);
animTextBox = new UITextBox(nameTextBox.x + 100 + 12, nameTextBox.y, animData.anim, 146, 22, false, true);
animTextBox.onChange = (newAnim:String) -> {this.changeAnim(newAnim);};
members.push(animTextBox);
foldableButtons.push(animTextBox);
addLabelOn(animTextBox, "Animation", 12);
positionXStepper = new UINumericStepper(animTextBox.x, animTextBox.y+32+18, animData.x, 0.001, 2, null, null, 64, 22, true);
positionXStepper.onChange = (text:String) -> {
@:privateAccess positionXStepper.__onChange(text);
this.changeOffset(positionXStepper.value, null);
};
members.push(positionXStepper);
foldableButtons.push(positionXStepper);
addLabelOn(positionXStepper, "Position (X,Y)", 12);
members.push(XYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 18));
foldableButtons.push(XYComma);
positionYStepper = new UINumericStepper(positionXStepper.x+104-32+26, positionXStepper.y, animData.y, 0.001, 2, null, null, 64, 22, true);
positionYStepper.onChange = (text:String) -> {
@:privateAccess positionYStepper.__onChange(text);
this.changeOffset(null, positionYStepper.value);
};
members.push(positionYStepper);
foldableButtons.push(positionYStepper);
fpsStepper = new UINumericStepper(animTextBox.x + 200 + 26, animTextBox.y, 0, 0.1, 2, animData.fps, 100, 52, 22, true);
fpsStepper = new UINumericStepper(animTextBox.x + 200 + 26, animTextBox.y, animData.fps, 0.1, 2, 1, 100, 52, 22, true);
fpsStepper.onChange = (text:String) -> {
@:privateAccess fpsStepper.__onChange(text);
this.changeFPS(fpsStepper.value);
};
members.push(fpsStepper);
foldableButtons.push(fpsStepper);
addLabelOn(fpsStepper, "FPS", 12);
loopedCheckbox = new UICheckbox(fpsStepper.x + 82 - 32 + 26, fpsStepper.y, "Looping?", animData.loop, 0, true);
loopedCheckbox.onChecked = (newLooping:Bool) -> {this.changeLooping(newLooping);};
members.push(loopedCheckbox);
foldableButtons.push(loopedCheckbox);
addLabelOn(loopedCheckbox, "Looped", 12);
loopedCheckbox.x += 8; loopedCheckbox.y += 6;
indicesTextBox = new UITextBox(nameTextBox.x, nameTextBox.y, animData.indices.getDefault([]).join(","), 278, 22, false, true);
indicesTextBox.onChange = (text:String) -> {
var indices:Array<Int> = [];
for(indice in text.split(",")) {
var i = Std.parseInt(indice.trim());
if (i != null) indices.push(i);
}
this.changeIndicies(indices);
}
members.push(indicesTextBox);
foldableButtons.push(indicesTextBox);
addLabelOn(indicesTextBox, "Indices (frames)", 12);
playIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/character/play"));
@@ -102,7 +147,7 @@ class CharacterAnimButton extends UIButton {
deleteIcon.antialiasing = false;
members.push(deleteIcon);
editButton = new UIButton(0, 0, "", null, 28,24);
editButton = new UIButton(0, 0, "", () -> {this.closed = !this.closed; this.update(0);}, 28,24);
editButton.frames = Paths.getFrames("editors/ui/grayscale-button");
editButton.color = 0xFFAFAA12;
members.push(editButton);
@@ -128,49 +173,208 @@ class CharacterAnimButton extends UIButton {
}
public override function update(elapsed:Float) {
playIcon.follow(this, 22, 19);
field.follow(this, 22+15+8, 16);
bHeight = closed ? 52 : 208;
deleteButton.follow(this, (500-16-32)-16-(28*2), 14);
playIcon.follow(this, 22, (52/2) - (playIcon.height/2));
field.follow(this, 22+16+10, (52/2) - (field.height/2) + 1);
deleteButton.follow(this, 380, 14);
deleteIcon.follow(deleteButton, (deleteButton.bWidth/2)-6.5, (deleteButton.bHeight/2)-6);
editButton.follow(this, (500-16-32)-16-(28*2)-12-28, 14);
editButton.follow(this, 340, 14);
editIcon.follow(editButton, (editButton.bWidth/2)-8, (editButton.bHeight/2)-6);
ghostButton.follow(this, (500-16-32)-16-(28*2)-12-28-12-28, 14);
ghostButton.follow(this, 300, 14);
ghostIcon.follow(ghostButton, (ghostButton.bWidth/2)-8, (ghostButton.bHeight/2)-6);
animationDisplayBG.follow(this, 16, 8+32+8+2+11);
nameTextBox.follow(this, 14+128+16, 8+32+8+2+8+14);
animTextBox.follow(this, 14+128+12+116+20, 8+32+8+2+8+14);
animationDisplayBG.follow(this, 16, 61);
nameTextBox.follow(this, 158, 72);
animTextBox.follow(this, 290, 72);
positionXStepper.follow(this, 14+128+16, 8+32+8+2+8+10+24+26);
positionYStepper.follow(this, 14+128+16+64-32+28, 8+32+8+2+8+10+24+26);
positionXStepper.follow(this, 158, 118);
positionYStepper.follow(this, 218, 118);
XYComma.follow(positionXStepper, 64-24, 6);
fpsStepper.follow(this, 14+128+16+64-32+28+64-22+20, 8+32+8+2+8+10+24+26);
loopedCheckbox.follow(this, 14+128+16+64-32+28+64-22+20+52-22+18+8, 8+32+8+2+8+10+24+32);
fpsStepper.follow(this, 280, 118);
loopedCheckbox.follow(this, 336, 124);
indicesTextBox.follow(this, 14+128+16, 8+32+8+2+8+10+24+26+24+22);
indicesTextBox.follow(this, 158, 164);
for (ui => text in labels)
for (button in foldableButtons)
button.visible = button.active = !closed;
for (ui => text in labels) {
text.follow(ui, -(2+(ui is UICheckbox?12:0)), -(18+(ui is UICheckbox?6:0)));
text.visible = text.active = ui.visible;
}
super.update(elapsed);
}
public function changeName(newName:String) @:privateAccess {
if (newName == anim) return;
if (parent.character.animateAtlas != null) {
var animSymbol:SymbolStuff = parent.character.animateAtlas.anim.animsMap[anim];
parent.character.animateAtlas.anim.animsMap.remove(anim);
parent.character.animateAtlas.anim.animsMap.set(newName, animSymbol);
} else {
var flxAnimation:FlxAnimation = parent.character.animation._animations[anim];
flxAnimation.name = newName;
parent.character.animation._animations.remove(anim);
parent.character.animation._animations.set(newName, flxAnimation);
}
var animData:AnimData = parent.character.animDatas[anim];
animData.name = newName;
parent.character.animDatas.remove(anim);
parent.character.animDatas.set(newName, animData);
var animOffset:FlxPoint = parent.character.animOffsets[anim];
parent.character.animOffsets.remove(anim);
parent.character.animOffsets.set(newName, animOffset);
var displayFrame:{scale:Float, animBounds:Rectangle, frame:Int} = parent.displayAnimsFramesList[anim];
parent.displayAnimsFramesList.remove(anim);
parent.displayAnimsFramesList.set(newName, displayFrame);
this.anim = newName;
field.text = '${animData.name} (${animData.x}, ${animData.y})';
}
public function changeAnim(newAnim:String) @:privateAccess {
var animData:AnimData = parent.character.animDatas[anim];
if (newAnim == animData.anim) return;
animData.anim = newAnim;
if (parent.character.animateAtlas != null) {
var animSymbol:SymbolStuff = parent.character.animateAtlas.anim.animsMap[anim];
refreshSymbolKeyFrames(animSymbol, animData);
} else {
var flxAnimation:FlxAnimation = parent.character.animation._animations[anim];
flxAnimation.prefix = newAnim;
refreshFlxAnimationFrames(flxAnimation, animData);
parent.buildAnimDisplay(anim, flxAnimation);
}
if (parent.character.getAnimName() == anim)
CharacterEditor.instance.playAnimation(anim);
}
public function changeOffset(newOffsetX:Null<Float>, newOffsetY:Null<Float>) {
var animData:AnimData = parent.character.animDatas[anim];
if (newOffsetX != null && newOffsetY != null && newOffsetX == animData.x && newOffsetY == animData.y)
return;
else {
if (newOffsetX != null && newOffsetX == animData.x) return;
if (newOffsetY != null && newOffsetY == animData.y) return;
}
if (newOffsetX != null) animData.x = newOffsetX;
if (newOffsetY != null) animData.y = newOffsetY;
parent.character.animOffsets[anim].set(animData.x, animData.y);
if (parent.character.getAnimName() == anim)
CharacterEditor.instance.playAnimation(anim);
field.text = '${animData.name} (${animData.x}, ${animData.y})';
}
public function changeFPS(newFPS:Float) @:privateAccess {
var animData:AnimData = parent.character.animDatas[anim];
animData.fps = newFPS;
if (parent.character.animateAtlas != null) {
var animSymbol:SymbolStuff = parent.character.animateAtlas.anim.animsMap[anim];
animSymbol.frameRate = newFPS;
} else {
var flxAnimation:FlxAnimation = parent.character.animation._animations[anim];
flxAnimation.frameRate = newFPS;
}
if (parent.character.getAnimName() == anim)
CharacterEditor.instance.playAnimation(anim);
}
public function changeLooping(newLooping:Bool) @:privateAccess {
var animData:AnimData = parent.character.animDatas[anim];
animData.loop = newLooping;
if (parent.character.animateAtlas != null) {
var animSymbol:SymbolStuff = parent.character.animateAtlas.anim.animsMap[anim];
animSymbol.instance.symbol.loop = animData.loop ? Loop : PlayOnce;
} else {
var flxAnimation:FlxAnimation = parent.character.animation._animations[anim];
flxAnimation.looped = animData.loop;
}
if (parent.character.getAnimName() == anim)
CharacterEditor.instance.playAnimation(anim);
}
public function changeIndicies(indicies:Array<Int>) @:privateAccess {
var animData:AnimData = parent.character.animDatas[anim];
animData.indices = indicies;
if (parent.character.animateAtlas != null) {
var animSymbol:SymbolStuff = parent.character.animateAtlas.anim.animsMap[anim];
refreshSymbolKeyFrames(animSymbol, animData);
} else {
var flxAnimation:FlxAnimation = parent.character.animation._animations[anim];
refreshFlxAnimationFrames(flxAnimation, animData);
}
if (parent.character.getAnimName() == anim)
CharacterEditor.instance.playAnimation(anim);
}
public inline function refreshFlxAnimationFrames(flxAnimation:FlxAnimation, animData:AnimData) @:privateAccess {
try {
if (animData.indices.length > 0) {
var frameIndices:Array<Int> = new Array<Int>();
parent.character.animation.byIndicesHelper(frameIndices, flxAnimation.prefix, animData.indices, "");
flxAnimation.frames = frameIndices;
} else {
final animFrames:Array<FlxFrame> = new Array<FlxFrame>();
parent.character.animation.findByPrefix(animFrames, flxAnimation.prefix);
final frameIndices:Array<Int> = [];
parent.character.animation.byPrefixHelper(frameIndices, animFrames, flxAnimation.prefix);
flxAnimation.frames = frameIndices;
}
} catch (e) {
trace('TODO: ERROR HANDLING $e');
}
}
public inline function refreshSymbolKeyFrames(symbol:SymbolStuff, animData:AnimData) @:privateAccess {
if (animData.indices.length > 0) {
// keeps on crashing, look at flxanimate FlxAnim.hx for refrence
} else {
for (name in parent.character.animateAtlas.anim.symbolDictionary.keys())
if (parent.character.animateAtlas.anim.startsWith(name, animData.anim))
{symbol.instance.symbol.name = name; break;}
}
}
public override function draw() {
super.draw();
if (parent.displayAnimsFramesList.exists(anim)) {
if (!closed && parent.displayAnimsFramesList.exists(anim)) {
var displayData:{frame:Int, scale:Float, animBounds:Rectangle} = parent.displayAnimsFramesList.get(anim);
parent.displayWindowSprite.frame = parent.displayWindowSprite.frames.frames[displayData.frame];
parent.displayWindowSprite.scale.x = parent.displayWindowSprite.scale.y = displayData.scale;
parent.displayWindowSprite.updateHitbox();
// parent.displayWindowSprite.origin.set();
parent.displayWindowSprite.follow(
this, 16+(128/2)-((parent.displayWindowSprite.frame.sourceSize.x*displayData.scale)/2),
8+32+8+2+11+(128/2)-((parent.displayWindowSprite.frame.sourceSize.y*displayData.scale)/2)
@@ -1,92 +0,0 @@
package funkin.editors.character;
import flixel.math.FlxPoint;
import flixel.util.FlxColor;
class CharacterAnimButtons extends UIButton {
public var anim:String = "";
public var editButton:UIButton;
public var editIcon:FlxSprite;
public var ghostButton:UIButton;
public var ghostIcon:FlxSprite;
public var deleteButton:UIButton;
public var deleteIcon:FlxSprite;
public function new(x:Float,y:Float,anim:String, offset:FlxPoint) {
this.anim = anim;
super(x,y, '$anim (${offset.x}, ${offset.y})', function () {
CharacterEditor.instance.playAnimation(this.anim);
}, 282);
autoAlpha = false;
ghostButton = new UIButton(x+282+17, y, "", function () {
CharacterEditor.instance.ghostAnim(this.anim);
}, 32);
ghostButton.autoAlpha = false;
members.push(ghostButton);
ghostIcon = new FlxSprite(ghostButton.x + 8, ghostButton.y + 8).loadGraphic(Paths.image('editors/character/ghost-button'), true, 16, 16);
ghostIcon.animation.add("alive", [0]);
ghostIcon.animation.add("dead", [1]);
ghostIcon.animation.play("dead"); ghostIcon.alpha = 0.5;
ghostIcon.antialiasing = false;
ghostIcon.updateHitbox();
members.push(ghostIcon);
editButton = new UIButton(ghostButton.x+32+17, y, "", function () {
CharacterEditor.instance.editAnimWithUI(this.anim);
}, 32);
editButton.frames = Paths.getFrames("editors/ui/grayscale-button");
editButton.color = FlxColor.YELLOW;
editButton.autoAlpha = false;
members.push(editButton);
editIcon = new FlxSprite(editButton.x + 8, editButton.y + 8).loadGraphic(Paths.image('editors/character/edit-button'), true, 16, 16);
editIcon.animation.add("edit", [0]);
editIcon.animation.add("advanced", [1]);
editIcon.animation.play("edit");
editIcon.antialiasing = false;
members.push(editIcon);
deleteButton = new UIButton(editButton.x+32+17, y, "", function () {
CharacterEditor.instance.deleteAnim(this.anim);
}, 32);
deleteButton.color = FlxColor.RED;
deleteButton.autoAlpha = false;
members.push(deleteButton);
deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button'));
deleteIcon.antialiasing = false;
members.push(deleteIcon);
}
public override function update(elapsed:Float) {
editButton.selectable = ghostButton.selectable = deleteButton.selectable = selectable;
editButton.shouldPress = ghostButton.shouldPress = deleteButton.shouldPress = shouldPress;
hovered = !deleteButton.hovered;
updatePos();
super.update(elapsed);
}
public function updateInfo(anim:String, offset:FlxPoint, ghost:Bool) {
this.anim = anim;
field.text = '$anim (${offset.x}, ${offset.y})';
ghostIcon.animation.play(ghost ? "alive" : "dead");
ghostIcon.alpha = ghost ? 1 : 0.5;
}
public inline function updatePos() {
// buttons
deleteButton.x = (editButton.x = (ghostButton.x = (x+282+17))+32+17)+32+17;
deleteButton.y = editButton.y = ghostButton.y = y;
// icons
ghostIcon.x = ghostButton.x + 8; ghostIcon.y = ghostButton.y + 8;
editIcon.x = editButton.x + 8; editIcon.y = editButton.y + 8;
deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8;
}
}
@@ -1,5 +1,6 @@
package funkin.editors.character;
import flixel.animation.FlxAnimation;
import flixel.graphics.FlxGraphic;
import openfl.geom.Rectangle;
import flixel.graphics.frames.FlxFrame;
@@ -31,17 +32,22 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> {
displayWindowSprite.flipX = character.flipX;
@:privateAccess
for (name => anim in displayWindowSprite.animation._animations) {
var frameIndex:Int = anim.frames.getDefault([0])[0];
var frame:FlxFrame = displayWindowSprite.frames.frames[frameIndex];
var frameRect:Rectangle = new Rectangle(frame.offset.x, frame.offset.y, frame.sourceSize.x, frame.sourceSize.y);
var animBounds:Rectangle = displayWindowGraphic != null ? displayWindowGraphic.bitmap.bounds(frameRect) : frameRect;
displayAnimsFramesList.set(name, {frame: anim.frames.getDefault([0])[0], scale: 104/animBounds.height, animBounds: animBounds});
}
for (name => anim in displayWindowSprite.animation._animations)
buildAnimDisplay(name, anim);
for (anim in character.getAnimOrder())
add(new CharacterAnimButton(0,0, character.animDatas.get(anim), this));
this.character = character;
}
public function buildAnimDisplay(name:String, anim:FlxAnimation) {
var frameIndex:Int = anim.frames.getDefault([0])[0];
var frame:FlxFrame = displayWindowSprite.frames.frames[frameIndex];
var frameRect:Rectangle = new Rectangle(frame.offset.x, frame.offset.y, frame.sourceSize.x, frame.sourceSize.y);
var animBounds:Rectangle = displayWindowGraphic != null ? displayWindowGraphic.bitmap.bounds(frameRect) : frameRect;
displayAnimsFramesList.set(name, {frame: anim.frames.getDefault([0])[0], scale: 104/animBounds.height, animBounds: animBounds});
}
}
@@ -14,8 +14,6 @@ class CharacterEditor extends UIState {
static var __character:String;
public var character:Character;
public var ghosts:CharacterGhostsHandler;
public static var instance(get, null):CharacterEditor;
private static inline function get_instance()
@@ -38,8 +36,6 @@ class CharacterEditor extends UIState {
public var charCamera:FlxCamera;
public var uiCamera:FlxCamera;
public var undos:UndoList<CharacterChange> = new UndoList<CharacterChange>();
public function new(character:String) {
super();
if (character != null) __character = character;
@@ -84,12 +80,10 @@ class CharacterEditor extends UIState {
{
label: "Undo",
keybind: [CONTROL, Z],
onSelect: _edit_undo
},
{
label: "Redo",
keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]],
onSelect: _edit_redo
}
]
},
@@ -99,21 +93,17 @@ class CharacterEditor extends UIState {
{
label: "New Animation",
keybind: [CONTROL, N],
onSelect: _char_add_anim,
},
{
label: "Edit Animation",
onSelect: _char_edit_anim,
},
{
label: "Delete Animation",
keybind: [DELETE],
onSelect: _char_remove_anim,
},
null,
{
label: "Edit Info",
onSelect: _char_edit_info,
}
]
},
@@ -137,49 +127,40 @@ class CharacterEditor extends UIState {
{
label: "Move Left",
keybind: [LEFT],
onSelect: _offsets_left,
},
{
label: "Move Up",
keybind: [UP],
onSelect: _offsets_up,
},
{
label: "Move Down",
keybind: [DOWN],
onSelect: _offsets_down,
},
{
label: "Move Right",
keybind: [RIGHT],
onSelect: _offsets_right,
},
null,
{
label: "Move Extra Left",
keybind: [SHIFT, LEFT],
onSelect: _offsets_extra_left,
},
{
label: "Move Extra Up",
keybind: [SHIFT, UP],
onSelect: _offsets_extra_up,
},
{
label: "Move Extra Down",
keybind: [SHIFT, DOWN],
onSelect: _offsets_extra_down,
},
{
label: "Move Extra Right",
keybind: [SHIFT, RIGHT],
onSelect: _offsets_extra_right,
},
null,
{
label: "Clear Offsets",
keybind: [CONTROL, R],
onSelect: _offsets_clear,
}
]
},
@@ -226,10 +207,6 @@ class CharacterEditor extends UIState {
character.debugMode = true;
character.cameras = [charCamera];
ghosts = new CharacterGhostsHandler(character);
ghosts.cameras = [charCamera];
add(ghosts);
add(character);
uiGroup.cameras = [uiCamera];
@@ -305,16 +282,13 @@ class CharacterEditor extends UIState {
characterBG.scale.set(characterBG.scale.x / charCamera.zoom, characterBG.scale.y / charCamera.zoom);
super.update(elapsed);
WindowUtils.prefix = undos.unsaved ? "* " : "";
SaveWarning.showWarning = undos.unsaved;
}
// TOP MENU OPTIONS
#if REGION
function _file_exit(_) {
if (undos.unsaved) SaveWarning.triggerWarning();
else FlxG.switchState(new CharacterSelection());
/*if (undos.unsaved) SaveWarning.triggerWarning();
else*/ FlxG.switchState(new CharacterSelection());
}
function _file_new(_) {
@@ -326,7 +300,6 @@ class CharacterEditor extends UIState {
'${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml',
buildCharacter()
);
undos.save();
#else
_file_saveas(_);
#end
@@ -336,7 +309,6 @@ class CharacterEditor extends UIState {
openSubState(new SaveSubstate(buildCharacter(), {
defaultSaveFile: '${character.curCharacter}.xml'
}));
undos.save();
}
function buildCharacter():String {
@@ -352,162 +324,6 @@ class CharacterEditor extends UIState {
return "<!DOCTYPE codename-engine-character>\n" + Printer.print(charXML, true);
}
function _edit_undo(_) {
var undo = undos.undo();
switch(undo) {
case null:
// do nothing
case CEditInfo(oldInfo, newInfo):
editInfo(oldInfo, false);
case CCreateAnim(animID, animData):
deleteAnim(animData.name, false);
case CEditAnim(name, oldData, animData):
editAnim(name, oldData, false);
case CDeleteAnim(animID, animData):
createAnim(animData, animID, false);
case CChangeOffset(name, change):
changeOffset(name, change * -1, false);
case CResetOffsets(oldOffsets):
for (anim => offsets in oldOffsets) {
character.animOffsets.set(anim, offsets.clone());
ghosts.setOffsets(anim, offsets.clone());
}
changeOffset(character.getAnimName(), FlxPoint.get(0, 0), false); // apply da new offsets
}
}
function _edit_redo(_) {
var redo = undos.redo();
switch(redo) {
case null:
// do nothing
case CEditInfo(oldInfo, newInfo):
editInfo(newInfo, false);
case CCreateAnim(animID, animData):
createAnim(animData, animID, false);
case CEditAnim(name, oldData, animData):
editAnim(oldData.name, animData, false);
case CDeleteAnim(animID, animData):
deleteAnim(animData.name, false);
case CChangeOffset(name, change):
changeOffset(name, change, false);
case CResetOffsets(oldOffsets):
clearOffsets(false);
}
}
function _char_add_anim(_) {
createAnimWithUI();
}
function _char_edit_anim(_) {
editAnimWithUI(character.getAnimName());
}
function _char_remove_anim(_) {
deleteAnim(character.getAnimName());
}
function _char_edit_info(_) {
editInfoWithUI();
}
public function createAnimWithUI() {
}
public function editAnimWithUI(name:String) {
}
public function createAnim(animData:AnimData, animID:Int = -1, addtoUndo:Bool = true) {
/*
XMLUtil.addAnimToSprite(character, animData);
ghosts.createGhost(animData.name);
var newButton = new CharacterAnimButton(0, 0, animData.name, FlxPoint.get(animData.x,animData.y));
if (animID == -1) characterAnimsWindow.add(newButton);
else characterAnimsWindow.insert(newButton, animID);
playAnimation(animData.name);
if (addtoUndo)
undos.addToUndo(CCreateAnim(character.getNameList().length, animData));
*/
}
public function editAnim(name:String, animData:AnimData, addtoUndo:Bool = true) {
var oldAnimData:AnimData = character.animDatas.get(name);
var buttoner:CharacterAnimButton = null;
for (button in characterAnimsWindow.buttons.members)
if (button.anim == name) buttoner = button;
ghosts.removeGhost(name);
XMLUtil.addAnimToSprite(character, animData);
ghosts.createGhost(animData.name);
if (character.getAnimName() == animData.name) // update anim ifs its currently selected
playAnimation(animData.name);
if (addtoUndo)
undos.addToUndo(CEditAnim(animData.name, oldAnimData, animData));
}
public function deleteAnim(name:String, addtoUndo:Bool = true) {
var animOrder:Array<String> = [
for (button in characterAnimsWindow.buttons.members)
button.anim
];
if (character.getNameList().length-1 == 0)
@:privateAccess character.animation._curAnim = null;
else
playAnimation(animOrder[Std.int(Math.abs(animOrder.indexOf(name)-1))]);
for(button in characterAnimsWindow.buttons.members)
if(button.anim == name) characterAnimsWindow.remove(button);
// undo shit blah blah
var oldID:Int = animOrder.indexOf(name);
var oldAnimData:AnimData = character.animDatas.get(name);
ghosts.removeGhost(name);
character.removeAnimation(name);
if (character.animOffsets.exists(name)) character.animOffsets.remove(name);
if (character.animDatas.exists(name)) character.animDatas.remove(name);
if (addtoUndo)
undos.addToUndo(CDeleteAnim(oldID, oldAnimData));
}
public function editInfoWithUI() {
FlxG.state.openSubState(new CharacterInfoScreen(character, (_) -> {
if (_ != null) editInfo(_);
}));
}
public function editInfo(newInfo:Xml, addtoUndo:Bool = true) {
var lastAnim:String = character.getAnimName();
var oldInfo = character.buildXML();
character.applyXML(new Access(newInfo));
ghosts.updateInfos(newInfo);
playAnimation(lastAnim);
if (addtoUndo)
undos.addToUndo(CEditInfo(oldInfo, newInfo));
}
public function ghostAnim(anim:String) {
var ghost:Character = ghosts.animGhosts.get(anim);
ghost.visible = !ghost.visible;
var animButton:CharacterAnimButton = null;
for (button in characterAnimsWindow.buttons.members)
if (button.anim == anim) animButton = button;
}
function _playback_play_anim(_) {
if (character.getNameList().length != 0)
playAnimation(character.getAnimName());
@@ -525,68 +341,6 @@ class CharacterEditor extends UIState {
i.alpha = i.anim == anim ? 1 : 0.25;
}
function _offsets_left(_) {
changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? 1 : -1, 0));
}
function _offsets_up(_) {
changeOffset(character.getAnimName(), FlxPoint.get(0, 1));
}
function _offsets_down(_) {
changeOffset(character.getAnimName(), FlxPoint.get(0, -1));
}
function _offsets_right(_) {
changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? -1 : 1, 0));
}
function _offsets_extra_left(_) {
changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? 5 : -5, 0));
}
function _offsets_extra_up(_) {
changeOffset(character.getAnimName(), FlxPoint.get(0, 5));
}
function _offsets_extra_down(_) {
changeOffset(character.getAnimName(), FlxPoint.get(0, -5));
}
function _offsets_extra_right(_) {
changeOffset(character.getAnimName(), FlxPoint.get(!character.isFlippedOffsets() ? -5 : 5, 0));
}
function _offsets_clear(_) {
clearOffsets();
}
function changeOffset(anim:String, change:FlxPoint, addtoUndo:Bool = true) {
if (character.getNameList().length == 0) return;
character.animOffsets.set(anim, character.getAnimOffset(anim) + change);
character.frameOffset.set(character.getAnimOffset(anim).x, character.getAnimOffset(anim).y);
ghosts.updateOffsets(anim, change);
if (addtoUndo)
undos.addToUndo(CChangeOffset(anim, change));
}
function clearOffsets(addtoUndo:Bool = true) {
if (character.getNameList().length == 0) return;
var oldOffsets:Map<String, FlxPoint> = [
for (anim => offsets in character.animOffsets)
anim => offsets.clone()
];
changeOffset(character.getAnimName(), FlxPoint.get(), false);
ghosts.clearOffsets();
if (addtoUndo)
undos.addToUndo(CResetOffsets(oldOffsets));
}
var zoom(default, set):Float = 0;
var __camZoom(default, set):Float = 1;
function set_zoom(val:Float) {
@@ -619,13 +373,4 @@ class CharacterEditor extends UIState {
characterMidpoint.put();
}
#end
}
enum CharacterChange {
CEditInfo(oldInfo:Xml, newInfo:Xml);
CCreateAnim(animID:Int, animData:AnimData);
CEditAnim(name:String, oldData:AnimData, animData:AnimData);
CDeleteAnim(animID:Int, animData:AnimData);
CChangeOffset(name:String, change:FlxPoint);
CResetOffsets(oldOffsets:Map<String, FlxPoint>);
}
@@ -1,75 +0,0 @@
package funkin.editors.character;
import flixel.math.FlxPoint;
import funkin.game.Character;
import haxe.xml.Access;
class CharacterGhostsHandler extends FlxTypedGroup<Character> {
public var character:Character;
public var animGhosts:Map<String, Character> = [];
public function new(character:Character) {
this.character = character;
super();
for (anim in character.getNameList())
createGhost(anim);
}
public function createGhost(anim:String) {
var ghost:Character = new Character(0,0, character.curCharacter);
ghost.applyXML(character.xml); // apply cur character stuff
ghost.playAnim(anim);
ghost.stopAnimation();
ghost.alpha = 0.5;
ghost.visible = false;
add(ghost);
animGhosts.set(anim, ghost);
}
public function removeGhost(anim:String) {
var ghost:Character = animGhosts.get(anim);
@:privateAccess if (ghost != null) {
ghost.animation._curAnim = null;
animGhosts.remove(anim);
ghost.destroy();
}
}
public function updateInfos(xml:Xml) {
for (anim => ghost in animGhosts) {
ghost.applyXML(new Access(xml));
ghost.playAnim(anim);
ghost.stopAnimation();
}
rePositionGhosts();
}
public function updateOffsets(name:String, change:FlxPoint) {
for (anim => ghost in animGhosts)
ghost.animOffsets.set(name, ghost.getAnimOffset(name) + change);
rePositionGhosts();
}
public function setOffsets(name:String, offset:FlxPoint) {
for (anim => ghost in animGhosts)
ghost.animOffsets.set(name, offset);
rePositionGhosts();
}
public function clearOffsets() {
for (anim => ghost in animGhosts)
ghost.animOffsets[anim].zero();
rePositionGhosts();
}
inline function rePositionGhosts() {
for (ghost in animGhosts)
ghost.frameOffset.set(ghost.getAnimOffset(ghost.getAnimName()).x, ghost.getAnimOffset(ghost.getAnimName()).y);
}
}
@@ -1,199 +0,0 @@
package funkin.editors.character;
import flixel.math.FlxPoint;
import funkin.editors.extra.PropertyButton;
import funkin.game.Character;
import haxe.xml.Access;
class CharacterInfoScreen extends UISubstateWindow {
public var character:Character;
public var spriteTextBox:UITextBox;
public var iconTextBox:UITextBox;
public var iconSprite:FlxSprite;
public var gameOverCharTextBox:UITextBox;
public var antialiasingCheckbox:UICheckbox;
public var flipXCheckbox:UICheckbox;
public var iconColorWheel:UIColorwheel;
public var positionXStepper:UINumericStepper;
public var positionYStepper:UINumericStepper;
public var cameraXStepper:UINumericStepper;
public var cameraYStepper:UINumericStepper;
public var scaleStepper:UINumericStepper;
public var singTimeStepper:UINumericStepper;
public var customPropertiesButtonList:UIButtonList<PropertyButton>;
public var isPlayerCheckbox:UICheckbox;
public var saveButton:UIButton;
public var closeButton:UIButton;
public var onSave:(xml:Xml) -> Void = null;
public function new(character:Character, ?onSave:(xml:Xml) -> Void) {
this.character = character;
this.onSave = onSave;
super();
}
public override function create() {
winTitle = "Editing Character";
winWidth = 1014;
winHeight = 600;
super.create();
function addLabelOn(ui:UISprite, text:String)
add(new UIText(ui.x, ui.y - 24, 0, text));
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Sprite Data", 28));
spriteTextBox = new UITextBox(title.x, title.y + title.height + 38, character.sprite, 200);
add(spriteTextBox);
addLabelOn(spriteTextBox, "Sprite");
iconTextBox = new UITextBox(spriteTextBox.x + 200 + 26, spriteTextBox.y, character.getIcon(), 150);
iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);}
add(iconTextBox);
addLabelOn(iconTextBox, "Icon");
updateIcon(character.getIcon());
gameOverCharTextBox = new UITextBox(iconTextBox.x + 150 + (75 + 12), iconTextBox.y, character.gameOverCharacter, 200);
add(gameOverCharTextBox);
addLabelOn(gameOverCharTextBox, "Game Over Character");
antialiasingCheckbox = new UICheckbox(spriteTextBox.x, spriteTextBox.y + 10 + 32 + 28, "Antialiasing", character.antialiasing);
add(antialiasingCheckbox);
addLabelOn(antialiasingCheckbox, "Antialiased");
flipXCheckbox = new UICheckbox(antialiasingCheckbox.x + 172, spriteTextBox.y + 10 + 32 + 28, "FlipX", character.flipX);
add(flipXCheckbox);
addLabelOn(flipXCheckbox, "Flipped");
iconColorWheel = new UIColorwheel(gameOverCharTextBox.x + 200 + 20, gameOverCharTextBox.y, character.iconColor);
add(iconColorWheel);
addLabelOn(iconColorWheel, "Icon Color");
add(title = new UIText(spriteTextBox.x, spriteTextBox.y + 10 + 46 + 84, 0, "Character Data", 28));
positionXStepper = new UINumericStepper(title.x, title.y + title.height + 36, character.globalOffset.x, 0.001, 2, null, null, 84);
add(positionXStepper);
addLabelOn(positionXStepper, "Position (X,Y)");
add(new UIText(positionXStepper.x + 84 - 32 + 0, positionXStepper.y + 9, 0, ",", 22));
positionYStepper = new UINumericStepper(positionXStepper.x + 84 - 32 + 26, positionXStepper.y, character.globalOffset.y, 0.001, 2, null, null, 84);
add(positionYStepper);
cameraXStepper = new UINumericStepper(positionYStepper.x + 36 + 84 - 32, positionYStepper.y, character.cameraOffset.x, 0.001, 2, null, null, 84);
add(cameraXStepper);
addLabelOn(cameraXStepper, "Camera Position (X,Y)");
add(new UIText(cameraXStepper.x + 84 - 32 + 0, cameraXStepper.y + 9, 0, ",", 22));
cameraYStepper = new UINumericStepper(cameraXStepper.x + 84 - 32 + 26, cameraXStepper.y, character.cameraOffset.y, 0.001, 2, null, null, 84);
add(cameraYStepper);
scaleStepper = new UINumericStepper(cameraYStepper.x + 84 - 32 + 90, cameraYStepper.y, character.scale.x, 0.001, 2, null, null, 74);
add(scaleStepper);
addLabelOn(scaleStepper, "Scale");
singTimeStepper = new UINumericStepper(scaleStepper.x + 74 - 32 + 36, scaleStepper.y, character.holdTime, 0.001, 2, null, null, 74);
add(singTimeStepper);
addLabelOn(singTimeStepper, "Sing Duration (Steps)");
customPropertiesButtonList = new UIButtonList<PropertyButton>(singTimeStepper.x + singTimeStepper.width + 200, singTimeStepper.y, 290, 200, '', FlxPoint.get(280, 35), null, 5);
customPropertiesButtonList.frames = Paths.getFrames('editors/ui/inputbox');
customPropertiesButtonList.cameraSpacing = 0;
customPropertiesButtonList.addButton.callback = function() {
customPropertiesButtonList.add(new PropertyButton("newProperty", "valueHere", customPropertiesButtonList));
}
for (prop=>val in character.extra)
customPropertiesButtonList.add(new PropertyButton(prop, val, customPropertiesButtonList));
add(customPropertiesButtonList);
addLabelOn(customPropertiesButtonList, "Custom Values (Advanced)");
isPlayerCheckbox = new UICheckbox(positionXStepper.x, positionXStepper.y + 10 + 32 + 28, "isPlayer", character.playerOffsets);
add(isPlayerCheckbox);
addLabelOn(isPlayerCheckbox, "Is Player");
for (checkbox in [isPlayerCheckbox, antialiasingCheckbox, flipXCheckbox])
{checkbox.y += 4; checkbox.x += 6;}
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight- 20, "Save & Close", function() {
saveCharacterInfo();
close();
}, 125);
saveButton.x -= saveButton.bWidth;
saveButton.y -= saveButton.bHeight;
closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
if (onSave != null) onSave(null);
close();
}, 125);
closeButton.x -= closeButton.bWidth;
closeButton.color = 0xFFFF0000;
add(closeButton);
add(saveButton);
}
function updateIcon(icon:String) {
if (iconSprite == null) add(iconSprite = new FlxSprite());
if (iconSprite.animation.exists(icon)) return;
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/face');
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
iconSprite.antialiasing = true;
iconSprite.animation.play(icon);
iconSprite.scale.set(0.5, 0.5);
iconSprite.updateHitbox();
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
}
function saveCharacterInfo() {
for (stepper in [positionXStepper, positionYStepper, cameraXStepper, cameraYStepper, singTimeStepper, scaleStepper])
@:privateAccess stepper.__onChange(stepper.label.text);
var xml = Xml.createElement("character");
xml.set("isPlayer", isPlayerCheckbox.checked ? "true" : "false");
xml.set("x", Std.string(positionXStepper.value));
xml.set("y", Std.string(positionYStepper.value));
xml.set("gameOverChar", gameOverCharTextBox.label.text);
xml.set("camx", Std.string(cameraXStepper.value));
xml.set("camy", Std.string(cameraYStepper.value));
xml.set("holdTime", Std.string(singTimeStepper.value));
xml.set("flipX", Std.string(flipXCheckbox.checked));
xml.set("icon", iconTextBox.label.text);
xml.set("scale", Std.string(scaleStepper.value));
xml.set("antialiasing", antialiasingCheckbox.checked ? "true" : "false");
xml.set("sprite", spriteTextBox.label.text);
if (iconColorWheel.colorChanged)
xml.set("color", iconColorWheel.curColor.toWebString());
for (val in customPropertiesButtonList.buttons.members)
xml.set(val.propertyText.label.text, val.valueText.label.text);
for (anim in character.animDatas)
{
var animXml:Xml = Xml.createElement('anim');
animXml.set("name", anim.name);
animXml.set("anim", anim.anim);
animXml.set("loop", Std.string(anim.loop));
animXml.set("fps", Std.string(anim.fps));
var offset:FlxPoint = character.getAnimOffset(anim.name);
animXml.set("x", Std.string(offset.x));
animXml.set("y", Std.string(offset.y));
if (anim.indices.length > 0)
animXml.set("indices", anim.indices.join(","));
xml.addChild(animXml);
}
if (onSave != null) onSave(xml);
}
}
@@ -87,6 +87,9 @@ class UISliceSprite extends UISprite {
}
public override function draw() @:privateAccess {
var lastPixelPerfect:Bool = cameras[0].pixelPerfectRender;
cameras[0].pixelPerfectRender = false;
var x:Float = this.x;
var y:Float = this.y;
@@ -184,6 +187,7 @@ class UISliceSprite extends UISprite {
}
alpha = oldAlpha;
}
cameras[0].pixelPerfectRender = true;
setPosition(x, y);
super.drawMembers();