Javafx mouse events working, key press events not caught - javafx

I have an existing application that I use to move a 3 d plot of star patterns around (translate, rotate, etc.)
I have the mouse events (press, drag, etc) working fine.
I wanted to added a keyboard event handler, but the odd thing is the it never catches any of the key press events.
I define a subscene to manage things. Here is the event setup and processing code. In the below code, all the mouse events work exactly as expected. No key press events are logged.
private void handleUserEvents() {
subScene.setOnKeyPressed((KeyEvent key)-> {
log.info("key={}", key);
});
subScene.setOnScroll((ScrollEvent event) -> {
double deltaY = event.getDeltaY();
zoomGraph(deltaY * 5);
updateLabels();
});
subScene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
);
subScene.setOnMouseDragged((MouseEvent me) -> {
int direction = userControls.isControlSense() ? +1 : -1;
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
// double modifier = 0.2;
double modifier = UserControls.NORMAL_SPEED;
if (me.isPrimaryButtonDown() && me.isControlDown()) {
log.info("shift sideways");
camera.setTranslateX(mousePosX);
camera.setTranslateY(mousePosY);
} else if (me.isPrimaryButtonDown()) {
if (me.isAltDown()) { //roll
rotateZ.setAngle(((rotateZ.getAngle() + direction * mouseDeltaX * modifier) % 360 + 540) % 360 - 180); // +
} else {
rotateY.setAngle(((rotateY.getAngle() + direction * mouseDeltaX * modifier) % 360 + 540) % 360 - 180); // +
rotateX.setAngle(
clamp(
(((rotateX.getAngle() - direction * mouseDeltaY * modifier) % 360 + 540) % 360 - 180),
-60,
60
)
); // -
}
}
updateLabels();
}
);
}

So in the question above. I played around with things and found an answer that seems to work. I don't like it much but it works so...
I added one line to the below code "subScene.requestFocus();" This forces the focus and makes it work.
subScene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
subScene.requestFocus();
}
);

Related

Every other rectangle detects the collison in my Java FX game

Okay so I have this little mock up game of clash royale and when I click to spawn a character and move him across the bridge on pane, I have a enemy on the bridge and my character stops as he should.
BUT, if I click to spawn my second character BEFORE he gets to enemy then the FIRST character moves through enemy and goes to tower.
Now, what I want to do is detect for every character, and they should all act the same. Not too sure what is going on here but I am needing a little assistance as this is the first game I've really made before.
private void createCharacter(CHARACTER choosenCharacter) {
gamePane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (elixir > 4) {
character = new ImageView(choosenCharacter.getUrl());
healthbar = new Rectangle(50.0, 10.0, Color.BLUE);
character.setLayoutX(event.getSceneX() - 40);
character.setLayoutY(event.getSceneY() - 110);
healthbar.setLayoutX(event.getSceneX() - 40);
healthbar.setLayoutY(event.getSceneY() - 110);
System.out.println(event.getSceneX() + "\n" + event.getSceneY());
elixir = elixir - 6;
if (detectCollision()){
gameTimer.stop();
}
createGameLoop(character, healthbar);
gamePane.getChildren().add(character);
gamePane.getChildren().add(healthbar);
}
}
});
}
private void moveCharacter(ImageView character, Rectangle health) {
if (character.getLayoutX() > 180 && character.getLayoutX() < 450) {
character.setLayoutX(character.getLayoutX() - 2);
}
if (health.getLayoutX() > 180 && health.getLayoutX() < 450) {
health.setLayoutX(health.getLayoutX() - 2);
}
if (character.getLayoutX() <= 200 && character.getLayoutY() > 2) {
character.setLayoutY(character.getLayoutY() - 2);
}
if (health.getLayoutX() <= 200 && health.getLayoutY() > 2) {
health.setLayoutY(health.getLayoutY() - 2);
}
if (character.getLayoutX() < 700 && character.getLayoutX() >= 450) {
character.setLayoutX(character.getLayoutX() + 2);
}
if (health.getLayoutX() < 700 && health.getLayoutX() >= 450) {
health.setLayoutX(health.getLayoutX() + 2);
}
if (character.getLayoutX() >= 700 && character.getLayoutY() > 2) {
character.setLayoutY(character.getLayoutY() - 2);
}
if (health.getLayoutX() >= 700 && health.getLayoutY() > 2) {
health.setLayoutY(health.getLayoutY() - 2);
}
}
private void createGameLoop(ImageView character, Rectangle health) {
gameTimer = new AnimationTimer() {
#Override
public void handle(long now) {
if (detectCollision()){
gameTimer.stop();
}
moveCharacter(character, health);
}
};
gameTimer.start();
}
As you can see it's not the cleanest code but I need some way to detect each instance of my game character and I'm just not able to think of how to solve this little problem of mine. Any suggestions on how to handle something like this would be greatfully appreciated. Thanks!

