I've created a game that uses 4 different GameStates: READY,RUNNING,GAMEOVER,and HIGHSCORE(variation of GAMEOVER except this one notifies the player that a highscore has been reached). My issue is that the way I've set up my InputHandler is that in the GameState.READY form the user can touch anywhere in the screen to advance into the GameState.RUNNING form. I've tried multiple tactics into creating a playButton, however nothing seems to be working my way. I created a PlayButton class as such:
public class PlayButton {
private Vector2 position;
private Rectangle boundingRectangle;
private int width;
private int height;
private PlayScreen playScreen;
public PlayButton (float x, float y, int width, int height) {
this.width = width;
this.height = height;
position = new Vector2(x, y);
boundingRectangle = new Rectangle();
}
public void update(float delta) {
boundingRectangle.set(position.x,(position.y),width,height);
}
public float getTheX() {
return position.x;
}
public float getY() {
return position.y;
}
public float getWidth() {
return width;
}
public float getHeight() {
return height;
}
public Rectangle getBoundingRectangle(){
return boundingRectangle;
}
}
and in my InputHandler i tried this:
public InputHandler(GameWrold myWorld, float scaleFactorX, float scaleFactorY){
this.myWorld = myWorld;
mySam = myWorld.getSam();
playButton = new PlayButton(45,gameHeight-75,50,-35);
buttonPlay = myWorld.getPlayButton();
int midPointY = myWorld.getMidPointY();
this.scaleFactorX = scaleFactorX;
this.scaleFactorY = scaleFactorY;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Vector2 touchPos = new Vector2();
touchPos.set(Gdx.input.getX(), Gdx.input.getY());
Vector2 tuch = new Vector2(screenX,screenY);
buttonPlay.getBoundingRectangle();
touch = new Rectangle(touchPos.x,touchPos.y,5,5);
if (myWorld.isReady()) {
if(playButton.getBoundingRectangle().contains(touchPos)){
myWorld.start();
}
}
mySam.onClick();
if (myWorld.isGameOver() || myWorld.isHighScore()) {
// Reset all variables, go to GameState.READ
myWorld.restart();
}
return true;
}
}
As you can see I tried creating a Rectangle with the touch variable and I tried checking if touch collided with the playButton boundingRectangle like this:
if(Intersector.overlaps(touch,playButton.getBoundingRectangle())){ or
(playButton.getBoundingRectangle().overlaps(playButton.getBoundingRectangle()))
Do not invent a circle and don't break open doors.
Use Scene2d :)
It is a simple UI framework fully compatible with libGDX that allows you to create Buttons, Sliders, Windows and another widgets in about one line of code.
You will find a nice description in a link I've attached above but TLDR version is:
Create Skin and pack a texture atlas for this using TexturePacker (free version is enough usually)
Create a Stage with appropriate Viewport
Stage stage = new Stage(new FitViewport(WIDTH, HEIGHT));
Set the stage as input processor (it means that all event on the stage will be catched)
Gdx.input.setInputProcessor(stage);
Create a Button with appropriate style (defined in Skin)
Button button = new Button(skin, "style");
Attach a ClickListener with appropriate action to the Button
button.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y)
{
//some action
}
});
Set it's position and add it to the stage
button.setPosition(x, y);
stage.addActor(button);
Fire act() and draw() stage's methods in your render()
//render()
stage.act();
stage.draw();
You've earned few hours ;)
Related
I want to use moveToAction and move the actor. But its not moving if i use moveToAction. If change x,y in draw method it works, but not with movetoaction
public class Aks extends Actor {
private State state;
private MainGame game;
private TextureAtlas movingAtlas;
private Animation movingAnimation;
private float elapsedTime = 0f;
public Aks(MainGame game) {
this.game = game;
movingAtlas = new TextureAtlas(Gdx.files.internal("atlas/myaltas/atlas.atlas"));
movingAnimation = new Animation(1f/15f, movingAtlas.getRegions());
TextureRegion texture = (TextureRegion) movingAnimation.getKeyFrame(elapsedTime, true);
setBounds(getX(),getY(),texture.getRegionWidth(),texture.getRegionHeight());
MoveToAction moveAction = new MoveToAction();
moveAction.setPosition(300f, 300f);
moveAction.setDuration(10f);
this.addAction(moveAction);
addListener(new ActorGestureListener(){
#Override
public void tap(InputEvent event, float x, float y, int count, int button) {
Gdx.app.log("Tag", "Actor touched x = " );
super.tap(event, x, y, count, button);
}
});
}
#Override
public void draw(Batch batch, float alpha){
drawFlying(batch);
}
void drawFlying(Batch batch){
TextureRegion texture = (TextureRegion) movingAnimation.getKeyFrame(elapsedTime, true);
setBounds(getX(),getY(),texture.getRegionWidth(),texture.getRegionHeight());
Gdx.app.log("Tag", "x =" + getX() + " y =" + getY() );
batch.draw(texture, getX(),getY(),50,50);
}
#Override
public void act(float delta){
elapsedTime = elapsedTime+delta;
}
}
You needs to call super.act(delta), in the actor class, the act method control all the actions so if you don't call it, the actions wont happen, on the contrary you don't need to call super.draw() that's because actor class was created just for logical part of a entity, you must define what it will draw in case you need it.
I hope it helps!
Your draw function needs to call super.draw(batch, alpha) for it to affect child actions.
I'm trying to implement undo/redo in JavaFX - I draw all my shapes using graphicsContext(). I have looked around and found that there's a save method on Graphics Context but it just saves attributes and not the actual shape/state of the canvas. What would be the best way of going about this?
This is one of my code snippets when I create a circle, for instance:
public CircleDraw(Canvas canvas, Scene scene, BorderPane borderPane) {
this.borderPane = borderPane;
this.scene = scene;
this.graphicsContext = canvas.getGraphicsContext2D();
ellipse = new Ellipse();
ellipse.setStrokeWidth(1.0);
ellipse.setFill(Color.TRANSPARENT);
ellipse.setStroke(Color.BLACK);
pressedDownMouse = event -> {
startingPosX = event.getX();
startingPosY = event.getY();
ellipse.setCenterX(startingPosX);
ellipse.setCenterY(startingPosY);
ellipse.setRadiusX(0);
ellipse.setRadiusY(0);
borderPane.getChildren().add(ellipse);
};
releasedMouse = event -> {
borderPane.getChildren().remove(ellipse);
double width = Math.abs(event.getX() - startingPosX);
double height = Math.abs(event.getY() - startingPosY);
graphicsContext.setStroke(Color.BLACK);
graphicsContext.strokeOval(Math.min(startingPosX, event.getX()), Math.min(startingPosY, event.getY()), width, height);
removeListeners();
};
draggedMouse = event -> {
ellipse.setCenterX((event.getX() + startingPosX) / 2);
ellipse.setCenterY((event.getY() + startingPosY) / 2);
ellipse.setRadiusX(Math.abs((event.getX() - startingPosX) / 2));
ellipse.setRadiusY(Math.abs((event.getY() - startingPosY) / 2));
};
}
The problem here is that there is that information like this is not saved in a Canvas. Furthermore there is no inverse operation that allows you to get back to the previous state for every draw information. Surely you could stroke the same oval, but with backgrund color, however the information from previous drawing information could have been overwritten, e.g. if you're drawing multiple intersecting ovals.
You could store the drawing operations using the command pattern however. This allows you to redraw everything.
public interface DrawOperation {
void draw(GraphicsContext gc);
}
public class DrawBoard {
private final List<DrawOperation> operations = new ArrayList<>();
private final GraphicsContext gc;
private int historyIndex = -1;
public DrawBoard(GraphicsContext gc) {
this.gc = gc;
}
public void redraw() {
Canvas c = gc.getCanvas();
gc.clearRect(0, 0, c.getWidth(), c.getHeight());
for (int i = 0; i <= historyIndex; i++) {
operations.get(i).draw(gc);
}
}
public void addDrawOperation(DrawOperation op) {
// clear history after current postion
operations.subList(historyIndex+1, operations.size()).clear();
// add new operation
operations.add(op);
historyIndex++;
op.draw(gc);
}
public void undo() {
if (historyIndex >= 0) {
historyIndex--;
redraw();
}
}
public void redo() {
if (historyIndex < operations.size()-1) {
historyIndex++;
operations.get(historyIndex).draw(gc);
}
}
}
class EllipseDrawOperation implements DrawOperation {
private final double minX;
private final double minY;
private final double width;
private final double height;
private final Paint stroke;
public EllipseDrawOperation(double minX, double minY, double width, double height, Paint stroke) {
this.minX = minX;
this.minY = minY;
this.width = width;
this.height = height;
this.stroke = stroke;
}
#Override
public void draw(GraphicsContext gc) {
gc.setStroke(stroke);
gc.strokeOval(minX, minY, width, height);
}
}
Pass a DrawBoard instance to your class instead of the Canvas and replace
graphicsContext.setStroke(Color.BLACK);
graphicsContext.strokeOval(Math.min(startingPosX, event.getX()), Math.min(startingPosY, event.getY()), width, height);
with
drawBoard.addDrawOperation(new EllipseDrawOperation(
Math.min(startingPosX, event.getX()),
Math.min(startingPosY, event.getY()),
width,
height,
Color.BLACK));
The undo and redo to move through the history.
I am trying to implement movement to a point, where mouse was clicked.
But I have a problem with mirrored behaviour agains X axis.
When I click on top -> it moves to the bottom, when I click on bottom -> it moves to the top.
Here is for example original position
I clicked on the screen in position with red cross.
But it moves down (as arrow showed).
What's the problem? It seems something with movement vector I presume.
public class Player {
private static final float PLAYER_CIRCLE_RADIUS = 24f;
private static final float MOVEMENT_SPEED = 200f;
private final Circle playerCircle;
private Vector2 direction = new Vector2();
private Vector2 position;
private Vector2 velocity = new Vector2();
private Vector2 movement = new Vector2();
private Vector2 mouseClick = new Vector2();
public Player(float x, float y) {
position = new Vector2(x, y);
playerCircle = new Circle(x, y, PLAYER_CIRCLE_RADIUS);
}
public void draw(ShapeRenderer shapeRenderer) {
shapeRenderer.circle(position.x, position.y, playerCircle.radius);
}
public void update(float delta) {
movement.set(velocity).scl(delta);
if (position.dst2(mouseClick) > movement.len2()) { position.add(movement); }
else { position.set(mouseClick); }
}
public void setDirection(float x, float y) {
mouseClick.set(x, y);
direction.set(mouseClick).sub(position).nor();
velocity.set(direction).scl(MOVEMENT_SPEED);
}
public Vector2 getDirection() {
return direction;
}
public Circle getPlayerCircle() {
return playerCircle;
}
public Vector2 getMouseClick() {
return mouseClick;
}
}
public class GameScreen extends ScreenAdapter {
private static final float WORLD_WIDTH = 640;
private static final float WORLD_HEIGHT = 480;
private ShapeRenderer shapeRenderer;
private Viewport viewport;
private Camera camera;
private Player player;
private Destination dest;
#Override
public void render(float delta) {
clearScreen();
update(delta);
shapeRenderer.setProjectionMatrix(camera.projection);
shapeRenderer.setTransformMatrix(camera.view);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
dest.draw(shapeRenderer);
player.draw(shapeRenderer);
shapeRenderer.end();
}
#Override
public void resize(int width, int height) {
viewport.update(width, height);
}
#Override
public void show() {
camera = new OrthographicCamera();
camera.position.set(WORLD_WIDTH / 2, WORLD_HEIGHT / 2, 0);
viewport = new FitViewport(WORLD_WIDTH, WORLD_HEIGHT, camera);
shapeRenderer = new ShapeRenderer();
player = new Player(WORLD_WIDTH / 2, WORLD_HEIGHT / 2);
dest = new Destination();
Gdx.input.setInputProcessor(new InputAdapter() {
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
dest.setPosition(screenX, screenY);
camera.unproject(new Vector3(screenX, screenY, 0));
player.setDirection(screenX, screenY);
return true;
}
});
}
private void clearScreen() {
Gdx.gl.glClearColor(Color.BLACK.r, Color.BLACK.g, Color.BLACK.b, Color.BLACK.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
private void update(float delta) {
player.update(delta);
}
}
What you describe is actually mirrored on the Y-Axis.
The reason for this behaviour is most likely that your drawing matrix is set in such a way that the origin is in the bottom-left corner with the Y-Axis pointing up, but gui's have their origin at the top-left corner with the Y-Axis pointing down. So when you get your mouse position you should do something like:
actualPosY = screenHeight - mousePosY
This effectively transforms your mouse position to your drawing space.
I have a tiled map on which I set characters(every character is of the size of one tile). I managed to make them clickable, even when screen resizes everything works perfect. Every time I click on character I want a button to show up above it. For a button I use stage and place the button in the place I clicked with small transition and it also works.
My problem is when I try to use clicklistener on this button. If the screen does not resize, clicklistener works. Problem starts when the screen get resized - clicks on players works well, only button does not work - after a resize the clicking space and the button space are not aligned. For test purposes I made a test map that shows me grids. It seems that stage is not properly resized. Picture for a reference. I tried many solutions I came upon on the Internet and still can't find a solution to my problem. I have shortened my code down to a minimum basic example:
public class Test extends ApplicationAdapter {
public static Stage stage;
public TiledMap tiledMap;
static OrthographicCamera camera;
TiledMapRenderer tiledMapRenderer;
public static boolean showMenu;
GestureDetector gesture;
InputMultiplexer myInputMultiplexer;
public static int posx, posy;
public static Image move;
public Texture moveMenu;
#Override
public void create() {
moveMenu = new Texture(Gdx.files.internal("move.png"));
gesture = new GestureDetector(new MyGestureListener());
myInputMultiplexer = new InputMultiplexer();
float unitScale = 1 / 32f;
camera = new OrthographicCamera();
camera.setToOrtho(true, 33, 21);
stage = new Stage(new ScreenViewport());
stage.getViewport().setCamera(camera);
tiledMap = new TmxMapLoader().load("test.tmx");
tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap, unitScale);
myInputMultiplexer.addProcessor(stage);
myInputMultiplexer.addProcessor(gesture);
Gdx.input.setInputProcessor(myInputMultiplexer);
move = new Image(moveMenu);
move.setWidth(2);
move.setHeight(2);
move.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
move(); //my action, works fine
showMenu = false;
}
});
stage.addActor(move);
}
#Override
public void render() {
super.render();
stage.act();
tiledMapRenderer.setView(camera);
camera.update();
tiledMapRenderer.render();
if (showMenu) {
mainMenuDraw();
}
}
public static void mainMenuDraw() {
move.setPosition(posx, posy-2);
stage.draw();
}
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
public static OrthographicCamera getCamera() {
return camera;
}
public static Vector3 unprojectCoords(Vector3 coords) {
camera.unproject(coords);
return coords;
} }
and part of my gesturelistener:
public boolean touchDown(float x, float y, int pointer, int button) {
Vector3 temp_coord = new Vector3(x, y, 0);
Vector3 coords = Test.unprojectCoords(temp_coord);
return false;
}
#Override
public boolean tap(float x, float y, int count, int button) {
Vector3 temp_coord = new Vector3(x, y, 0);
Vector3 coords = Test.unprojectCoords(temp_coord);
Test.posx = (int) coords.x;
Test.posy = (int) coords.y;
tap = true;
Test.showMenu = true;
return false;
}
I would suggest to you read more about re-sizing and displaying pixels.
You need to recalculate pixels always when you render something.
- player, background image, buttons, events.
Actually you don't need to use resize method, just get the camera width and height.
You need to share more code, because it depends on everything.
I don't see how you are doing rendering.
tiledMapRenderer.render(), background rendering / layers?
player rendering?
menu rendering?
buttons rendering?
Example: (the same should be for event handler)
public GameButton(TextureRegion reg, float x, float y, OrthographicCamera cam) {
this.reg = reg;
this.x = x;
this.y = y;
this.cam = cam;
width = reg.getRegionWidth();
height = reg.getRegionHeight();
vec = new Vector3();
Texture tex = Game.res.getTexture("hud");
font = new TextureRegion[11];
for(int i = 0; i < 11; i++) {
font[i] = new TextureRegion(tex, 32 + i * 9, 16, 9, 9); //use height and width here)
}
}
I am rather new to the libgdx Framework so I hope I am not asking anything stupid, but I have a problem with updating my screen on the toucheEvent. It seems that the touch event fires, but the stage is not updated so the screen is all the time the same. Here is the code
MainClass
public class MainGame implements Screen {
public LabirintGame game;
public Stage stage;
public OrthographicCamera camera;
public ActorM rigth;
public ActorM wrong;
public MainGame(LabirintGame game) {
this.game = game;
this.camera = new OrthographicCamera();
}
#Override
public void show() {
this.camera.setToOrtho(false, 800, 480);
stage = new Stage(new ScreenViewport());
stage.clear();
Words group = new Words(stage);
InputMultiplexer inputMultiplexer = new InputMultiplexer();
inputMultiplexer.addProcessor(stage);
inputMultiplexer.addProcessor(new MyInputProcessor(stage, camera));
Gdx.input.setInputProcessor(inputMultiplexer);
//Add wrong and rigth boxes
rigth = new ActorM("box", 0, 0, 200,200);
wrong = new ActorM("box",(game.width - 230), 0, 200, 200);
wrong.moveBy(200,200);
Button createButtons = new Button();
createButtons.setStyle("atlas-besede/besede.atlas", "buttonOff", "buttonOn");
TextButton ValidationButton = createButtons.createButton("Validate", (game.width/2), 0, 150, 150);
ValidationButton.addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
game.setScreen(new Labirint(game));
return true;
}
});
stage.addActor(ValidationButton);
stage.addActor(rigth);
stage.addActor(wrong);
List<String> backgrounds = Arrays.asList("s", "z");
for (int i = 0; i < backgrounds.size(); i++) {
Word actor = new Word(backgrounds.get(i),(i + 1) * 300, 300, 100, 100);
actor.setPosition((i + 1) * 300, 300);
actor.setName(backgrounds.get(i));
group.addActor(actor);
}
stage.addActor(group);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
game.batch.begin();
game.batch.setProjectionMatrix(camera.combined);
game.batch.end();
}
ActorM
package com.mygdx.game;
public class ActorM extends Actor {
public SpriteBatch batch;
public TextureAtlas atlas;
public TextureAtlas.AtlasRegion region;
Sprite sprite;
public int x;
public int y;
public int width;
public int height;
public ActorM(String actorName, int x, int y, int width, int height) {
//this.region = region;
super();
batch = new SpriteBatch();
atlas = new TextureAtlas(Gdx.files.internal("atlas-start/atlas-start.atlas"));
sprite = atlas.createSprite(actorName);
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.setBounds(0, 0, sprite.getWidth(), sprite.getHeight());
setTouchable(Touchable.disabled);
setName(actorName);
setPosition(x,y);
}
#Override
public void draw (Batch batch, float parentAlpha) {
batch.draw(sprite, x,y, width, height);
}
public void move(int posX){
this.x = this.x + posX;
}
}
MyInputProcessor
public class MyInputProcessor implements InputProcessor {
private OrthographicCamera camera;
private Stage stage;
private Vector2 coordinates;
private Music sound;
public MyInputProcessor( Stage stage, OrthographicCamera camera) {
this.stage = stage;
this.camera = camera;
}
#Override
public boolean keyDown(int keycode) {
return false;
}
#Override
public boolean keyUp(int keycode) {
return false;
}
#Override
public boolean keyTyped(char character) {
return false;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button)
{
//Gdx.app.log("", "x " + screenX + " y " + screen`enter code here`Y + " pointer " + pointer);
Vector2 coordinates = stage.screenToStageCoordinates(new Vector2((float)screenX,(float)screenY));
Actor hitactor = stage.hit(coordinates.x, coordinates.y, true);
Gdx.app.log("", coordinates.toString());
if (hitactor != null){
//Gdx.app.log("", "HIT" + hitactor.getName());
Gdx.app.log("", "HIT" + hitactor.getRotation());
hitactor.setRotation(hitactor.getRotation() + 1f);
hitactor.setPosition(5,5);
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
return true;
}
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
return false;
}
#Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
coordinates = stage.screenToStageCoordinates(new Vector2((float)screenX,(float)screenY));
Actor hitactor = stage.hit(coordinates.x, coordinates.y, true);
if (hitactor != null){
Gdx.app.log("", "Drag");
hitactor.setRotation(hitactor.getRotation() + 1f);
}
camera.update();
return true;
}
#Override
public boolean mouseMoved(int screenX, int screenY) {
return false;
}
#Override
public boolean scrolled(int amount) {
return false;
}
}
Your "ValidationButton" uses an InputProcessor that always returns true and it's the first actor in the stage, so nothing else in the stage will ever get an opportunity to respond to touch down events. Furthermore, since your stage is the first input processor in your InputMultiplexer, your other input processor never gets an opportunity to respond to touch down events either.
You should use an EventListener on your button instead of an InputListener, so much of the logic will be taken care of for you.
By the way, your ActorM class is spawning a SpriteBatch that it never uses. SpriteBatch takes up significant memory, and there's no need for there to be more than one of them in your game. The Stage already has a reference to a SpriteBatch that it passes into your Actor's draw method, so the Actor does not need to create or even reference a SpriteBatch.
Also, your ActorM class is loading a complete copy of a TextureAtlas for itself so there will be duplicate Textures loaded for each instance of ActorM, and you lose all the benefits of a TextureAtlas, since you won't be using it for sprite batching. You need to load the TextureAtlas only one time, and pass a reference of it into the constructor of your ActorM class, so they can all share the same Texture.