I used ‘openfl’ to draw a circle before, and then converted it into a texture to use ‘mask’, but it didn’t work. I still used a rectangular border for ‘mask’
I just tried it out and circles drawn using Canvas can indeed achieve ‘mask’. Thank you very much.
However, it seems that Canvas does not support setting border line styles?
Okay, because I found that some bitmap font making tools can only export fonts of one color and do not support setting different colors for individual fonts
Because I want to implement a text box where each font is set to a different color
If bitmap fonts cannot be implemented, I can only use text boxes to draw fonts
I am a person who pursues perfection, and I want to pursue some exquisite effects. Personally, I will prioritize achievable exquisite effects before considering performance issues.
I instantiated a text box, set the font size of the text box to 50, and then reduced the text box to 0.5. Is the rendering area size of the text box the original 50 or the “scale value”?
From the bitmap font making tool, you should export it as white. Then set the font colour with Starling.
With respect to per-character colour, remember you can always extend Starling’s capabilities yourself. I’ve created two wrapper classes below for starling.text.BitmapFont and starling.text.TextField, which allow you to set per-character colour for bitmap fonts.
With these, instead of using BitmapFont and TextField, you’d use ExcuisiteBitmapFont and ExcuisiteTextField
ExquisiteBitmapFont.hx
package;
import starling.display.MeshBatch;
import starling.text.BitmapFont;
import starling.text.ITextCompositor;
import starling.text.TextFormat;
import starling.text.TextOptions;
import starling.styles.MeshStyle;
/**
* Wrapper for BitmapFont that adds per-character color support.
* Set the `charColors` field before rendering.
*/
class ExquisiteBitmapFont implements ITextCompositor
{
private var base:BitmapFont;
public var charColors:Map<Int, UInt>; // Set this before calling fillMeshBatch
public function new(base:BitmapFont)
{
this.base = base;
this.charColors = null;
}
/**
* Fills the mesh batch with per-character color support.
* If charColors is provided, it will override the color for the character at that index.
*/
public function fillMeshBatch(
meshBatch:MeshBatch,
width:Float,
height:Float,
text:String,
format:TextFormat,
options:TextOptions = null
):Void
{
var charLocations = base.arrangeChars(width, height, text, format, options);
var charColors = this.charColors;
var defaultColor = format.color;
// Use our own Image for batching
var helperImage:starling.display.Image = null;
if (charLocations.length > 0) {
helperImage = new starling.display.Image(charLocations[0].char.texture);
} else {
return; // nothing to render
}
helperImage.color = defaultColor;
for (i in 0...charLocations.length)
{
var charLocation = charLocations[i];
var charIdx = charLocation.index; // original string index
helperImage.texture = charLocation.char.texture;
helperImage.readjustSize();
helperImage.x = charLocation.x;
helperImage.y = charLocation.y;
helperImage.scale = charLocation.scale;
// Per-char color if present
if (charColors != null && charColors.exists(charIdx))
helperImage.color = charColors[charIdx];
else
helperImage.color = defaultColor;
meshBatch.addMeshAt(helperImage);
}
starling.text.BitmapCharLocation.rechargePool();
}
/**
* Sets character color for a specified range.
*/
public function setCharColorRange(startIndex:Int, endIndex:Int, color:UInt):Void {
if (charColors == null) charColors = new Map();
for (i in startIndex...endIndex) {
charColors.set(i, color);
}
}
public function clearMeshBatch(meshBatch:MeshBatch):Void
base.clearMeshBatch(meshBatch);
public function getDefaultMeshStyle(style:MeshStyle, format:TextFormat, options:TextOptions):MeshStyle
return base.getDefaultMeshStyle(style, format, options);
public function dispose():Void
base.dispose();
}
ExquisiteTextField.hx
package;
import starling.text.TextField;
import starling.text.TextFieldAutoSize;
import starling.text.TextFormat;
import starling.text.TextOptions;
/**
* Wrapper for Starling's TextField, adding per-character color support with ExquisiteBitmapFont.
* Use setCharColor() to override the color of a specific character.
* Use with a font registered as ExquisiteBitmapFont for effect.
*/
class ExquisiteTextField extends TextField {
private var _charColors:Map<Int, UInt>;
public function new(width:Int, height:Int, text:String = "", format:TextFormat = null, options:TextOptions = null) {
super(width, height, text, format, options);
_charColors = new Map();
}
/**
* Sets the color for a specific character index.
*/
public function setCharColor(idx:Int, color:UInt):Void {
_charColors[idx] = color;
setRequiresRecomposition();
}
/**
* Clears all per-character color overrides.
*/
public function clearCharColors():Void {
_charColors = new Map();
setRequiresRecomposition();
}
/**
* Returns the color for a given character index, or null if not overridden.
*/
public function getCharColor(idx:Int):Null<UInt> {
return _charColors.exists(idx) ? _charColors[idx] : null;
}
/**
* Override updateText to pass charColors to the compositor if possible.
*/
override private function updateText():Void {
var width:Float = _hitArea.width;
var height:Float = _hitArea.height;
// Horizontal autoSize does not work for HTML text, since it supports custom alignment.
// What should we do if one line is aligned to the left, another to the right?
if (isHorizontalAutoSize && !_options.isHtmlText)
width = 100000;
if (isVerticalAutoSize)
height = 100000;
_meshBatch.x = _meshBatch.y = 0;
_options.textureScale = starling.core.Starling.current.contentScaleFactor;
// Use our ExquisiteBitmapFont API if compositor supports charColors
var exq:Dynamic = _compositor;
if (Reflect.hasField(exq, "fillMeshBatch")) {
try {
exq.fillMeshBatch(_meshBatch, width, height, _text, _format, _options, _charColors);
} catch (e:Dynamic) {
// fallback
_compositor.fillMeshBatch(_meshBatch, width, height, _text, _format, _options);
}
} else {
_compositor.fillMeshBatch(_meshBatch, width, height, _text, _format, _options);
}
if (_customStyle != null)
_meshBatch.style = _customStyle;
else {
_defaultStyle = _compositor.getDefaultMeshStyle(_defaultStyle, _format, _options);
if (_defaultStyle != null)
_meshBatch.style = _defaultStyle;
}
if (_options.autoSize != TextFieldAutoSize.NONE) {
_textBounds = _meshBatch.getBounds(_meshBatch, _textBounds);
if (isHorizontalAutoSize) {
_meshBatch.x = _textBounds.x = -_textBounds.x;
_hitArea.width = _textBounds.width;
_textBounds.x = 0;
}
if (isVerticalAutoSize) {
_meshBatch.y = _textBounds.y = -_textBounds.y;
_hitArea.height = _textBounds.height;
_textBounds.y = 0;
}
} else {
// hit area doesn't change, and text bounds can be created on demand
_textBounds = null;
}
}
}
Then to use these, for example:
Usage example:
import ExcuisiteBitmapFont;
import ExcuisiteTextField;
import starling.text.BitmapFont;
import starling.text.TextField;
import starling.text.TextFieldAutoSize;
import starling.textures.Texture;
//...
// Step 1: Load the font assets
var fontTexture:Texture = Texture.fromBitmapData(Assets.getBitmapData("img/some-font.png"));
var fontXml:Xml = Xml.parse(Assets.getText("xml/some-font.xml"));
// Step 2: Create and register the custom ExquisiteBitmapFont
var bitmapFont = new ExquisiteBitmapFont(new BitmapFont(fontTexture, fontXml));
TextField.registerCompositor(bitmapFont, "SomeFont");
bitmapFont.charColors = new Map<Int, UInt>();
bitmapFont.charColors.set(0, 0xFF0000); // Red
bitmapFont.charColors.set(1, 0xFF0000); // Green
bitmapFont.charColors.set(2, 0x0000FF); // Blue
bitmapFont.charColors.set(3, 0xFF0000); // Red
bitmapFont.charColors.set(4, 0xFF0000); // Green
bitmapFont.charColors.set(5, 0x0000FF); // Blue
bitmapFont.charColors.set(6, 0xFF0000); // Red
bitmapFont.charColors.set(7, 0xFF0000); // Green
bitmapFont.charColors.set(8, 0x0000FF); // Blue
bitmapFont.charColors.set(9, 0xFF0000); // Red
bitmapFont.charColors.set(10, 0xFF0000); // Green
bitmapFont.setCharColorRange(11, 15, 0xFF00FF); // Pink
// Step 3: Use the ExquisiteBitmapFont in a custom ExquisiteTextField
var textField = new ExquisiteTextField(400, 100, "Hello, Starling!", new TextFormat("SomeFont", 70));
textField.autoSize = TextFieldAutoSize.BOTH_DIRECTIONS;
addChild(textField);
I provide these as examples only. They have not been heavily scrutinised, but it works in my testing.
If you’re adjusted font size and text content, the text field’s size does not change unless you have set textField.autoSize. To have the textField size adjust with font-size and content changes, use:
As I work with graphic designers, 99.9% of UI’s I assemble are purely Image and TextField based. The sorts of UI’s I’m building though are perhaps not very menu heavy. I’ve dabbled with FeathersUI, and would recommend having a look at that. I’m not sure how it may or may not integrate with Starling, but @joshtynjala would be able to clarify that I imagine.
HaxeUI is the other popular option, but I’ve not tried it at all.
I’d suggest it’s not so much a matter of what everyone else is doing, but understanding what ATF offers, and discerning if that’s useful and practical for you. Simply put:
ATF opens up access to GPU compression formats, is decoded in GPU, which optimises GPU memory usage.
PNG by comparison has no lossy compression, is decoded by CPU, and consumes the most GPU memory.
Will the color of each text you set above affect the clarity of the text and the filters around it?
Will the text color classes you package incur performance overhead?
Will it affect the filter strokes around the text? For example, would drawing black affect the black color of the drawing, causing it to turn into other colors?
As I mentioned before, regarding the animation switching method, you suggested using multiple ‘mc’ to implement it. Now I have a new idea
I noticed that ‘assets. getTextures’ returns an array containing the textures obtained from the string prefix
I can use this method to retrieve multiple times, knowing that I have obtained all of my animations and several arrays of textures
Then I merge these array textures into one array texture and give it to ‘mc’, so that I should be able to get a timeline containing all my animations. Then I plan to switch animations through ‘CurrentFrame’
Okay, it seems that you prefer to use multiple “mcs” to switch animations. This method requires managing the states of other “mcs” every time you switch animations. Isn’t this a bit troublesome?
From what you’re saying, it’s true that a timeline animation requires the use of a “timer” to play a certain segment of the animation, and you need to implement the logic yourself.
Using multiple “mcs” requires managing the state of other “mcs” each time the animation is switched