d3js: Use view port center for zooming focal point

We are trying to implement zoom buttons on top of a map created in D3 - essentially as it works on Google maps. The zoom event can be dispatched programmatically using
d3ZoomBehavior.scale(myNewScale);
d3ZoomBehavior.event(myContainer);
and the map will zoom using the current translation for the view. If using zoom buttons the focal point (zoom center) is no longer the translation but the center of the view port. For zoom using the scroll wheel we have the option of using zoom.center - but this apparently have no effect when dispatching your own event.
I'm confused as to how a calculate the next translation taking the new scaling factor and the view port center into account.
Given that I know the current scale, the next scale, the current translation and the dimensions of the map view port how do I calculate the next translation, so that the center of the view port do not change?
I've recently had to do the same thing, and I've got a working example up here http://bl.ocks.org/linssen/7352810. Essentially it uses a tween to smoothly zoom to the desired target scale as well as translating across by calculating the required difference after zooming to centre.
I've included the gist of it below, but it's probably worth looking at the working example to get the full effect.
html
<button id="zoom_in">+</button>
<button id="zoom_out">-</button>
js
var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoomed);
function zoomed() {
svg.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
function interpolateZoom (translate, scale) {
var self = this;
return d3.transition().duration(350).tween("zoom", function () {
var iTranslate = d3.interpolate(zoom.translate(), translate),
iScale = d3.interpolate(zoom.scale(), scale);
return function (t) {
zoom
.scale(iScale(t))
.translate(iTranslate(t));
zoomed();
};
});
}
function zoomClick() {
var clicked = d3.event.target,
direction = 1,
factor = 0.2,
target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
view = {x: translate[0], y: translate[1], k: zoom.scale()};
d3.event.preventDefault();
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() * (1 + factor * direction);
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
d3.selectAll('button').on('click', zoomClick);
A more succinct version of Wil's solution:
var vis = d3.select('.vis');
var zoom = d3.behavior.zoom()...
var width = .., height = ..;
function zoomByFactor(factor) {
var scale = zoom.scale();
var extent = zoom.scaleExtent();
var newScale = scale * factor;
if (extent[0] <= newScale && newScale <= extent[1]) {
var t = zoom.translate();
var c = [width / 2, height / 2];
zoom
.scale(newScale)
.translate(
[c[0] + (t[0] - c[0]) / scale * newScale,
c[1] + (t[1] - c[1]) / scale * newScale])
.event(vis.transition().duration(350));
}
};
function zoomIn() { zoomByFactor(1.2); }
function zoomOut() { zoomByFactor(0.8); }
I've found this to be quite difficult to do in practice. The approach I've taken here is to simply create a mouse event that triggers the zoom when the zoom buttons are used. This event is created at the center of the map.
Here's the relevant code:
.on("click", function() {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent(
'dblclick', // in DOMString typeArg,
true, // in boolean canBubbleArg,
true, // in boolean cancelableArg,
window,// in views::AbstractView viewArg,
120, // in long detailArg,
width/2, // in long screenXArg,
height/2, // in long screenYArg,
width/2, // in long clientXArg,
height/2, // in long clientYArg,
0, // in boolean ctrlKeyArg,
0, // in boolean altKeyArg,
(by > 0 ? 0 : 1), // in boolean shiftKeyArg,
0, // in boolean metaKeyArg,
0, // in unsigned short buttonArg,
null // in EventTarget relatedTargetArg
);
this.dispatchEvent(evt);
});
The whole thing is a bit of a hack, but it works in practice and I've found this much easier than to calculate the correct center for every offset/zoom.

Items drawing relative to player when near edge of map

I have a tile engine and that's all working swell, my player walks around all good, I'm working on adding items, the player is always in the centre of the screen, until he gets close to the edges of the world then he starts going close to the edges.
When I draw items in the world, they draw fine, except when the player leaves the centre (at the edge of the world). I just can't wrap my head around how to fix this.
public static void Draw(SpriteBatch spriteBatch, World w, Item i, Player p, Point screenDimensions)
{
bool IsOnScreen = true;
float leftX = p.X - ((screenDimensions.X / 32) / 2);
float rightX = leftX + (screenDimensions.X / 32);
float topY = p.Y - ((screenDimensions.Y / 32) / 2);
float bottomY = topY + (screenDimensions.Y / 32);
if (i.x < leftX || i.x > rightX || i.y < topY || i.y > bottomY)
IsOnScreen = false;
if (IsOnScreen)
i.animation.Draw(spriteBatch, (int)Math.Floor((i.x - leftX) * 32), (int)Math.Floor((i.y - topY) * 32));
}
Its pretty self explainatory, the world is passed in to get the dimensions (w.worldDimensions.x for width, and .y for height), the item is used to get the i.x and i.y (location in game world, not on screen), the player for drawing it relative (contains .x and .y for location) and then the screenDimensions.
Well it does not look very clear to me. Are you using a camera class? If you use a camera class and use that to navigate your world this should never happen.
Here is a basic one i currently use for my project.
class Camera
{
float zoom;
public float Rotation { get; private set; }
public Vector2 Position { get; private set; }
Matrix transform;
int velocity = 60;
UserInput input;
public float Zoom
{
get { return zoom; }
set { zoom = value; if (zoom < 0.1f) zoom = 0.1f; } // Negative zoom will flip image
}
public Camera(UserInput input)
{
zoom = 1.0f;
Rotation = 0f;
Position = new Vector2(0, 0);
this.input = input;
}
public void MoveCam()
{
if (input.IsKeyHold(Keys.Up))
{
Position += new Vector2(0, -velocity);
}
if (input.IsKeyHold(Keys.Left))
{
Position += new Vector2(-velocity, 0);
}
if (input.IsKeyHold(Keys.Down))
{
Position += new Vector2(0, velocity);
}
if (input.IsKeyHold(Keys.Right))
{
Position += new Vector2(velocity, 0);
}
if (input.IsKeyHold(Keys.OemMinus))
{
Zoom -= .01f * Zoom;
}
else if (input.IsKeyHold(Keys.OemPlus))
{
Zoom += .01f * Zoom;
}
}
public void FollowCam(int xPos, int yPos)
{
Position = new Vector2(xPos * TileData.Width, yPos * TileData.Height);
}
public Matrix TransformMatrix(GraphicsDevice device)
{
transform = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
Matrix.CreateRotationX(MathHelper.ToRadians(Rotation)) *
Matrix.CreateRotationY(MathHelper.ToRadians(Rotation)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation)) *
Matrix.CreateScale(new Vector3(zoom, zoom, 0)) *
Matrix.CreateTranslation(new Vector3(device.Viewport.Width * 0.5f, device.Viewport.Height * 0.5f, 0));
return transform;
}
}
Just instantiate the class like in main and use this in your draw method.
batch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, camera.TransformMatrix(graphicsDevice));
batch.End()
Draw everything in your world within this spritebatch and use a new basic to draw to screen cooridinates, like a gui/hud. You can use the camera move method to move it manually and the lock to lock it on any location (it follows if updated).
If you have large maps you might want to render only necessary tiles. I do it like this in my map class:
public void Draw(SpriteBatch batch, Vector2 camPosition, float camZoom, GraphicsDevice device)
{
float top = (camPosition.Y / TileData.Height) - ((device.Viewport.Height / 2) / TileData.Height + 1) / camZoom;
float bottom = (camPosition.Y / TileData.Height) + ((device.Viewport.Height / 2) / TileData.Height + 2) / camZoom;
float left = (camPosition.X / TileData.Width) - ((device.Viewport.Width / 2) / TileData.Width + 1) / camZoom;
float right = (camPosition.X / TileData.Width) + ((device.Viewport.Width / 2) / TileData.Width + 2) / camZoom;
for (int y = (int)top; y < (int)bottom; y++)
{
for (int x = (int)left; x < (int)right; x++)
{
if (y >= 0 && y < map.GetLength(1) && x >= 0 && x < map.GetLength(0))
{
batch.Draw(map[x, y].texture, new Rectangle(x * TileData.Width, y * TileData.Height, TileData.Width, TileData.Height), Color.White);
}
}
}
}
Here first i figure out which tiles to draw from each direction. Note the camZoom, you want more tiles to be drawn when zooming out. Then i use these "bounderies" in my for loops, the if statement makes sure i am not accessing tiles that dont exist (out of bounds).

