Just as a preface, I am very new to programming in JavaFX. (We had an introduction to JavaFX in one of my classes last semester, and for the last month or so I've been working on making a simple game in JavaFX.)
An issue I've run into is trying to detect the collision of a Pane within one StackPane with the Pane inside another StackPane. Specifically, I have a "Player" node in the Game class ("Player" extends abstract "Sprite" which extends StackPane) along with some "Asset" nodes ("Asset" being an abstract parent class that like "Sprite" also extends StackPane). Both "Player" and every "Asset" node are comprised of an ImageView and Pane objects that are to be the "boundaries" of the node.
Here's how I'm attempting to track collisions in the Game class, but it's not working:
protected void update() {
// Call playerBoundsHandler in update cycle
for (Asset asset : this.gameArea.getAssets()) {
playerBoundsHandler(asset);
} // for
} // update
private void playerBoundsHandler(Asset asset) {
for (Pane boundary : asset.getAssetBoundaries()) {
if (player.getPlayerStandingAreaBox()
.getBoundsInParent()
.intersects(boundary.getBoundsInParent())) {
// do stuff here
} // if
} // for
} // playerBoundsHandler
I'm guessing there's something wrong with using getBoundsInParent() here since I'm trying to track the intersection of child nodes within two separate nodes, but I have no idea what the solution is. Is there something I need to do with getBoundsInLocal or some other method?
Here's the relevant part of the Player class for clarification's sake:
/**
* Player class constructor.
* Player class extends "Sprite" (abstract class)
* which extends StackPane.
*/
public Player(double xSpawn, double ySpawn) {
// Add Player Standing Box (a Pane situated at the feet of the Player sprite)
this.playerStandingAreaBox = new Pane();
// width, height, etc. set here
this.getChildren().add(playerStandingAreaBox);
this.setAlignment(playerStandingAreaBox, Pos.BOTTOM_CENTER);
} // Player constructor
public Pane getPlayerStandingAreaBox() {
return this.playerStandingAreaBox;
} // getPlayerStandingAreaBox
The Asset child classes follow a design almost identical to the Player class here. In case it's also needed for clarification, here's the "Highway" class:
public class Highway extends Asset {
public Highway(double translateX, double translateY) {
// call super here
setAssetBoundaries();
} // Highway constructor
#Override
setAssetBoundaries() {
Pane boundaryOne = new Pane();
// set boundaryOne settings
this.getChildren().add(boundaryOne);
this.assetBoundaries.add(boundaryOne);
Pane boundaryTwo = new Pane();
// set boundaryTwo settings
this.getChildren().add(boundaryTwo);
this.assetBoundaries.add(boundaryTwo);
} // setAssetBoundaries
/**
* assetBoundaries is an ArrayList<Asset> object also inherited.
* getAssetBoundaries() is inherited from the "Asset" class
* which returns assetBoundaries.
*/
The screenshot below shows my Player sprite (don't judge the awful pixel art! I already know the guy's right arm looks janky and the rifle looks ridiculous!) with his standing box highlighted in red, and the boundaries of a "Highway" Asset highlighted in yellow at both the very top and very bottom. I want to register when the Player's box intersects one of the boxes of the Highway.
Player and Highway
Thanks, James_D. The following change does exactly what I want it to do.
private void playerBoundsHandler(Asset asset) {
for (Pane boundary : asset.getAssetBoundaries()) {
Bounds boundaryBoundsInScene = boundary.localToScene(boundary.getBoundsInLocal());
Bounds playerStandingBoxBoundsInScene = player.getPlayerStandingBoxArea()
.localToScene(player.getPlayerStandingBoxArea().getBoundsInLocal());
if (boundaryBoundsInScene.intersects(playerStandingBoundsInScene)) {
// do stuff here
} // if
} // for
} // playerBoundsHandler
Related
I installed a Layer in the GlassPane like that:
MobileApplication.getInstance().getGlassPane().getLayers().add(myLayer);
Now I would expect the content of myLayer to be alligned in the center of the glassPane like it is when I add a layer to View:
view.getLayers().add(myLayer)
Although alignment is set to center I get the following result:
I noticed the layoutBounds of the layer added to glassPane being all "0", while the layoutBounds of the layer in view are identical to the view layoutBounds.
Furthermore I don't need to call layer.show() as stated in the gluon documentation ("Showing a Layer is then achieved either by calling show(), ..."), because the layer is immediately shown after it is added to a layer.
Am I missing something?
I suggest you have a look at the detailed documentation here, and not only to the JavaDoc.
There you will find a more detailed explanation on how layers can be added and created.
The preferred way is by using addLayerFactory(), like:
#Override
public void init() {
addViewFactory(BASIC_VIEW, () -> new BasicView(BASIC_VIEW));
addLayerFactory("My Layer", () -> new SidePopupView(new StackPane(new Button("Side"))));
}
The layer will be hidden unless you show it with MobileApplication.getInstance().showLayer("My Layer").
You can create your own Layer implementation, like:
private class MyLayer extends Layer {
private final Node root;
private final double size = 150;
public MyLayer() {
root = new StackPane(new Button("A custom layer"));
root.setStyle("-fx-background-color: white;");
getChildren().add(root);
getGlassPane().getLayers().add(this);
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(size, size);
resizeRelocate((getGlassPane().getWidth() - size)/2, (getGlassPane().getHeight()- size)/2, size, size);
}
}
and add it as well to the factory:
addLayerFactory("My Layer", () -> new MyLayer());
But notice you will have to resize and relocate it, otherwise you will get 0,0 location as in your picture, and take care of its visibility.
Or you can use built-in layers, like SidePopupView, and you won't need to worry about these more low level details.
I'm trying to use a TranslateTransition object in JavaFX to move an onscreen object in a LOGO program I am building. I have an onscreen TurtleDisplay object, which extends ImageView, and this is what I'm trying to move. The code to move it is here:
public void drawTurtle(TurtleData currentData) {
TurtleImage inList = getTurtleImage(currentData);
if (inList==null) {
TurtleImage temp = new TurtleImage(currentData.getX(),
currentData.getY(), currentData.getHeading(), turtleImage);
myTurtlesGroup.getChildren().add(temp);
myTurtlesList.add(temp);
}
else {
TranslateTransition tt = new TranslateTransition(Duration.seconds(3),inList);
tt.setFromX(inList.getX());
tt.setFromY(inList.getY());
tt.setToX(inList.getX()+currentData.getX());
tt.setToY(inList.getY()+currentData.getY());
tt.setCycleCount(Timeline.INDEFINITE);
tt.play();
}
}
This code, which is part of the front end, is called from the back end via a Listener on an ObservableList. The backend contains this ObservableList of TurtleData objects that contain the information necessary to move a turtle on screen -- which turtle to move, the coordinate to move to, and the rotation of the turtle. The code to call this is here:
ObservableList<TurtleData> myTurtles = FXCollections
.observableArrayList();
myTurtles.addListener(new ListChangeListener<TurtleData>() {
#Override
public void onChanged(Change<? extends TurtleData> c) {
myDisplay.getSelectedWorkspace().getTV().clearTurtles();
while (c.next()) {
for (TurtleData addItem : c.getAddedSubList()) {
myDisplay.getSelectedWorkspace().getTV().drawTurtle(addItem);
}
}
}
});
I have stepped through with a debugger and ensured that this code is called -- specifically, the tt.play() line is run. Nothing moves on screen. Does anyone have any idea what is wrong? Do I need to setup an Animation Timeline? Thank you for any help!
This is a follow up to my last question:
How can I draw a circle to the screen with PlayN?
For my simple case, I want to programmatically create a single colored circle and move it across a 2-D plain (doesn't need to use box2d lib).
A real-world example would likely involve animating several circles. Two real-world examples for this case (sorry, I had to remove the links -- not enough karma!):
Browsmos for Chrome
Ants AI Challenge
It was suggested in response to my last question that I would want to use the ImmediateLayer class, so I am looking to understand how to properly incorporate this into my game loop.
Here's is my code sample:
public class SimpleCircleAnimation implements Game {
// Surface
private GroupLayer rootLayer;
private ImmediateLayer surface;
private Canvas canvas;
private Circle circle;
private CanvasImage circleImage;
#Override
public void init() {
// create root layer
rootLayer = graphics().rootLayer();
// a simple circle object
int circleX = 0; int circleY = 0;
int circleRadius = 20;
circle = new Circle(circleX, circleY, circleRadius);
// create an immediate layer and add to root layer
ImmediateLayer circleLayer = graphics().createImmediateLayer(new ImmediateLayer.Renderer() {
public void render (Surface surf) {
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circle.radius, circle.radius, circle.radius);
surf.drawImage(circleImage, circle.x, circle.y);
}
});
rootLayer.add(circleLayer);
}
#Override
public void paint(float alpha) {
}
#Override
public void update(float delta) {
// move circle
int newX = circle.x + 4; int newY = circle.y + 4;
circle.setPoint(newX, newY);
}
#Override
public int updateRate() {
return 25;
}
}
This successfully moves the circle diagonally down the screen from left to right. A couple questions:
Is this implemented properly?
In the case of multiple animated circles, is the idea with ImmediateLayer that you would create a circle image for each circle within the Renderer callback? Or would you perhaps create an Immediate Layer for each circle and add those to the root layer?
I would not use ImmediateLayer wiht render (Surface surf) adapter. Here u have, inside the render method creation of an image
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
just put this in the paint method
surf.drawImage(circleImage, circle.x, circle.y);
using the normal layer and u should be fine
Painting is done in paint method, and do not put calculations there
Update is for calculations, and physics oriented stuff
I discovered a detailed practical example of ImmediateLayer usage in the Cute Game source within the PlayN Samples:
CuteGame.java (code.google.com)
I wondered if anyone knew whether it was possible to remove the '+' Sign that appears next to the root item in a GWT Tree? I didn't see a CSS rule to handle it. Can one replace the + sign with say, a GIF?
(Adapted code from GWT manual below, for illustrative purposes)
TreeItem root = new TreeItem("root"); // Wish to remove + sign from next to this item
root.addItem("item0");
root.addItem("item1");
root.addItem("item2");
root.setState(true, true);
// Add a CheckBox to the tree
TreeItem item = new TreeItem(new CheckBox("item3"));
root.addItem(item);
Tree t = new Tree();
t.addItem(root);
// Add it to the root panel.
RootPanel.get().add(t);
CSS styling does not apply to this at all. Rather, it requires using a different constructor for tree.. If you set spacer.png to a 1x1 transparent PNG it will remove the + signs. Here is the full code I used.
The correct way to do it is as follows:
public interface MyResources extends ClientBundle {
public static final MyResources INSTANCE = GWT.create(MyResources.class);
#Source("spacer.png")
public ImageResource leaf();
}
public class TreeImagesExample implements Tree.Resources
{
#Override
public ImageResource treeClosed() {
// TODO Auto-generated method stub
return MyResources.INSTANCE.leaf();
}
#Override
public ImageResource treeLeaf() {
// TODO Auto-generated method stub
return MyResources.INSTANCE.leaf();
}
#Override
public ImageResource treeOpen() {
// TODO Auto-generated method stub
return MyResources.INSTANCE.leaf();
}
}
Main Function:
TreeImagesExample tx= new TreeImagesExample();
Tree t = new Tree(tx);
t.addItem(root);
For this to work properly, we will also need a SelectionHandler, of course, so that we can register tree clicks on the tree items rather than the (now non-existent) + signs.
You'll have to use CSS to override the plus icon image, which is set as the background image of the item with a left-margin to move the text off of it.
The root item doesn't have its own specific style so you'll have to add a style name (i.e., CSS class) on the tree item yourself and remove the background image yourself, in CSS.
I am writing an info-screen program. I created a full-screen widget and draw contents onto it.
In order to extend the life cycle of the TFT-display device, I want to implement a pixel-shifting feature. With other words, in every X minutes, I shift the screen to left/right/top/down for Y pixels.
My approach is as follows:
I use two layers (two QWidget).
I paint contents on the top layer.
When a pixel-shifting is performed, I just move the top layer for specified offset.
And then fill a background color to the bottom layer.
However, I found a problem:
If I move up the top layer for 10 pixels, the 10-pixel-content goes out of the screen. But when I move this layer down for 10 pixels. The 10-pixel-content will not be updated, it is gone.
How can I keep these 10-pixel-content? Is there any magic widget flag to solve this problem?
UPDATE 1:
The code is written in language D, but it is easy to understand:
class Canvas: QWidget
{
private QPixmap content;
this(QWidget parent)
{
super(parent);
setAttribute(Qt.WA_OpaquePaintEvent, true);
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.content = content;
update(region);
}
protected override void paintEvent(QPaintEvent event)
{
if (this.content !is null)
{
QPainter painter = new QPainter(this);
painter.setClipping(event.region);
painter.fillRect(event.region.boundingRect, new QColor(0, 0, 0));
painter.drawPixmap(event.region.rect, this.content);
this.content = null;
painter.setClipping(false);
}
}
}
class Screen: QWidget
{
private Canvas canvas;
this()
{
super(); // Top-Level widget
setAutoFillBackground(True);
this.canvas = new Canvas(this);
showFullScreen();
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.canvas.requestForPaint(content, region);
}
private updateBackgroundColor(QColor backgroundColor)
{
QPalette newPalette = palette();
newPalette.setColor(backgroundRole(), backgroundColor);
setPalette(newPalette);
}
public shiftPixels(int dx, int dy)
{
this.canvas.move(dx, dy);
updateBackgroundColor(new QColor(0, 0, 0)); // Just a demo background color
}
}
Screen screen = new Screen;
screen.requestForPaint(some_content, some_region);
screen.shiftPixels(0, -10);
screen.shiftPixels(0, 10);
Looking at the code, my first guess is that your region might be wrong. Try repainting the whole widget each time, and see if that solves the missing 10 pixel problem. If it does, then try working out why your region isn't covering the newly exposed portion.
One possibility along those lines: I notice in your Screen::requestForPaint method that you directly call the Canvas::requestForPaint without doing anything with the region. In Qt, the coordinates for anything like that are often assumed to be local, so if you don't account for the current position of the canvas widget, you might get an incorrect region.
Why not setting the position of the widget directly...? Another options might be using QPainter::translate(-1,-1) or something similar.