Floating text : Would you use Nifty or BitmapText ? [Solved]

Hello,

I have a battle scene with a free camera. ( The player can move the camera freely with the mouse )

On some event, I want some texts to be displayed. ( For damage notification for example. )



In this picture, the ‘-256’ has been added manually. I wish to implement something similar.

My problem is as follow :

If the camera were static, I would use some BitmapText and put the at the correct location.

But since I don’t know when the user will rotate the camera, I need something readable even when the camera rotate.

I’ve think about Nifty text element that relocate them self when the camera move, but I don’t know of it’s wise.

If someone has already done something like that, please share your solution :smiley:

Edit :

I’ve found the class BillboardControl in the wiki. It may meet my needs !

But I don’t really know how to use it. Do someone have an example ? ( Including adding the BitmapText over the top of a Character node, if possible :slight_smile: )

Check the BillBoard control in the wiki. Should solve your problem. :slight_smile:

Thanks, madjack. Seems like I was reading the ‘custom control’ section while you were answering. :slight_smile:



During the past few months, I have build some knowledge on managing Nifty GUI.

But when it comes to manage 3D objects, I’m still a noob.



I will try to add a Bitmap text on the top of a Character and link a BillboardControl to it.

When you use the BillboardControl, whatever it is used with will always face the camera as you rotate, move etc. Like a sprite if you prefer.



If you want, you can also implement something along those lines when the object is off camera so an indicator is displayed to show interaction. We see that often in games where you have objectives outside the camera with some kind of graphical arrow or radar blip pointing the player in the right direction.

drooling

It would be awesome !



But, first I think, I will need to redo the basic trainings for newbe. :slight_smile:

I don’t even know how to add a BitmapText over the head of my characters …



I’m glad I asked before acting. Before writing this post, I was ready to create a Nifty GUI for that xD

The hard part is to figure out the screen coordinates.



rummages through code

By using the camera you can find the screen coordinates of an object. You need the object’s location vector.



[java]camera.getScreenCoordinates(vectorLocationOfObject);[/java]



By using the above, you don’t even need to use a BillBoardControl (mainly used for objects/pictures and such) if you only use text. As you have the screen coordinates, depending on the origin of whatever you’re referencing, you need to find the algorithm so that the text is centered above enemy or whatnot.



On the other hand, if it’s an object in 3D space you want to always face the camera (like a picture I use in my game as a selector), you will need to use the BillBoardControl.



I’m sure there’s other ways. I hope it’s clearer.

Solution 3 is probably the most difficult and computation heavy option, as well as contain little improvement over the simple getScreenCoordinates() tactic.

I suggest going with getScreenCoordinates() and a simple guiNode implementation, but do note, there is a pitfall: Objects BEHIND the camera are also simply cast to screen so stuff that should not be visible will be visible. I’d say cull these with a simple check (ie if(lengthsquared(CamForwardDir-normalize(3dTextPosition - camPosition) < 2)). This may sound computationally intensive but it actually barely is anything to the CPU imho (compare it to physics and AI).



Also, using nifty in general for this sounds a bit complicated to me, I think simply using the guiNode ‘by hand’ for this specific purpose is far easier, including updating the text’s alpha and scale values (really, you may scale guiNodes, it works!)

No offense to nifty of course, I think it’s a beautiful toolkit for gui’s, but a bit of a hassle for this specific purpose :wink:

getScreenCoordinates is a fairly fast operation, it just multiplies a matrix by a vector.


baalgarnaal said:
I suggest going with getScreenCoordinates() and a simple guiNode implementation, but do note, there is a pitfall: Objects BEHIND the camera are also simply cast to screen so stuff that should not be visible will be visible.

I am pretty sure there's a way to make sure that won't happen. Check the result from getScreenCoordinates() for anything outside the screen bounds ..

In any case, what should go into your decision:
1) Do you want the text to remain the same size regardless of how far away the battle is going on, and use niftygui effects on it? Use the niftygui text placed at a getScreenCoordinates() location
2) You want the text to be blocked by walls, and change size depending on how far away you are from it? Use BitmapText with BillboardControl

Well, you can use Nifty for HUD like stuff but it will work only for plain 2d stuff. The benefit of using Nifty for this would be that you could add Nifty effects to the elements making things more interessting.

However, you can’t link them to 3D elements directly.



super-big-fontBUT/super-big-font :wink:



(thinking out loud)

What would happen when we would have a Nifty effect that knows about JME and the current camera. Since effects are applied each frame to the element Nifty could find the screen coordinates of the 3d object and apply them to the Nifty element automatically (using an absolute position layout for the element of course). This way you could link the 2d position of the Nifty element to the 3d object pretty easily, I would think :slight_smile:



Opinions? :smiley:

At first, I was planning to do something like that :slight_smile:



With that method, I will be able to retrieve where to place the element

[java]

camera.getScreenCoordinates(vectorLocationOfObject);

[/java]



And you’re right : With the nifty method, it will be easier to add effects, like making the element scaling and disapear. ( Easier for me, at least )



