diff --git a/source/funkin/ui/haxeui/components/CharacterPlayer.hx b/source/funkin/ui/haxeui/components/CharacterPlayer.hx
index c45aac35e..0e6981535 100644
--- a/source/funkin/ui/haxeui/components/CharacterPlayer.hx
+++ b/source/funkin/ui/haxeui/components/CharacterPlayer.hx
@@ -1,25 +1,19 @@
 package funkin.ui.haxeui.components;
 
-import flixel.FlxSprite;
-import flixel.graphics.frames.FlxAtlasFrames;
-import flixel.graphics.frames.FlxFramesCollection;
-import flixel.math.FlxRect;
-import funkin.modding.events.ScriptEvent;
-import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
+import funkin.modding.events.ScriptEvent.GhostMissNoteScriptEvent;
+import funkin.modding.events.ScriptEvent.NoteScriptEvent;
+import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
+import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
+import haxe.ui.core.IDataComponent;
 import funkin.play.character.BaseCharacter;
 import funkin.play.character.CharacterData.CharacterDataParser;
 import haxe.ui.containers.Box;
 import haxe.ui.core.Component;
-import haxe.ui.core.IDataComponent;
-import haxe.ui.data.DataSource;
 import haxe.ui.events.AnimationEvent;
-import haxe.ui.events.UIEvent;
 import haxe.ui.geom.Size;
 import haxe.ui.layouts.DefaultLayout;
-import haxe.ui.styles.Style;
-import openfl.Assets;
 
-private typedef AnimationInfo =
+typedef AnimationInfo =
 {
   var name:String;
   var prefix:String;
@@ -29,6 +23,10 @@ private typedef AnimationInfo =
   var flipY:Null<Bool>; // default false
 }
 
