I have a libgdx game where I want to move a sprite when it is touched and dragged. If it comes close to other sprites I want to highlight them and I want to automatically slide into position if the sprite is released (touch up) and the sprite is close enough to another sprite.
So if I use actors the actors have to know about the other actors.
Is there already some mechanism to handle this in libgdx? Should I use something else than actors?
I could have a "manager" class which is called by the actors but I rather it would be the other way around, the "manager" knows about all the actors.
There's no easier mechanism in Scene2d than just iterating over actors and checking theirs position. If there are very many actors on the stage the operation can be expensive because of taking O(n2) (you have to check every actor with every others).
The good news is that you have to check only one actor with other which will make O(n) and is totally acceptable.
You also do not need any outer manager to check actors - just when actor is grabbed you should set it the flag and if the flag is true in the act actor's method you should check another actors - of course you need to create your own MyActor class extending Actor
class MyActor extends Actor
{
...
public boolean amIBeingDragged = false;
public void act(float delta)
{
super.act(delta);
if( amIBeingDragged )
{
Vector2 position = new Vector2(this.getX(), this.getY());
for(Actor actor : this.getStage().getActors())
{
if(actor == this) continue;
if(position.dst( new Vector2( actor.getX(), actor.getY() < thresholdDistance )
{
this.setPosition( actor.getX(), actor.getY() );
}
}
}
}
}
//drag's listener methods
public void dragStart(InputEvent event, float x, float y, int pointer)
{
( (MyActor) event.getTarget() ).amIBeingDragged = true;
}
public void dragStop(InputEvent event, float x, float y, int pointer)
{
( (MyActor) event.getTarget() ).amIBeingDragged = false;
}
...
There are of course geometric alghoritms to optimize case like this but in such simple usage I don't think it worths to deal with them.
If you want to try some alternative you can use Box2D (default Libgdx physics engine) collision system. The idea would be to give every actor a body and the body should have fixture bigger than actor. Then on collision you could handle the contact - but again if you aren't using box2d already it totally does not worth to implement it just for this.
Read more about Box2d collisions here.
Related
I am making a simple game with JavaFX, where a ball bounces around the screen after being released from the bottom (via a button). When the ball is bouncing around the pane, if it hits a Rectangle it changes its color to blue.
I am attempting to add a method called checkBounds to keep track of when the ball (a Circle) hits the Rectangle. As soon as the ball comes into contact with the rectangle, the score should increase by 10. The scoring mechanism works, but it continues to increment with each frame that the ball takes through the Rectangle, instead of only incrementing once when it enters. (eg. It should only go up by 10 one time, not continue going up by 10 the whole time the ball passes through). I callcheckBounds() 3 times in my timeline loop to check each rectangle on each loop iteration.
checkBounds(ball, gameSquare);
checkBounds(ball, gameSquare2);
checkBounds(ball, gameSquare3);
How would I fix this logic error? I've attempted several different options, but none seem to work.
private void checkBounds(Shape block, Rectangle rect) {
boolean collisionDetected = false;
Shape intersect = Shape.intersect(block, rect);
if (intersect.getBoundsInLocal().getWidth() != -1) {
collisionDetected = true;
}
if (collisionDetected) {
score.setText(Integer.toString(currentScore.getCurrentValue()));
currentScore.incrementBy(10);
rect.setFill(Color.BLUE);
}
}
I believe what you want to detect is the state change of your collisionDetected from false to true. One way to do this is to keep the state of the previous value as a member variable for each collision object. To do this, we need some identifying id for the rectangle, so you may want to pass in the id into the checkBounds method:
private void checkBounds(Shape block, Rectangle rect, int id) {
we also need to create a member variable to store the states:
private HashMap<Integer,Boolean> previousCollisionStateMap = new HashMap<>();
and then inside your checkBounds code, you can modify the condition to check for changes
Boolean prevState = previousCollisionStateMap.get(id);
if (prevState == null) { // this is just to initialize value
prevState = false;
previousCollisionStateMap.put(id,false);
}
if (!prevState && collisionDetected) {
score.setText(Integer.toString(currentScore.getCurrentValue()));
currentScore.incrementBy(10);
rect.setFill(Color.BLUE);
}
and don't forget to update the state at the end
previousCollisionStateMap.put(id,collisionDetected);
I'm learning how to make an Applet using Java in the form of a game. In the game, I have a character sprite drawn at the center and moves when the player presses w a s d.
It goes like this:
public game extends applet implements KeyListener {
int x, y;
URL url;
Image image;
public void init() {
x = getSize().width/2;
y = getSize().height/2;
url = new URL(getCodeBase());
image = getImage(url, "player.gif"); //take note that this is a still image
addKeyListener(this);
}
public void paint(Graphics g) {
g.drawImage(image, x, y, 32, 32, this); //the size of the image is 32x32
}
public void KeyPressed(arg0) {
char c = arg0.getKeyChar();
switch(c) {
case 'w':
y -= 10;
break;
/*And so on. You guys know how it works.*/
}
repaint();
}
My problem is, the character sprite seems dull when the user doesn't press anything. What I want to do is to make the Image an array of images and put a simple image animation by looping the array in paint like so:
public void paint(Graphics g) {
for(int i = 0; ; i++) {
g.drawImage(image[i], x, y, 32, 32, this);
if(i == image.size() - 1) { i = 0;}
}
}
However, if I do this, I won't be able to get anymore KeyEvents that would activate when the user wants to move. My question is this: How will I make it so that my character does an animation when the program is "idle" (i.e. the user isn't pressing anything) while still maintaining the capability to take in KeyEvents (e.g. moving when the player types in w, a, s, or d, and then continuing the idle animation after repainting)?
Thanks in advance.
PS. I'm still quite a beginner in Java so sorry if my code is not very nice. Any advice is welcome.
You need to make your application multithreaded so that the painting runs in a separate thread.
Otherwise you will have the painting blocked while waiting for the next key.
This is not a trivial change to your code though.
Perhaps you would give yourself a new class, say InterestingCharacter, which can cycle through any of N states (corresponding to your N images). You clearly can't let any paint method run infinitely, but if your InterestingCharacter could render itself in its current state you might be onto something with that. Maybe it will be enough that this InterestingCharacter knows what state it is in and then some other object manages the rendering. Would it be helpful if the InterestingCharacter could tell you that its state has changed and so needs to be rendered again? If so, you could implement the Observer pattern such that the character is observed and your game an observer.
I think the trick will be to break the problem down into a few classes that have appropriate responsibilities--ideally a class should have one responsibility and each of its methods should do one thing.
Just some ideas to help you move forward. Experiment with it and see how it goes. Hope it helps!
i am currently going through some tutorials on android development and libgdx/scene2d atm but got stuck on the touchevents:
For starters i am implementing a board game. By now i managed to draw the board and some meeples (player tokens) as well as adjust their position to the correct coordinates. When i started using libgdx i also successfully implemented a first test to see if the touchinput is working. The test looked something like this:
if(Gdx.input.isTouched()){
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
stone.x=touchPos.x - stone.width/2;
stone.y=touchPos.y - stone.height/2;
By now i rearranged my code to use scene2d because of some very convenient features. However it seems like i can't manage to get touchevents to work with scene2d despite i followed some rather simple tutorials. Since some of them reference some recent major changes to scene2d, i wondered if those tutorials might be outdated and hope you guys can help me fix my problem :) I will try and only paste the relevant parts of my code in here to present a minimal example of my non-working code.
Of course i am also glad about any hints on how to "improve" my code in general since i am still learning and probably break some conventions here ^^
Lets start with my actor-class:
//deleted: imports
public class Player extends Actor{
private int xval; //x Position of the Player on the board
private int yval; //y Position of the Player on the board
private Color color;
private TextureRegion playerTexture;
//deleted: some variables that are unimportant for touchevents
public Player(int x, int y, TextureRegion texture){
//initializing some Variables with default values
this.xval = x;
this.yval = y;
color = new Color(1,1,1,1);
playerTexture = texture;
this.setTouchable(Touchable.enabled); //this should be set by default, but i added it just to be sure.
//setBounds seems to be necessary in addition to the "touchable" flag to be able to "grab" or "touch" Actors.
//getX and getY are methods of the actor class which i do not overwrite. I use distinct getters and setters to modify the Variables xval and yval.
setBounds(getX(), getY(), playerTexture.getRegionWidth(), playerTexture.getRegionHeight());
//now i implement the InputListener. This doesnt seem to get called, since the "touch started" message doesn't appear in LogCat.
//In Theory the testevent should move the meeple one space to the right.
//To do that i call event.getTarget() which returns the actor of which the TouchDown is called. I then cast it to my Actor class (Player) and set xval to xval+1.
this.addListener(new InputListener(){
public boolean touchDown(InputEvent event, float x, float y, int pointer, int buttons){
Gdx.app.log("Example", "touch started at (" + x + ", " + y + ")");
((Player)event.getTarget()).setXf(((Player)event.getTarget()).getXf()+1);
return true;
}
});
}
//This is my draw method, which just sets the color of my meeple and then draws it to its current position.
public void draw(Batch batch, float alpha){
batch.setColor(color);
batch.draw(playerTexture, getX(), getY());
}
//deleted: several getters and setters
}
So if i got it right, the gdx inputProcessor is managing all inputs. If i set the Gdx InputProcessor to the stage which contains the actors, it will in case of an event (for example a touchDown) call the inputProcessors of all actors on the stage. Thus since i just added one to my class which includes a touchDown, this should handle the touchDown event in the case that the actor is actually touched. The hitbox to verify that is set by the statement "setBounds" in my Player class.
I implemented this in my ApplicationListener class:
//deleted: imports
public class QuoridorApplicationListener implements ApplicationListener{
Stage stage;
OrthographicCamera camera;
//deleted: several variable declarations.
//deleted: constructor - this only fills some of my variables with values depending on game settings.
#Override
public void create() {
//set stage - since "stage = new Stage(800,400,false)" doesn't seem to work anymore, i did set up a viewport manually. If you got suggestions how to this straightforward, please leave a note :)
Gdx.app.log("Tag", "game started");
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
Viewport viewport = new Viewport() {};
viewport.setCamera(camera);
stage = new Stage(viewport);
Gdx.input.setInputProcessor(stage); //like i described above, this should forward incoming touchevents to the actors on the stage.
//deleted: setting textures, music, setting up the board (the boardtiles are actors which are added to the stage)
//deleted: setting TextureRegion, player colors, player positions and player attributes.
for (int i = 0; i < playercount; i++){
stage.addActor(players[i]);
}
}
//deleted: dispose(), pause(), resume(), resize() methods.
#Override
public void render() {
camera.update();
for (int i=0; i< playercount; i++){
players[i].setX(/*Formula to determine x-coordinates from Player.xval*/);
players[i].setY(/*Formula to determine x-coordinates from Player.yval*/);
}
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
}
I can't figure out what i am missing to get the touchevents working. So i hope you can help me :) Thanks a lot in advance!
I think your problem originates from your manual creation of a Viewport object, which handles the unprojecting internally.
Instead of
stage = new Stage(800,400,false);
you should use
stage = new Stage();
To resize the the viewport to the correct size once a resize event occurs you need to call
stage.getViewport().update( newWidth, newHeight )
in your ApplicationListener's resize method.
I asked this at the libgdx forums but didn't get a response so I was hoping y'all could help me out:
I have Actors that represent game pieces. What I'm trying to do is make it so the player can click-and-drag the tile to move it around the screen and rotate it multiple times before submitting the placeTile command. From what I understand of DragAndDrop it doesn't seem to be designed with my use case in mind so I figured I'd instead attach a dragListener listener to each game piece (code below). It works well for dragging, except I can't figure out how to set the 'minimum distance before drag starts' to 0... but that's not my main question (though any insights would be appreciated )
Anyway, the big problem comes in when I rotate the actor, and then try to drag it: At 30 degrees rotation, drag acts almost like normal: at 60 degrees, very small movements of the mouse send the actor moving in a tight circle very quickly. Another 30 degrees, the tile actor exits the screen in 1-2 frames, moving in a wide arc. If the actor is rotated clockwise, it's movements are clockwise; same pattern for counter-clockwise.
It looks like the translation of the actor is taking rotation into account; I guess my question is, is it possible to rotate an Actor/Group without the rotation affecting future translations? Alternatively, is there a better way to drag an Actor around the screen based on touch/mouse input? I included some code below: I imagine I'm screwing up something basic, but I can't figure out what:
// during initial stage creation
tileActor.setOrigin(tileActor.getWidth() / 2, tileActor.getHeight() / 2);
tileActor.addListener(new DragListener() {
public void dragStart(InputEvent event, float x, float y,
int pointer) {
chosenTileActor = event.getTarget();
}
public void drag(InputEvent event, float x, float y, int pointer) {
Actor target = event.getTarget();
target.translate(x, y);
}
});
And for the listener that deals with rotation via scrolling mouse wheel:
multiplexer.addProcessor(new InputAdapter() {
#Override
public boolean scrolled(int amt) {
if (chosenTileActor == null)
return false;
else
chosenTileActor.rotate(amt * 30);
return true;
}
});
Any pointers? Am I even going the right direction by using DragListener?
Thanks for reading!
Instead of translating, just set the actor's position directly to the stage coordinates of your drag event:
tileActor.addListener(new DragListener() {
private float offsetX, offsetY;
#Override
public void dragStart(InputEvent event, float x, float y, int pointer) {
Actor target = event.getTarget();
this.offsetX = event.getStageX() - target.getX();
this.offsetY = event.getStageY() - target.getY();
}
#Override
public void drag(InputEvent event, float x, float y, int pointer) {
event.getTarget().setPosition(event.getStageX() - offsetX, event.getStageY() - offsetY);
}
});
I'm computing the offsets in dragStart so that the actor doesn't immediately jump to wherever I clicked when I started dragging (making the drags relative to my mouse). Tested this and it works with any rotation.
I have been writing a Game Engine and I have a problem with my actor class. I want to determine the collision with a rectangle (above) and a side of a rectangle. I had written two methods for them.
public boolean isLeftCollision(Actor actor) {
boolean bool = false;
Rectangle LeftBounds = new Rectangle(x, y, x-velocity, image.getHeight(null));
bool = LeftBounds.intersects(actor.getBounds());
return bool;
}
public boolean isRightCollision(Actor actor) {
boolean bool = false;
Rectangle RightBounds = new Rectangle(x+image.getWidth(null), y, image.getWidth(null)+velocity, image.getHeight(null));
bool = RightBounds.intersects(actor.getBounds());
return bool;
}
Here velocity is the movement for next step.
But they both give me error (ie., false judgements). How can I solve this in the actor class.
I admit I can hardly read your code and I'm sorry if my answer isn't helpful. My guess is that the velocity in the collision produces the errors. Depending on how often you check and what values velocity holds you might register collisions that haven't occured yet...
I'd do the collision detection in two steps.
Test for a collision
Determine if it's above or to one side.
here's some pseudocode:
Rectangle self_shape=this.getBounds();
Rectangle other_shape=actor.getBounds();
bool collision = self_shape.intersects(other_shape);
if(collision){
//create two new variables self_centerx and self_centery
//and two new variables other_centerx and other_centery
//let them point to the coordinates of the center of the
//corresponding rectangle
bool left=self_centerx - other_centerx<0
bool up=self_centery - other_centery<0
}
That way you can look where the other actor is positioned relative to you. If it's above or to one side.
Remember that the third parameter to Rectangle is the width, not the x of the other side. So, what you really want is probably like this:
public boolean isLeftCollision(Actor actor) {
return new Rectangle(x - velocity, y, velocity, image.getHeight(null))
.intersects(actor.getBounds());
}
public boolean isRightCollision(Actor actor) {
return new Rectangle(x + image.getWidth(null), y, velocity, image.getHeight(null))
.intersects(actor.getBounds());
}
(Assuming velocity is the (positive) distance to move left or right, and only the method for the direction you are moving will be called)