cocos2d extend touch area from a button

I got some radiobuttons but the toucharea is to small. The toucharea depends on the image size. Is there an elegant way to extend the touch area with cocos2d without using a bigger image or make my own touch areas with cgrect?
setContentSize do what I want. Unfortunately the image moves to the left bottom corner of the contentsize. Set the anchorpoint moves the contentsize around but the image stays in the left bottom corner.
CCMenuItem* pickEasy = [CCMenuItemImage itemFromNormalImage:#"radiobutton_off.png" selectedImage:#"radiobutton_on.png" target:self selector:#selector(pickEasyTapped:)];
pickEasy.position = ccp(ss.width * 0.40, ss.height * 0.78);
[pickEasy setContentSize:CGSizeMake(50, 50)];
Thanks in advance.
Taking the original answer code...
CCMenuItem* pickEasy = [CCMenuItemImage itemFromNormalImage:#"radiobutton_off.png" selectedImage:#"radiobutton_on.png" target:self selector:#selector(pickEasyTapped:)];
pickEasy.position = ccp(ss.width * 0.40, ss.height * 0.78);
[pickEasy setContentSize:CGSizeMake(50, 50)];
... you only have to set the image in the correct position...
[[[pickEasy children] objectAtIndex:0] setAnchorPoint:ccp(0.5,0.5)];
[[[pickEasy children] objectAtIndex:1] setAnchorPoint:ccp(0.5,0.5)];
[[[pickEasy children] objectAtIndex:0] setPosition:ccp(pickEasy.contentSize.width/2,pickEasy.contentSize.height/2)];
[[[pickEasy children] objectAtIndex:1] setPosition:ccp(pickEasy.contentSize.width/2,pickEasy.contentSize.height/2)];
...only with 4 lines of code! Have fun!
Also, you can change activeArea property of CCMenuItem. (cocos2d 2.x)
CGRect active = [someMenuItem activeArea];
[someMenuItem setActiveArea:CGRectMake(active.origin.x - active.size.width * 2.f, active.origin.y - active.size.height * 2.5f, active.size.width * 2.f, active.size.height * 2.f)];
[someMenu addChild:someMenuItem];
You need to override the rectInPixels method
- (CGRect)rectInPixels
{
CGSize s = [self contentSize];
return CGRectMake(0, 0, s.width, s.height);
}
- (BOOL)containsTouchLocation:(UITouch *)touch
{
CGPoint p = [self convertTouchToNodeSpace:touch];
CGRect r = [self rectInPixels];
return CGRectContainsPoint(r, p);
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
for (UITouch *aTouch in allTouches) {
if ( ![self containsTouchLocation:aTouch] ) return NO;
}
return YES;
}
This just tells the sprite to check that the touch lyes within your altered CGRect
Edit to show CCSprite subclass ---
- (void)onEnter
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[super onEnter];
}
- (void)onExit
{
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
[super onExit];
}
I made a workaround by overriding -(CCMenuItem*) itemForTouch:(UITouch *)touch from CCMenu.
-(CCMenuItem*) itemForTouch:(UITouch *)touch
{
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
CCMenuItem* item;
CCARRAY_FOREACH(children_, item)
{
if ([item visible] && [item isEnabled]) {
CGPoint local = [item convertToNodeSpace:touchLocation];
CGRect r = [item rect];
r.origin = CGPointZero;
// increase rect by * 2
// a rect at bottom left of the image
CGRect bigR = CGRectMake(r.origin.x - r.size.width, r.origin.y - r.size.height, r.size.width * 2, r.size.width * 2);
// a rect at top right of the image
CGRect bigR2 = CGRectMake(0, 0, r.size.width * 2, r.size.width * 2);
if (CGRectContainsPoint(bigR, local) || CGRectContainsPoint(bigR2, local)) {
return item;
}
}
}
return nil;
}
Center the rect in the middle of the image didnt worked

How to get a stage calculation to resize an image w/r/t c

I am trying to center an image in the middle of the stage and scale it proportionally to the correct aspect ratio based on its loader's size when the image is loading.
In my main app runner I do:
private var rec:Rectangle = new Rectangle();
private var img:BitmapDisplay = new BitmapDisplay();
stage.addEventListener(Event.RESIZE, setRect);
img.imageURL = "some/path/to/image.jpg";
addChild(img);
private function setRect():void
{
var horz:Number = stage.stageWidth / 16;
var vert:Number = stage.y + stage.stageHeight / 16;
rec.x = horz;
rec.y = 80;
rec.width = stage.stageWidth - horz;
rec.height = stage.stageHeight - (vert * 2) - rec.y;
img.boundary = rec;
}
Then in my image (BitmapDisplay) class I have:
// sets the boundary of the image and resizes it
public function set boundary(rect:Rectangle):void
{
_imageBoundary = rect;
resizeImage();
}
private function resizeImage():void
{
var aspect:Number = _ldr.content.width / _ldr.content.height;
var cAspect:Number = _imageBoundary.width / _imageBoundary.height;
if (aspect <= cAspect)
{
_ldr.height = _imageBoundary.height;
_ldr.width = aspect * _ldr.height;
}
else
{
_ldr.width = _imageBoundary.width;
_ldr.height = _ldr.width / aspect;
}
var _pad:int = 7;
_ldr.x = (_imageBoundary.width - _ldr.width) / 2 + _imageBoundary.x - _pad;
_ldr.y = (_imageBoundary.height - _ldr.height) / 2 + _imageBoundary.y - _pad;
}
}
Is there something obvious keeping this from working right?
I want a 16th of the stage to be the bounds for the image and for the image to scale/resize based on this rect.
Thanks...
It turns out that this had more to do with the loader not being ready to be resized. There was some error checking in my code that needed to be reformatted, so now when the resize function gets called, it doesn't do anything until the loader is ready and the boundary is set.

Resources