mirror of
https://github.com/Heretek-AI/GDevelop-Heretek.git
synced 2026-10-12 02:02:42 +00:00
Add Bitmap Text objects, an efficient way to display texts using custom "bitmap fonts" (#2432)
* This allows to display texts on screen that use a "bitmap font", generated with softwares like [BMFont](https://www.angelcode.com/products/bmfont/) or [bmGlyph](http://www.bmglyph.com/). * Bitmap fonts allow advanced effects and custom design of each character, with complete control over the appearance of the text. This is useful for making a custom score counter, titles, button labels... * They also render very well in a pixel-perfect, pixel-art or retro-like game. * Finally, these Bitmap Texts are fast and efficient to render on screen: useful for scores or texts that are updated frequently.
This commit is contained in:
@@ -55,6 +55,21 @@ void ArbitraryResourceWorker::ExposeFont(gd::String& fontName) {
|
||||
ExposeFile(fontName);
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeBitmapFont(gd::String& bitmapFontName) {
|
||||
for (auto resources : GetResources()) {
|
||||
if (!resources) continue;
|
||||
|
||||
if (resources->HasResource(bitmapFontName) &&
|
||||
resources->GetResource(bitmapFontName).GetKind() == "bitmapFont") {
|
||||
// Nothing to do, the font is a reference to a resource that
|
||||
// is already exposed.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ExposeFile(bitmapFontName);
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeResources(
|
||||
gd::ResourcesManager* resourcesManager) {
|
||||
if (!resourcesManager) return;
|
||||
|
||||
@@ -70,6 +70,12 @@ class GD_CORE_API ArbitraryResourceWorker {
|
||||
*/
|
||||
virtual void ExposeFont(gd::String &fontName);
|
||||
|
||||
/**
|
||||
* \brief Expose a bitmap font, which is either a reference to a "bitmapFont" resource,
|
||||
* or a filename if no resource with this name exists.
|
||||
*/
|
||||
virtual void ExposeBitmapFont(gd::String &bitmapFontName);
|
||||
|
||||
/**
|
||||
* \brief Expose a shader.
|
||||
* \warn Currently unsupported.
|
||||
|
||||
@@ -38,12 +38,15 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
|
||||
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
|
||||
std::set<gd::String>& GetAll(const gd::String& resourceType) {
|
||||
return resourceType == "image"
|
||||
? allImages
|
||||
: (resourceType == "audio"
|
||||
? allAudios
|
||||
: (resourceType == "font") ? allFonts : emptyResources);
|
||||
: (resourceType == "font")
|
||||
? allFonts
|
||||
: (resourceType == "bitmapFont") ? allBitmapFonts : emptyResources);
|
||||
};
|
||||
|
||||
virtual void ExposeFile(gd::String& resource) override{
|
||||
@@ -58,11 +61,15 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
allFonts.insert(fontResourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
|
||||
allBitmapFonts.insert(bitmapFontResourceName);
|
||||
};
|
||||
|
||||
protected:
|
||||
std::set<gd::String> allImages;
|
||||
std::set<gd::String> allAudios;
|
||||
std::set<gd::String> allFonts;
|
||||
std::set<gd::String> allBitmapFonts;
|
||||
std::set<gd::String> emptyResources;
|
||||
};
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ std::shared_ptr<Resource> ResourcesManager::CreateResource(
|
||||
return std::make_shared<VideoResource>();
|
||||
else if (kind == "json")
|
||||
return std::make_shared<JsonResource>();
|
||||
else if (kind == "bitmapFont")
|
||||
return std::make_shared<BitmapFontResource>();
|
||||
|
||||
std::cout << "Bad resource created (type: " << kind << ")" << std::endl;
|
||||
return std::make_shared<Resource>();
|
||||
@@ -654,6 +656,26 @@ bool JsonResource::UpdateProperty(const gd::String& name,
|
||||
|
||||
#endif
|
||||
|
||||
void BitmapFontResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
|
||||
// Convert all backslash to slashs.
|
||||
while (file.find('\\') != gd::String::npos)
|
||||
file.replace(file.find('\\'), 1, "/");
|
||||
}
|
||||
|
||||
void BitmapFontResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetUserAdded(element.GetBoolAttribute("userAdded"));
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void BitmapFontResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
|
||||
|
||||
|
||||
@@ -374,6 +374,34 @@ class GD_CORE_API JsonResource : public Resource {
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a bitmap font file used by a project.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
*/
|
||||
class GD_CORE_API BitmapFontResource : public Resource {
|
||||
public:
|
||||
BitmapFontResource() : Resource() { SetKind("bitmapFont"); };
|
||||
virtual ~BitmapFontResource(){};
|
||||
virtual BitmapFontResource* Clone() const override {
|
||||
return new BitmapFontResource(*this);
|
||||
}
|
||||
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
private:
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Inventory all resources used by a project
|
||||
*
|
||||
|
||||
@@ -348,7 +348,7 @@ module.exports = {
|
||||
type: 'stringWithSelector',
|
||||
paramLabel: _('Alignment'),
|
||||
options: ['left', 'right', 'center'],
|
||||
conditionDescription: _('Check the current text alignment'),
|
||||
conditionDescription: _('Check the current text alignment.'),
|
||||
conditionSentence: _('The text alignment of _PARAM0_ is _PARAM1_'),
|
||||
actionDescription: _('Change the alignment of the text.'),
|
||||
actionSentence: _('Set text alignment of _PARAM0_ to _PARAM1_'),
|
||||
@@ -360,7 +360,7 @@ module.exports = {
|
||||
iconPath: 'res/actions/scaleWidth24.png',
|
||||
type: 'boolean',
|
||||
paramLabel: _('Word wrap'),
|
||||
conditionDescription: _('Check if word wrap is enabled'),
|
||||
conditionDescription: _('Check if word wrap is enabled.'),
|
||||
conditionSentence: _('Word wrap is enabled'),
|
||||
actionDescription: _('Set word wrap'),
|
||||
actionSentence: _('Activate word wrap for _PARAM0_: _PARAM1_'),
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace gdjs {
|
||||
// @ts-ignore - parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string.
|
||||
this._opacity = parseFloat(objectData.content.opacity);
|
||||
this._text = objectData.content.text;
|
||||
this._color = BBTextRuntimeObject.hexToRGBColor(objectData.content.color);
|
||||
this._color = gdjs.hexToRGBColor(objectData.content.color);
|
||||
this._fontFamily = objectData.content.fontFamily;
|
||||
// @ts-ignore - parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string.
|
||||
this._fontSize = parseFloat(objectData.content.fontSize);
|
||||
@@ -69,11 +69,6 @@ namespace gdjs {
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
static hexToRGBColor(hex) {
|
||||
const hexNumber = parseInt(hex.replace('#', ''), 16);
|
||||
return [(hexNumber >> 16) & 255, (hexNumber >> 8) & 255, hexNumber & 255];
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
@@ -93,9 +88,7 @@ namespace gdjs {
|
||||
this.setBBText(newObjectData.content.text);
|
||||
}
|
||||
if (oldObjectData.content.color !== newObjectData.content.color) {
|
||||
this._color = BBTextRuntimeObject.hexToRGBColor(
|
||||
newObjectData.content.color
|
||||
);
|
||||
this._color = gdjs.hexToRGBColor(newObjectData.content.color);
|
||||
this._renderer.updateColor();
|
||||
}
|
||||
if (
|
||||
|
||||
@@ -0,0 +1,723 @@
|
||||
// @flow
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
* ℹ️ Changes in this file are watched and automatically imported if the editor
|
||||
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
|
||||
*
|
||||
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
|
||||
* ⚠️ If you make a change and the extension is not loaded, open the developer console
|
||||
* and search for any errors.
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/*::
|
||||
// Import types to allow Flow to do static type checking on this file.
|
||||
// Extensions declaration are typed using Flow (like the editor), but the files
|
||||
// for the game engine are checked with TypeScript annotations.
|
||||
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'BitmapText',
|
||||
_('Bitmap Text Object'),
|
||||
_('Displays a text as a bitmap image.'),
|
||||
'Aurélien Vivet',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/objects/bitmaptext');
|
||||
|
||||
const bitmapTextObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
if (propertyName in objectContent) {
|
||||
if (typeof objectContent[propertyName] === 'boolean')
|
||||
objectContent[propertyName] = newValue === '1';
|
||||
else if (typeof objectContent[propertyName] === 'number')
|
||||
objectContent[propertyName] = parseFloat(newValue);
|
||||
else objectContent[propertyName] = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.getProperties = function (objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('text')
|
||||
.setValue(objectContent.text)
|
||||
.setType('textarea')
|
||||
.setLabel(_('Text'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('opacity')
|
||||
.setValue(objectContent.opacity.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Opacity (0-255)'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('align')
|
||||
.setValue(objectContent.align)
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.setLabel(_('Alignment, when multiple lines are displayed'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('bitmapFontResourceName')
|
||||
.setValue(objectContent.bitmapFontResourceName)
|
||||
.setType('resource')
|
||||
.addExtraInfo('bitmapFont') //fnt or xml files
|
||||
.setLabel(_('Bitmap Font'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('textureAtlasResourceName')
|
||||
.setValue(objectContent.textureAtlasResourceName)
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Bitmap atlas image'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('scale')
|
||||
.setValue(objectContent.scale.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Text scale'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('tint')
|
||||
.setValue(objectContent.tint)
|
||||
.setType('color')
|
||||
.setLabel(_('Font tint'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('wordWrap')
|
||||
.setValue(objectContent.wordWrap ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Word wrapping'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
bitmapTextObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text:
|
||||
'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
opacity: 255,
|
||||
scale: 1,
|
||||
fontSize: 20,
|
||||
tint: '#ffffff',
|
||||
bitmapFontResourceName: '',
|
||||
textureAtlasResourceName: '',
|
||||
align: 'left',
|
||||
wordWrap: true,
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue,
|
||||
project,
|
||||
layout
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
project,
|
||||
layout
|
||||
) {
|
||||
var instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
return instanceProperties;
|
||||
};
|
||||
|
||||
const object = extension
|
||||
.addObject(
|
||||
'BitmapTextObject',
|
||||
_('BitmapText'),
|
||||
_(
|
||||
'Displays a text using a "Bitmap Font", which is more performant than a traditional text but also less flexible (only alphanumerical characters are supported).'
|
||||
),
|
||||
'JsPlatform/Extensions/bitmapfont32.png',
|
||||
bitmapTextObject
|
||||
)
|
||||
.setIncludeFile('Extensions/BitmapText/bitmaptextruntimeobject.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.js'
|
||||
);
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'string',
|
||||
'Text',
|
||||
_('Text'),
|
||||
_('the text'),
|
||||
_('the text'),
|
||||
'',
|
||||
'res/conditions/text24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('string')
|
||||
.setFunctionName('setText')
|
||||
.setGetter('getText');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'Opacity',
|
||||
_('Opacity'),
|
||||
_('the opacity, between 0 (fully transparent) and 255 (opaque)'),
|
||||
_('the opacity'),
|
||||
'',
|
||||
'res/conditions/opacity24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setOpacity')
|
||||
.setGetter('getOpacity');
|
||||
|
||||
object
|
||||
.addExpressionAndCondition(
|
||||
'number',
|
||||
'FontSize',
|
||||
_('Font size'),
|
||||
_('the font size, defined in the Bitmap Font'),
|
||||
_('the font size'),
|
||||
'',
|
||||
'res/conditions/characterSize24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('getFontSize');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'Scale',
|
||||
_('Scale'),
|
||||
_('the scale (1 by default)'),
|
||||
_('the scale'),
|
||||
'',
|
||||
'res/actions/scale24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setScale')
|
||||
.setGetter('getScale');
|
||||
|
||||
object
|
||||
.addExpressionAndCondition(
|
||||
'string',
|
||||
'FontName',
|
||||
_('Font name'),
|
||||
_('the font name (defined in the Bitmap font)'),
|
||||
_('the font name'),
|
||||
'',
|
||||
'res/conditions/font24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('string')
|
||||
.setFunctionName('getFontName');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
'SetTint',
|
||||
_('Tint'),
|
||||
_('Set the tint of the Bitmap Text object.'),
|
||||
_('Set tint of _PARAM0_ to _PARAM1_'),
|
||||
'',
|
||||
'res/actions/color24.png',
|
||||
'res/actions/color.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter('color', _('Color'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setTint');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
'SetBitmapFontAndTextureAtlasResourceName',
|
||||
_('Bitmap files resources'),
|
||||
_('Change the Bitmap Font and/or the atlas image used by the object.') +
|
||||
' ' +
|
||||
_(
|
||||
'The resource name can be found in: `Project Manager > Game settings > Resources`.'
|
||||
),
|
||||
_(
|
||||
'Set the bitmap font of _PARAM0_ to _PARAM1_ and the atlas to _PARAM2_'
|
||||
),
|
||||
'',
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter('bitmapFont', _('Bitmap font resource name'), '', false)
|
||||
.setParameterLongDescription(
|
||||
'The resource name of the font file, without quotes.'
|
||||
)
|
||||
.addParameter('string', _('Texture atlas resource name'), '', false)
|
||||
.setParameterLongDescription(
|
||||
'The resource name of the image exported with the font, with quotes.'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
|
||||
|
||||
object
|
||||
.addExpressionAndCondition(
|
||||
'string',
|
||||
'Alignment',
|
||||
_('Alignment'),
|
||||
_('the text alignment'),
|
||||
_('the text alignment'),
|
||||
'',
|
||||
'res/actions/textAlign24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('string')
|
||||
.setFunctionName('getAlignment');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
'SetAlignment',
|
||||
_('Alignment'),
|
||||
_('Change the alignment of a Bitmap text object.'),
|
||||
_('Set the alignment of _PARAM0_ to _PARAM1_'),
|
||||
'',
|
||||
'res/actions/textAlign24.png',
|
||||
'res/actions/textAlign.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter(
|
||||
'stringWithSelector',
|
||||
_('Alignment'),
|
||||
'["left", "center", "right"]',
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setAlignment');
|
||||
|
||||
object
|
||||
.addCondition(
|
||||
'WordWrap',
|
||||
_('Word wrap'),
|
||||
_('Check if word wrap is enabled.'),
|
||||
_('_PARAM0_ word wrap is enabled'),
|
||||
'',
|
||||
'res/conditions/wordWrap24.png',
|
||||
'res/conditions/wordWrap.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getWordWrap');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
'SetWordWrap',
|
||||
_('Word wrap'),
|
||||
_('De/activate word wrapping.'),
|
||||
_('Activate word wrap of _PARAM0_: _PARAM1_'),
|
||||
'',
|
||||
'res/actions/wordWrap24.png',
|
||||
'res/actions/wordWrap.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter('yesorno', _('Activate word wrap'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setWordWrap');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'WrappingWidth',
|
||||
_('Wrapping width'),
|
||||
_('the width, in pixels, after which the text is wrapped on next line'),
|
||||
_('the wrapping width'),
|
||||
'',
|
||||
'res/actions/scaleWidth24.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setWrappingWidth')
|
||||
.setGetter('getWrappingWidth');
|
||||
|
||||
return extension;
|
||||
},
|
||||
|
||||
/**
|
||||
* You can optionally add sanity tests that will check the basic working
|
||||
* of your extension behaviors/objects by instanciating behaviors/objects
|
||||
* and setting the property to a given value.
|
||||
*
|
||||
* If you don't have any tests, you can simply return an empty array.
|
||||
*
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* Register editors for objects.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'BitmapText::BitmapTextObject',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
helpPagePath: '/objects/bitmaptext',
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Register renderers for instance of objects on the scene editor.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
|
||||
/** The bitmap font used in case another font can't be loaded. */
|
||||
let defaultBitmapFont = null;
|
||||
|
||||
const defaultBitmapFontInstallKey = 'GD-DEFAULT-BITMAP-FONT';
|
||||
|
||||
/**
|
||||
* Map counting the number of "reference" to a bitmap font. This is useful
|
||||
* to uninstall a bitmap font when not used anymore.
|
||||
*/
|
||||
const bitmapFontUsageCount = {};
|
||||
|
||||
/**
|
||||
* We patch the installed font to use a name that is unique for each font data and texture,
|
||||
* to avoid conflicts between different font files using the same font name (by default, the
|
||||
* font name used by Pixi is the one inside the font data, but this name is not necessarily unique.
|
||||
* For example, 2 resources can use the same font, or we can have multiple objects with the same
|
||||
* font data and different textures).
|
||||
*/
|
||||
const patchBitmapFont = (bitmapFont, bitmapFontInstallKey) => {
|
||||
const defaultName = bitmapFont.font;
|
||||
bitmapFont.font = bitmapFontInstallKey;
|
||||
PIXI.BitmapFont.available[bitmapFontInstallKey] = bitmapFont;
|
||||
|
||||
delete PIXI.BitmapFont.available[defaultName];
|
||||
return PIXI.BitmapFont.available[bitmapFontInstallKey];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a default bitmap font to be used in case another font can't be loaded.
|
||||
*/
|
||||
const getDefaultBitmapFont = () => {
|
||||
if (defaultBitmapFont) return defaultBitmapFont;
|
||||
|
||||
const defaultBitmapFontStyle = new PIXI.TextStyle({
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 20,
|
||||
padding: 5,
|
||||
align: 'left',
|
||||
fill: '#ffffff',
|
||||
wordWrap: true,
|
||||
lineHeight: 20,
|
||||
});
|
||||
|
||||
defaultBitmapFont = patchBitmapFont(
|
||||
PIXI.BitmapFont.from(
|
||||
defaultBitmapFontStyle.fontFamily,
|
||||
defaultBitmapFontStyle,
|
||||
{
|
||||
chars: [
|
||||
[' ', '~'], // All the printable ASCII characters
|
||||
],
|
||||
}
|
||||
),
|
||||
defaultBitmapFontInstallKey
|
||||
);
|
||||
return defaultBitmapFont;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a bitmap font resource name and a texture atlas resource name, returns the PIXI.BitmapFont
|
||||
* for it.
|
||||
* The font must be released with `releaseBitmapFont` when not used anymore - so that it can be removed
|
||||
* from memory when not used by any instance.
|
||||
*
|
||||
* @param pixiResourcesLoader
|
||||
* @param project
|
||||
* @param bitmapFontResourceName
|
||||
* @param textureAtlasResourceName
|
||||
*/
|
||||
const obtainBitmapFont = (
|
||||
pixiResourcesLoader,
|
||||
project,
|
||||
bitmapFontResourceName,
|
||||
textureAtlasResourceName
|
||||
) => {
|
||||
const bitmapFontInstallKey =
|
||||
bitmapFontResourceName + '@' + textureAtlasResourceName;
|
||||
|
||||
if (PIXI.BitmapFont.available[bitmapFontInstallKey]) {
|
||||
// Return the existing BitmapFont that is already in memory and already installed.
|
||||
bitmapFontUsageCount[bitmapFontInstallKey] =
|
||||
(bitmapFontUsageCount[bitmapFontInstallKey] || 0) + 1;
|
||||
return Promise.resolve(PIXI.BitmapFont.available[bitmapFontInstallKey]);
|
||||
}
|
||||
|
||||
// Get the atlas texture, the bitmap font data and install the font:
|
||||
const texture = pixiResourcesLoader.getPIXITexture(
|
||||
project,
|
||||
textureAtlasResourceName
|
||||
);
|
||||
|
||||
const loadBitmapFont = () =>
|
||||
pixiResourcesLoader
|
||||
.getBitmapFontData(project, bitmapFontResourceName)
|
||||
.then((fontData) => {
|
||||
if (!texture.valid)
|
||||
throw new Error(
|
||||
'Tried to install a BitmapFont with an invalid texture.'
|
||||
);
|
||||
|
||||
const bitmapFont = patchBitmapFont(
|
||||
PIXI.BitmapFont.install(fontData, texture),
|
||||
bitmapFontInstallKey
|
||||
);
|
||||
bitmapFontUsageCount[bitmapFontInstallKey] =
|
||||
(bitmapFontUsageCount[bitmapFontInstallKey] || 0) + 1;
|
||||
|
||||
return bitmapFont;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('Unable to load font data:', err);
|
||||
console.info(
|
||||
'Is the texture atlas properly set for the Bitmap Text object? The default font will be used instead.'
|
||||
);
|
||||
|
||||
const bitmapFont = getDefaultBitmapFont();
|
||||
return bitmapFont;
|
||||
});
|
||||
|
||||
if (!texture.valid) {
|
||||
// Post pone texture update if texture is not loaded.
|
||||
// (otherwise, the bitmap font would not get updated when the
|
||||
// texture is loaded and udpated).
|
||||
return new Promise((resolve) => {
|
||||
texture.once('update', () => {
|
||||
resolve(loadBitmapFont());
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// We're ready to load the bitmap font now, as the texture
|
||||
// is already loaded.
|
||||
return loadBitmapFont();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When a font is not used by an object anymore (object destroyed or font changed),
|
||||
* call this function to decrease the internal count of objects using the font.
|
||||
*
|
||||
* Fonts are unloaded when not used anymore.
|
||||
*/
|
||||
const releaseBitmapFont = (bitmapFontInstallKey) => {
|
||||
if (bitmapFontInstallKey === defaultBitmapFontInstallKey) {
|
||||
// Never uninstall the default bitmap font.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bitmapFontUsageCount[bitmapFontInstallKey]) {
|
||||
console.error(
|
||||
'BitmapFont with name ' +
|
||||
bitmapFontInstallKey +
|
||||
' was tried to be released but was never marked as used.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
bitmapFontUsageCount[bitmapFontInstallKey]--;
|
||||
|
||||
if (bitmapFontUsageCount[bitmapFontInstallKey] === 0) {
|
||||
PIXI.BitmapFont.uninstall(bitmapFontInstallKey);
|
||||
console.info(
|
||||
'Uninstalled BitmapFont "' +
|
||||
bitmapFontInstallKey +
|
||||
'" from memory.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renderer for instances of BitmapText inside the IDE.
|
||||
*
|
||||
* @extends RenderedBitmapTextInstance
|
||||
* @class RenderedBitmapTextInstance
|
||||
* @constructor
|
||||
*/
|
||||
function RenderedBitmapTextInstance(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObject,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
) {
|
||||
RenderedInstance.call(
|
||||
this,
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObject,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
// We'll track changes of the font to trigger the loading of the new font.
|
||||
this._currentBitmapFontResourceName = '';
|
||||
this._currentTextureAtlasResourceName = '';
|
||||
|
||||
this._pixiObject = new PIXI.BitmapText('', {
|
||||
// Use a default font. The proper font will be loaded in `update` method.
|
||||
fontName: getDefaultBitmapFont().font,
|
||||
});
|
||||
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
RenderedBitmapTextInstance.prototype = Object.create(
|
||||
RenderedInstance.prototype
|
||||
);
|
||||
|
||||
/**
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
*/
|
||||
RenderedBitmapTextInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
object
|
||||
) {
|
||||
return 'JsPlatform/Extensions/bitmapfont24.png';
|
||||
};
|
||||
|
||||
// This is called to update the PIXI object on the scene editor
|
||||
RenderedBitmapTextInstance.prototype.update = function () {
|
||||
const properties = this._associatedObject.getProperties();
|
||||
|
||||
// Update the rendered text properties (note: Pixi is only
|
||||
// applying changes if there were changed).
|
||||
const rawText = properties.get('text').getValue();
|
||||
this._pixiObject.text = rawText;
|
||||
|
||||
const opacity = properties.get('opacity').getValue();
|
||||
this._pixiObject.alpha = opacity / 255;
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
this._pixiObject.align = align;
|
||||
|
||||
const color = properties.get('tint').getValue();
|
||||
this._pixiObject.tint = parseInt(color.replace('#', '0x'), 16);
|
||||
|
||||
const scale = properties.get('scale').getValue() || 1;
|
||||
this._pixiObject.scale.set(scale);
|
||||
|
||||
// Track the changes in font to load the new requested font.
|
||||
const bitmapFontResourceName = properties
|
||||
.get('bitmapFontResourceName')
|
||||
.getValue();
|
||||
const textureAtlasResourceName = properties
|
||||
.get('textureAtlasResourceName')
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
this._currentBitmapFontResourceName !== bitmapFontResourceName ||
|
||||
this._currentTextureAtlasResourceName !== textureAtlasResourceName
|
||||
) {
|
||||
// Release the old font (if it was installed).
|
||||
releaseBitmapFont(this._pixiObject.fontName);
|
||||
|
||||
this._currentBitmapFontResourceName = bitmapFontResourceName;
|
||||
this._currentTextureAtlasResourceName = textureAtlasResourceName;
|
||||
obtainBitmapFont(
|
||||
this._pixiResourcesLoader,
|
||||
this._project,
|
||||
this._currentBitmapFontResourceName,
|
||||
this._currentTextureAtlasResourceName
|
||||
).then((bitmapFont) => {
|
||||
this._pixiObject.fontName = bitmapFont.font;
|
||||
this._pixiObject.fontSize = bitmapFont.size;
|
||||
this._pixiObject.dirty = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Set up the wrapping width if enabled.
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
if (wordWrap && this._instance.hasCustomSize()) {
|
||||
this._pixiObject.maxWidth =
|
||||
this._instance.getCustomWidth() / this._pixiObject.scale.x;
|
||||
this._pixiObject.dirty = true;
|
||||
} else {
|
||||
this._pixiObject.maxWidth = 0;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
this._pixiObject.position.x =
|
||||
this._instance.getX() + this._pixiObject.width / 2;
|
||||
this._pixiObject.position.y =
|
||||
this._instance.getY() + this._pixiObject.height / 2;
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
this._instance.getAngle()
|
||||
);
|
||||
};
|
||||
|
||||
RenderedBitmapTextInstance.prototype.onRemovedFromScene = function () {
|
||||
RenderedInstance.prototype.onRemovedFromScene.call(this);
|
||||
|
||||
releaseBitmapFont(this._pixiObject.fontName);
|
||||
this._pixiObject.destroy();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the width of the instance, when it's not resized.
|
||||
*/
|
||||
RenderedBitmapTextInstance.prototype.getDefaultWidth = function () {
|
||||
return this._pixiObject.width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the height of the instance, when it's not resized.
|
||||
*/
|
||||
RenderedBitmapTextInstance.prototype.getDefaultHeight = function () {
|
||||
return this._pixiObject.height;
|
||||
};
|
||||
|
||||
objectsRenderingService.registerInstanceRenderer(
|
||||
'BitmapText::BitmapTextObject',
|
||||
RenderedBitmapTextInstance
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace gdjs {
|
||||
/**
|
||||
* The Cocos2D-JS renderer for the BitmapTextRuntimeObject.
|
||||
*
|
||||
* The implementation is empty as the object is not supported in Cocos2D-JS for now.
|
||||
*/
|
||||
export class BitmapTextRuntimeObjectCocosRenderer {
|
||||
getRendererObject() {}
|
||||
onDestroy() {}
|
||||
getFontSize() {}
|
||||
updateFont() {}
|
||||
updateTint() {}
|
||||
getTint() {}
|
||||
updateScale() {}
|
||||
getScale() {}
|
||||
updateWrappingWidth() {}
|
||||
updateTextContent() {}
|
||||
updateAlignment() {}
|
||||
updatePosition() {}
|
||||
updateAngle() {}
|
||||
updateOpacity() {}
|
||||
|
||||
getWidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export const BitmapTextRuntimeObjectRenderer = BitmapTextRuntimeObjectCocosRenderer;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
namespace gdjs {
|
||||
import PIXI = GlobalPIXIModule.PIXI;
|
||||
|
||||
/**
|
||||
* The PIXI.js renderer for the Bitmap Text runtime object.
|
||||
*/
|
||||
export class BitmapTextRuntimeObjectPixiRenderer {
|
||||
_object: gdjs.BitmapTextRuntimeObject;
|
||||
_pixiObject: PIXI.BitmapText;
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
* @param runtimeScene The gdjs.RuntimeScene in which the object is
|
||||
*/
|
||||
constructor(
|
||||
runtimeObject: gdjs.BitmapTextRuntimeObject,
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
|
||||
// Obtain the bitmap font to use in the object.
|
||||
const bitmapFont = runtimeScene
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.obtainBitmapFont(
|
||||
runtimeObject._bitmapFontResourceName,
|
||||
runtimeObject._textureAtlasResourceName
|
||||
);
|
||||
this._pixiObject = new PIXI.BitmapText(runtimeObject._text, {
|
||||
fontName: bitmapFont.font,
|
||||
fontSize: bitmapFont.size,
|
||||
});
|
||||
|
||||
// Set the object on the scene
|
||||
runtimeScene
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());
|
||||
|
||||
// Set the anchor in the center, so that the object rotates around
|
||||
// its center.
|
||||
// @ts-ignore
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
// @ts-ignore
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
|
||||
this.updateAlignment();
|
||||
this.updateTextContent();
|
||||
this.updateAngle();
|
||||
this.updateOpacity();
|
||||
this.updateScale();
|
||||
this.updateWrappingWidth();
|
||||
this.updateTint();
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return this._pixiObject;
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
// Mark the font from the object as not used anymore.
|
||||
this._object._runtimeScene
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.releaseBitmapFont(this._pixiObject.fontName);
|
||||
|
||||
this._pixiObject.destroy();
|
||||
}
|
||||
|
||||
getFontSize() {
|
||||
return this._pixiObject.fontSize;
|
||||
}
|
||||
|
||||
updateFont(): void {
|
||||
// Get the new bitmap font to use
|
||||
const bitmapFont = this._object._runtimeScene
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.obtainBitmapFont(
|
||||
this._object._bitmapFontResourceName,
|
||||
this._object._textureAtlasResourceName
|
||||
);
|
||||
|
||||
// Mark the old font as not used anymore
|
||||
this._object._runtimeScene
|
||||
.getGame()
|
||||
.getBitmapFontManager()
|
||||
.releaseBitmapFont(this._pixiObject.fontName);
|
||||
|
||||
// Update the font used by the object:
|
||||
this._pixiObject.fontName = bitmapFont.font;
|
||||
this._pixiObject.fontSize = bitmapFont.size;
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
updateTint(): void {
|
||||
this._pixiObject.tint = gdjs.rgbToHexNumber(
|
||||
this._object._tint[0],
|
||||
this._object._tint[1],
|
||||
this._object._tint[2]
|
||||
);
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tint of the bitmap object as a "R;G;B" string.
|
||||
* @returns the tint of bitmap object in "R;G;B" format.
|
||||
*/
|
||||
getTint(): string {
|
||||
return (
|
||||
this._object._tint[0] +
|
||||
';' +
|
||||
this._object._tint[1] +
|
||||
';' +
|
||||
this._object._tint[2]
|
||||
);
|
||||
}
|
||||
|
||||
updateScale(): void {
|
||||
this._pixiObject.scale.set(Math.max(this._object._scale, 0));
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
getScale() {
|
||||
return Math.max(this._pixiObject.scale.x, this._pixiObject.scale.y);
|
||||
}
|
||||
|
||||
updateWrappingWidth(): void {
|
||||
if (this._object._wordWrap) {
|
||||
this._pixiObject.maxWidth =
|
||||
this._object._wrappingWidth / this._object._scale;
|
||||
this._pixiObject.dirty = true;
|
||||
} else {
|
||||
this._pixiObject.maxWidth = 0;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
updateTextContent(): void {
|
||||
this._pixiObject.text = this._object._text;
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
updateAlignment(): void {
|
||||
// @ts-ignore - assume align is always a valid value.
|
||||
this._pixiObject.align = this._object._align;
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
this._pixiObject.position.x = this._object.x + this._pixiObject.width / 2;
|
||||
this._pixiObject.position.y =
|
||||
this._object.y + this._pixiObject.height / 2;
|
||||
}
|
||||
|
||||
updateAngle(): void {
|
||||
this._pixiObject.rotation = gdjs.toRad(this._object.angle);
|
||||
}
|
||||
|
||||
updateOpacity(): void {
|
||||
this._pixiObject.alpha = this._object._opacity / 255;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._pixiObject.textWidth;
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._pixiObject.height;
|
||||
}
|
||||
}
|
||||
export const BitmapTextRuntimeObjectRenderer = BitmapTextRuntimeObjectPixiRenderer;
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
namespace gdjs {
|
||||
/** Base parameters for {@link gdjs.BitmapTextRuntimeObject} */
|
||||
export type BitmapTextObjectDataType = {
|
||||
/** The base parameters of the Bitmap Text */
|
||||
content: {
|
||||
/** The opacity of the text. */
|
||||
opacity: float;
|
||||
/** Content of the text. */
|
||||
text: string;
|
||||
/** The tint of the text. */
|
||||
tint: string;
|
||||
/** The name of the resource containing the bitmap font for the text. */
|
||||
bitmapFontResourceName: string;
|
||||
/** The name of the resource containing the atlas image file for the text. */
|
||||
textureAtlasResourceName: string;
|
||||
/** The scale of the text. */
|
||||
scale: float;
|
||||
/** Activate word wrap if set to true. */
|
||||
wordWrap: boolean;
|
||||
/** Wrapping with from custom size properties. */
|
||||
wrappingWidth: float;
|
||||
/** Alignment of the text. */
|
||||
align: 'left' | 'center' | 'right';
|
||||
};
|
||||
};
|
||||
export type BitmapTextObjectData = ObjectData & BitmapTextObjectDataType;
|
||||
|
||||
/**
|
||||
* Displays a text using a "Bitmap Font", generated in a external editor like bmFont.
|
||||
* This is more efficient/faster to render than a traditional text (which needs
|
||||
* to have its whole texture re-rendered anytime it changes).
|
||||
*
|
||||
* Bitmap Font can be created with softwares like:
|
||||
* * BMFont (Windows, free): http://www.angelcode.com/products/bmfont/|http://www.angelcode.com/products/bmfont/
|
||||
* * Glyph Designer (OS X, commercial): http://www.71squared.com/en/glyphdesigner|http://www.71squared.com/en/glyphdesigner
|
||||
* * Littera (Web-based, free): http://kvazars.com/littera/|http://kvazars.com/littera/
|
||||
*/
|
||||
export class BitmapTextRuntimeObject extends gdjs.RuntimeObject {
|
||||
_opacity: float;
|
||||
_text: string;
|
||||
/** color in format [r, g, b], where each component is in the range [0, 255] */
|
||||
_tint: integer[];
|
||||
_bitmapFontResourceName: string;
|
||||
_textureAtlasResourceName: string;
|
||||
_scale: number;
|
||||
_wordWrap: boolean;
|
||||
_wrappingWidth: float;
|
||||
_align: string;
|
||||
|
||||
_renderer: gdjs.BitmapTextRuntimeObjectPixiRenderer;
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param objectData The object data used to initialize the object
|
||||
*/
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
objectData: BitmapTextObjectData
|
||||
) {
|
||||
super(runtimeScene, objectData);
|
||||
|
||||
this._opacity = objectData.content.opacity;
|
||||
this._text = objectData.content.text;
|
||||
this._tint = gdjs.hexToRGBColor(objectData.content.tint);
|
||||
|
||||
this._bitmapFontResourceName = objectData.content.bitmapFontResourceName; // fnt/xml files
|
||||
this._textureAtlasResourceName =
|
||||
objectData.content.textureAtlasResourceName; // texture file used with fnt/xml (bitmap font file)
|
||||
this._scale = objectData.content.scale;
|
||||
this._wordWrap = objectData.content.wordWrap;
|
||||
this._wrappingWidth = 0;
|
||||
this._align = objectData.content.align;
|
||||
|
||||
this._renderer = new gdjs.BitmapTextRuntimeObjectRenderer(
|
||||
this,
|
||||
runtimeScene
|
||||
);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
updateFromObjectData(
|
||||
oldObjectData: BitmapTextObjectDataType,
|
||||
newObjectData: BitmapTextObjectDataType
|
||||
): boolean {
|
||||
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
|
||||
this.setOpacity(newObjectData.content.opacity);
|
||||
}
|
||||
if (oldObjectData.content.text !== newObjectData.content.text) {
|
||||
this.setText(newObjectData.content.text);
|
||||
}
|
||||
if (oldObjectData.content.tint !== newObjectData.content.tint) {
|
||||
this._tint = gdjs.hexToRGBColor(newObjectData.content.tint);
|
||||
this._renderer.updateTint();
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.bitmapFontResourceName !==
|
||||
newObjectData.content.bitmapFontResourceName
|
||||
) {
|
||||
this.setBitmapFontResourceName(
|
||||
newObjectData.content.bitmapFontResourceName
|
||||
);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.textureAtlasResourceName !==
|
||||
newObjectData.content.textureAtlasResourceName
|
||||
) {
|
||||
this.setTextureAtlasResourceName(
|
||||
newObjectData.content.textureAtlasResourceName
|
||||
);
|
||||
}
|
||||
if (oldObjectData.content.scale !== newObjectData.content.scale) {
|
||||
this.setScale(newObjectData.content.scale);
|
||||
}
|
||||
if (oldObjectData.content.wordWrap !== newObjectData.content.wordWrap) {
|
||||
this.setWordWrap(newObjectData.content.wordWrap);
|
||||
}
|
||||
if (oldObjectData.content.align !== newObjectData.content.align) {
|
||||
this.setAlignment(newObjectData.content.align);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the extra parameters that could be set for an instance.
|
||||
*/
|
||||
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
||||
if (initialInstanceData.customSize) {
|
||||
this.setWrappingWidth(initialInstanceData.width);
|
||||
}
|
||||
}
|
||||
|
||||
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
|
||||
super.onDestroyFromScene(runtimeScene);
|
||||
this._renderer.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text to display.
|
||||
*/
|
||||
setText(text: string): void {
|
||||
this._text = text;
|
||||
this._renderer.updateTextContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text displayed by the object.
|
||||
*/
|
||||
getText(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
setTint(rgbColorString: string): void {
|
||||
const splitValue = rgbColorString.split(';');
|
||||
if (splitValue.length !== 3) return;
|
||||
|
||||
this._tint[0] = parseInt(splitValue[0], 10);
|
||||
this._tint[1] = parseInt(splitValue[1], 10);
|
||||
this._tint[2] = parseInt(splitValue[2], 10);
|
||||
this._renderer.updateTint();
|
||||
}
|
||||
|
||||
getTint(): string {
|
||||
return this._tint[0] + ';' + this._tint[1] + ';' + this._tint[2];
|
||||
}
|
||||
|
||||
setScale(scale: float): void {
|
||||
this._scale = scale;
|
||||
this._renderer.updateScale();
|
||||
}
|
||||
|
||||
getScale(): float {
|
||||
return this._scale;
|
||||
}
|
||||
|
||||
getFontSize(): float {
|
||||
return this._renderer.getFontSize();
|
||||
}
|
||||
|
||||
setBitmapFontAndTextureAtlasResourceName(
|
||||
bitmapFontResourceName: string,
|
||||
textureAtlasResourceName: string
|
||||
): void {
|
||||
if (bitmapFontResourceName) {
|
||||
this.setBitmapFontResourceName(bitmapFontResourceName);
|
||||
this._renderer.updateFont();
|
||||
}
|
||||
if (textureAtlasResourceName) {
|
||||
this.setTextureAtlasResourceName(textureAtlasResourceName);
|
||||
this._renderer.updateFont();
|
||||
}
|
||||
}
|
||||
|
||||
setBitmapFontResourceName(bitmapFontResourceName: string): void {
|
||||
this._bitmapFontResourceName = bitmapFontResourceName;
|
||||
}
|
||||
|
||||
getBitmapFontResourceName(): string {
|
||||
return this._bitmapFontResourceName;
|
||||
}
|
||||
|
||||
setTextureAtlasResourceName(textureAtlasResourceName: string): void {
|
||||
this._textureAtlasResourceName = textureAtlasResourceName;
|
||||
}
|
||||
|
||||
getTextureAtlasResourceName(): string {
|
||||
return this._textureAtlasResourceName;
|
||||
}
|
||||
|
||||
setAlignment(align: string): void {
|
||||
this._align = align;
|
||||
this._renderer.updateAlignment();
|
||||
}
|
||||
|
||||
getAlignment(): string {
|
||||
return this._align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object position on X axis.
|
||||
* @param x The new position X of the object.
|
||||
*/
|
||||
setX(x: float): void {
|
||||
super.setX(x);
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object position on Y axis.
|
||||
* @param y The new position Y of the object.
|
||||
*/
|
||||
setY(y: float): void {
|
||||
super.setY(y);
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the angle of the object.
|
||||
* @param angle The new angle of the object.
|
||||
*/
|
||||
setAngle(angle: float): void {
|
||||
super.setAngle(angle);
|
||||
this._renderer.updateAngle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object opacity.
|
||||
* @param opacity The new opacity of the object (0-255).
|
||||
*/
|
||||
setOpacity(opacity: float): void {
|
||||
this._opacity = opacity;
|
||||
this._renderer.updateOpacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object opacity.
|
||||
*/
|
||||
getOpacity(): float {
|
||||
return this._opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the wrapping width.
|
||||
* @param width The new width in pixels.
|
||||
*/
|
||||
setWrappingWidth(width: float): void {
|
||||
this._wrappingWidth = width;
|
||||
this._renderer.updateWrappingWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapping width of the object.
|
||||
*/
|
||||
getWrappingWidth(): float {
|
||||
return this._wrappingWidth;
|
||||
}
|
||||
|
||||
setWordWrap(wordWrap: boolean): void {
|
||||
this._wordWrap = wordWrap;
|
||||
this._renderer.updateWrappingWidth();
|
||||
}
|
||||
|
||||
getWordWrap(): boolean {
|
||||
return this._wordWrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the object.
|
||||
*/
|
||||
getWidth(): float {
|
||||
return this._renderer.getWidth() * this.getScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the object.
|
||||
*/
|
||||
getHeight(): float {
|
||||
return this._renderer.getHeight() * this.getScale();
|
||||
}
|
||||
}
|
||||
gdjs.registerObject(
|
||||
'BitmapText::BitmapTextObject',
|
||||
// @ts-ignore
|
||||
gdjs.BitmapTextRuntimeObject
|
||||
);
|
||||
}
|
||||
@@ -456,7 +456,7 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Wrapping"),
|
||||
_("De/activate word wrapping. Note that word wrapping is a graphical "
|
||||
"option,\nyou can't get the number of lines displayed"),
|
||||
_("Set word wrapping style of _PARAM0_: _PARAM1_"),
|
||||
_("Activate wrapping style of _PARAM0_: _PARAM1_"),
|
||||
_("Style"),
|
||||
"res/actions/wordWrap24.png",
|
||||
"res/actions/wordWrap.png")
|
||||
|
||||
@@ -644,6 +644,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "pixi-renderers/runtimescene-pixi-renderer.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/layer-pixi-renderer.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/pixi-image-manager.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/pixi-bitmapfont-manager.js");
|
||||
InsertUnique(includesFiles,
|
||||
"pixi-renderers/spriteruntimeobject-pixi-renderer.js");
|
||||
InsertUnique(includesFiles,
|
||||
@@ -660,6 +661,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
if (cocosRenderers) {
|
||||
InsertUnique(includesFiles, "cocos-renderers/cocos-director-manager.js");
|
||||
InsertUnique(includesFiles, "cocos-renderers/cocos-image-manager.js");
|
||||
InsertUnique(includesFiles, "cocos-renderers/cocos-bitmapfont-manager.js");
|
||||
InsertUnique(includesFiles, "cocos-renderers/cocos-tools.js");
|
||||
InsertUnique(includesFiles, "cocos-renderers/layer-cocos-renderer.js");
|
||||
InsertUnique(includesFiles,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2021-present Florian Rival ([email protected]). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
/**
|
||||
* Unimplemented Bitmap font manager.
|
||||
*/
|
||||
export class CocosBitmapFontManager {
|
||||
loadBitmapFontData(
|
||||
onProgress: (count: integer, total: integer) => void
|
||||
): Promise<void[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
// Register the class to let the engine use it.
|
||||
gdjs.BitmapFontManager = gdjs.CocosBitmapFontManager;
|
||||
}
|
||||
@@ -54,6 +54,22 @@ namespace gdjs {
|
||||
return '' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a string hex color value to an array [r, g, b], where each component is in the range [0, 255].
|
||||
*
|
||||
* @param {string} hex Color hexadecimal
|
||||
*/
|
||||
export const hexToRGBColor = function (
|
||||
hexString: string
|
||||
): [number, number, number] {
|
||||
var hexNumber = parseInt(hexString.replace('#', ''), 16);
|
||||
return [
|
||||
(hexNumber >> 16) & 0xff,
|
||||
(hexNumber >> 8) & 0xff,
|
||||
hexNumber & 0xff,
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a rgb color value to a hex value.
|
||||
* @param r Red
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2021-present Aurélien Vivet ([email protected]). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
import PIXI = GlobalPIXIModule.PIXI;
|
||||
|
||||
const defaultBitmapFontKey = 'GDJS-DEFAULT-BITMAP-FONT';
|
||||
|
||||
// When a font is unused, we put it in a cache of unused fonts. It's unloaded
|
||||
// from memory only when the cache is full and the font is at the last position
|
||||
// in the cache.
|
||||
// Set this to 0 to unload from memory ("uninstall") as soon as a font is unused.
|
||||
const uninstallCacheSize = 5;
|
||||
|
||||
/**
|
||||
* We patch the installed font to use a name that is unique for each font data and texture,
|
||||
* to avoid conflicts between different font files using the same font name (by default, the
|
||||
* font name used by Pixi is the one inside the font data, but this name is not necessarily unique.
|
||||
* For example, 2 resources can use the same font, or we can have multiple objects with the same
|
||||
* font data and different textures).
|
||||
*/
|
||||
const patchInstalledBitmapFont = (
|
||||
bitmapFont: PIXI.BitmapFont,
|
||||
bitmapFontInstallKey: string
|
||||
) => {
|
||||
const defaultName = bitmapFont.font;
|
||||
// @ts-ignore - we "hack" into Pixi to change the font name
|
||||
bitmapFont.font = bitmapFontInstallKey;
|
||||
PIXI.BitmapFont.available[bitmapFontInstallKey] = bitmapFont;
|
||||
delete PIXI.BitmapFont.available[defaultName];
|
||||
return PIXI.BitmapFont.available[bitmapFontInstallKey];
|
||||
};
|
||||
|
||||
/**
|
||||
* PixiBitmapFontManager loads fnt/xml files (using `fetch`), from the "bitmapFont" resources of the game.
|
||||
*
|
||||
* It installs the "BitmapFont" with PixiJS to be used with PIXI.BitmapText.
|
||||
*/
|
||||
export class PixiBitmapFontManager {
|
||||
private _resources: ResourceData[];
|
||||
private _imageManager: gdjs.PixiImageManager;
|
||||
|
||||
/** Pixi.BitmapFont used, indexed by their BitmapFont name. */
|
||||
private _pixiBitmapFontsInUse: Record<
|
||||
string,
|
||||
{ objectsUsingTheFont: number }
|
||||
> = {};
|
||||
|
||||
/** Pixi.BitmapFont not used anymore, but not yet uninstalled, indexed by their BitmapFont name. */
|
||||
private _pixiBitmapFontsToUninstall: string[] = [];
|
||||
|
||||
/** Loaded fonts data, indexed by resource name. */
|
||||
private _loadedFontsData: Record<string, any> = {};
|
||||
|
||||
private _defaultSlugFontName: string | null = null;
|
||||
|
||||
/**
|
||||
* @param resources The resources data of the game.
|
||||
* @param imageManager The image manager to be used to get textures used by fonts.
|
||||
*/
|
||||
constructor(
|
||||
resources: ResourceData[],
|
||||
imageManager: gdjs.PixiImageManager
|
||||
) {
|
||||
this._resources = resources;
|
||||
this._imageManager = imageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of the default `Pixi.BitmapFont`, always available.
|
||||
*/
|
||||
getDefaultBitmapFont() {
|
||||
if (this._defaultSlugFontName !== null) {
|
||||
return PIXI.BitmapFont.available[this._defaultSlugFontName];
|
||||
}
|
||||
|
||||
// Default bitmap font style
|
||||
const fontFamily = 'Arial';
|
||||
const bitmapFontStyle = new PIXI.TextStyle({
|
||||
fontFamily: fontFamily,
|
||||
fontSize: 20,
|
||||
padding: 5,
|
||||
align: 'left',
|
||||
fill: '#ffffff',
|
||||
wordWrap: true,
|
||||
lineHeight: 20,
|
||||
});
|
||||
|
||||
// Generate default bitmapFont, and replace the name of PIXI.BitmapFont by a unique name
|
||||
const defaultBitmapFont = patchInstalledBitmapFont(
|
||||
PIXI.BitmapFont.from(fontFamily, bitmapFontStyle, {
|
||||
// All the printable ASCII characters
|
||||
chars: [[' ', '~']],
|
||||
}),
|
||||
defaultBitmapFontKey
|
||||
);
|
||||
|
||||
// Define the default name used for the default bitmap font.
|
||||
this._defaultSlugFontName = defaultBitmapFont.font;
|
||||
return defaultBitmapFont;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
* @param resources The resources data of the game.
|
||||
*/
|
||||
setResources(resources: ResourceData[]): void {
|
||||
this._resources = resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to specify that the bitmap font with the specified key is used by an object
|
||||
* (i.e: this is reference counting).
|
||||
* `releaseBitmapFont` *must* be called to mark the font as not used anymore when the
|
||||
* object is destroyed or its font changed.
|
||||
*
|
||||
* @param bitmapFontInstallKey Name of the font of the BitmapFont (`bitmapFont.font`)
|
||||
*/
|
||||
private _markBitmapFontAsUsed(bitmapFontInstallKey: string): void {
|
||||
this._pixiBitmapFontsInUse[bitmapFontInstallKey] = this
|
||||
._pixiBitmapFontsInUse[bitmapFontInstallKey] || {
|
||||
objectsUsingTheFont: 0,
|
||||
};
|
||||
this._pixiBitmapFontsInUse[bitmapFontInstallKey].objectsUsingTheFont++;
|
||||
|
||||
for (let i = 0; i < this._pixiBitmapFontsToUninstall.length; ) {
|
||||
if (this._pixiBitmapFontsToUninstall[i] === bitmapFontInstallKey) {
|
||||
// The font is in the cache of fonts to uninstall, because it was previously used and then marked as not used anymore.
|
||||
// Remove it from the cache to avoid the font getting uninstalled.
|
||||
this._pixiBitmapFontsToUninstall.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a font is not used by an object anymore (object destroyed or font changed),
|
||||
* call this function to decrease the internal count of objects using the font.
|
||||
*
|
||||
* When a font is not unused anymore, it goes in a temporary cache. The cache holds up to 10 fonts.
|
||||
* If the cache reaches its maximum capacity, the oldest font is uninstalled from memory.
|
||||
*
|
||||
* @param bitmapFontInstallKey Name of the font of the BitmapFont (`bitmapFont.font`)
|
||||
*/
|
||||
releaseBitmapFont(bitmapFontInstallKey: string) {
|
||||
if (bitmapFontInstallKey === defaultBitmapFontKey) {
|
||||
// Never uninstall the default font.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._pixiBitmapFontsInUse[bitmapFontInstallKey]) {
|
||||
console.error(
|
||||
'BitmapFont with name ' +
|
||||
bitmapFontInstallKey +
|
||||
' was tried to be released but was never marked as used.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._pixiBitmapFontsInUse[bitmapFontInstallKey].objectsUsingTheFont--;
|
||||
|
||||
if (
|
||||
this._pixiBitmapFontsInUse[bitmapFontInstallKey].objectsUsingTheFont ===
|
||||
0
|
||||
) {
|
||||
delete this._pixiBitmapFontsInUse[bitmapFontInstallKey];
|
||||
|
||||
// Add the font name at the last position of the cache.
|
||||
if (!this._pixiBitmapFontsToUninstall.includes(bitmapFontInstallKey)) {
|
||||
this._pixiBitmapFontsToUninstall.push(bitmapFontInstallKey);
|
||||
}
|
||||
if (this._pixiBitmapFontsToUninstall.length > uninstallCacheSize) {
|
||||
// Remove the first font (i.e: the oldest one)
|
||||
const oldestUnloadedPixiBitmapFontName = this._pixiBitmapFontsToUninstall.shift() as string;
|
||||
|
||||
PIXI.BitmapFont.uninstall(oldestUnloadedPixiBitmapFontName);
|
||||
console.log(
|
||||
'Uninstalled BitmapFont "' +
|
||||
oldestUnloadedPixiBitmapFontName +
|
||||
'" from memory.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a bitmap font resource name and a texture atlas resource name, returns the PIXI.BitmapFont
|
||||
* for it.
|
||||
* The font is register and should be released with `releaseBitmapFont` - so that it can be removed
|
||||
* from memory when unused.
|
||||
*/
|
||||
obtainBitmapFont(
|
||||
bitmapFontResourceName: string,
|
||||
textureAtlasResourceName: string
|
||||
): PIXI.BitmapFont {
|
||||
const bitmapFontInstallKey =
|
||||
bitmapFontResourceName + '@' + textureAtlasResourceName;
|
||||
|
||||
if (PIXI.BitmapFont.available[bitmapFontInstallKey]) {
|
||||
// Return the existing BitmapFont that is already in memory and already installed.
|
||||
this._markBitmapFontAsUsed(bitmapFontInstallKey);
|
||||
return PIXI.BitmapFont.available[bitmapFontInstallKey];
|
||||
}
|
||||
|
||||
// The Bitmap Font is not loaded, load it in memory.
|
||||
|
||||
// First get the font data:
|
||||
const fontData = this._loadedFontsData[bitmapFontResourceName];
|
||||
if (!fontData) {
|
||||
console.warn(
|
||||
'Could not find Bitmap Font for resource named "' +
|
||||
bitmapFontResourceName +
|
||||
'". The default font will be used.'
|
||||
);
|
||||
return this.getDefaultBitmapFont();
|
||||
}
|
||||
|
||||
// Get the texture to be used in the font:
|
||||
const texture = this._imageManager.getPIXITexture(
|
||||
textureAtlasResourceName
|
||||
);
|
||||
|
||||
try {
|
||||
// Create and install the Pixi.BitmapFont in memory:
|
||||
const bitmapFont = patchInstalledBitmapFont(
|
||||
PIXI.BitmapFont.install(fontData, texture),
|
||||
bitmapFontInstallKey
|
||||
);
|
||||
this._markBitmapFontAsUsed(bitmapFontInstallKey);
|
||||
return bitmapFont;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Could not load the Bitmap Font for resource named "' +
|
||||
bitmapFontResourceName +
|
||||
'". The default font will be used. Error is: ' +
|
||||
error
|
||||
);
|
||||
return this.getDefaultBitmapFont();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the "bitmapFont" resources of the game, so that they are ready
|
||||
* to be used when `obtainBitmapFont` is called.
|
||||
*/
|
||||
loadBitmapFontData(
|
||||
onProgress: (count: integer, total: integer) => void
|
||||
): Promise<void[]> {
|
||||
const bitmapFontResources = this._resources.filter(
|
||||
(resource) => resource.kind === 'bitmapFont' && !resource.disablePreload
|
||||
);
|
||||
if (bitmapFontResources.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
let loadedCount = 0;
|
||||
return Promise.all(
|
||||
bitmapFontResources.map((bitmapFontResource) => {
|
||||
return fetch(bitmapFontResource.file)
|
||||
.then((response) => response.text())
|
||||
.then((fontData) => {
|
||||
this._loadedFontsData[bitmapFontResource.name] = fontData;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"Can't fetch the bitmap font file " +
|
||||
bitmapFontResource.file +
|
||||
', error: ' +
|
||||
error
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
loadedCount++;
|
||||
onProgress(loadedCount, bitmapFontResources.length);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register the class to let the engine use it.
|
||||
export const BitmapFontManager = gdjs.PixiBitmapFontManager;
|
||||
export type BitmapFontManager = gdjs.PixiBitmapFontManager;
|
||||
}
|
||||
@@ -45,7 +45,14 @@ namespace gdjs {
|
||||
*/
|
||||
getPIXITexture(resourceName: string): PIXI.Texture {
|
||||
if (this._loadedTextures.containsKey(resourceName)) {
|
||||
return this._loadedTextures.get(resourceName);
|
||||
const texture = this._loadedTextures.get(resourceName);
|
||||
if (texture.valid) {
|
||||
return texture;
|
||||
} else {
|
||||
console.error(
|
||||
'Texture for ' + resourceName + ' is not valid anymore.'
|
||||
);
|
||||
}
|
||||
}
|
||||
if (resourceName === '') {
|
||||
return this._invalidTexture;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -40,6 +40,7 @@ namespace gdjs {
|
||||
_soundManager: SoundManager;
|
||||
_fontManager: FontManager;
|
||||
_jsonManager: JsonManager;
|
||||
_bitmapFontManager: BitmapFontManager;
|
||||
_maxFPS: integer;
|
||||
_minFPS: integer;
|
||||
_gameResolutionWidth: integer;
|
||||
@@ -89,6 +90,10 @@ namespace gdjs {
|
||||
);
|
||||
this._fontManager = new gdjs.FontManager(this._data.resources.resources);
|
||||
this._jsonManager = new gdjs.JsonManager(this._data.resources.resources);
|
||||
this._bitmapFontManager = new gdjs.BitmapFontManager(
|
||||
this._data.resources.resources,
|
||||
this._imageManager
|
||||
);
|
||||
this._maxFPS = this._data ? this._data.properties.maxFPS : 60;
|
||||
this._minFPS = this._data ? this._data.properties.minFPS : 15;
|
||||
this._gameResolutionWidth = this._data.properties.windowWidth;
|
||||
@@ -122,6 +127,7 @@ namespace gdjs {
|
||||
this._soundManager.setResources(this._data.resources.resources);
|
||||
this._fontManager.setResources(this._data.resources.resources);
|
||||
this._jsonManager.setResources(this._data.resources.resources);
|
||||
this._bitmapFontManager.setResources(this._data.resources.resources);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,6 +174,15 @@ namespace gdjs {
|
||||
return this._fontManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gdjs.BitmapFontManager of the RuntimeGame.
|
||||
* @return The bitmap font manager.
|
||||
*/
|
||||
getBitmapFontManager(): gdjs.BitmapFontManager {
|
||||
// @ts-ignore
|
||||
return this._bitmapFontManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input manager of the game, storing mouse, keyboard
|
||||
* and touches states.
|
||||
@@ -420,6 +435,10 @@ namespace gdjs {
|
||||
);
|
||||
const allAssetsTotal = this._data.resources.resources.length;
|
||||
const that = this;
|
||||
|
||||
// TODO: All the `loadXXX` (or `preloadXXX`) methods would be
|
||||
// better converted to return promises, for better readability of the code.
|
||||
// See how `loadBitmapFontData` is done.
|
||||
this._imageManager.loadTextures(
|
||||
function (count, total) {
|
||||
const percent = Math.floor((count / allAssetsTotal) * 100);
|
||||
@@ -468,9 +487,25 @@ namespace gdjs {
|
||||
progressCallback(percent);
|
||||
}
|
||||
},
|
||||
function () {
|
||||
loadingScreen.unload();
|
||||
callback();
|
||||
function (jsonTotalCount) {
|
||||
that._bitmapFontManager
|
||||
.loadBitmapFontData((count) => {
|
||||
var percent = Math.floor(
|
||||
((texturesTotalCount +
|
||||
audioTotalCount +
|
||||
fontTotalCount +
|
||||
jsonTotalCount +
|
||||
count) /
|
||||
allAssetsTotal) *
|
||||
100
|
||||
);
|
||||
loadingScreen.render(percent);
|
||||
if (progressCallback) progressCallback(percent);
|
||||
})
|
||||
.then(() => {
|
||||
loadingScreen.unload();
|
||||
callback();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Vendored
+7
-1
@@ -207,4 +207,10 @@ declare interface ResourceData {
|
||||
preloadAsMusic?: boolean;
|
||||
}
|
||||
|
||||
declare type ResourceKind = 'audio' | 'image' | 'font' | 'video' | 'json';
|
||||
declare type ResourceKind =
|
||||
| 'audio'
|
||||
| 'image'
|
||||
| 'font'
|
||||
| 'video'
|
||||
| 'json'
|
||||
| 'bitmapFont';
|
||||
|
||||
@@ -32,6 +32,7 @@ module.exports = function (config) {
|
||||
'../Runtime-dist/fontfaceobserver-font-manager/fontfaceobserver-font-manager.js',
|
||||
'../Runtime-dist/jsonmanager.js',
|
||||
'../Runtime-dist/timemanager.js',
|
||||
'../Runtime-dist/timemanager.js',
|
||||
'../Runtime-dist/runtimeobject.js',
|
||||
'../Runtime-dist/runtimescene.js',
|
||||
'../Runtime-dist/scenestack.js',
|
||||
|
||||
@@ -804,6 +804,11 @@ interface FontResource {
|
||||
};
|
||||
FontResource implements Resource;
|
||||
|
||||
interface BitmapFontResource {
|
||||
void BitmapFontResource();
|
||||
};
|
||||
BitmapFontResource implements Resource;
|
||||
|
||||
interface VideoResource {
|
||||
void VideoResource();
|
||||
};
|
||||
@@ -2242,6 +2247,7 @@ interface ResourcesInUseHelper {
|
||||
[Ref] SetString GetAllImages();
|
||||
[Ref] SetString GetAllAudios();
|
||||
[Ref] SetString GetAllFonts();
|
||||
[Ref] SetString GetAllBitmapFonts();
|
||||
[Ref] SetString GetAll([Const] DOMString resourceType);
|
||||
};
|
||||
ResourcesInUseHelper implements ArbitraryResourceWorker;
|
||||
|
||||
@@ -186,6 +186,8 @@ void ObjectJsImplementation::ExposeResources(
|
||||
// Not supported in gd::ArbitraryResourceWorker
|
||||
} else if (resourceType == "json") {
|
||||
// Not supported in gd::ArbitraryResourceWorker
|
||||
} else if (resourceType == "bitmapFont") {
|
||||
worker.ExposeBitmapFont(newPropertyValue);
|
||||
}
|
||||
|
||||
if (newPropertyValue != oldPropertyValue) {
|
||||
|
||||
@@ -933,8 +933,26 @@ describe('libGD.js', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.VideoResource', function () {
|
||||
it('should have name and file', function () {
|
||||
describe('gd.BitmapFontResource', function() {
|
||||
it('should have name and file', function() {
|
||||
const resource = new gd.BitmapFontResource();
|
||||
resource.setName('MyBitmapFontResource');
|
||||
resource.setFile('MyBitmapFontFile');
|
||||
expect(resource.getName()).toBe('MyBitmapFontResource');
|
||||
expect(resource.getFile()).toBe('MyBitmapFontFile');
|
||||
resource.delete();
|
||||
});
|
||||
it('can have metadata', function() {
|
||||
const resource = new gd.BitmapFontResource();
|
||||
expect(resource.getMetadata()).toBe('');
|
||||
resource.setMetadata(JSON.stringify({ hello: 'world' }));
|
||||
expect(resource.getMetadata()).toBe('{"hello":"world"}');
|
||||
resource.delete();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.VideoResource', function() {
|
||||
it('should have name and file', function() {
|
||||
const resource = new gd.VideoResource();
|
||||
resource.setName('MyVideoResource');
|
||||
resource.setFile('MyVideoFile');
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdBitmapFontResource extends gdResource {
|
||||
constructor(): void;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
||||
@@ -4,6 +4,7 @@ declare class gdResourcesInUseHelper extends gdArbitraryResourceWorker {
|
||||
getAllImages(): gdSetString;
|
||||
getAllAudios(): gdSetString;
|
||||
getAllFonts(): gdSetString;
|
||||
getAllBitmapFonts(): gdSetString;
|
||||
getAll(resourceType: string): gdSetString;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
|
||||
@@ -85,6 +85,7 @@ declare class libGDevelop {
|
||||
ImageResource: Class<gdImageResource>;
|
||||
AudioResource: Class<gdAudioResource>;
|
||||
FontResource: Class<gdFontResource>;
|
||||
BitmapFontResource: Class<gdBitmapFontResource>;
|
||||
VideoResource: Class<gdVideoResource>;
|
||||
JsonResource: Class<gdJsonResource>;
|
||||
InitialInstance: Class<gdInitialInstance>;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0"?>
|
||||
<font>
|
||||
<info face="bitmatt" size="24" bold="0" italic="0" charset="" unicode="1" stretchH="100" smooth="0" aa="1" padding="1,1,1,1" spacing="1,1" outline="0"/>
|
||||
<common lineHeight="24" base="24" scaleW="128" scaleH="128" pages="1" packed="0" alphaChnl="0" redChnl="4" greenChnl="4" blueChnl="4"/>
|
||||
<pages>
|
||||
<page id="0" file="bitmatt_24_white_0.png" />
|
||||
</pages>
|
||||
<chars count="29">
|
||||
<char id="32" x="122" y="23" width="3" height="3" xoffset="-1" yoffset="-1" xadvance="10" page="0" chnl="15" />
|
||||
<char id="33" x="120" y="46" width="6" height="22" xoffset="1" yoffset="-1" xadvance="6" page="0" chnl="15" />
|
||||
<char id="46" x="35" y="69" width="6" height="6" xoffset="1" yoffset="15" xadvance="6" page="0" chnl="15" />
|
||||
<char id="65" x="112" y="0" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="66" x="17" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="67" x="32" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="68" x="47" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="69" x="62" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="70" x="77" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="71" x="92" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="72" x="107" y="23" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="73" x="28" y="69" width="6" height="22" xoffset="3" yoffset="-1" xadvance="8" page="0" chnl="15" />
|
||||
<char id="74" x="15" y="69" width="12" height="22" xoffset="1" yoffset="-1" xadvance="12" page="0" chnl="15" />
|
||||
<char id="75" x="0" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="76" x="15" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="77" x="0" y="0" width="18" height="22" xoffset="1" yoffset="-1" xadvance="18" page="0" chnl="15" />
|
||||
<char id="78" x="95" y="0" width="16" height="22" xoffset="1" yoffset="-1" xadvance="16" page="0" chnl="15" />
|
||||
<char id="79" x="30" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="80" x="45" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="81" x="0" y="23" width="16" height="22" xoffset="1" yoffset="-1" xadvance="16" page="0" chnl="15" />
|
||||
<char id="82" x="60" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="83" x="75" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="84" x="90" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="85" x="105" y="46" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
<char id="86" x="19" y="0" width="18" height="22" xoffset="1" yoffset="-1" xadvance="18" page="0" chnl="15" />
|
||||
<char id="87" x="38" y="0" width="18" height="22" xoffset="1" yoffset="-1" xadvance="18" page="0" chnl="15" />
|
||||
<char id="88" x="57" y="0" width="18" height="22" xoffset="1" yoffset="-1" xadvance="18" page="0" chnl="15" />
|
||||
<char id="89" x="76" y="0" width="18" height="22" xoffset="1" yoffset="-1" xadvance="18" page="0" chnl="15" />
|
||||
<char id="90" x="0" y="69" width="14" height="22" xoffset="1" yoffset="-1" xadvance="14" page="0" chnl="15" />
|
||||
</chars>
|
||||
</font>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 409 B |
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import ResourceSelector from '../../ResourcesList/ResourceSelector';
|
||||
import ResourcesLoader from '../../ResourcesLoader';
|
||||
import { type ParameterFieldProps } from './ParameterFieldCommons';
|
||||
|
||||
export default class BitmapFontResourceField extends Component<
|
||||
ParameterFieldProps,
|
||||
void
|
||||
> {
|
||||
_field: ?ResourceSelector;
|
||||
|
||||
focus() {
|
||||
if (this._field) this._field.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
!this.props.resourceSources ||
|
||||
!this.props.onChooseResource ||
|
||||
!this.props.resourceExternalEditors ||
|
||||
!this.props.project
|
||||
) {
|
||||
console.error(
|
||||
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for BitmapFontResourceField'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSelector
|
||||
margin={this.props.isInline ? 'none' : 'dense'}
|
||||
project={this.props.project}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
resourceKind="bitmapFont"
|
||||
fullWidth
|
||||
initialResourceName={this.props.value}
|
||||
onChange={this.props.onChange}
|
||||
floatingLabelText={
|
||||
<Trans>Choose the bitmap font file (.fnt, .xml) to use</Trans>
|
||||
}
|
||||
ref={field => (this._field = field)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import LayerField from './ParameterFields/LayerField';
|
||||
import AudioResourceField from './ParameterFields/AudioResourceField';
|
||||
import VideoResourceField from './ParameterFields/VideoResourceField';
|
||||
import JsonResourceField from './ParameterFields/JsonResourceField';
|
||||
import BitmapFontResourceField from './ParameterFields/BitmapFontResourceField';
|
||||
import ColorExpressionField from './ParameterFields/ColorExpressionField';
|
||||
import ForceMultiplierField, {
|
||||
renderInlineForceMultiplier,
|
||||
@@ -70,6 +71,7 @@ const components = {
|
||||
soundfile: AudioResourceField,
|
||||
videoResource: VideoResourceField,
|
||||
jsonResource: JsonResourceField,
|
||||
bitmapFontResource: BitmapFontResourceField,
|
||||
color: ColorExpressionField,
|
||||
police: DefaultField, //TODO
|
||||
joyaxis: DefaultField, //TODO
|
||||
@@ -109,6 +111,7 @@ const userFriendlyTypeName: { [string]: MessageDescriptor } = {
|
||||
musicfile: t`Audio resource`,
|
||||
soundfile: t`Audio resource`,
|
||||
videoResource: t`Video resource`,
|
||||
bitmapFontResource: t`Bitmap font resource`,
|
||||
jsonResource: t`JSON resource`,
|
||||
color: t`Color`,
|
||||
forceMultiplier: t`Instant or permanent force`,
|
||||
|
||||
@@ -30,6 +30,7 @@ export const getDeprecatedBehaviorsInformation = (): {
|
||||
export const getExperimentalObjects = (): {
|
||||
[string]: boolean,
|
||||
} => ({
|
||||
'BitmapText::BitmapTextObject': true,
|
||||
'TileMap::TileMap': true,
|
||||
});
|
||||
|
||||
@@ -50,6 +51,20 @@ export const getExtraObjectsInformation = (): {
|
||||
message: t`Video format supported can vary according to devices and browsers. For maximum compatibility, use H.264/mp4 file format (and AAC for audio).`,
|
||||
},
|
||||
],
|
||||
'BitmapText::BitmapTextObject': [
|
||||
{
|
||||
kind: 'warning',
|
||||
message: t`This object is experimental and not yet complete. It might have bugs or incomplete support in GDevelop, be sure to read the wiki by clicking on help button bellow.`,
|
||||
},
|
||||
{
|
||||
kind: 'info',
|
||||
message: t`For a pixel type font, you must disable the Smooth checkbox related to your texture in the game resources to disable anti-aliasing.`,
|
||||
},
|
||||
{
|
||||
kind: 'info',
|
||||
message: t`The font size is stored directly inside the font. If you want to change it, export again your font using an external editor like bmFont. Click on the help button to learn more.`,
|
||||
},
|
||||
],
|
||||
'TileMap::TileMap': [
|
||||
{
|
||||
kind: 'info',
|
||||
@@ -68,6 +83,12 @@ export const getExtraInstructionInformation = (type: string): ?Hint => {
|
||||
Physics2, you can't mix the behaviors).`,
|
||||
};
|
||||
}
|
||||
if (type === 'BitmapText::Scale') {
|
||||
return {
|
||||
kind: 'info',
|
||||
message: t`A scale under 1 on a Bitmap text object can downgrade the quality text, prefer to remake a bitmap font smaller in the external bmFont editor.`,
|
||||
};
|
||||
}
|
||||
if (type === 'TextObject::Size') {
|
||||
return {
|
||||
kind: 'warning',
|
||||
|
||||
@@ -207,7 +207,7 @@ export default class LayerRenderer {
|
||||
);
|
||||
this._updatePixiObjectsZOrder();
|
||||
this._updateVisibility();
|
||||
this._cleanRenderers();
|
||||
this._destroyUnusedInstanceRenderers();
|
||||
}
|
||||
|
||||
_updatePixiObjectsZOrder() {
|
||||
@@ -227,12 +227,12 @@ export default class LayerRenderer {
|
||||
* the next render.
|
||||
* @param {string} objectName The name of the object for which instance must be re-rendered.
|
||||
*/
|
||||
resetRenderersFor(objectName) {
|
||||
resetInstanceRenderersFor(objectName) {
|
||||
for (let i in this.renderedInstances) {
|
||||
if (this.renderedInstances.hasOwnProperty(i)) {
|
||||
const renderedInstance = this.renderedInstances[i];
|
||||
if (renderedInstance.getInstance().getObjectName() === objectName) {
|
||||
renderedInstance.instanceRemovedFromScene();
|
||||
renderedInstance.onRemovedFromScene();
|
||||
delete this.renderedInstances[i];
|
||||
}
|
||||
}
|
||||
@@ -240,15 +240,15 @@ export default class LayerRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up rendered instances that are not associated to any instance anymore
|
||||
* Remove rendered instances that are not associated to any instance anymore
|
||||
* (this can happen after an instance has been deleted).
|
||||
*/
|
||||
_cleanRenderers() {
|
||||
_destroyUnusedInstanceRenderers() {
|
||||
for (let i in this.renderedInstances) {
|
||||
if (this.renderedInstances.hasOwnProperty(i)) {
|
||||
const renderedInstance = this.renderedInstances[i];
|
||||
if (!renderedInstance.wasUsed) {
|
||||
renderedInstance.instanceRemovedFromScene();
|
||||
renderedInstance.onRemovedFromScene();
|
||||
delete this.renderedInstances[i];
|
||||
} else renderedInstance.wasUsed = false;
|
||||
}
|
||||
@@ -256,6 +256,19 @@ export default class LayerRenderer {
|
||||
}
|
||||
|
||||
delete() {
|
||||
// Destroy all instances
|
||||
for (let i in this.renderedInstances) {
|
||||
if (this.renderedInstances.hasOwnProperty(i)) {
|
||||
const renderedInstance = this.renderedInstances[i];
|
||||
renderedInstance.onRemovedFromScene(); // TODO: pass argument to tell it's even worth removing from container?
|
||||
delete this.renderedInstances[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the container
|
||||
this.pixiContainer.destroy();
|
||||
|
||||
// Destroy the object iterating on isntances
|
||||
this.instancesRenderer.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export default class InstancesRenderer {
|
||||
}
|
||||
|
||||
this._updatePixiObjectsZOrder();
|
||||
this._cleanRenderers();
|
||||
this._cleanUnusedLayerRenderers();
|
||||
}
|
||||
|
||||
_updatePixiObjectsZOrder() {
|
||||
@@ -134,11 +134,11 @@ export default class InstancesRenderer {
|
||||
* the next render.
|
||||
* @param {string} objectName The name of the object for which instance must be re-rendered.
|
||||
*/
|
||||
resetRenderersFor(objectName) {
|
||||
resetInstanceRenderersFor(objectName) {
|
||||
for (let i in this.layersRenderers) {
|
||||
if (this.layersRenderers.hasOwnProperty(i)) {
|
||||
const layerRenderer = this.layersRenderers[i];
|
||||
layerRenderer.resetRenderersFor(objectName);
|
||||
layerRenderer.resetInstanceRenderersFor(objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export default class InstancesRenderer {
|
||||
/**
|
||||
* Clean up rendered layers that are not existing anymore
|
||||
*/
|
||||
_cleanRenderers() {
|
||||
_cleanUnusedLayerRenderers() {
|
||||
for (let i in this.layersRenderers) {
|
||||
if (this.layersRenderers.hasOwnProperty(i)) {
|
||||
const layerRenderer = this.layersRenderers[i];
|
||||
@@ -160,10 +160,14 @@ export default class InstancesRenderer {
|
||||
}
|
||||
|
||||
delete() {
|
||||
// Destroy the layers first.
|
||||
for (let i in this.layersRenderers) {
|
||||
if (this.layersRenderers.hasOwnProperty(i)) {
|
||||
this.layersRenderers[i].delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Finish by the pixi container.
|
||||
this.pixiContainer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
this.longTouchHandler.unmount();
|
||||
if (this.nextFrame) cancelAnimationFrame(this.nextFrame);
|
||||
stopPIXITicker();
|
||||
this.pixiRenderer.destroy();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
@@ -448,9 +449,9 @@ export default class InstancesEditor extends Component<Props> {
|
||||
* See also ResourcesLoader and PixiResourcesLoader.
|
||||
* @param {string} objectName The name of the object for which instance must be re-rendered.
|
||||
*/
|
||||
resetRenderersFor(objectName: string) {
|
||||
resetInstanceRenderersFor(objectName: string) {
|
||||
if (this.instancesRenderer)
|
||||
this.instancesRenderer.resetRenderersFor(objectName);
|
||||
this.instancesRenderer.resetInstanceRenderersFor(objectName);
|
||||
}
|
||||
|
||||
zoomBy(value: number) {
|
||||
|
||||
@@ -123,6 +123,12 @@ const jsExtensions = [
|
||||
extensionModule: require('GDJS-for-web-app-only/Runtime/Extensions/SpatialSound/JsExtension.js'),
|
||||
objectsRenderingServiceModules: {},
|
||||
},
|
||||
{
|
||||
name: 'BitmapText',
|
||||
// $FlowExpectedError - this path is ignored for Flow.
|
||||
extensionModule: require('GDJS-for-web-app-only/Runtime/Extensions/BitmapText/JsExtension.js'),
|
||||
objectsRenderingServiceModules: {},
|
||||
},
|
||||
];
|
||||
|
||||
type MakeExtensionsLoaderArguments = {|
|
||||
|
||||
@@ -99,6 +99,10 @@ export default {
|
||||
console.warn(
|
||||
`Tried to register renderer for object "${objectType}", but a renderer already exists.`
|
||||
);
|
||||
|
||||
// If you want to update a renderer, this is currently unsupported.
|
||||
// To implement this, we need to add support for instance renderers to be released/destroyed
|
||||
// (some can have reference counting for some PIXI resources, etc... that would need to be properly released).
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import ResourcesLoader from '../ResourcesLoader';
|
||||
import { loadFontFace } from '../Utils/FontFaceLoader';
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
const loadedBitmapFonts = {};
|
||||
const loadedFontFamilies = {};
|
||||
const loadedTextures = {};
|
||||
const invalidTexture = PIXI.Texture.from('res/error48.png');
|
||||
@@ -97,6 +98,10 @@ export default class PixiResourcesLoader {
|
||||
*/
|
||||
static getPIXITexture(project: gdProject, resourceName: string) {
|
||||
if (loadedTextures[resourceName]) {
|
||||
// TODO: we never consider textures as not valid anymore. When we
|
||||
// update the IDE to unload textures, we should handle loading them again
|
||||
// here (and also be careful to return the same texture if it's not valid
|
||||
// but still loading, when multiple objects are rapidly asking for the same texture).
|
||||
return loadedTextures[resourceName];
|
||||
}
|
||||
|
||||
@@ -128,6 +133,10 @@ export default class PixiResourcesLoader {
|
||||
*/
|
||||
static getPIXIVideoTexture(project: gdProject, resourceName: string) {
|
||||
if (loadedTextures[resourceName]) {
|
||||
// TODO: we never consider textures as not valid anymore. When we
|
||||
// update the IDE to unload textures, we should handle loading them again
|
||||
// here (and also be careful to return the same texture if it's not valid
|
||||
// but still loading, when multiple objects are rapidly asking for the same texture).
|
||||
return loadedTextures[resourceName];
|
||||
}
|
||||
|
||||
@@ -218,6 +227,47 @@ export default class PixiResourcesLoader {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the the data from a bitmap font file (fnt/xml) resource in the IDE.
|
||||
*/
|
||||
static getBitmapFontData(
|
||||
project: gdProject,
|
||||
resourceName: string
|
||||
): Promise<any> {
|
||||
if (loadedBitmapFonts[resourceName]) {
|
||||
return Promise.resolve(loadedBitmapFonts[resourceName].data);
|
||||
}
|
||||
|
||||
if (!project.getResourcesManager().hasResource(resourceName))
|
||||
return Promise.reject(
|
||||
new Error(`Can't find resource called ${resourceName}.`)
|
||||
);
|
||||
|
||||
const resource = project.getResourcesManager().getResource(resourceName);
|
||||
if (resource.getKind() !== 'bitmapFont')
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`The resource called ${resourceName} is not a bitmap font file. Require .fnt or .xml format.`
|
||||
)
|
||||
);
|
||||
|
||||
const fullUrl = ResourcesLoader.getResourceFullUrl(project, resourceName, {
|
||||
isResourceForPixi: true,
|
||||
});
|
||||
if (!fullUrl) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`The resource called ${resourceName} was no found.\nThe default bitmap font will be used.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return axios.get(fullUrl).then(response => {
|
||||
loadedBitmapFonts[resourceName] = response;
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
static getInvalidPIXITexture() {
|
||||
return invalidTexture;
|
||||
}
|
||||
|
||||
@@ -50,11 +50,8 @@ RenderedInstance.prototype.getInstance = function() {
|
||||
* the scene. The PIXI object should probably be removed from the container: This is what
|
||||
* the default implementation of the method does.
|
||||
*/
|
||||
RenderedInstance.prototype.instanceRemovedFromScene = function() {
|
||||
if (
|
||||
this._pixiObject !== null &&
|
||||
this._pixiContainer.children.indexOf(this._pixiObject) !== -1
|
||||
)
|
||||
RenderedInstance.prototype.onRemovedFromScene = function() {
|
||||
if (this._pixiObject !== null)
|
||||
this._pixiContainer.removeChild(this._pixiObject);
|
||||
};
|
||||
|
||||
|
||||
@@ -27,17 +27,19 @@ function RenderedTextInstance(
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
const style = new PIXI.TextStyle({
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 20,
|
||||
align: 'left',
|
||||
padding: 5,
|
||||
});
|
||||
|
||||
//Setup the PIXI object:
|
||||
const textObject = gd.asTextObject(this._associatedObject);
|
||||
this._pixiObject = new PIXI.Text(' ', { align: 'left' });
|
||||
this._pixiObject = new PIXI.Text('', style);
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this._styleFontDirty = true;
|
||||
this._fontFamily = this._pixiResourcesLoader.getFontFamily(
|
||||
this._project,
|
||||
textObject.getFontName()
|
||||
);
|
||||
this.update();
|
||||
}
|
||||
RenderedTextInstance.prototype = Object.create(RenderedInstance.prototype);
|
||||
@@ -90,7 +92,7 @@ RenderedTextInstance.prototype.update = function() {
|
||||
|
||||
if (this._styleFontDirty) {
|
||||
this._pixiObject.style.fontFamily = this._fontFamily || 'Arial';
|
||||
this._pixiObject.style.fontSize = Math.max(1, this._characterSize) + 'px';
|
||||
this._pixiObject.style.fontSize = Math.max(1, this._characterSize);
|
||||
this._pixiObject.style.fontStyle = this._isItalic ? 'italic' : 'normal';
|
||||
this._pixiObject.style.fontWeight = this._isBold ? 'bold' : 'normal';
|
||||
this._pixiObject.style.wordWrap = this._wrapping;
|
||||
|
||||
@@ -185,4 +185,25 @@ export default [
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'publicBitmapFontUrlChooser',
|
||||
displayName: 'Choose a bitmap font from library',
|
||||
kind: 'bitmapFont',
|
||||
component: class BitmapFontResourceChooser extends React.Component {
|
||||
chooseResources = () => {
|
||||
if (this._chooser) return this._chooser.chooseResources();
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<GenericResourcesChooser
|
||||
{...this.props}
|
||||
urlsAreImages={false}
|
||||
createNewResource={() => new gd.BitmapFontResource()}
|
||||
title={<Trans>Choose a bitmap font from the library</Trans>}
|
||||
ref={chooser => (this._chooser = chooser)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -212,6 +212,50 @@ export default [
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'localBitmapFontFileOpener',
|
||||
displayName: 'Choose a new bitmap font file (.fnt, .xml)',
|
||||
kind: 'bitmapFont',
|
||||
component: class LocalBitmapFontFileOpener extends Component<ResourceSourceComponentProps> {
|
||||
chooseResources = (
|
||||
project: gdProject,
|
||||
multiSelections: boolean = true
|
||||
): Promise<Array<any>> => {
|
||||
const { i18n, getLastUsedPath, setLastUsedPath } = this.props;
|
||||
const options = {
|
||||
multiSelections,
|
||||
title: i18n._(t`Choose a bitmap font file`),
|
||||
name: i18n._(t`FNT, XML file`),
|
||||
extensions: ['fnt', 'xml'],
|
||||
};
|
||||
return selectLocalResourcePath(
|
||||
i18n,
|
||||
project,
|
||||
options,
|
||||
getLastUsedPath,
|
||||
setLastUsedPath,
|
||||
'bitmapFont'
|
||||
).then(resources => {
|
||||
return resources.map(resourcePath => {
|
||||
const bitmapFontResource = new gd.BitmapFontResource();
|
||||
const projectPath = path.dirname(project.getProjectFile());
|
||||
bitmapFontResource.setFile(
|
||||
path.relative(projectPath, resourcePath)
|
||||
);
|
||||
bitmapFontResource.setName(
|
||||
path.relative(projectPath, resourcePath)
|
||||
);
|
||||
|
||||
return bitmapFontResource;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ export default class ResourcePreview extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
);
|
||||
case 'font':
|
||||
case 'bitmapFont':
|
||||
return (
|
||||
<GenericIconPreview
|
||||
renderIcon={props => <FontDownload {...props} />}
|
||||
|
||||
@@ -4,7 +4,13 @@ import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
// These are all the kind of resources that can be found in
|
||||
// Core/GDCore/Project/ResourcesManager.h
|
||||
export type ResourceKind = 'image' | 'audio' | 'font' | 'video' | 'json';
|
||||
export type ResourceKind =
|
||||
| 'image'
|
||||
| 'audio'
|
||||
| 'font'
|
||||
| 'video'
|
||||
| 'json'
|
||||
| 'bitmapFont';
|
||||
|
||||
export type ResourceSourceComponentProps = {|
|
||||
i18n: I18nType,
|
||||
|
||||
@@ -10,6 +10,7 @@ export const RESOURCE_EXTENSIONS = {
|
||||
image: 'png,jpg,jpeg,PNG,JPG,JPEG',
|
||||
audio: 'wav,mp3,ogg,WAV,MP3,OGG',
|
||||
font: 'ttf,ttc,otf,TTF,TTC,OTF',
|
||||
bitmapFont: 'fnt,xml,FNT,XML',
|
||||
video: 'mp4,MP4',
|
||||
};
|
||||
|
||||
|
||||
@@ -256,6 +256,10 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
RESOURCE_EXTENSIONS.font,
|
||||
() => new gd.FontResource()
|
||||
);
|
||||
this._scanForNewResources(
|
||||
RESOURCE_EXTENSIONS.bitmapFont,
|
||||
() => new gd.BitmapFontResource()
|
||||
);
|
||||
},
|
||||
enabled: hasElectron,
|
||||
},
|
||||
@@ -286,6 +290,7 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
label: i18n._(t`Remove Unused Fonts`),
|
||||
click: () => {
|
||||
this.props.onRemoveUnusedResources('font');
|
||||
this.props.onRemoveUnusedResources('bitmapFont');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -926,7 +926,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
objectResourceNames,
|
||||
() => {},
|
||||
() => {
|
||||
if (this.editor) this.editor.resetRenderersFor(object.getName());
|
||||
if (this.editor)
|
||||
this.editor.resetInstanceRenderersFor(object.getName());
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as PIXI from 'pixi.js-legacy';
|
||||
export const stopPIXITicker = () => {
|
||||
const ticker = PIXI.Ticker && PIXI.Ticker.shared;
|
||||
if (ticker) ticker.stop();
|
||||
// TODO: same for the PIXI.Ticker.system
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -22,5 +23,6 @@ export const startPIXITicker = () => {
|
||||
setTimeout(() => {
|
||||
const ticker = PIXI.Ticker && PIXI.Ticker.shared;
|
||||
if (ticker) ticker.start();
|
||||
// TODO: same for the PIXI.Ticker.system
|
||||
}, 50);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user