mimshwright / pixi-tagged-text

A component for pixi similar to Text that supports multiple styles in a single component as well as inline images.
MIT License
31 stars 15 forks source link

imgMap does not work with complex sprites #414

Closed reececomo closed 8 months ago

reececomo commented 8 months ago

Hey there, not sure if we're doing something wrong.

When using a simple texture, or a Sprite.from(TEXTURE), it works great, but if we have a Sprite that contains two sprites, it doesn't render anything for the <key/>:

// For "WASD" layout, returns 'W' on QWERTY/QWERTZ, 'Z' on AZERTY, 'Š’' on JCUKEN, etc.
//  but could also be bound to another key layout like "ESDF".
const localizedKey = getLocalizedDisplayKey(KeyBind.Jump);

// This is just a plain Sprite with a PIXI.Text inside of it:
const localizedLabelKeySprite = new KeyboardKeySprite( localizedKey );

// In practice the "Press <key/> to jump" would be localized:
const instructionText = new TaggedText('Press <key/> to jump', {
  key: {
    imgSrc: 'keyImg',
    imgDisplay: 'icon',
  }
}, {
  imgMap: {
    keyImg: localizedLabelKeySprite,
  }
});

As a counter-example, showing a plain sprite, a texture or raw asset url works great (but doesn't help with dynamically-configured keys):

  imgMap: {
    keyImg: '/assets/img/ui/input/blank_key.png',
  }

Note: This is not related to #393 (PIXI.BitmapText), this is using plain PIXI.Text / TaggedText.

Would a RenderTexture help? šŸ¤”

reececomo commented 8 months ago

For context, this is for showing localized labels like "Press <key> to <action>" for different, user-configured layouts:

keys

Would like to avoid creating spritesheets/hundreds of textures for all the different layouts and languages, given that all the keys would be basically identical šŸ˜†

mimshwright commented 8 months ago

To be honest, I haven't tested this with dynamic sprites. Part of the reason is that I was developing this library based on requests for a specific use case. You have been using it for another use case and it's been showing a bunch of important issues with the library.

When you create a new TaggedText and provide an imgMap, you're actually providing a template for the sprite you want to display. That template is recreated by the library to make as many copies as needed (so you can do like )

The code that clones the sprite is:

// pixiUtils.ts
export const cloneSprite = (sprite: PIXI.Sprite): PIXI.Sprite =>
  new PIXI.Sprite(sprite.texture);

So you can see that I never anticipated the interactive sprites. šŸ˜“

I think it should be possible to either rewrite the cloneSprite function or use something like https://pixijs.download/dev/docs/PIXI.Renderer.html#generateTexture . The only real constraint is the sprite would need to have some fixed width and height at the time it's rendered. I presume you'd need to recreate or redraw each time the key label changes too.

reececomo commented 8 months ago

Nice, all good!

You have been using it for another use case and it's been showing a bunch of important issues with the library.

It's still the best library out there for what it does do.

I think it should be possible to either rewrite the cloneSprite function

Will do - just wanted to check that it definitely wasn't supported out of the box. I think we can make all our input icons (gamepad button, keyboard keys) fit into some fixed-size container. Will report back!

sitenote, I wonder if it could be as easy as:

export const cloneSprite = (sprite: PIXI.Sprite): PIXI.Sprite =>
  sprite.clone();

(or ...come to think of it, do we need to clone? or is that just for multiple occurrences)

reececomo commented 8 months ago

Ignore me, I think I imagined that sprite.clone() exists šŸ˜†

reececomo commented 8 months ago

Had a quick crack on my lunchbreak and it didn't work first try (boo).

In practice these for us would just be one-liners (no wraparound), so might just glue together raw PIXI.Text.

const TOKEN = "<key>";
const value: string = t("Press <key> to jump"); // translated value

const startMiddleOrEnd = value.startsWith(TOKEN) ? 'start' : value.endsWidth(TOKEN) ? 'end' : 'middle';
const parts = text.split(TOKEN) as [string] | [string, string];

const instruction = new Container();

switch (startMiddleOrEnd) {
  case 'start':
    instruction.addChild(keySprite, makePixiText(parts[0]));
    break;

  case 'end':
    instruction.addChild(makePixiText(parts[0]), keySprite);
    break;

  case 'middle':
    const texts = parts.map(makePixiText);
    instruction.addChild(texts[0], keySprite, texts[1]);
    break;
}

// do layout for instruction.children, etc.