The problem is in term of CPU consumption. ( I may have many floating texts appearing on the same time )



I don’t really know how it works, but calling

[java]

camera.getScreenCoordinates(vectorLocationOfObject);

[/java]

on each frame may cause some problem ? (or maybe not )



… After some thought, it might event be faster since there is no culling or collision to calculate with the other objects.

well, thinking about it again … maybe it will suck :slight_smile: because doing it with nifty it will always rendered above all else so it won’t affect the z-buffer.



In your example picture above when you move the camera a bit more to the right so that the -256 will be behind that right wall you will still see it rendered above the wall :frowning:

The fact the the damage are displayed above the wall is not a problem. ( I think it’s even better. You are informed that there is a battle behind it without knowing who are battling )



If the effects is always moving ( → Need to be redrawn at each frame ), will it still sucks ?

I’m totally clueless about how the Z-buffer works



The solution 3 would be to create a 3D vertical plane in the scene and put a Nifty texture on it :slight_smile:

This way It will be possible to have nifty effects and advantages of 3D elements.



I plan to implement that point in the next sprint.

Until then, all comments or different point of view are welcomed ! :smiley:

Thanks for all your answers.



I will choose this solution :


1) Do you want the text to remain the same size regardless of how far away the battle is going on, and use niftygui effects on it? Use the niftygui text placed at a getScreenCoordinates() location


For doing this, I created this control :
[java]
<controlDefinition name="battleNotif" controller="com.didigame.acariaempire.gui.controls.BattleNotifControl">
<panel width="100%" height="100%" childLayout="absolute" >
<text id="#notif" font="textDialog.fnt" textHAlign="center" textVAlign="bottom" wrap="true" >
<effect>
<onCustom customKey="damage" name="move" mode="in" direction="top" length="1000" inherit="true"/>
<onCustom customKey="damage" name="fade" start="#ff" end="#00" length="1000" />

<onCustom customKey="shout" name="fade" start="#ff" end="#00" length="1000" />
</effect>
</text>
</panel>
</controlDefinition>
[/java]

When I want to add a notif I do something like this :
[java]
private void buildShoutNotif( SceneCharacter sceneChar, String text )
{
BattleNotifControl control = buildNotif();

control.createShoutNotif( sceneChar, text );
}

private synchronized BattleNotifControl buildNotif() {
String id = "battleInfo-battleNotif_" + increment;
increment++;

ControlBuilder builder = new ControlBuilder( id, "battleNotif" );
builder.build( nifty, screen, notifPanel );
Element notif = screen.findElementByName( id );
notifPanel.layoutElements();
return notif.getControl( BattleNotifControl.class );
}
[/java]

This should create the control and give it the correct parameters.
But I have a problem, the effects do not starts