+/**
+ * A variant of SparrowPlayer which loads a BaseCharacter instead.
+ * This allows it to play appropriate animations based on song events.
+ */
 @:composite(Layout)
 class CharacterPlayer extends Box
 {
@@ -37,7 +35,7 @@ class CharacterPlayer extends Box
   public function new(?defaultToBf:Bool = true)
   {
     super();
-    this._overrideSkipTransformChildren = false;
+    _overrideSkipTransformChildren = false;
 
     if (defaultToBf)
     {
@@ -45,52 +43,39 @@ class CharacterPlayer extends Box
     }
   }
 
-  var _charId:String;
-
   public var charId(get, set):String;
 
   function get_charId():String
   {
-    return _charId;
+    return character.characterId;
   }
 
   function set_charId(value:String):String
   {
-    _charId = value;
-    loadCharacter(_charId);
+    loadCharacter(value);
     return value;
   }
 
-  var _redispatchLoaded:Bool = false; // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... needs thinking about, is it smart to "collect and redispatch"? Not sure
-  var _redispatchStart:Bool = false; // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... needs thinking about, is it smart to "collect and redispatch"? Not sure
+  public var charName(get, null):String;
 
-  public override function onReady()
+  function get_charName():String
   {
-    super.onReady();
-
-    invalidateComponentLayout();
-
-    if (_redispatchLoaded)
-    {
-      _redispatchLoaded = false;
-      dispatch(new AnimationEvent(AnimationEvent.LOADED));
-    }
-
-    if (_redispatchStart)
-    {
-      _redispatchStart = false;
-      dispatch(new AnimationEvent(AnimationEvent.START));
-    }
-
-    parentComponent._overrideSkipTransformChildren = false;
+    return character.characterName;
   }
 
-  public function loadCharacter(id:String)
+  // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... is it smart to "collect and redispatch"? Not sure
+  var _redispatchLoaded:Bool = false;
+  // possible haxeui bug: if listener is added after event is dispatched, event is "lost"... is it smart to "collect and redispatch"? Not sure
+  var _redispatchStart:Bool = false;
+  var _characterLoaded:Bool = false;
+
+  /**
+   * Loads a character by ID.
+   * @param id The ID of the character to load.
+   */
+  public function loadCharacter(id:String):Void
   {
-    if (id == null)
-    {
-      return;
-    }
+    if (id == null) return;
 
     if (character != null)
     {
@@ -99,34 +84,24 @@ class CharacterPlayer extends Box
       character = null;
     }
 
-    var newCharacter:BaseCharacter = CharacterDataParser.fetchCharacter(id);
-
-    if (newCharacter == null)
-    {
-      return;
-    }
+    // Prevent script issues by fetching with debug=true.
+    var newCharacter:BaseCharacter = CharacterDataParser.fetchCharacter(id, true);
+    if (newCharacter == null) return; // Fail if character doesn't exist.
 
+    // Assign character.
     character = newCharacter;
-    if (_characterType != null)
-    {
-      character.characterType = _characterType;
-    }
-    if (flip)
-    {
-      character.flipX = !character.flipX;
-    }
 
-    character.scale.x *= _scale;
-    character.scale.y *= _scale;
+    // Set character properties.
+    if (characterType != null) character.characterType = characterType;
+    if (flip) character.flipX = !character.flipX;
+    if (targetScale != 1.0) character.setScale(targetScale);
 
-    character.animation.callback = function(name:String = "", frameNumber:Int = -1, frameIndex:Int = -1)
-    {
+    character.animation.callback = function(name:String = '', frameNumber:Int = -1, frameIndex:Int = -1) {
       @:privateAccess
       character.onAnimationFrame(name, frameNumber, frameIndex);
       dispatch(new AnimationEvent(AnimationEvent.FRAME));
     };
-    character.animation.finishCallback = function(name:String = "")
-    {
+    character.animation.finishCallback = function(name:String = '') {
       @:privateAccess
       character.onAnimationFinished(name);
       dispatch(new AnimationEvent(AnimationEvent.END));
@@ -145,28 +120,15 @@ class CharacterPlayer extends Box
     }
   }
 
-  override function repositionChildren()
+  /**
+   * The character type (such as BF, Dad, GF, etc).
+   */
+  public var characterType(default, set):CharacterType;
+
+  function set_characterType(value:CharacterType):CharacterType
   {
-    super.repositionChildren();
-
-    @:privateAccess
-    var animOffsets = character.animOffsets;
-
-    character.x = this.screenX + ((this.width / 2) - (character.frameWidth / 2));
-    character.x -= animOffsets[0];
-    character.y = this.screenY + ((this.height / 2) - (character.frameHeight / 2));
-    character.y -= animOffsets[1];
-  }
-
-  var _characterType:CharacterType;
-
-  public function setCharacterType(value:CharacterType)
-  {
-    _characterType = value;
-    if (character != null)
-    {
-      character.characterType = value;
-    }
+    if (character != null) character.characterType = value;
+    return characterType = value;
   }
 
   public var flip(default, set):Bool;
@@ -183,89 +145,133 @@ class CharacterPlayer extends Box
     return flip = value;
   }
 
-  var _scale:Float = 1.0;
+  public var targetScale(default, set):Float = 1.0;
 
-  public function setScale(value)
+  function set_targetScale(value:Float):Float
   {
-    _scale = value;
+    if (value == targetScale) return value;
+
     if (character != null)
     {
-      character.scale.x *= _scale;
-      character.scale.y *= _scale;
+      character.setScale(value);
     }
+
+    return targetScale = value;
   }
 
-  public function onUpdate(event:UpdateScriptEvent)
+  function onFrame(name:String, frameNumber:Int, frameIndex:Int):Void
+  {
+    dispatch(new AnimationEvent(AnimationEvent.FRAME));
+  }
+
+  function onFinish(name:String):Void
+  {
+    dispatch(new AnimationEvent(AnimationEvent.END));
+  }
+
+  override function repositionChildren():Void
+  {
+    super.repositionChildren();
+    character.x = this.screenX;
+    character.y = this.screenY;
+
+    // Apply animation offsets, so the character is positioned correctly based on the animation.
+    @:privateAccess var animOffsets:Array<Float> = character.animOffsets;
+
+    character.x -= animOffsets[0] * targetScale * (flip ? -1 : 1);
+    character.y -= animOffsets[1] * targetScale;
+  }
+
+  /**
+   * Called when an update event is hit in the song.
+   * Used to play character animations.
+   * @param event The event.
+   */
+  public function onUpdate(event:UpdateScriptEvent):Void
   {
     if (character != null) character.onUpdate(event);
   }
 
+  /**
+   * Called when an beat is hit in the song
+   * Used to play character animations.
+   * @param event The event.
+   */
   public function onBeatHit(event:SongTimeScriptEvent):Void
   {
     if (character != null) character.onBeatHit(event);
-
-    this.repositionChildren();
   }
 
+  /**
+   * Called when a step is hit in the song
+   * Used to play character animations.
+   * @param event The event.
+   */
   public function onStepHit(event:SongTimeScriptEvent):Void
   {
     if (character != null) character.onStepHit(event);
   }
 
+  /**
+   * Called when a note is hit in the song
+   * Used to play character animations.
+   * @param event The event.
+   */
   public function onNoteHit(event:NoteScriptEvent):Void
   {
     if (character != null) character.onNoteHit(event);
-
-    this.repositionChildren();
   }
 
+  /**
+   * Called when a note is missed in the song
+   * Used to play character animations.
+   * @param event The event.
+   */
   public function onNoteMiss(event:NoteScriptEvent):Void
   {
     if (character != null) character.onNoteMiss(event);
-
-    this.repositionChildren();
   }
 
+  /**
+   * Called when a key is pressed but no note is hit in the song
+   * Used to play character animations.
+   * @param event The event.
+   */
   public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void
   {
     if (character != null) character.onNoteGhostMiss(event);
-
-    this.repositionChildren();
   }
 }
 
 @:access(funkin.ui.haxeui.components.CharacterPlayer)
 private class Layout extends DefaultLayout
 {
-  public override function repositionChildren()
+  public override function resizeChildren():Void
   {
-    var player = cast(_component, CharacterPlayer);
-    var sprite:BaseCharacter = player.character;
-    if (sprite == null)
+    super.resizeChildren();
+
+    var player:CharacterPlayer = cast(_component, CharacterPlayer);
+    var character:BaseCharacter = player.character;
+    if (character == null)
     {
-      return super.repositionChildren();
+      return super.resizeChildren();
     }
 
-    @:privateAccess
-    var animOffsets = sprite.animOffsets;
-
-    sprite.x = _component.screenLeft + ((_component.width / 2) - (sprite.frameWidth / 2));
-    sprite.x += animOffsets[0];
-    sprite.y = _component.screenTop + ((_component.height / 2) - (sprite.frameHeight / 2));
-    sprite.y += animOffsets[1];
+    character.cornerPosition.set(0, 0);
+    // character.setGraphicSize(Std.int(innerWidth), Std.int(innerHeight));
   }
 
   public override function calcAutoSize(exclusions:Array<Component> = null):Size
   {
-    var player = cast(_component, CharacterPlayer);
-    var sprite = player.character;
-    if (sprite == null)
+    var player:CharacterPlayer = cast(_component, CharacterPlayer);
+    var character:BaseCharacter = player.character;
+    if (character == null)
     {
       return super.calcAutoSize(exclusions);
     }
-    var size = new Size();
-    size.width = sprite.frameWidth + paddingLeft + paddingRight;
-    size.height = sprite.frameHeight + paddingTop + paddingBottom;
+    var size:Size = new Size();
+    size.width = character.width + paddingLeft + paddingRight;
+    size.height = character.height + paddingTop + paddingBottom;
     return size;
   }
 }