collinhover / impactplusplus

Impact++ is a collection of additions to ImpactJS with full featured physics, dynamic lighting, UI, abilities, and more.
http://collinhover.github.com/impactplusplus
MIT License
276 stars 59 forks source link

How to position an UITextBubble? #112

Open Pattentrick opened 10 years ago

Pattentrick commented 10 years ago

Hi there,

i have some trouble with the UITextBubble element. I want to spawn my text bubble at the position of my player. But when i do something like this:

// EntityTextOutput is my UITextBubble element
ig.game.spawnEntity( ig.EntityTextOutput, 100, 300 );

The text bubble will spawn at the top left corner of the canvas. Adding a performance: 'dynamic' to it will at least spawn it at the top left corner of the gameworld, but it's still ignoring the spawning coordinates mentioned above. Even adding a this.pos.x = 451 to the constructor does nothing.

So obviously i am doing this the wrong way. How do i position/spawn a text bubble at a desired location?

By the way, here is my constructor/class:

javascript ig.module( 'game.entities.text-output' ) .requires( 'plusplus.ui.ui-text-bubble', 'game.entities.text-output-content', 'plusplus.core.config' ) .defines(function () {

var _c  = ig.CONFIG;

/**
 * Textoutput.
 *
 * @class
 * @extends ig.UITextBubble
 * @memeberof ig
 */
ig.EntityTextOutput = ig.global.EntityTextOutput = ig.UITextBubble.extend({

    cornerRadius: 10,

    triangleLength: 0,

    textSettings: {

        text: 'Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor',
        font: new ig.Font( _c.PATH_TO_MEDIA + 'command_preview_font.png' )

    }

});

});



Thanks for any help on this :-)   
collinhover commented 10 years ago

Three things are relevant, moveTo, fixed, and posAsPct. UI elements are by default fixed === true, which means they position themselves relative to the camera, not the game world. They are also posAsPct === true by default, which means they position themselves based on a percent of the screen size as set by posPct. If you want a UI element to follow an entity, the easiest way is to spawn it and then tell it to moveTo(playerEntity) where playerEntity is a reference to your player. Alternatively, you can set fixed to false and posAsPct to false, then just set the pos as normal.

Pattentrick commented 10 years ago

Thanks for explaning this to me @collinhover! I do understand UI elements much better now!

However i still have issues with the positioning. Setting fixed === false and posAsPct === false enables me to set the position via the spawnEntity-method, but altering the position after that via pos.x/pos.y is not possible.

The moveTo-method will move the text bubble to the specified entity, but only the text bubble. The text of the text bubble will stay in the upper left corner of the canvas (it gets somehow a negative positioning and is just partially visible).

Here is my modified code.

The text bubble:

    ig.EntityTextOutput = ig.global.EntityTextOutput = ig.UITextBubble.extend({

        fixed: false,

        posAsPct: false,

        cornerRadius: 10,

        triangleLength: 0,

        textSettings: {

            text: 'Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor',
            font: new ig.Font( _c.PATH_TO_MEDIA + 'command_preview_font.png' )

        }

    });

Adding the text bubble to the game (right now for testing purpose in the main.js game init, it will get it's own module later):


// will spawn the text bubble at the given coordinates
this.bubble = ig.game.spawnEntity( ig.EntityTextOutput, 40, 40 );

// this won't change the position of the text bubble
this.bubble.pos.x = 200;
this.bubble.pos.y = 150;

// will move the text bubble, but not the text inside, to the player
this.bubble.moveTo( ig.game.getPlayer() );

Is there something i missed, or i did wrong? I also can upload a demo if it helps!

I could position my text bubble via the spawnEntity-method. In my case it does not need to alter it's position after it spawns, but i have the feeling that the behavior mentioned above might not be intended. And someday, in another game, i might need an text bubble that alter it's position after spawning.

So as always, any help is highly appreciated! Thanks :-)

collinhover commented 10 years ago

Try setting fixed and posAsPct back to true and then call moveTo. Does that move the text with it?

Pattentrick commented 10 years ago

Setting fixed and posAsPct back to true, and call moveTo, will display the text bubble in the upper left corner without any text inside it.

I also ran into a new problem. The dimensions of the text bubble gets messed up on browser resize, with small font sizes even on "start up". I am making a video about that problem right now, should i post it in here or start a new issue?

