animation - sprites - descargar aseprite completo
En JavaFX, ¿cómo muevo un sprite a través de la pantalla? (1)
Soy nuevo en JavaFX y estoy tratando de escribir un juego donde un personaje animado en 2D camina por la pantalla (por ejemplo, como el juego original Legend of Zelda). Había hecho esto en Swing, creando mi propia clase de Sprite y reemplazando el método paintComponent () en Swing, y poniendo mi propio g2d.drawImage (...); invoca allí, donde copiaría la subtrama correcta de un Sprite al destino x, y correcto en un JPanel, logrando así el movimiento de una imagen 2D animada (caminando) por la pantalla.
¿Cómo puedo hacer esto en JavaFX? Encontré este excelente ejemplo sobre cómo crear un sprite: http://blog.netopyr.com/2012/03/09/creating-a-sprite-animation-with-javafx/ en JavaFX, pero ¿cómo hago el PaintComponent? y drawImage (...) partes? ¿Tiene JavaFX un método equivalente a PaintComponent donde todo se vuelve a dibujar? Intenté llamar al primaryStage.show (); método, pero eso no funcionó.
Supongo que no tengo claro exactamente cómo configurar mi loop maestro de juego, donde se activa una actualización y se pinta una imagen en la escena en una ubicación x, y diferente.
¿Cuál es el JavaFX equivalente al método drawImage (...)? este método me permite copiar una parte de una imagen de origen en una escena de destino en cualquier desplazamiento x, y dado. Así es como logré mover un objeto animado animado en la pantalla en Swing.
Atentamente,
Zareh
Su pregunta es demasiado para ser cubierto en SO. Sin embargo, creé un simple "motor" para que comiences. Es de propósito general, por lo que también es adecuado para sus necesidades.
La clase principal con el bucle del juego donde se carga el juego, se verifica la entrada, se mueven los sprites, se verifica la colisión, se actualiza la puntuación, etc.
public class Game extends Application {
Random rnd = new Random();
Pane playfieldLayer;
Pane scoreLayer;
Image playerImage;
Image enemyImage;
List<Player> players = new ArrayList<>();
List<Enemy> enemies = new ArrayList<>();
Text collisionText = new Text();
boolean collision = false;
Scene scene;
@Override
public void start(Stage primaryStage) {
Group root = new Group();
// create layers
playfieldLayer = new Pane();
scoreLayer = new Pane();
root.getChildren().add( playfieldLayer);
root.getChildren().add( scoreLayer);
scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
primaryStage.setScene( scene);
primaryStage.show();
loadGame();
createScoreLayer();
createPlayers();
AnimationTimer gameLoop = new AnimationTimer() {
@Override
public void handle(long now) {
// player input
players.forEach(sprite -> sprite.processInput());
// add random enemies
spawnEnemies( true);
// movement
players.forEach(sprite -> sprite.move());
enemies.forEach(sprite -> sprite.move());
// check collisions
checkCollisions();
// update sprites in scene
players.forEach(sprite -> sprite.updateUI());
enemies.forEach(sprite -> sprite.updateUI());
// check if sprite can be removed
enemies.forEach(sprite -> sprite.checkRemovability());
// remove removables from list, layer, etc
removeSprites( enemies);
// update score, health, etc
updateScore();
}
};
gameLoop.start();
}
private void loadGame() {
playerImage = new Image( getClass().getResource("player.png").toExternalForm());
enemyImage = new Image( getClass().getResource("enemy.png").toExternalForm());
}
private void createScoreLayer() {
collisionText.setFont( Font.font( null, FontWeight.BOLD, 64));
collisionText.setStroke(Color.BLACK);
collisionText.setFill(Color.RED);
scoreLayer.getChildren().add( collisionText);
// TODO: quick-hack to ensure the text is centered; usually you don''t have that; instead you have a health bar on top
collisionText.setText("Collision");
double x = (Settings.SCENE_WIDTH - collisionText.getBoundsInLocal().getWidth()) / 2;
double y = (Settings.SCENE_HEIGHT - collisionText.getBoundsInLocal().getHeight()) / 2;
collisionText.relocate(x, y);
collisionText.setText("");
collisionText.setBoundsType(TextBoundsType.VISUAL);
}
private void createPlayers() {
// player input
Input input = new Input( scene);
// register input listeners
input.addListeners(); // TODO: remove listeners on game over
Image image = playerImage;
// center horizontally, position at 70% vertically
double x = (Settings.SCENE_WIDTH - image.getWidth()) / 2.0;
double y = Settings.SCENE_HEIGHT * 0.7;
// create player
Player player = new Player(playfieldLayer, image, x, y, 0, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED, input);
// register player
players.add( player);
}
private void spawnEnemies( boolean random) {
if( random && rnd.nextInt(Settings.ENEMY_SPAWN_RANDOMNESS) != 0) {
return;
}
// image
Image image = enemyImage;
// random speed
double speed = rnd.nextDouble() * 1.0 + 2.0;
// x position range: enemy is always fully inside the screen, no part of it is outside
// y position: right on top of the view, so that it becomes visible with the next game iteration
double x = rnd.nextDouble() * (Settings.SCENE_WIDTH - image.getWidth());
double y = -image.getHeight();
// create a sprite
Enemy enemy = new Enemy( playfieldLayer, image, x, y, 0, 0, speed, 0, 1,1);
// manage sprite
enemies.add( enemy);
}
private void removeSprites( List<? extends SpriteBase> spriteList) {
Iterator<? extends SpriteBase> iter = spriteList.iterator();
while( iter.hasNext()) {
SpriteBase sprite = iter.next();
if( sprite.isRemovable()) {
// remove from layer
sprite.removeFromLayer();
// remove from list
iter.remove();
}
}
}
private void checkCollisions() {
collision = false;
for( Player player: players) {
for( Enemy enemy: enemies) {
if( player.collidesWith(enemy)) {
collision = true;
}
}
}
}
private void updateScore() {
if( collision) {
collisionText.setText("Collision");
} else {
collisionText.setText("");
}
}
public static void main(String[] args) {
launch(args);
}
}
Una clase base para sprites que incluye métodos comunes como movimiento, etc.
public abstract class SpriteBase {
Image image;
ImageView imageView;
Pane layer;
double x;
double y;
double r;
double dx;
double dy;
double dr;
double health;
double damage;
boolean removable = false;
double w;
double h;
boolean canMove = true;
public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
this.layer = layer;
this.image = image;
this.x = x;
this.y = y;
this.r = r;
this.dx = dx;
this.dy = dy;
this.dr = dr;
this.health = health;
this.damage = damage;
this.imageView = new ImageView(image);
this.imageView.relocate(x, y);
this.imageView.setRotate(r);
this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();
addToLayer();
}
public void addToLayer() {
this.layer.getChildren().add(this.imageView);
}
public void removeFromLayer() {
this.layer.getChildren().remove(this.imageView);
}
public Pane getLayer() {
return layer;
}
public void setLayer(Pane layer) {
this.layer = layer;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getR() {
return r;
}
public void setR(double r) {
this.r = r;
}
public double getDx() {
return dx;
}
public void setDx(double dx) {
this.dx = dx;
}
public double getDy() {
return dy;
}
public void setDy(double dy) {
this.dy = dy;
}
public double getDr() {
return dr;
}
public void setDr(double dr) {
this.dr = dr;
}
public double getHealth() {
return health;
}
public double getDamage() {
return damage;
}
public void setDamage(double damage) {
this.damage = damage;
}
public void setHealth(double health) {
this.health = health;
}
public boolean isRemovable() {
return removable;
}
public void setRemovable(boolean removable) {
this.removable = removable;
}
public void move() {
if( !canMove)
return;
x += dx;
y += dy;
r += dr;
}
public boolean isAlive() {
return Double.compare(health, 0) > 0;
}
public ImageView getView() {
return imageView;
}
public void updateUI() {
imageView.relocate(x, y);
imageView.setRotate(r);
}
public double getWidth() {
return w;
}
public double getHeight() {
return h;
}
public double getCenterX() {
return x + w * 0.5;
}
public double getCenterY() {
return y + h * 0.5;
}
// TODO: per-pixel-collision
public boolean collidesWith( SpriteBase otherSprite) {
return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);
}
/**
* Reduce health by the amount of damage that the given sprite can inflict
* @param sprite
*/
public void getDamagedBy( SpriteBase sprite) {
health -= sprite.getDamage();
}
/**
* Set health to 0
*/
public void kill() {
setHealth( 0);
}
/**
* Set flag that the sprite can be removed from the UI.
*/
public void remove() {
setRemovable(true);
}
/**
* Set flag that the sprite can''t move anymore.
*/
public void stopMovement() {
this.canMove = false;
}
public abstract void checkRemovability();
}
Las subclases de la clase sprite como jugador ...
public class Player extends SpriteBase {
double playerShipMinX;
double playerShipMaxX;
double playerShipMinY;
double playerShipMaxY;
Input input;
double speed;
public Player(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed, Input input) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
this.speed = speed;
this.input = input;
init();
}
private void init() {
// calculate movement bounds of the player ship
// allow half of the ship to be outside of the screen
playerShipMinX = 0 - image.getWidth() / 2.0;
playerShipMaxX = Settings.SCENE_WIDTH - image.getWidth() / 2.0;
playerShipMinY = 0 - image.getHeight() / 2.0;
playerShipMaxY = Settings.SCENE_HEIGHT -image.getHeight() / 2.0;
}
public void processInput() {
// ------------------------------------
// movement
// ------------------------------------
// vertical direction
if( input.isMoveUp()) {
dy = -speed;
} else if( input.isMoveDown()) {
dy = speed;
} else {
dy = 0d;
}
// horizontal direction
if( input.isMoveLeft()) {
dx = -speed;
} else if( input.isMoveRight()) {
dx = speed;
} else {
dx = 0d;
}
}
@Override
public void move() {
super.move();
// ensure the ship can''t move outside of the screen
checkBounds();
}
private void checkBounds() {
// vertical
if( Double.compare( y, playerShipMinY) < 0) {
y = playerShipMinY;
} else if( Double.compare(y, playerShipMaxY) > 0) {
y = playerShipMaxY;
}
// horizontal
if( Double.compare( x, playerShipMinX) < 0) {
x = playerShipMinX;
} else if( Double.compare(x, playerShipMaxX) > 0) {
x = playerShipMaxX;
}
}
@Override
public void checkRemovability() {
// TODO Auto-generated method stub
}
}
... y enemigos
public class Enemy extends SpriteBase {
public Enemy(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {
super(layer, image, x, y, r, dx, dy, dr, health, damage);
}
@Override
public void checkRemovability() {
if( Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
setRemovable(true);
}
}
}
También necesita un mecanismo de entrada para controlar el jugador sprite
public class Input {
/**
* Bitset which registers if any {@link KeyCode} keeps being pressed or if it is released.
*/
private BitSet keyboardBitSet = new BitSet();
// -------------------------------------------------
// default key codes
// will vary when you let the user customize the key codes or when you add support for a 2nd player
// -------------------------------------------------
private KeyCode upKey = KeyCode.UP;
private KeyCode downKey = KeyCode.DOWN;
private KeyCode leftKey = KeyCode.LEFT;
private KeyCode rightKey = KeyCode.RIGHT;
private KeyCode primaryWeaponKey = KeyCode.SPACE;
private KeyCode secondaryWeaponKey = KeyCode.CONTROL;
Scene scene;
public Input( Scene scene) {
this.scene = scene;
}
public void addListeners() {
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.addEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
public void removeListeners() {
scene.removeEventFilter(KeyEvent.KEY_PRESSED, keyPressedEventHandler);
scene.removeEventFilter(KeyEvent.KEY_RELEASED, keyReleasedEventHandler);
}
/**
* "Key Pressed" handler for all input events: register pressed key in the bitset
*/
private EventHandler<KeyEvent> keyPressedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// register key down
keyboardBitSet.set(event.getCode().ordinal(), true);
}
};
/**
* "Key Released" handler for all input events: unregister released key in the bitset
*/
private EventHandler<KeyEvent> keyReleasedEventHandler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// register key up
keyboardBitSet.set(event.getCode().ordinal(), false);
}
};
// -------------------------------------------------
// Evaluate bitset of pressed keys and return the player input.
// If direction and its opposite direction are pressed simultaneously, then the direction isn''t handled.
// -------------------------------------------------
public boolean isMoveUp() {
return keyboardBitSet.get( upKey.ordinal()) && !keyboardBitSet.get( downKey.ordinal());
}
public boolean isMoveDown() {
return keyboardBitSet.get( downKey.ordinal()) && !keyboardBitSet.get( upKey.ordinal());
}
public boolean isMoveLeft() {
return keyboardBitSet.get( leftKey.ordinal()) && !keyboardBitSet.get( rightKey.ordinal());
}
public boolean isMoveRight() {
return keyboardBitSet.get( rightKey.ordinal()) && !keyboardBitSet.get( leftKey.ordinal());
}
public boolean isFirePrimaryWeapon() {
return keyboardBitSet.get( primaryWeaponKey.ordinal());
}
public boolean isFireSecondaryWeapon() {
return keyboardBitSet.get( secondaryWeaponKey.ordinal());
}
}
Y algunos ajustes globales
public class Settings {
public static double SCENE_WIDTH = 400;
public static double SCENE_HEIGHT = 800;
public static double PLAYER_SHIP_SPEED = 4.0;
public static double PLAYER_SHIP_HEALTH = 100.0;
public static double PLAYER_MISSILE_SPEED = 4.0;
public static double PLAYER_MISSILE_HEALTH = 200.0;
public static int ENEMY_SPAWN_RANDOMNESS = 100;
}
Puedes usar cualquier imagen para los sprites. Tomé el mío de Wikipedia:
player.png
enemigo.png
Si lo pones todo en un paquete de juego, puedes iniciar Game.java. Te dará un smiley controlable con emoticones de zombies que se desplazan hacia abajo. Tienes que evadirlos. Dejé las imágenes sin transparencia para que notara que utilizo una detección de colisión de rectángulo simple. Probablemente buscarás una detección de colisión por píxel.
Se parece a esto:
No pretendo que esta sea la solución, es solo una solución. Por ejemplo, tendrías que limitar el temporizador de animación. O quizás desee establecer el movimiento por segundos en lugar de por cuadro, etc.
Si quieres más información, no dudes en consultar mi blog en el que me descubro cómo crear un Shoot''em''up en 2D con JavaFX . Allí también encontrará, por ejemplo, cómo agregar sprites animados (también aprendí eso del enlace que publicó), un fondo de desplazamiento, una capa de nubes sobre las otras capas, etc. Espero que lo ayude.