Here is my JAVA class for this control :
[java]
public class BattleNotifControl implements Controller
{
private Element mainElement;
private Element notifElt;

private String text;
private String customKey;
private Color color;
private boolean characterLinked;
private boolean init;

private int x, y, w, h;

public BattleNotifControl() {
text = "???";
customKey = "";
color = Color.WHITE;
characterLinked = false;
init = false;
x = 0;
y = 0;
w = 0;
h = 0;
}

public void bind ( final Nifty nifty,
final Screen screenParam,
final Element element,
final Properties parameter,
final Attributes controlDefinitionAttributes )
{
this.mainElement = element;
mainElement.setFocusable( false );

// Element
this.notifElt = mainElement.findElementByName("#notif");
}

public void createShoutNotif( SceneCharacter sceneChar, String text )
{
this.color = Color.WHITE;
this.customKey = "shout";

createNotif( sceneChar, text );
}

private void createNotif(SceneCharacter sceneChar, String text)
{
// Element position
x = 80;
y = 200;
w = 400;
h = 80;

this.text = text;
this.characterLinked = true;

if( init )
reloadEffect();
}


private void reloadEffect() {

//TODO
notifElt.setConstraintX( new SizeValue( x + "px" ) );
notifElt.setConstraintY( new SizeValue( y +"px" ) );
notifElt.setConstraintWidth( new SizeValue( w + "px" ) );
notifElt.setConstraintHeight( new SizeValue( h + "px" ) );

// Content
TextRenderer notif = notifElt.getRenderer( TextRenderer.class );
notif.setColor( this.color );
notif.setText( text );

//Effect -- TODO
notifElt.startEffect( EffectEventId.onCustom, null, customKey );
}

@Override
public void init(Properties arg0, Attributes arg1) {
init = true;
if( characterLinked )
reloadEffect();
}
[/java]

At first, I thought it was because I tried to start the effect before the initialization method ( So I created the boolean characterLinked and init )
But I seems that's not the problem...
Is there something I don't know about the launch of customEffects ?

One other thing :

What does the method camera.getScreenCoordinates() return ?



I thought it would something like :

result = { x position, y position, 1 }



But it seems its not the case :frowning:



I have a screen in 1280x780, but when I click on the middle of my screen, I retrieve something like :

{ 950.579, 526.82196, 0.9665849 }

AFAIK, you can dismiss the Z coordinate (in your case 0.9665849). I’m not sure exactly what it means but since you’re getting 2D coordinates it shouldn’t make a difference. Maybe it’s some rounding thing or shrug.

Does the screen have a default size that is taken into acount when using getScreenCoordinate ?



When I check settings.getWidth(), it returns 1280, but it seems it’s not the Screen width known by the camera. ( 1280 / 2 > 950 )

What exactly do you use to get those coordinates? A 3D object? A model? Simply clicking on the screen?



If it’s a 3D object, you might be getting its origin. Until I have more info I’m not sure I can venture more suggestions, not that I can give that many anyway. :wink:

It’s a 3D object : I use the method getLocation to retrieve it.



[java]

private void loadPhysicsNode( Vector3f position, Vector3f direction )

{

//


//Calculate collision shape
//
BoundingBox box = ((BoundingBox)model.getWorldBound());
float xHalfExtent = box.getXExtent();
float yHalfExtent = box.getYExtent();
float zHalfExtent = box.getZExtent();
float radius = Math.max(xHalfExtent, zHalfExtent);
//float height = yHalfExtent - radius;
float height = yHalfExtent;
CapsuleCollisionShape shape = new CapsuleCollisionShape( radius, height, 1);

//
//Create physics node
//
this.physicNode = new CharacterControl(shape, 0.01f );
//this.physicNode = new CharacterControl(shape, 0.01f );
this.model.addControl( physicNode );
this.physicNode.setUpAxis(1);
this.physicNode.setGravity(10.0f);
this.physicNode.setFallSpeed(0.1f);
this.physicNode.setJumpSpeed(10);
this.physicNode.setMaxSlope( FastMath.PI / 2.0f );

//
// Place it at the correct position
//
physicNode.setPhysicsLocation( position );

faceDirection( direction.getX(), direction.getZ() );
}
[...]
@Override
public Vector3f getLocation()
{
return model.getLocalTranslation();
}
[/java]

And this is how I retrieve it screen coordinates
[java]
public static Vector3f getCharacterTopPositionOnScreen( SceneCharacter sceneChar )
{
Vector3f sceneCharLocation = sceneChar.getLocation();
sceneCharLocation.setY( sceneCharLocation.y + (float) sceneChar.getCharacter().getAppearance().getHeight() + 1.0f );
Camera cam = application.getCamera();

Vector3f result = cam.getScreenCoordinates( sceneCharLocation );

System.out.println( "-- Char pos " + result.x + ", " + result.y + ", " + result.z );
if( result.getX() < 0 || result.getX() > getScreenWidth() )
return null;

if( result.getY() < 0 || result.getY() > getScreenHeight() )
return null;

return result;
}
[/java]

I might be wrong on this, but I do think that if you use a 3D object, you will get its origin.



So for example, if you have a dog model and you click on its nose but the model’s origin is at the tip of its tail, you’ll get the tip of the tail’s coordinates, not the nose where you’ve clicked.



There might be a way to get that point if you use a Ray that you’ll cast from your click on the screen. If the ray hits a model, you can extrapolates the hit part (retrieved by the ray) by doing some operation from the model’s origin you could get that area where you clicked… Convoluted I know. My knowledge is limited. :wink:

Sorry, I didn’t gave you the whole process :



For testing purpose, I test my message system while adding my character to the scene.


  • When the battle begin, I set my camera so that the ara in which I can add my character is in the middle of the screen
  • I click on this area
  • The game interpret my click : He create a Ray from it and retrieve the collision with my selection Area. ( Collision which is still in the middle of my screen )
  • The game add my character on that position ( via the method loadPhysicsNode )
  • After adding it, it launch a custom event saying that a new Character has entered the game.
  • This event is caught by my Nyfty ScreenController and try to create a message on the top of the newly created Character :

    [java]

    @Override

    public void treatEvent(GameEvent event)

    {

    […]

    //

// New Character
//
else if( AddCharacterToSceneEvent.class.equals( event.getClass() ))
{
AddCharacterToSceneEvent evt = (AddCharacterToSceneEvent) event;
int index = characterList.indexOf( evt.getNewCharacter() );
if( index == -1 )
newCharacter( evt.getNewCharacter() );
}
[...]
}

private void newCharacter(SceneCharacter newCharacter)
{
[...]
buildDamageNotif( newCharacter, -500 );
}

private void buildDamageNotif(SceneCharacter sceneChar, int dmg)
{
BattleNotifControl control = buildNotif();

control.createDamageNotif( sceneChar, dmg );
}
private synchronized BattleNotifControl buildNotif() {
String id = "battleInfo-battleNotif_" + increment;
increment++;

ControlBuilder builder = new ControlBuilder( id, "battleNotif" );
builder.build( nifty, screen, notifPanel );
Element notif = screen.findElementByName( id );
//notifPanel.layoutElements();
return notif.getControl( BattleNotifControl.class );
}
[/java]

And it's during the method createDamageNotif(...) that I retrieve the position of the character ( Via getCharacterTopPositionOnScreen(...) )

I will try to test it in a part of my code when less change occurs.