collinhover commented 10 years ago

You can post it here. Are you using the r6 release (master branch)?

collinhover commented 10 years ago

Also you can look through the conversation entity source code to see exactly how it handles text bubbles, but what you're describing is not normal behavior.

Pattentrick commented 10 years ago

Here is the video:

http://www.youtube.com/watch?v=5_mdpw229nM

The text bubble is too big/small in comparison to the text when i minimize/maximize the browser window. On my 10px font it's initially broken (first example in the video), the second example (11px font) works fine at the beginning but gets messed up after the window resize.

Yes, i switched to the current r6 release a few days before, because of all the cool new changes ;-)

Hmmm ... really strange. I just played your game BIO FLEET and there it works like a charm.

collinhover commented 10 years ago

Do you have a demo? I'll try to look at it after Thanksgiving =)

Pattentrick commented 10 years ago

Happy Thanksgiving @collinhover!

I made a demo to showcase my problems:

https://github.com/Pattentrick/text-bubble-issue

I spawn the the text bubble (i changed it to a textbox in the meantime) inside the speak-method of this file:

'game.entities.player'

The text bubble/text box is located under:

'game.entities.text-output'

Thanks for looking at this after Thanksgiving! I am sure that i just messed something up.

Pattentrick commented 10 years ago

Small update: The demo does not use the r6 release! I switched some files by accident. However, using the r6 release with the demo will not solve the problem. I tried that just a few minutes ago.

collinhover commented 10 years ago

Sorry it took me so long to look at this! This issue is caused by not pre-loading the font file, so the text box/bubble doesn't know what size the text is when it tries to resize it. For now, you can fix this one of two ways while I work on a fix to handle late loading fonts:

A. Add the font to your text output class (which extends the text box/bubble class).

ig.EntityTextOutput = ig.global.EntityTextOutput = ig.UITextBox.extend({
...
textSettings: {
    font: new ig.Font( _c.PATH_TO_MEDIA + 'my_font_file.png' )
}
...
});

B. Change the CHAT font config by modifying your user config file.

ig.CONFIG_USER = {
...
FONT: {
    CHAT_NAME: 'my_font_file.png'
}
...
};

Both methods work for me when I resize, no matter how big or small the screen is. In your video it looks like some of the text was missing when you made the window really small, and that is expected behavior. Basically, if the text is too large for the text box, the text box only displays what will fit. My suggestion is that when you call your custom speak method on the player, instead of spawning a text box, spawn a conversation and trigger it. Let the conversation handle all the specifics for you ;)

collinhover commented 10 years ago

I'm pushing a change to the dev branch that should fix this issue without needing to preload all images, and it fixes a nasty little bug with resizing too. Now all images loaded after the game has started will force a global resize on the next update.

Pattentrick commented 10 years ago

Glad to hear from you @collinhover :D I hope that you had a pleasant thanksgiving!

Thanks for looking at this again. I downloaded the current dev branch. As you said the bug is now fixed. Thanks! Unfortunately i still can't position the UITextBox via pos or moveTo after spawning. I also had a new problem with resizing. Some of my UI elements were not resizing properly.

You can see a video of this problem here:

http://www.youtube.com/watch?v=hr9ff_groTI&feature=youtu.be

I fixed that by setting the performance back to static. Setting performance to dynamic fixed another problem of mine. But for that problem i found a better solution now. So setting performance to dynamic seems to be also an issue right now (maybe just on UI elements).

Pattentrick commented 10 years ago

Small update. When i try to load a level inside Weltmeister i get a a TypeError:

Uncaught TypeError: Cannot call method 'add' of undefined entity.js:1202

I am using the dev branch right now.

collinhover commented 10 years ago

What is the call stack for the error in WM?

Pattentrick commented 10 years ago

Here is the WM call stack:

ig.EntityExtended.ig.Entity.extend.resetExtras entity.js:1202
ig.EntityExtended.ig.Entity.extend.reset entity.js:1062
ig.EntityExtended.ig.Entity.extend.init entity.js:1021
Class impact.js:538
wm.EditEntities.ig.Class.extend.spawnEntity edit-entities.js:342
wm.Weltmeister.ig.Class.extend.loadResponse weltmeister.js:316
n jquery-1.7.1.min.js:2
o.fireWith jquery-1.7.1.min.js:2
w jquery-1.7.1.min.js:4
d jquery-1.7.1.min.js:4
send jquery-1.7.1.min.js:4
f.extend.ajax jquery-1.7.1.min.js:4
wm.Weltmeister.ig.Class.extend.load weltmeister.js:290
wm.ModalDialogPathSelect.wm.ModalDialog.extend.clickOk modal-dialogs.js:102
f.event.dispatch jquery-1.7.1.min.js:3
h.handle.i
collinhover commented 10 years ago

UI Elements should not be dynamic. If you need to move them, set their performance to movable, otherwise they are static by default. Dynamic is for entities with full collisions, which UI elements can't do because they're built to be static.

The WM bug is fixed in the latest dev. It was a silly mistake on my part, sorry! I'm not sure what the best way to fix the issue of positioning after spawn for UI elements is... I'll keep working on it.

Pattentrick commented 10 years ago

Hi @collinhover,

you suggested to spawn a conversation and trigger it. To be honest, i dont need to position my textbox manually in my special case. In my game the player needs to speak, if he does something (like combining items in his inventory, picking up items, interacting with objects in the game world). So I think the conversation entity does exactly what i need.

But how do i spawn and trigger a conversation entity? I looked at the example in the conversation.js file, but i had some troubles getting it to work. Also i found no code on this at the supercollider demo (except for some conversation entity in the level source?).

So could you please give me an example how i can spawn and trigger a conversation entity? That would be so great! Thanks in advance :D

collinhover commented 10 years ago

Programmatically, you can do it as you would with any trigger:

var myConversation = ig.game.spawnEntity(ig.EntityConversation, 0.0, 0.0, { /* conversation settings, steps, etc */ });
// if you don't want to add all the steps in a giant settings/options object in the spawn call
// call: myConversation.addStep(...);
myConversation.trigger();

You should be able to replace trigger with activate and it will do the same.

Pattentrick commented 10 years ago

