I use a Group to store some Images and draw them to a SpriteBatch. Now I want to detect which Image has been clicked. For this reason I add an InputListener to the Group to get an event on touch down. The incoming InputEvent got an method (getTarget) that returns an reference to the clicked Actor.
If I click on a transparent area of an Actor I want to ignore the incoming event. And if there is an Actor behind it I want to use this instead. I thought about something like this:
myGroup.addListener(new InputListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
Actor targetActor = event.getTarget();
// is the touched pixel transparent: return false
// else: use targetActor to handle the event and return true
};
});
Is this the right way to do it? I thought returning false for the method touchDown would continue propagation for the event and let me also receive touchDown events for other Actors at the same position. But this seems to be a misunderstanding...
UPDATE
P.T.s answer solves the problem of getting the right Event. Now I have got the problem to detect if the hit pixel is transparent. It seems to me that I need the Image as a Pixmap to get access. But I do not know how to convert an Image to a Pixmap. I also wonder if this is a good solution in terms of performance and memory usage..
I think you want to override the Actor.hit() method. See the scene2d Hit Detection wiki.
To do this, subclass Image, and put your hit specialization in there. Something like:
public Actor hit(float x, float y, boolean touchable) {
Actor result = super.hit(x, y, touchable);
if (result != null) { // x,y is within bounding box of this Actor
// Test if actor is really hit (do nothing) or it was missed (set result = null)
}
return result;
}
I believe you cannot accomplish this within the touchDown listener because the Stage will have skipped the Actors "behind" this one (only "parent" actors will get the touchDown event if you return false here).
Related
Here's a constructor for a card class I was making. When I create one that is scaled and click on it, only clicks within the 1.0x scaled area are actually registered. i.e. if I pass in 1.5 for the scale, clicks on the borders don't work. Why not? I've scaled the actor itself.
public Card(float x, float y, float scale)
{
this.setPosition(x, y);
faceSprite = new Sprite(MyResources.getInstance().cardTextureRegion);
faceSprite.setPosition(x, y);
faceSprite.setScale(scale);
borderSprite = new Sprite(MyResources.getInstance().cardBorderTextureRegion);
borderSprite.setPosition(x, y);
borderSprite.setScale(scale);
// Set boundaries for ourselves (the actor). Note that we have to match the scale of the sprites.
setSize(borderSprite.getWidth(), borderSprite.getHeight());
setScale(scale);
// Add ClickListener
final Card thisCard = this;
addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
((MyStage)(thisCard.getStage())).cardClicked(thisCard);
}
});
}
Basic Actor class might be missing some functionalities, including proper scale handling. If all you want is displaying some images (judging by the Sprite objects you use), I'd suggest using the existing classes rather than making custom actors - especially since you might have trouble rendering Sprites with exact Actor parameters if you use a lot of custom actions.
Image allows you to display "sprites" - although somewhat simplified, it should be enough. To store the images, you can use a Table (more flexible) or a Stack (will work with multiple images of the same size).
If you really want to stick with the custom actor approach, try this instead of changing the scale:
setSize(borderSprite.getWidth() * scale, borderSprite.getHeight() * scale);
If you don't change Card scale manually in runtime (only set it up during creation), it should just work.
You Can use image Class to display Images.
Texture texture=new Texture("test.jpg");
Image image=new Image(texture);
//you can use setsize() or setBounds() as per your requirement.
image.setScale(scale);
image.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y) {
((MyStage)(thisCard.getStage())).cardClicked(event.getTarget());
}
});
I am trying to implement a HUD stage on top of the current stage. I am able to render and draw the HUD stage but its buttons' touch events are not responding.
I am calling my drawHUD() from my draw() method of GameScreen. Code for drawHUD() is
public void drawHud(int scoreVal){
Hud.this.getCamera().update();
Hud.this.hudSpriteBatch.setProjectionMatrix(Hud.this.getCamera().combined);
hudSpriteBatch.begin();
scoreStr = "Score: " + scoreVal;
glyphLayout.setText(scoreFont, scoreStr);
halfWidth = glyphLayout.width/2;
halfHeight = glyphLayout.height/2;
scoreFont.draw(hudSpriteBatch, scoreStr, EatGame.CAMERA_WIDTH/2 - halfWidth, EatGame.CAMERA_HEIGHT - halfHeight);
hudSpriteBatch.end();
}
I have added a listener to my pauseBtn like this when creating my HUD
pauseBtn.addListener(new InputListener() {
#Override
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
System.out.println("PauseBtn Touched");
return false;
}
}
I have also tested by setting the bounds of the button, but it is not responding to events.
Thanks for reading and helping.
Usually you use the scene2d API for implementing buttons and stuff like that. The Stage class is responsible for handling the events on the correct actor. Look at the Stage#touchDown() how they do it. If you want to implement this on your own, you would have to implement a global touch-listener and check, if the coordinates are inside the bounds of the button and then call fire on the button with the correct Event object.
I would highly recommend to use the scene2d api, its far more easier then to write your own hit-detection-firing-event-system.
I had to use InputMultiplexer for taking input events from both the stages. I have done this that way.
InputMultiplexer multiplexer = new InputMultiplexer();
multiplexer.addProcessor(GameScreen.this);
multiplexer.addProcessor(hud);
Gdx.input.setInputProcessor(multiplexer);
This way it will receive events from both the stages. GameScreen and Hud are Stages.
I want to create a friendly-fire mechanism. Essentially, my cubes fire repeatedly a bullet to hit enemies. I also want to detect the hit between the bullet and another cube (friendly fire). In order to avoid that the bullet's boundaries overlap with the cube that fired it, in my "Cube" class i overrided equals. Each Cube has a unique id, initialized as soon as the Cube is created.
The issue is that i have a very bad bug. When i add the first Cube all behaves normally, but as soon as i add the second one, a collision is detected for both the cubes' bullets, instantly, even if they are not firing to each other. This happens even if i add more cubes. I checked the boundaries and they seems fine.
I put some code:
collision detection
// check collisions between bullets and cubes (friendly fire)
for(BaseBullet bullet : bullets) {
for(BaseSquare square : squares) {
// exclude collision between bullet and its own Cube
if (!bullet.getBaseSquare().equals(square)) {
// check if bounds overlap
if (square.getBounds().overlaps(bullet.getBounds())) {
System.out.println("collision between bullet and qube");
// propagate the event to anyone is interested in it
EventsManager.qubeHitByQube(bullet, square);
bullet.remove();
bulletsToDestroy.add(bullet);
}
}
}
}
cube's equals method
#Override
public boolean equals(Object o) {
if(!(o instanceof BaseSquare) || o == null) {
return false;
}
BaseSquare baseSquare = (BaseSquare)o;
return baseSquare.getUniqueID().equals(getUniqueID());
}
NOTE
With "friendly fire" i mean when your objects hit themselves and not the enemies.
I would suspect the problem is more likely coming from 'if (square.getBounds().overlaps(bullet.getBounds()))' than the square's equals() method.
Are 'BaseBullet' and 'BaseSquare' extending LibGDX's Sprite class? Should you not be calling getBoundingRectangle()?
I'm seeking a way to get the scrolling state of CCScrollView.
It seems not to be a rare requirement, but do I need to implement it?
Thanks:)
Edit: Following is my two 'brute force' ways, but they seems work.
The goal is to get the scroll condition of a CCScrollView s from a cocos2d::Layer l.
Way #1
In each iteration of update() function of l, get the content offset of s by
ScrollView::getContentOffset()
if it stays the same, we can assume that the ScrollView is not scrolling.
Way #2
Create class S which inherits CCScrollView and CCScrollViewDelegate, then in the override of the delegate's function
void scrollViewDidScroll(ScrollView* view)
(which seems to be called every time the ScrollView scrolls.) use a variable to save current time
/*uint64_t*/ lastScrollTime = mach_absolute_time();
then in the update() function of l, assume the ScrollView is not scrolling by a time threshold
curTime = mach_absolute_time();
if (GlobalUtils::machTimeToSecs(curTime - lastScrollTime) > 0.1)
Hope that works :)
So, in your's initScroll() method you should set:
scrollView = ScrollView::create();
// initialisation scrollview
scrollView->addEventListener(CC_CALLBACK_2(YourScene::testScroll, this));
this->addChild(scrollView);
and make bool variable isScrolled. Then in method testScroll() you need to check listener's event type and depending from it set the variable isScrolled:
void YourScene::testScroll(Ref* sender, ui::ScrollView::EventType type)
{
if (type == ui::ScrollView::EventType::SCROLLING)
isScrolling = true;
else
isScrolling = false;
}
You can see other values EventType here
One way without modifying the source code of CCScrollView is to check the flag of ScrollView::isDragging() and whether one of the scrolling selectors of ScrollView is scheduled.
bool isDeaccelerateScrolling = scrollView->getScheduler()->isScheduled(CC_SCHEDULE_SELECTOR(ScrollView::deaccelerateScrolling));
bool isPerformedAnimatedScroll = scrollView->getScheduler()->isScheduled(CC_SCHEDULE_SELECTOR(ScrollView::performedAnimatedScroll));
bool isScrolling = scrollView->isDragging() || isDeaccelerateScrolling || isPerformedAnimatedScroll;
I'm trying to implement a collision detector between my Player (Actor) and my Obstacle (Actor too), and I wonder what's the best way to perform a collision detection between them. I see some users saying that they create a Rectangle object in the class and update its bounds every frame, but I don't know if this is the best way to perform this (I'm also trying to make this, but my collision detector method triggers before my Player touches the Obstacle).
This is what I'm trying to check:
public boolean touchedWall() {
// Loop for the obstacles
for (Obstacle obstacle : this.world.getObstacles()) {
// Check if player collided with the wall
if (this.bounds.overlaps(obstacle.getBounds())) {
Gdx.app.log(Configuration.TAG, "collided with " + obstacle.getName());
return true;
}
}
return false;
}
And this is where the method triggers (it should trigger when the Player bounds hit the wall):
I figured out! Instead of using this.bounds.set(x, y, width, height), I was using this.bounds.set(x, y, height, width) like this:
this.bounds.set(this.getX(), this.getY(), this.getHeight(), this.getWidth());
Sorry for my mistake.