Hello StackOverflow community!
I have been working on a game project and have recently run into an error in my Spritesheet class.
Problem: The game is developed in LibGDX which has a built-in feature that lets you split apart an image into a 2D array with the first dimension being the row and the second being the column.
E.g: spriteSheet[0][1] would give you the second column of the first row.
So I came up with a little method to generate animations using the row but when I run the game, the animation doesn't seem to work as the player remains static the whole time!
There are 4 classes involved in this process:
The Player class:
package com.darkbyte.games.tfa.game.entity.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.SpriteSheet;
import com.darkbyte.games.tfa.render.RenderManager;
public class Player extends Entity {
//The constructor for the player class
public Player(String name, SpriteSheet spriteSheet) {
super(name, spriteSheet);
direction = Direction.DOWN;
}
//A flag to see if the player is moving
private boolean isMoving;
//The player's walking animations
private Animation[] walkAnimations = {
spriteSheet.getAnimation(4, 1/16f),
spriteSheet.getAnimation(5, 1/16f),
spriteSheet.getAnimation(6, 1/16f),
spriteSheet.getAnimation(7, 1/16f)
};
//The player's static frames
private TextureRegion[] staticFrames = {
spriteSheet.getTexture(4, 0),
spriteSheet.getTexture(5, 0),
spriteSheet.getTexture(6, 0),
spriteSheet.getTexture(7, 0)
};
//The render code for the player
#Override
public void render() {
//Gets the player's direction, if the player's moving, it sets the current frame to the frame that would be played at the current moment based on the state time
//If the player isn't moving, it sets the current frame to the static frame associated to the direction
switch(direction) {
case UP:
if(isMoving)
currentFrame = walkAnimations[0].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[0];
break;
case LEFT:
if(isMoving)
currentFrame = walkAnimations[1].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[1];
break;
case DOWN:
if(isMoving)
currentFrame = walkAnimations[2].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[2];
break;
case RIGHT:
if(isMoving)
currentFrame = walkAnimations[3].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[3];
break;
}
}
//The tick code for the player
#Override
public void tick() {
if(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)) {
direction = Direction.UP;
y += 2;
} else if(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)) {
direction = Direction.LEFT;
x -= 2;
} else if(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)) {
direction = Direction.DOWN;
y -= 2;
} else if(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)) {
direction = Direction.RIGHT;
x += 2;
} else {
}
}
//Returns if the player is moving
public boolean isMoving() {
return isMoving;
}
}
The RenderManager class:
package com.darkbyte.games.tfa.render;
import javax.swing.JOptionPane;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.darkbyte.ammarlib.files.DataWriter;
import com.darkbyte.games.tfa.Core;
import com.darkbyte.games.tfa.music.MusicManager;
import com.darkbyte.games.tfa.screens.ScreenManager;
import com.darkbyte.games.tfa.screens.UnidentifiedScreenStateException;
public class RenderManager {
//timeSinceLastFramesPerSecondCheck is used to store the clock time since the last frames per second check was done
private static long timeSinceLastFramesPerSecondCheck;
//framesPassed is used to store the number of frames passed in a second
private static int framesPassed, framesPerSecond;
//The variable that stores the current state time of the render, used for animations
private static float stateTime;
//Initialisation for everything render related
public static void init() {
Camera.init();
Batch.init();
Button.init();
}
//Disposal for everything render related
public static void dispose() {
Batch.dispose();
Button.dispose();
}
//The main render method
public static void render() {
//Gets the current clock time in milliseconds
long now = System.currentTimeMillis();
//Increments the frames passed
framesPassed++;
//Updates the state time variable
stateTime += Gdx.graphics.getDeltaTime();
//Checks if 1 second has passed since the last frames per second check
if(now >= timeSinceLastFramesPerSecondCheck + 1000) {
//The frames per second is set to the frames passed that second
framesPerSecond = framesPassed;
//The frames passed is reset
framesPassed = 0;
//Sets the time since the last frames per second check to now
timeSinceLastFramesPerSecondCheck = now;
}
//Clears the screen from the previous render call
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//Renders the correct screen from the screen manager and plays the music for that screen
try {
ScreenManager.renderScreen();
MusicManager.playMusic();
} catch (UnidentifiedScreenStateException e) { //If the screen state runs into an error
//Prints the error log to a time-stamped file
DataWriter writer = new DataWriter(); //Creates a new data writer to write to the file
writer.openFile(Core.getTime(true) + ".txt"); //Opens a the file
writer.write("Tetros: First Age - An error has occurred!\nType: UnidentifiedScreenStateException in RenderManager!"); //Writes some basic data about the error
writer.write(e.getMessage()); //Prints a detailed error log from the exception
writer.closeFile(); //Closes the file, housekeeping reasons
e.printStackTrace(); //Prints the error log to the console
//Shows the client an error message in a dialog box
JOptionPane.showMessageDialog(null, "An unexpected error occured: UnidentifiedScreenStateException in RenderManager!", "Error", JOptionPane.ERROR_MESSAGE);
//Closes the game
System.exit(0);
}
}
//Returns the ticks per second rate
public static int getFPS() {
return framesPerSecond;
}
//Returns the frames passed
public static int getFramesPassed() {
return framesPassed;
}
//Returns the current state time
public static float getStateTime() {
return stateTime;
}
}
The SpriteSheet class:
package com.darkbyte.games.tfa.game.entity;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.*;
public class SpriteSheet {
//All the frames from the entity
private TextureRegion[][] textureSheet;
//Gets the sprite sheet for the entity and splits it into frames
public SpriteSheet(Texture spriteSheet, int tileWidth, int tileHeight) {
//Splits the textures into a two dimensional array
/*
* Dimension 1: Row
* Dimension 2: Column
* Example:
* ********
* ****0***
* ********
* ********
* 0 = textureSheet[1][4] to access the second row and the fifth column
* Some of the columns may be blank, check the sprite sheet to reference
*/
textureSheet = TextureRegion.split(spriteSheet, tileWidth, tileHeight);
}
//Disposes of the textures by looping through the rows then the individual columns and disposing of all the textures
public void dispose() {
for(TextureRegion[] textureRow : textureSheet) for(TextureRegion texture : textureRow) texture.getTexture().dispose();
}
//Returns the texture at a certain row or column (starting at 1,1 being textureSheet[0][0])
public TextureRegion getTexture(int row, int column) {
return textureSheet[row][column];
}
//Returns an animation using pieces of the sprite sheet
public Animation getAnimation(int row, float animationFPS) {
//Returns the animation generated using the frames from the row specified and the FPS from the parameters
return new Animation(animationFPS, textureSheet[row]);
}
}
The TestRoom class:
package com.darkbyte.games.tfa.game.world.room.rooms;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.EntityManager;
import com.darkbyte.games.tfa.game.world.room.Room;
import com.darkbyte.games.tfa.render.Batch;
import com.darkbyte.games.tfa.render.Camera;
public class TestRoom extends Room {
//Initialises the room's tiled map and map renderer
#Override
public void init() {
this.tileMap = Room.generateMap("assets/world/rooms/test.tmx");
this.mapRenderer = new OrthogonalTiledMapRenderer(this.tileMap);
entitiesInRoom.add(EntityManager.getPlayer(0, 0, 0));
}
//Disposes of the room
#Override
public void dispose() {
this.tileMap.dispose();
}
//Runs the render code for the room
#Override
public void render() {
//Sets the map renderer's viewpoint to the viewpoint of the camera
mapRenderer.setView(Camera.getCamera());
//Renders the map
mapRenderer.render();
//Begins the drawing batch
Batch.getGameBatch().begin();
//Loops through the entities in the room and renders them
for(Entity entityToRender : entitiesInRoom) {
entityToRender.render();
Batch.getGameBatch().draw(entityToRender.getCurrentFrame(), entityToRender.getX(), entityToRender.getY());
}
//Ends the drawing batch
Batch.getGameBatch().end();
}
//Runs the tick code for the room
#Override
public void tick() {
//Loops through the entities in the room and runs their tick code
for(Entity entityToRender : entitiesInRoom) entityToRender.tick();
}
}
Instead of building all the animation mechanism yourself, you should use the Libgdx's Animation, which is much easier to use. Read the wiki.
Related
So I'm trying to create a game and it's my first time. My game is a 2D side scroller and is about the player in space avoiding the incoming meteors. I have successfully managed to get the meteors spawning randomly on the x and y axis off screen and re position once it has gone pass the screen.
But the problem I face now is sometimes the spawn of the meteors will clump together which I don't want. How do I get the meteors to spawn at a certain distance from each other so they don't clump together. I couldn't find any good tutorials or if anyone can point me to the right direction. Below are my codes so far.
Meteor Class
public class Meteors {
private Texture bigMeteor;
private Vector2 posBigMeteor;
private Random yrand;
//Constructor
public Meteors(float x){
bigMeteor = new Texture("meteor.png");
yrand = new Random();
//Spawn location of meteor
posBigMeteor = new Vector2(x, yrand.nextInt(AstroDemo.HEIGHT/2 - bigMeteor.getHeight()));
}
public Texture getBigMeteor() {
return bigMeteor;
}
public Vector2 getPosBigMeteor() {
return posBigMeteor;
}
//Reposition the meteors
public void reposition(float x){
posBigMeteor.set(x, yrand.nextInt(AstroDemo.HEIGHT/2 - bigMeteor.getHeight()));
}
}
PlayState Class
public class PlayState extends State {
//Total meteor count on screen
private static final int METEOR_COUNT = 8;
private Naught naught;
private Texture bg;
private Random xrand;
private Array <Meteors> meteors;
public PlayState(GameStateManager gsm) {
super(gsm);
//Starting co-ordinates of main character (Naught)
naught = new Naught(50, 100);
//Setting viewport of the camera
cam.setToOrtho(false, AstroDemo.WIDTH/2, AstroDemo.HEIGHT/2);
bg = new Texture("bg.png");
xrand = new Random();
meteors = new Array <Meteors>();
//Spawn meteors randomly off screen
for (int i = 1; i <= METEOR_COUNT; i++){
meteors.add(new Meteors(AstroDemo.WIDTH/2 + (xrand.nextInt(300))));
}
}
#Override
protected void handleInput() {
//If screen/mouse is held
if(Gdx.input.isTouched()){
//Main Character jumps/flys
naught.jump();
}
}
#Override
public void update(float dt) {
handleInput();
naught.update(dt);
//If meteors are left side of the screen, re-position to the right side of the screen
for(Meteors meteor : meteors){
if (cam.position.x - (cam.viewportWidth/2) > meteor.getPosBigMeteor().x + meteor.getBigMeteor().getWidth()){
meteor.reposition(meteor.getPosBigMeteor().x + (AstroDemo.WIDTH/2 + 20 + (xrand.nextInt(300))));
}
}
cam.position.x = naught.getPosition().x + 80;
cam.update();
}
#Override
public void render(SpriteBatch sb) {
//Adjust the spritebatch for co-ordinate system in relation to camera
sb.setProjectionMatrix(cam.combined);
sb.begin();
//Draw background where the camera is
sb.draw(bg, cam.position.x - (cam.viewportWidth/2), 0);
sb.draw(naught.getTexture(), naught.getPosition().x, naught.getPosition().y);
for (Meteors meteor : meteors) {
sb.draw(meteor.getBigMeteor(), meteor.getPosBigMeteor().x, meteor.getPosBigMeteor().y);
}
sb.end();
}
#Override
public void dispose() {
}
}
create a array of predefined value for your y position and then get value randomly from that array.
or
Divide height into sub portion then get random value from that portion so that each random value not collide with other value.
I testing to implement graphics into MVC structure but Im a bit stuck. Here is what I got so far. For now I just want to get the red ball to bounce back and forth. And use the button start to start the thread and button stop to stop the thread that runs the GameLoop in the controller.
But I think Im mixing this up a bit. Would very much appreciate some feedback!
Heres what I got so far:
GameModell
suppose to controll the bouncing. If the location of the ball is under 40 px or above 80 px - multiply the locationX with -1 to make the ball change direction
GameView
Here Im putting the labels on a JFrame. I also want to display the buttons start and stop to controll the thread but I guess they are hidden by the JPanel in TheGraphics class
GameController
Starts and stops the thread with ActionListeners. Contains the GameLoop
TheGraphics
Paints the ball and controll the direction
I guess I got a lot of thing that are all wrong but this is the best I can do at the moment. Would very much apreciate some help!
Thanks!
MAIN:
public class MVCgame {
public static void main(String[] args) {
GameModel gm = new GameModel();
GameView gv = new GameView();
GameController gc = new GameController(gm, gv);
}
}
MODEL:
public class GameModel {
private int multi = 1;
public void setMulti(int locX) {
if(locX < 40 || locX > 80) {
multi = multi * -1;
}
}
public int multi() {
return multi;
}
}
VIEW:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GameView extends JFrame {
private JPanel jp = new JPanel();
private JButton start = new JButton("Start");
private JButton stop = new JButton("Stop");
TheGraphics gr = new TheGraphics();
public GameView() {
add(jp);
add(gr);
jp.add(start);
jp.add(stop);
setSize(250, 250);
setVisible(true);
}
public void addListener(ActionListener theListener) {
start.addActionListener(theListener);
stop.addActionListener(theListener);
}
public JButton getStart() {
return start;
}
public JButton getStop() {
return stop;
}
// GUESS I SHOULD PUT THIS IN THE VIEW???
public void paintEllipse(Graphics theG) {
Graphics2D g2d = (Graphics2D) theG;
g2d.setColor(new Color(255, 0, 0));
g2d.fillOval(0, 0, 10, 10);
}
}
CONTROLLER:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GameController implements Runnable {
GameView gv;
GameModel gm;
private Thread thread;
private boolean running = false;
public GameController(GameModel gm, GameView gv) {
this.gv = gv;
this.gm = gm;
gv.addListener(theListener);
start();
}
ActionListener theListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == gv.getStart()) {
start();
System.out.println("PLAY = ");
} else if (e.getSource() == gv.getStop()) {
stop();
System.out.println("STOP = ");
}
}
};
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
public synchronized void stop() {
thread.interrupt();
running = false;
}
// GameLoop
public void run() {
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 10) {
// tick();
delta--;
// repainting the graphics
gv.gr.drawer();
gm.setMulti(gv.gr.drawer());
System.out.println("gv.gr.drawer() = " + gv.gr.drawer() + " gm.multi() " + gm.multi());
// I want to use this value in the model to change the direction
}
if (running) {
}
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
}
}
THE GRAPHICS:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class TheGraphics extends JPanel {
private int locX = 40;
public TheGraphics() {
}
public int drawer() {
locX++;
repaint();
return locX;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(new Color(255, 0, 0));
g2d.fillOval(locX, 30, 10, 10);
}
}
GameModell suppose to controll the bouncing. If the location of the ball is under 40 px or above 80 px - multiply the locationX with -1 to make the ball change direction
public void setMulti(int locX) {
if(locX < 40 || locX > 80) {
multi = multi * -1;
}
}
Really bad idea. You should always check position and direction (sign(speed)). Otherwise, your object might get stuck out of bounds always changing direction without moving from place forever.
Apart from this, using the MVC concept is overkill in my eyes and shouldn't be used in such a small project nor in a game. In a game, you should more or less put all three together. Of course you can, but the advantages and disadvantages of the MVC concept don't fit the needs of a game in many ways (except for the GUI, perhaps).
Your main loop might look something like this (you kind of did this already, but why is the tick() commented out in your code?):
while (running) {
update(); // Update all game objects
paint(); // Paint them all
}
Each game object will have its own update() and paint() implementation. You absolutely need to separate the logic of update and paint, even if they are in the same class. So this one:
public int drawer() {
locX++;
repaint();
return locX;
}
is an absolute no-go.
Edit: (Referring your update answer)
You are using the method location() for different purposes. According to the Java name convention, you should rename it getLocation() and setLocation() depending on the use to clarify the code.
(Even if this is not really MVC anymore, I'd let GameFrame implement ActionListener instead of specifying it as variable of GameController.)
One thing you should really change is this one:
private int locX = 0;
public void location(int loc) {
this.locX = (int) loc;
}
Basically, you are duplicating the location value every frame and create unused redundant data. Another problem is, that this might work fine for only one variable, but what if you add more than the position to your model later on? Instead TheGraphics has to render on an instance of the data model, not its values. As long you are using one GameModel
private GameModel model; // set value once at initialisation
and rendering its values in paintComponent will work fine, but if you want to add more than one GameModel (handling GameModel more like a GameObjectModel), you will need to pass it as parameter in the paint method.
public void update() {
repaint();
}
Remove it and try getting around without. A method called from one place forwarding to a different method is a bad idea most of the time, especially if it obfuscates the functionality with a different name.
gv.gr.update();
gv.gr.location(gm.location());
You are first repainting your image and then setting the location? Basically, your game runs one frame behind all the time. Swap that order.
gv.gr.location(gm.location());
gv.gr.repaint();
Will be fine (I already said about location()).
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
Update: Just to specify, depending on how I change the rules I can set it so within a couple of generations all cells are either permanently alive or dead. I have checked this by echoing statements to console. HOWEVER, this doesn't reflect in the GUI which shows all cells as always the same color.
I am trying to implement a simple Cellular Automaton to replicate the game of life. This uses the MASON library. My three classes:
Cell.java
package sim.app.gol;
import sim.engine.SimState;
import sim.engine.Steppable;
import sim.field.grid.IntGrid2D;
import sim.util.IntBag;
public class Cell implements Steppable {
public IntGrid2D grid = new IntGrid2D(0,0);
public void step(SimState state) {
Matrix matrix = (Matrix) state;
grid.setTo(matrix.matrix);
for(int x = 0; x < grid.getWidth(); x++) {
for(int y = 0; y < grid.getHeight(); y++) {
IntBag nei = grid.getMooreNeighbors(x, y, 2, 0, false, new IntBag(), new IntBag(), new IntBag());
int count = 0;
for(int i = 0; i < nei.size(); i++) {
count += nei.get(i);
}
int currentState = grid.get(x, y);
if(currentState == 0) {
if(count > 3)
matrix.matrix.set(x, y, 1);
} else if(currentState == 1) {
matrix.matrix.set(x,y,0);
}
}
}
}
}
Matrix.java
package sim.app.gol;
import ec.util.MersenneTwisterFast;
import sim.engine.SimState;
import sim.field.grid.IntGrid2D;
public class Matrix extends SimState {
public final int HEIGHT = 10;
public final int WIDTH = 10;
public IntGrid2D matrix = new IntGrid2D(HEIGHT, WIDTH);
public final int NUM_CELLS = 80;
public Matrix(long seed) {
super(seed);
}
public void start() {
super.start();
// Utils for random number generator
MersenneTwisterFast g = new MersenneTwisterFast();
// We set everything to 0, no cells are active
matrix.setTo(0);
// Populating
for(int i = 0; i < NUM_CELLS; i++) {
int x = 0;
int y = 0;
// We don't want to mark as 'active' a cell that is already active
do {
x = g.nextInt(WIDTH);
y = g.nextInt(HEIGHT);
} while(matrix.get(x, y) == 1);
matrix.set(x, y, 1);
}
schedule.scheduleRepeating(new Cell());
}
public static void main(String[] args) {
doLoop(Matrix.class, args);
System.exit(0);
}
}
MatrixWithUI.java
package sim.app.gol;
import java.awt.Color;
import javax.swing.JFrame;
import sim.app.students.Students;
import sim.display.Console;
import sim.display.Controller;
import sim.display.Display2D;
import sim.display.GUIState;
import sim.engine.SimState;
import sim.portrayal.continuous.ContinuousPortrayal2D;
import sim.portrayal.grid.ObjectGridPortrayal2D;
import sim.portrayal.grid.ValueGridPortrayal2D;
import sim.portrayal.simple.OvalPortrayal2D;
public class MatrixWithUI extends GUIState {
public Display2D display;
public JFrame displayFrame;
public ValueGridPortrayal2D matrixPortrayal = new ValueGridPortrayal2D();
public static void main(String[] args) {
MatrixWithUI mwu = new MatrixWithUI();
Console c = new Console(mwu);
c.setVisible(true);
}
public void start() {
super.start();
setupPortrayals();
}
public void load(SimState state) {
super.load(state);
setupPortrayals();
}
public void setupPortrayals() {
Matrix matrix = (Matrix) state;
matrixPortrayal.setField(matrix.matrix);
matrixPortrayal.setPortrayalForAll(new OvalPortrayal2D());
display.reset();
display.setBackdrop(Color.white);
display.repaint();
}
public void init(Controller c) {
super.init(c);
display = new Display2D(600,600,this);
display.setClipping(true);
displayFrame = display.createFrame();
displayFrame.setTitle("Schoolyard Display");
c.registerFrame(displayFrame);
displayFrame.setVisible(true);
display.attach(matrixPortrayal, "Yard");
}
public void quit() {
super.quit();
if (displayFrame != null) displayFrame.dispose();
displayFrame = null;
display = null;
}
public MatrixWithUI() {
super(new Matrix (System.currentTimeMillis()));
}
public MatrixWithUI(SimState state) {
super(state);
}
public static String getName() {
return "Student Schoolyard Cliques";
}
}
However, for some reason all cells are continuously set to 0 (or off). Any thoughts?
Note: this is a tentative answer as I have no way of verifying it at the moment.
First, let's look at the documentation of ValueGridPortrayal2D. It says:
Like other FieldPortrayal2Ds, this class uses an underlying SimplePortrayal2D to draw each separate element in the grid. A default SimplePortrayal2D is provided which draws squares. In the default, the color for the square is determined by looking up the value of the square in a user-provided color-table, or if there is none, by interpolating it between two user-provided colors. See the setColorTable() and setLevels() methods.
So, if you settle for squares rather than ovals, you can drop this line:
matrixPortrayal.setPortrayalForAll(new OvalPortrayal2D());
And instead, add:
java.awt.Color[] colorTable = new java.awt.Color[2];
colorTable[0] = new java.awt.Color(1.0F,0.0F,0.0F,0.0F);
colorTable[1] = new java.awt.Color(1.0F,0.0F,0.0F,1.0F);
matrixPortrayal.setMap( new SimpleColorMap(colorTable) );
This should give you white squares (transparent on a white backdrop) for 0, and red squares for 1.
If you want to draw ovals, this default implementation of a SimplePortrayal2D that uses a map is not available. The documentation goes further to say:
You can also provide your own custom SimplePortrayal2D (use setPortrayalForAll(...) ) to draw elements as you see fit rather than as rectangles. Your SimplePortrayal2D should expect objects passed to its draw method to be of type MutableDouble.
So we need to override the draw() method and treat the passed object - the cell value - as a MutableDouble (by which I assume they mean the one from org.apache.commons.lang):
matrixPortrayal.setPortrayalForAll(new OvalPortrayal2D() {
public void draw(Object object, Graphics2D graphics, DrawInfo2D info) {
MutableDouble valueObj = (MutableDouble)object;
if ( valueObj.intValue() == 0 ) {
paint = new java.awt.Color(1.0F,0.0F,0.0F,0.0F);
} else {
paint = new java.awt.Color(1.0F,0.0F,0.0F,1.0F);
}
filled = true;
super.draw(object, graphics, info);
}
});
So we have created an anonymous subclass of OvalPortrayal2D. It inherits the fields paint, filled and scale from AbstractShapePortrayal2D. So we override paint (java.awt.Paint, which java.awt.Color extends) with the color we need for the particular value, and make sure the oval is filled.
How can I shoot in the direction that a cross-hair is pointing at?
Using the JMonkey engine, I am creating a game where I need a ship to shoot other ships.
So, I created cross-hairs that can move on the screen (up, down, left, right) according to user input, so the user can aim on a certain place.
Now I need I to shoot a cannon from my ship, in the direction that the cross-hair is standing at.
How can I shoot at the place that the cross-hair is pointing at?
You can get camera direction with:
directionXYZ=cam.getDirection(); //Vector3f form
and can get position from:
positionXYZ=cam.getLocation(); //Vector3f
You can have a ray casting:
Ray ray = new Ray(directionXYZ, positionXYZ);
then can collect data for collisions:
shootables.collideWith(ray, results)
where shootable is a "Node".
Finally, check for what you want:
for (int i = 0; i < results.size(); i++) {
// For each hit, we know distance, impact point, name of geometry.
float dist = results.getCollision(i).getDistance();
Vector3f pt = results.getCollision(i).getContactPoint();
String hit = results.getCollision(i).getGeometry().getName();
System.out.println("* Collision #" + i);
System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away.");
}
Taken from jmonkey wiki
My reading of this question is that the aim is not to shoot where the camera is facing but where a cursor (not in the centre of the screen) is pointing.
This can be achieved using the cam.getWorldCoordinates(screenPosition, zDepth); command, this returns the 3D point in space that would end up at the point screenPosition on the screen. So if we create a point at zDepth of zero and a point at zDepth of one we can create a ray coming from the cursor position travelling outwards so anything that the curser is "over" is selected. screenPosition is in pixels from the bottom left of the window
An example program that uses this technique is as follows and is based upon the second part of hello picking.
In my example the cursor is moved using the keyboard (H,J,K,U) but a mouse click could also be used (but I'm using the mouse for looking around)
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
public class Main extends SimpleApplication {
public static KeyBindings keyBindings;
public Vector2f cursorPosition=new Vector2f(100,100);
public Node shootables=new Node();
public Node crossHairNode=new Node();
public static void main(String[] args) {
Main app = new Main();
app.start();
}
#Override
public void simpleInitApp() {
//bind keys to move cursor
keyBindings=new KeyBindings(inputManager); //for managing keystrokes
keyBindings.registerKeyBinding(KeyInput.KEY_SPACE, "fire");
keyBindings.registerKeyBinding(KeyInput.KEY_U, "up");
keyBindings.registerKeyBinding(KeyInput.KEY_J, "down");
keyBindings.registerKeyBinding(KeyInput.KEY_H, "left");
keyBindings.registerKeyBinding(KeyInput.KEY_K, "right");
initGui();
Box b = new Box(Vector3f.ZERO, 2, 2, 2);
Geometry geom = new Geometry("BigBox", b);
Box b2 = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry geom2 = new Geometry("SmallBox", b2);
geom2.setLocalTranslation(3, 0, 3);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geom.setMaterial(mat);
geom2.setMaterial(mat);
rootNode.attachChild(shootables);
shootables.attachChild(geom);
shootables.attachChild(geom2);
}
#Override
public void simpleUpdate(float tpf) {
updateCrossHairs();
if (keyBindings.getBinding("fire").hasBecomeTrueSinceLastAccess()){
CollisionResults results = new CollisionResults();
Vector3f cursor3dLocation = cam.getWorldCoordinates(
new Vector2f(cursorPosition.x, cursorPosition.y), 0f).clone();
Vector3f dir = cam.getWorldCoordinates(
new Vector2f(cursorPosition.x, cursorPosition.y), 1f).subtractLocal(cursor3dLocation).normalizeLocal();
Ray ray = new Ray(cursor3dLocation, dir);
shootables.collideWith(ray, results);
if (results.size()>0){
resultsText.setText("Hit: " + results.getClosestCollision().getGeometry().getName());
resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
}else{
resultsText.setText("Missed");
resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
}
}
}
private void updateCrossHairs(){
if (keyBindings.getBinding("up").getValue()==true){
cursorPosition.y+=1;
}
if (keyBindings.getBinding("down").getValue()==true){
cursorPosition.y+=-1;
}
if (keyBindings.getBinding("left").getValue()==true){
cursorPosition.x+=-1;
}
if (keyBindings.getBinding("right").getValue()==true){
cursorPosition.x+=+1;
}
crossHairNode.setLocalTranslation(cursorPosition.x - crossHair.getLineWidth()/2,cursorPosition.y + crossHair.getLineHeight()/2, 0);
}
BitmapText crossHair;
BitmapText instructions;
BitmapText resultsText;
private void initGui() {
setDisplayStatView(false);
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
crossHair = new BitmapText(guiFont, false);
crossHair.setSize(guiFont.getCharSet().getRenderedSize() * 2);
crossHair.setText("+"); // crosshairs
crossHairNode.setLocalTranslation(cursorPosition.x - crossHair.getLineWidth()/2,cursorPosition.y + crossHair.getLineHeight()/2, 0);
guiNode.attachChild(crossHairNode);
crossHairNode.attachChild(crossHair);
instructions= new BitmapText(guiFont, false);
instructions.setSize(guiFont.getCharSet().getRenderedSize());
instructions.setText("Move cross hairs with U,H,J,K keys, fire with space \n (WSAD moves camera position and look with mouse)");
instructions.setLocalTranslation(0, settings.getHeight(), 0);
guiNode.attachChild(instructions);
resultsText= new BitmapText(guiFont, false);
resultsText.setSize(guiFont.getCharSet().getRenderedSize());
resultsText.setText("Press Space to fire");
resultsText.setLocalTranslation(settings.getWidth()-resultsText.getLineWidth(),resultsText.getLineHeight(), 0);
guiNode.attachChild(resultsText);
}
#Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
}
Keybindings, used only to control cursor movement:
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import java.util.ArrayList;
import java.util.Locale;
public class KeyBindings implements ActionListener{
private InputManager inputManager;
private ArrayList<String> bindingString=new ArrayList<String>(100);
private ArrayList<KeyBinding> binding=new ArrayList<KeyBinding>(100);
public KeyBindings(InputManager inputManager){
this.inputManager=inputManager;
}
public void registerKeyBinding(int key,String bindingName){
bindingName=preprocess(bindingName);
inputManager.addMapping( bindingName, new KeyTrigger(key));
inputManager.addListener(this, bindingName);
binding.add(new KeyBinding());
bindingString.add(bindingName);
}
public void registerMouseBinding(int button,String bindingName){
bindingName=preprocess(bindingName);
inputManager.addMapping( bindingName, new MouseButtonTrigger(button));
inputManager.addListener(this, bindingName);
binding.add(new KeyBinding());
bindingString.add(bindingName);
}
public KeyBinding getBinding(String bindingName){
//get which binding we're after
bindingName=preprocess(bindingName);
int index=bindingString.indexOf(bindingName);
if (index!=-1){
return binding.get(index);
}else{
return null;
}
}
public void onAction(String bindingName, boolean isPressed, float tpf) {
bindingName=preprocess(bindingName);
//get which binding we're after
int index=bindingString.indexOf(bindingName);
if (index!=-1){
binding.get(index).setValue(isPressed);
}
}
private String preprocess(String string){
return string.toUpperCase();
}
}
public class KeyBinding {
private boolean value;
private boolean changedSinceLastAccess; //to avoid multiclicks etc
private boolean valueTrueSinceLastAccess;
public void setValue(boolean value){
this.value=value;
changedSinceLastAccess=true;
if (value==true){
valueTrueSinceLastAccess=true;
}
}
public boolean hasChangedSinceLastAccess(){
return changedSinceLastAccess;
}
public boolean hasBecomeTrueSinceLastAccess(){
//this collects any trues since the last access, is good for things like mouse clicks,
//which are instantaneous and you want then recorded on the next tick
if (valueTrueSinceLastAccess==true){
valueTrueSinceLastAccess=false;
return true;
}else{
return false;
}
}
public boolean getValue(){
changedSinceLastAccess=false;
valueTrueSinceLastAccess=false;
return value;
}
public boolean getValue_withoutNotifying(){
return value;
}
}
I tried making a program that flips a coin(shows image of heads first and later shows image of tails) and I encountered problems trying to have the image of the coin viewed when I ran the problem; only a blank screen would show. I don't know whether this is from an improper saving method of the jpg images or from an error in the code. I also came across an error before again coding the program where I had the heads image show and tails image not show.
CoinTest.java runs coin runner and Coin.java is the class for the program.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CoinTest extends JPanel
implements ActionListener
{
private Coin coin;
public CoinTest ()
{
Image heads = (new ImageIcon("quarter-coin-head.jpg")).getImage();
Image tails = (new ImageIcon("Indiana-quarter.jpg")).getImage();
coin = new Coin(heads, tails);
Timer clock = new Timer(2000, this);
clock.start();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int x = getWidth() / 2;
int y = getHeight() / 2;
coin.draw(g, x, y);
}
public void actionPerformed(ActionEvent e)
{
coin.flip();
repaint();
}
public static void main(String[] args)
{
JFrame w = new JFrame("Flipping coin");
w.setSize(300, 300);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CoinTest panel = new CoinTest();
panel.setBackground(Color.WHITE);
Container c = w.getContentPane();
c.add(panel);
w.setVisible(true);
}
}
Now the actual Coin class.
import java.awt.Image;
import java.awt.Graphics;
public class Coin
{
private Image heads;
private Image tails;
private int side = 1;
public Coin(Image h, Image t)
{
heads = h;
tails = t;
}
//flips the coin
public void flip()
{
if (side == 1)
side = 0;
else
side = 1;
}
//draws the appropriate side of the coin - centered in the JFrame
public void draw(Graphics g, int x, int y)
{
if (side == 1)
g.drawImage(heads, heads.getWidth(null)/3, heads.getHeight(null)/3, null);
else
g.drawImage(heads, tails.getWidth(null)/3, tails.getHeight(null)/3, null);
}
}
Firstly, ensure that both images are in the correct location to load.
Secondly, you have a typo here:
if (side == 1)
g.drawImage(heads, heads.getWidth(null)/3, heads.getHeight(null)/3, null);
else
g.drawImage(heads, tails.getWidth(null)/3, tails.getHeight(null)/3, null);
^^^^
should be tails...
The width and height of the applet are coded in the tag. The code that draws the applet uses the two methods to get these values at run time. So now, different tags can ask for the same applet to paint different sized rectangles. The source code does not need to be recompiled for different sizes.