I am sorry, i still dont get it :-(

Like this? (Code does nothing)

javascript var myConversation = ig.game.spawnEntity(ig.EntityConversation, 0.0, 0.0);

myConversation.addStep( 'player', 'hello world', 1, { font: new ig.Font( _c.PATH_TO_MEDIA + 'monologue_font_10px.png' ) });

myConversation.trigger();



Could you help me with a an example on how to actually setup a step for the conversation entity?
collinhover commented 10 years ago

You've reversed the entity name and text, it should be:

myConversation.addStep( textToSay, entityName, ...);

Conversations hook into speakers, i.e. entities that are saying the words of that step in the conversation, and a step is skipped if a speaker cannot be found for that step. If you're looking for something more like comic book narration boxes that doesn't require a speaker for each step, try ig.EntityNarrative.

Pattentrick commented 10 years ago

Switching the first two parameters fixed my problem! You are the best collin :D

So is this example in the conversation.js a typo? (line 55)

javascript // the following is the same as the above // only it adds onto the end of the conversation conversation.addStep( 'player', 'hello world', 2, {...} );



Or did i just mistunderstood that part? 
collinhover commented 10 years ago

Oops, yes, that is a typo. Thank you for pointing that out.

Pattentrick commented 10 years ago

The conversation module is very cool, you did a great job on this!

But i noticed that the space between the speaker and the text bubble depends on the length of the text. Small textbubbles are not far away enough, and big textbubbles are too far away.

Video:

http://www.youtube.com/watch?v=Se3WzHDq6e4

It would be awesome if the space between the speaker and the textbubble is always the same, no matter how long the text is. Is this possible, or should i split up my text into different steps to avoid this issue?

Here is my whole code on this:

javascript var textbubble = ig.game.spawnEntity(ig.EntityConversation, 0.0, 0.0);

        textbubble.messageMoveToSettings = {
            matchPerformance: true,
            offsetPct: { x: 0, y: -1.8 },
        };

        textbubble.addStep( text, 'player', 1, {
            cornerRadius: 5,
            pixelPerfect: true,
            padding: {
                x: 5,
                y: 4
            },
            textSettings: {
                font: new ig.Font( _c.PATH_TO_MEDIA + 'monologue_font_10px.png' )
            },
            triangleLength: 5
        });

        textbubble.trigger();


Maybe i just messed something up :O

Edit: I added the wrong properties (cameraFollows, durationPerLetter) to the messageMoveToSettings. Fixed that. However, that had nothing to do with the spacing/gap problem.
collinhover commented 10 years ago

I think, instead of using offsetPct in the move to settings, use offset and give it a fixed value. See notes here: https://github.com/collinhover/impactplusplus/blob/master/lib/plusplus/core/entity.js#L3446. I always try build things in such a way that devs can position things by percent or by a fixed value. Let me know if that does not work as expected.

Pattentrick commented 10 years ago

Good to know that i can use a fixed value or or a percentage at any time! Unfortunately the problem stays the same when using a fixed value.

javascript textbubble.messageMoveToSettings = { matchPerformance: true, offset: { x: 0, y: -40 } };



I seems that the textbubble gets centered on the entity. On a small textbubble an offset of `-40` is enough to place it on top of the entity, but on a big textbubble `-40` is not enough.

Hmmm ...
collinhover commented 10 years ago

You're correct that entities are aligned center to center when using move to, so you'll also need to change the align property: https://github.com/collinhover/impactplusplus/blob/master/lib/plusplus/core/entity.js#L3442

Pattentrick commented 10 years ago

Thank you for clarifying this for me! Works like a charm!

Using it like this:

javascript textbubble.messageMoveToSettings = { matchPerformance: true, offset: { x: 0, y: -30 }, align: { x: 0.5, y: 1 } };



Does exactly what i was looking for! Thank you so much @collinhover!
Pattentrick commented 10 years ago

The game crashes in Firefox (v 25.01) when i try to spawn a conversation.

TypeError: this.data is undefined
image-drawing.js (Line103)

InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable ctx.drawImage( image, 0, 0, realWidth, realHeight );
impact.js (Line 222)

TypeError: this.data is undefined
this.width = this.dataWidth = this.data.width = width | 0;
image-drawing.js (Line103)

The last thing that i can see before the game chrashes, is an empty textbubble. Maybe that has something to do with not preloading the font that i am using there?

javascript textbubble.addStep( text, 'player', 1, { r: 1, g: 1, b: 1, cornerRadius: 5, pixelPerfect: true, padding: { x: 5, y: 4 }, textSettings: { font: new ig.Font( _c.PATH_TO_MEDIA + 'monologue_font_10px.png' ) }, triangleLength: 5 });



I could provide a demo if that helps :-/

PS: No problems in Chrome, IE and Opera though.
collinhover commented 10 years ago

this.data is a reference to the sprite sheet or image used to draw. In this case it would be the font file as you suggest. It shouldn't be drawing with the data if is doesn't exist, so this is likely a bug, but have you tried preloading the font?

Pattentrick commented 10 years ago

Preloading the font fixed the problem!

javascript ig.CONFIG_USER = { ... FONT: { CHAT_NAME: 'my_font_file.png' } ... };

collinhover commented 10 years ago

Good, but we still shouldn't be getting errors if an image isn't preloaded. Most recent push should fix that, would you mind double checking on your end by not preloading the font?

Pattentrick commented 10 years ago

I added the image.js, font.js and the image-drawing.js from the latest dev to my project. However the game still crashes in firefox when i dont preload the font in my config-user.js.

Should i upload a demo for you for further debugging?

collinhover commented 10 years ago

That would help! I'll try to look into it later today.

Pattentrick commented 10 years ago

Here is the link to the demo:

https://github.com/Pattentrick/firefox-issue

After the game starts, i spawn a textbubble (main.js --> tellBackgroundStory-method). If i dont preload the font image, firefox crashes.

PS: The impact and weltmeister source code is not included in the demo.

collinhover commented 10 years ago

This is related: http://lists.w3.org/Archives/Public/public-whatwg-archive/2013Mar/0005.html

When the image is not preloaded, it is getting a size of 0 in some cases, and Firefox doesn't appreciate dimensions of 0. Next push should fix all errors whether you preload or not. However, if you don't, fonts/images/ui may not resize correctly until the image is loaded, which is the correct behavior.

Pattentrick commented 10 years ago

Whoa, good to know. I think i will preload everything in the future. Better safe than sorry ;-)

Thanks for your hard work and support on this!