I have an arrow:
Arrow ballArrow = new Arrow("Ball Arrow", 2, 0.175f);
And I want to resize it to make it longer whenever a key is pressed (say Key_5).
Which method would I call to resize it as when i'm calling .setLength() and .setWidth() they keep getting slashed out and i'm not sure what that means.
BTW i'm using jMonkeyEngine 2 not 3.
Looking at the Arrow API, setLength and setWidth are deprecated. To do the resizing, you should be doing:
Arrow ballArrow = new Arrow("Ball Arrow", 2, 0.175f);
....
float newLength = (somevalue);
float newWidth = (somevalue);
ballArrow.updateGeometry(newLength, newWidth);
Hope this helps.
Related
I've spent several frustrating hours trying to implement (what I thought would be) a simple FontActor class.
The idea is to just draw text at a specific position using a provided BitmapFont. That much, I've managed to accomplish. However, I'm struggling to compute my actor's width/height based on the rendered text.
(Using a FitViewport for testing)
open class FontActor<T : BitmapFont>(val font: T, var text: CharSequence = "") : GameActor() {
val layout = Pools.obtain(GlyphLayout::class.java)!!
companion object {
val identity4 = Matrix4().idt()
val distanceFieldShader: ShaderProgram = DistanceFieldFont.createDistanceFieldShader()
}
override fun draw(batch: Batch?, parentAlpha: Float) {
if (batch == null) return
batch.end()
// grab ui camera and backup current projection
val uiCamera = Game.context.inject<OrthographicCamera>()
val prevTransform = batch.transformMatrix
val prevProjection = batch.projectionMatrix
batch.transformMatrix = identity4
batch.projectionMatrix = uiCamera.combined
if (font is DistanceFieldFont) batch.shader = distanceFieldShader
// the actor has pos = x,y in local coords, but we need UI coords
// start by getting group -> stage coords (world)
val coords = Vector3(localToStageCoordinates(Vector2(0f, 0f)), 0f)
// world coordinate destination -> screen coords
stage.viewport.project(coords)
// screen coords -> font camera world coords
uiCamera.unproject(coords,
stage.viewport.screenX.toFloat(),
stage.viewport.screenY.toFloat(),
stage.viewport.screenWidth.toFloat(),
stage.viewport.screenHeight.toFloat())
// adjust position by cap height so that bottom left of text aligns with x, y
coords.y = uiCamera.viewportHeight - coords.y + font.capHeight
/// TODO: use BitmapFontCache to prevent this call on every frame and allow for offline bounds calculation
batch.begin()
layout.setText(font, text)
font.draw(batch, layout, coords.x, coords.y)
batch.end()
// viewport screen coordinates -> world coordinates
setSize((layout.width / stage.viewport.screenWidth) * stage.width,
(layout.height / stage.viewport.screenHeight) * stage.height)
// restore camera
if (font is DistanceFieldFont) batch.shader = null
batch.projectionMatrix = prevProjection
batch.transformMatrix = prevTransform
batch.begin()
}
}
And in my parent Screen class implementation, I rescale my fonts on every window resize so that they don't become "smooshed" or stretched:
override fun resize(width: Int, height: Int) {
stage.viewport.update(width, height)
context.inject<OrthographicCamera>().setToOrtho(false, width.toFloat(), height.toFloat())
// rescale fonts
scaleX = width.toFloat() / Config.screenWidth
scaleY = height.toFloat() / Config.screenHeight
val scale = minOf(scaleX, scaleY)
gdxArrayOf<BitmapFont>().apply {
Game.assets.getAll(BitmapFont::class.java, this)
forEach { it.data.setScale(scale) }
}
gdxArrayOf<DistanceFieldFont>().apply {
Game.assets.getAll(DistanceFieldFont::class.java, this)
forEach { it.data.setScale(scale) }
}
}
This works and looks great until you resize your window.
After a resize, the fonts look fine and automatically adjust with the relative size of the window, but the FontActor has the wrong size, because my call to setSize is wrong.
Initial window:
After making window horizontally larger:
For example, if I then scale my window horizontally (which has no effect on the world size, because I'm using a FitViewport), the font looks correct, just as intended. However, the layout.width value coming back from the draw() changes, even though the text size hasn't changed on-screen. After investigation, I realized this is due to my use of setScale, but simply dividing the width by the x-scaling factor doesn't correct the error. And again, if I remove my setScale calls, the numbers make sense, but the font is now squished!
Another strategy I tried was converting the width/height into screen coordinates, then using the relevant project/unproject methods to get the width and height in world coordinates. This suffers from the same issue shown in the images.
How can I fix my math?
Or, is there a smarter/easier way to implement all of this? (No, I don't want Label, I just want a text actor.)
One problem was my scaling code.
The fix was to change the camera update as follows:
context.inject<OrthographicCamera>().setToOrtho(false, stage.viewport.screenWidth.toFloat(), stage.viewport.screenHeight.toFloat())
Which causes my text camera to match the world viewport camera. I was using the entire screen for my calculations, hence the stretching.
My scaleX/Y calculations were wrong for the same reason. After correcting both of those miscalculations, I have a nicely scaling FontActor with correct bounds in world coordinates.
I just added the animation part and now whenever I click on the duck it does nothing to the score, but when I comment out the animation code and the duck stays still, it increments just fine.
Full Code
Animation:
KeyValue start = new KeyValue(duck.translateXProperty(), 10);
KeyValue end = new KeyValue(duck.translateXProperty(), 400);
KeyFrame startF = new KeyFrame(Duration.ZERO, start);
KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
Timeline tl = new Timeline(startF, endF);
tl.setCycleCount(Timeline.INDEFINITE);
tl.setAutoReverse(true);
tl.play();
Event Handler for Clicks:
scene.setOnMouseClicked(event -> {
if (duck.contains(event.getX(), event.getY())){
//increment score
incrementScore();
scoreDisplay.setText(""+score+"00");
}
});
Based on what you've said in the question and comments:
when I comment out the animation code and the duck stays still, it increments just fine
Whenever I click the ducks initial position, it increments the score
I'm guessing that your ImageView (the duck)'s bounding box is not getting updated.
From the ImageView docs:
This variable can be used to alter the location of a node without disturbing its layoutBounds, which makes it useful for animating a node's location.
This means that the image itself moves, and the duck is still considered to be at its initial position, so the contains() method wouldn't work. You'll have to find a different way of figuring out clicks
Perhaps what you could do is use the getTranslateX() property for your contains(), it seems like it'll get the current offset of the visual duck from the geometry duck:
scene.setOnMouseClicked(event -> {
double rawMouseX = event.getX();
double rawMouseY = event.getY();
double normalizedMouseX = rawMouseX - duck.getTranslateX();
double normalizedMouseY = rawMouseY - duck.getTranslateY();
if (duck.contains(normalizedMouseX, normalizedMouseY)){
//increment score
incrementScore();
scoreDisplay.setText(""+score+"00");
}
});
Disclaimer: I have no experience with JavaFX, I'm going mainly off of vanilla Java experience
You needn't calculate the normalized position or the mouse X/Y coordinates. You need to use the getBoundsInParent() method:
if (duck.getBoundsInParent().contains(event.getX(), event.getY())){
//increment score
incrementScore();
scoreDisplay.setText(""+score+"00");
}
Set your mouse handler on the duck, not the scene.
Also, use pickOnBounds to only react when you click on the duck's pixels, not it's bounding box.
duck.setPickOnBounds(false);
duck.setOnMouseClicked(event -> {
incrementScore();
scoreDisplay.setText(score + "00");
});
I'm using libGDX ProgressBar. I create it with the next code:
style = new ProgressBar.ProgressBarStyle();
style.background = new SpriteDrawable(backgroundImage);
NinePatch fillImage = new NinePatch(new TextureRegion(img2, 1, 1, img2.getWidth(), img2.getHeight()), 25, 26, 30, 30);
style.knobBefore = new NinePatchDrawable(fillImage);
style.knobBefore.setMinWidth(0);
style.knobBefore.setRightWidth(0);
style.knobBefore.setLeftWidth(0);
progress = new ProgressBar(0, 30, 0.1f, false, style);
When I set a value to a big number, for example 15 this is how it looks:[1]
but when I set a small value 1/30 (smaller the the basic 9 patch image) it looks bad:
Any ideas how to handle this problem?
You are setting your NinePatch width to be less than the minimum. The left and right do not change, so the minimum width your NinePatch can be is 25 + 26 = 51px. Any less than this you will get erroneous results. I believe the reason it looks like that is because your sides are actually pushing through each other, creating a negative width for the centre, which is still completely acceptable to draw with. (You can see in your image that the right is at the very far left, the left is most likely at the very far right but behind the reversed middle) The simplest solution would just be to limit the progress bar width to the proper minimum with something like this (if progress value is between 0 and 1);
//convert min width to a ratio
float minProgress = style.knobBefore.getMinWidth() / progressBarLength;
float progress = Math.max(progress, minProgress);
getMinWidth() would probably tell you it is 51/52px. You also shouldn't force setMinWidth(), setLeftWidht(), setRightWidth().
I'm experimenting with TextField and having problems with it when flipping the font. My orthographic camera is set to yDown = true. With that settings, the text is flipped so I came up with a solution to set BitmapFont's flip constructor parameter to true. But then when I try the code below. The text "Hello World" is rendering outside it's ninepatch borders. Here's a screenshot of it:
TextFieldStyle tfs = new TextFieldStyle();
NinePatch np = new NinePatch(new Texture(Gdx.files.internal(ResourceConstants.IMAGE_NINEPATCH)), 8, 8, 8, 8);
tfs.font = new BitmapFont(true);
tfs.fontColor = Color.BLACK;
tfs.background = np;
TextField tf = new TextField("Hello World", tfs);
tf.x = 50;
tf.y = 90;
tf.width = 100;
tf.height = 32;
addActor(tf);
tf.pack();
The problem is in the method where draw is being called on tfs (which is where the coordinates are set). The cartesian y coordinates for font are opposite other GDX objects, I think because typesetting needs to work a certain way.
So if you call
myFont.draw(spriteBatch, "Hello World", 0, 0);
Then you would expect the message to be drawn right in the bottom left hand side. But this is wrong! The fonts themselves are drawn from the top left, so your message will be drawn on the bottom left corner of the screen, below the bottom edge. We wont even be able to see the message.
But if we change the coordinates:
myfont.draw(spriteBatch, "box2d x: " + String.format("%2.2f", x), 10, 20);
We will see the message because we've given it enough room in the negative y direction to be displayed.
Given that the misbehaving font is misbehaving in the Y direction, and rendering below where you expect it to, I suspect that the above misconception is indeed the problem.
If you're not controlling any of the drawing coordinates of the bitmapfont itself, and this is solely handled by the TextField class, and the font is always out of bounds, no matter the size of your text field, then I would suspect a bug in GDX. You might try asking the forums about that.
I had the same effect. If you add TextField, and after, for example, add CheckBox and for this widget set setScale, then you will see this effect
TextField textfield = new TextField("Text field",skin);
stage.addActor(textfield);
CheckBoxStyle checkBoxStyle = skin.get(CheckBoxStyle.class);
checkBoxStyle.font.getData().setScale(2f);
CheckBox checkbox = new CheckBox("CheckBox", checkBoxStyle);
stage.addActor(checkbox);
I'm having a little problem with figuring something out (Obviously).
I'm creating a 2D Top-down mmorpg, and in this game I wish the player to move around a tiled map similar to the way the game Pokemon worked, if anyone has ever played it.
If you have not, picture this: I need to load various areas, constructing them from tiles which contain an image and a location (x, y) and objects (players, items) but the player can only see a portion of it at a time, namely a 20 by 15 tile-wide area, which can be 100s of tiles tall/wide. I want the "camera" to follow the player, keeping him in the center, unless the player reaches the edge of the loaded area.
I don't need code necessarily, just a design plan. I have no idea how to go about this kind of thing.
I was thinking of possibly splitting up the entire loaded area into 10x10 tile pieces, called "Blocks" and loading them, but I'm still not sure how to load pieces off screen and only show them when the player is in range.
The picture should describe it:
Any ideas?
My solution:
The way I solved this problem was through the wonderful world of JScrollPanes and JPanels.
I added a 3x3 block of JPanels inside of a JScrollPane, added a couple scrolling and "goto" methods for centering/moving the JScrollPane around, and voila, I had my camera.
While the answer I chose was a little more generic to people wanting to do 2d camera stuff, the way I did it actually helped me visualize what I was doing a little better since I actually had a physical "Camera" (JScrollPane) to move around my "World" (3x3 Grid of JPanels)
Just thought I would post this here in case anyone was googling for an answer and this came up. :)
For a 2D game, it's quite easy to figure out which tiles fall within a view rectangle, if the tiles are rectangular. Basically, picture a "viewport" rectangle inside the larger world rectangle. By dividing the view offsets by the tile sizes you can easily determine the starting tile, and then just render the tiles in that fit inside the view.
First off, you're working in three coordinate systems: view, world, and map. The view coordinates are essentially mouse offsets from the upper left corner of the view. World coordinates are pixels distances from the upper left corner of tile 0, 0. I'm assuming your world starts in the upper left corner. And map cooridnates are x, y indices into the map array.
You'll need to convert between these in order to do "fancy" things like scrolling, figuring out which tile is under the mouse, and drawing world objects at the correct coordinates in the view. So, you'll need some functions to convert between these systems:
// I haven't touched Java in years, but JavaScript should be easy enough to convey the point
var TileWidth = 40,
TileHeight = 40;
function View() {
this.viewOrigin = [0, 0]; // scroll offset
this.viewSize = [600, 400];
this.map = null;
this.worldSize = [0, 0];
}
View.prototype.viewToWorld = function(v, w) {
w[0] = v[0] + this.viewOrigin[0];
w[1] = v[1] + this.viewOrigin[1];
};
View.prototype.worldToMap = function(w, m) {
m[0] = Math.floor(w[0] / TileWidth);
m[1] = Math.floor(w[1] / TileHeight);
}
View.prototype.mapToWorld = function(m, w) {
w[0] = m[0] * TileWidth;
w[1] = m[1] * TileHeight;
};
View.prototype.worldToView = function(w, v) {
v[0] = w[0] - this.viewOrigin[0];
v[1] = w[1] - this.viewOrigin[1];
}
Armed with these functions we can now render the visible portion of the map...
View.prototype.draw = function() {
var mapStartPos = [0, 0],
worldStartPos = [0, 0],
viewStartPos = [0, 0];
mx, my, // map coordinates of current tile
vx, vy; // view coordinates of current tile
this.worldToMap(this.viewOrigin, mapStartPos); // which tile is closest to the view origin?
this.mapToWorld(mapStartPos, worldStartPos); // round world position to tile corner...
this.worldToView(worldStartPos, viewStartPos); // ... and then convert to view coordinates. this allows per-pixel scrolling
mx = mapStartPos[0];
my = mapStartPos[y];
for (vy = viewStartPos[1]; vy < this.viewSize[1]; vy += TileHeight) {
for (vx = viewStartPos[0]; vx < this.viewSize[0]; vy += TileWidth) {
var tile = this.map.get(mx++, my);
this.drawTile(tile, vx, vy);
}
mx = mapStartPos[0];
my++;
vy += TileHeight;
}
};
That should work. I didn't have time to put together a working demo webpage, but I hope you get the idea.
By changing viewOrigin you can scroll around. To get the world, and map coordinates under the mouse, use the viewToWorld and worldToMap functions.
If you're planning on an isometric view i.e. Diablo, then things get considerably trickier.
Good luck!
The way I would do such a thing is to keep a variable called cameraPosition or something. Then, in the draw method of all objects, use cameraPosition to offset the locations of everything.
For example: A rock is at [100,50], while the camera is at [75,75]. This means the rock should be drawn at [25,-25] (the result of [100,50] - [75,75]).
You might have to tweak this a bit to make it work (for example maybe you have to compensate for window size). Note that you should also do a bit of culling - if something wants to be drawn at [2460,-830], you probably don't want to bother drawing it.
One approach is along the lines of double buffering ( Java Double Buffering ) and blitting ( http://download.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html ). There is even a design pattern associated with it ( http://www.javalobby.org/forums/thread.jspa?threadID=16867&tstart=0 ).