Java game lag, too many if statement? - java

I'm working on a game in java, based on the Atari game adventure. I got the basic KeyListener part working fine, but then I added another if statement, using another class, to test if if the player was going to hit a wall, and stopping movement if that was the case. The method I used also used if statements, and when I ran the code, it had MAJOR lag. I tried a while loop first, but that made it lag even worse. Anyway to make this not lag so much? It doesn't seem that complex a program to run, and I still have to add yet another if statement to make be able to move into another room, so I have to do something to massively cut down on the lag.
Here is the class:
class Player extends JPanel implements KeyListener{
private char c = 'e';
int x = 400;
int y = 400;
int mapX = 0;
int mapY = 0;
public Player() {
this.setPreferredSize(new Dimension(800, 500));
addKeyListener(this);
}
public void addNotify() {
super.addNotify();
requestFocus();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Environment Layout = new Environment();
Layout.drawRoom(mapX,mapY,g);
g.fillRect(x , y , 20, 20);
}
public void keyPressed(KeyEvent e) { }
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) {
c = e.getKeyChar();
repaint();
Environment Layout = new Environment();
if(Layout.isWall(x,y,c)){}
else{
if (c == 'a'){
x = x - 3;
}
else if (c == 'w'){
y = y - 3;
}
else if (c == 's'){
y = y + 3;
}
else if (c == 'd'){
x = x + 3;
}
}
}
public static void main(String[] s) throws IOException{
JFrame f = new JFrame();
f.getContentPane().add(new Player());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
}
The draw room method I used in this was just to put the background of the room into place.
Here is the isWall method from the Environment class:
public boolean isWall(int moveX, int moveY, char let){
BufferedImage room = null;
try {
room = ImageIO.read(new File(xNum + "," + yNum + ".png"));
}
catch (IOException e) {
}
int[][] walls = convertImage(room);
boolean blocked = false;
if(let == 'w') {
if(walls[moveY-8][moveX] == -3584){blocked = true;}
}
else if(let == 's') {
if(walls[moveY+20][moveX] == -3584){blocked = true;}
}
else if(let == 'a') {
if(walls[moveY][moveX-5] == -3584){blocked = true;}
}
else if(let == 'd') {
if(walls[moveY][moveX+20] == -3584){blocked = true;}
}
return blocked;
}
the convertImage method just converts the image of the room into an int array, for the value of the colors. -3584 is the color of the walls. It's possible this is what's lagging it, but this seemed like the best way for each room to have the walls done automatically.
I also tried a timer, but either I did that wrong, or it just didn't help.
I can give more of my code if that's needed, but help with this would be much appreciated. I'm relatively new to this kind of stuff, so it's likely I'm missing something big. Thanks.

The lag here is almost certainly not from the if statements. Those are really fast. I think the bigger issue is in isWall. Notice that any time you want to check for whether a wall is present, you
Open a file,
read the file contents,
convert the file contents from an image to a grid of pixels, and
read exactly one pixel.
Reading files from disk is extremely slow compared to looking at values in memory. For example, a regular magnetic hard drive works at around 7200 RPM, so the seek time is measured in milliseconds. On the other hand, your processor can do about a billion operations per second, so other operations take nanoseconds. That means that a disk read is roughly a million times slower than other operations, which is almost certainly where you're getting the lag from!
To fix this, consider rewriting your isWall code so that you only read the file and do the conversion once and, having done that, then just look up the part of the image you need. This converts doing tons of (glacially slow) file reads to one single (slow but inevitable) file read followed by tons of fast memory reads.

You appear to be moving your walls further than you are moving your player.
Is it possible that your player object is getting stuck in a wall there by producing "blocked = true" continuously?
Your character gets +- 3 in every direction, however your walls seem inconsistent and range from 8 up to 20 down to 5 left to 20 right.

This is an extension to #templatetypedef's answer.
Instead of loading the image files upon calling the isWall method, you might want to consider caching all of the walls on game start.
So I am thinking;
have a HashMap data structure keyed by <String, Integer>. Where String is your coordinates. E.g. coordinate string = "100,238"
parse all the .png image files in the directories and store the coordinates as key and the value can just be any dummy value like 1 or 2.
Then when isWall() is invoked. Given the X and Y coordinate, build the coordinate string as mentioned in point 1 and check if the key exists. If it does then we know it is a piece of wall else not.
This should drastically reduce the I/O disk contention.
In future, if you would like to extend the solution to incorporate APIs like isTreasureChest() or isMonster(). It can be extended by building a immutable class call "Room" or "Tile" to represent the object. Then modify the HashMap to take in <String, Room>.

Related

How to properly "layer" images (and changing the source) without inheriting from Swing

I'm finishing my homework in OOP Java. The assignment is to load images on a JFrame, be able to move them around (top layer should be prioritized, it is currently not) and click them to "flip them" (change the source of the image essentially). I'm currently having trouble finding a solution on how to properly "layer" images that are visible on the screen and to prioritize the images on the top first (currently the bottom ones are being prioritized).
I also have issues finding a good way to change the source of the images, as our teacher has prohibited extending the Picture class with Swing.
My first attempt at solving this was saving the information of every individual "Picture" object in an ArrayList. This works to save the position of the images but does not solve my issue with the layering. I also wanted to use JLayeredPane but as I found out, it was harder than I thought as I have yet to find a viable solution this way (I might be missing some obvious facts about how it works).
I'm thinking what probably needs to happen is that I save the "position" of each image in some type of Array, then using this array to print out the images via paintComponent # ImagePanel. This is currently what I am doing but it does not act as I wish it to. I think my way of loading in the images in the "Picture" class might have something to do with it. I don't have a lot of experience in Java so all of this is new to me.
I don't want to print out all of my codes as I have 4 classes, so I'm going to print out what I feel are the essential methods in each class. If there's something missing that you guys need in order to guide me in the right direction I'll provide that aswell.
draw # Picture
public void draw(Graphics g, int i) {
try {
BufferedImage img = ImageIO.read(new File("images/icon_"+ i +".gif"));
g.drawImage(img, x, y, null);
} catch(IOException ie) {
System.out.println("Could not find images");
}
}
mousePressed & mouseDragged # MouseHandler
public void mousePressed (MouseEvent e) {
Point point = e.getPoint();
chosen = imagepanel.locateImage(point);
}
public void mouseDragged (MouseEvent e) {
int x = e.getX();
int y = e.getY();
if (chosen != null) {
imagepanel.moveImage(chosen, x, y);
}
}
loadImages & paintComponent # ImagePanel
private final static int IMAGES = 7;
private ArrayList <Picture> imageCollection = new ArrayList<>();
private Picture im;
Random rand = new Random();
public void loadImages() {
for(int i=0; i<IMAGES; i++) {
int x = rand.nextInt(400) + 40;
int y = rand.nextInt(400) + 60;
im = new Picture(x,y);
imageCollection.add(im);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int i = 0;
for (Picture im : imageCollection) {
i++;
im.draw(g, i);
}
}
I expect the images to stack on top of eachother whenever "flipped" (clicked) or moved (dragged). They do not currently do this as they just maintain their "depth" position. I've tried implementing an Image[] without success.
I also have a flip method where I tried using setIcon (I was using ImageIcon instead of Image previously) but this did not really work for some reason.
I also would love for any feedback on the code so far and any improvements that could be made as I always want to improve.
EDIT: I manage to solve my problems, however I'm sure there's a better way to do this.
public void placeFirst(Picture im) {
int pos = imageCollection.indexOf(im);
imageCollection.remove(pos);
imageCollection.add(0, im);
}
public void flipImage(Picture im) {
im.flip();
placeFirst(im);
repaint();
}
public void moveImage(Picture im, Point point) {
im.move(point.x-(im.getWidth(im)/2), point.y-(im.getHeight(im)/2));
placeFirst(im);
repaint();
}
public Picture locateImage(Point point) {
for (int i=0; i<imageCollection.size(); i++) {
Picture im = imageCollection.get(i);
if (im.fits(point)) {
return im;
}
}
return null;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// There probably exists a better and visually nicer way of doing this
for (int i=imageCollection.size()-1; i>=0; i--) {
Picture im = imageCollection.get(i);
im.draw(g);
}
}
chosen = imagepanel.locateImage(point);
Well, we don't know how the locateImage(...) method works, but I would guess you just iterate through the array until you find a match.
So you will always find the same match.
So if you want an image to stack on top you have two issues:
you need to modify the search order so that when you click on an image you move it to position 0 in the ArrayList so it is always found first
but know when you paint images you need to paint images from the end of the ArrayList to the beginning, so the first image in the ArrayList gets painted last.

How to fix bug with endless writing of numbers using FileWriter with append argument

I faced one problem which I’m struggling to solve.
Imagine simple game, where some object , lets call it car, remains motionless on X-axis ( x = 50 ) and is able to move only on Y-axis (up and down). At the same time, another objects are created beyond the screen at random point ( and move toward my first object ) , so their coordinates decrementing on X-axis. As soon as every object reaches my first object coordinates, some variable int scores; increments.
int scores;
if(cars.getX() == getCarPos_X() && cars.getY() != getCarPos_Y() )
scores++;
Basically this game looks like car which goes between other cars and avoid hitting, and counter scores increments every time my car pass next moving car.
So what is the problem?
I use timer which count time between repainting. All objects pass to the paintComponent where actually all graphic draw. In actionPerformed I call methods for all moves, and one method which checks if collision with another car occurred. In case of collision, game stops, and scores should be written in some txt file.
The problem is that while two objects have same coordinates, JVM write endless number of figures (scores) into the file ( I think it’s because coordinates stop decrementing and every timer interval it checks for collision and it’s == true , as game is stoped , and object remains where they are.)
So my scores in txt file looks like :
0
0
0
0
In one column.
Or it displays any score which I’ve got.
And so on...
Here is the crucial code snippet which I used
public void actionPerformed(ActionEvent e)
{
animate();
checkTouch();
}
private void animate()
{
//here code that creates obstacles and moves them
}
checkTouch()
{
//objects creating in some inner class Cars and add to ArrayList ( I don’t mention about it as it is beside the point )
for(Cars car : cars)
{
if((cars.getX() == getCarPos_X && cars. getY() == getCarPos_Y())
{
//boolean var which stops game
inGame = false;
writeScore();
}
}
}
public void writeScore()
{
File scoresTxt = new File("scores.txt");
FileWriter fw = null;
BufferedWriter bw = null;
try
{
fw = new FileWriter(scoresTxt, true);
bw = new BufferedWriter(fw);
bw.write(scores + "\n");
}catch (IOException e)
{
e.printStackTrace();
}finally
{
try
{
bw.flush();
bw.close();
fw.close();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
public void paintComponent (Graphics g)
{
if(inGame)
{
g.drawImage(myCar, 50, 100, this);
for(Cars car : cars)
{
g.drawImage(obstacleCar, car.getX(), car.getY(), this);
}
}
}
Should you need some extra code I used, write comment and I’ll add it.
And again I need to fix bug which write endless column of numbers instead of one final score from the moment of collision.
What’s wrong with my code , and how to solve this problem?
Give me advice for simplest decision, as I’m beginner.
Thanks in advance!
If your timer is started like this, or something similar, the you could cancel it when the inGame variable becomes false. Nice article on timers.
TimerTask task = new TimerTask() {
public void run() {
if (!inGame)
cancel();
else
// whatever it is that you are doing now
}
};
You might also want to stop processing events in the actionPerformed(e) method in a similar way.
if (!inGame)
return;

How should I drag content from one array of JTextFields to another?

I figured out how to drag to highlight multiple cells in a GridLayout-designed grid (which wasn't too hard) and how to drag a cell from one such grid to another (which involved brute force and math, but it turned out not to be all that hard, either).
But the code looks and feels hacked.
How should I have done it?
Here are code fragments that typify what I did to drag content (one char):
For each cell in txtUser[] grid add mouse listener to identify the cell about to be dragged and also access its content:
txtUser[i].addMouseListener(new java.awt.event.MouseListener()
{
public void mousePressed(MouseEvent e) {
currentUserCell.index = interp(e.getXOnScreen(), ulcUser.x, txtUser[0].getWidth());
if(txtUser[currentUserCell.index].getText().length() > 0)
currentUserCell.content = txtUser[currentUserCell.index].getText().charAt(0);
}
Here's interp(), which converts from absolute screen pixel (x) to (returned) grid element number, given the upper-left corner of the text field array and the width of one element:
static int interp(int x, int ulc, int w){
return (x - ulc)/w;
}
If the user moves the frame, interp() above doesn't work, requiring need to reorient():
void reorient(){
ulcGrid = new Point(cells[ 0][ 0].getLocationOnScreen().x, cells[ 0][ 0].getLocationOnScreen().y);
ulcUser = new Point(txtUser[ 0] .getLocationOnScreen().x, txtUser[ 0] .getLocationOnScreen().y);
}
(I tried to use relative pixel locations, but couldn't make it work. I may revisit this.)
In order to drop the dragged content, the destination had better be inbounds():
boolean inbounds(int r, int c){
return ! (r >= N || c >= N || r < 0 || c < 0);
}
If inbounds, the letter is dropped, as long as destination is empty:
public void mouseReleased(MouseEvent e) {
int x, y;
if(! dragging)
return;
dragging = false;
x = e.getLocationOnScreen().x;
y = e.getLocationOnScreen().y;
int c = Utilities.interp(x, ulcGrid.x);
int r = Utilities.interp(y, ulcGrid.y);
if(! inbounds(r, c))
return;
if(cells[r][c].getText().length() > 0)
return;
cells[r][c].setText("" + currentUserCell.content);
The previous method required a MouseMotionAdapter for each cell of the source array.
And it just seems so hacked. One reason I say this is that I rely on several global variables, such as ulcGrid and ulcUser and currentUserCell and dragging:
private void txtUserMouseDragged(MouseEvent evt)
{
dragging = true;
}
I had a nice learning experience, but I'd rather have more-professional-looking code, most notably with fewer global variables. (I realize that a good start would be to not rely on absolute pixel addresses.)
So I'm asking where to find a better way, specifically how to identify the drag source and destination cells of a one- or two-dimensional array of text fields.
=================
--EDIT--
My program works. My question is about whether there is a library that would make it easier and more reliable than what I've written to drag from the one-dimenional array at the bottom of the screen below onto the large grid.
But now that I've read the comments, maybe this is just another bad question that should be deleted.

"Perfect" Collision Detection Algorithm Fixing

I'm making a game where i need to give my objects collision, but i have many fast small objects and normal collision algorithms (Intersection of shapes and such) do not work, because the position+speed iteration advances the walls and there's never actually an Intersection.
So i've started constructing my own (Maybe it already exists but i didnt see it anywhere) collision algorithm based on saving the last position the object was.
Please see the following image:
The idea is demonstrated in frame 1 and 2 of the image. Basicly by checking if there's a wall between the left side of the last rectangle and the right side of the new rectangle, i never skip zones while i check collision, and there's no risk of skipping a wall (so i thought).
This is the code of the algorithm:
private void bounce(GameElement b, Terrain t)
{
Rectangle tR = t.getRectangle();
int tRleft = tR.x;
int tRright = tR.x+tR.width;
int tRup = tR.y;
int tRdown = tR.y+tR.height;
Rectangle bRnow = b.getRectangle();
int bRnowLeft = bRnow.x;
int bRnowRight = bRnow.x+bRnow.width;
int bRnowUp = bRnow.y;
int bRnowDown = bRnow.y+bRnow.height;
Rectangle bRlast = b.getRectangleLast();
int bRlastLeft = bRlast.x;
int bRlastRight = bRlast.x+bRlast.width;
int bRlastUp = bRlast.y;
int bRlastDown = bRlast.y+bRlast.height;
boolean leftRight = false, rightLeft=false, upDown=false, downUp=false;
boolean betweenX = false, betweenY = false;
if(bRnow.x>bRlast.x)leftRight=true;
if(bRnow.x<bRlast.x)rightLeft=true;
if(bRnow.y>bRlast.y)upDown=true;
if(bRnow.y<bRlast.y)downUp=true;
if(bRlastRight>tRleft && bRlastLeft<tRright) betweenX = true;
if(bRlastDown>tRup && bRlastUp<tRdown) betweenY=true;
if(leftRight)
if((tRleft>bRnowLeft || tRleft>bRlastLeft) && tRleft<bRnowRight && betweenY)
{
b.setX(tR.x-bRnow.width - 1);
}
if(rightLeft)
if((tRright<bRnowRight || tRright<bRlastRight) && tRright>bRnowLeft && betweenY)
{
b.setX(tR.x+tR.width + 1);
}
if(upDown)
if((tRup>bRnowUp || tRup>bRlastUp) && tRup<bRnowDown && betweenX)
{
b.setY(tR.y-bRnow.height - 1);
}
if(downUp)
if((tRdown<bRnowDown || tRdown<bRlastDown) && tRdown>bRnowUp && betweenX)
{
b.setY(tR.y+tR.height + 1);
}
}
Its called bounce because its not really organized atm, i still have to think how to structure the algorithm so it becomes more generalized and pratical (Would appreciate help on that too)
This way of doing collision has one bug at the moment which is seen in image 3 (sorry for drawing circles, they are supposed to be squares) because FAST objects still pass diagonals :/ On the other hand, direct hits on walls are pretty neat.
How could i improve, optimize and organize this algorithm? Or is there any better algorithm and im just thinking too much for nothing? I appreciate your help.
Axis aligned bounding box trees are usually well suited to detecting object collisions. Here is a tutorial with some code - its examples are for 3D collision detection, but the data structure can be easily adapted to 2D collision detection.

Reason why my loop to find an object in an Array only works for the first time?

I am creating a program that displays several bases and the amount of troops each base has. There are two types of bases, friendly and enemy bases. Each Base extends GCompound and consists of a GRect and a GLabel(to display the number of troops). Two arrays are used to keep track of the bases, one for friendly, one for enemy.
I want the user to be able to press the mouse down on one friendly base and release on a different friendly base, causing the troop amount to be transferred from the first base to the second one.
My problem currently is I am only able to detect the base the user presses the mouse down on, and not the base that the mouse is released on. I am using the method getElementAt from the ACM library to return the GObject that a mouse action takes place on.
Code for the mouse press:
public void mousePressed(MouseEvent e){
int mouseX = e.getX();
int mouseY = e.getY();
for(int i = 0; i < PlayerBaseArray.length; i++){
if(getClickedObject(mouseX, mouseY) == PlayerBaseArray[i]){ //Checks to see if the clicked base is in the Array of friendly bases.
pressedIndex = i;
pressedBaseTroopCount = PlayerBaseArray[pressedIndex].getTroopCount();
}
}
}
Code for the mouse release:
public void mouseReleased(MouseEvent m){
int mouseX = m.getX();
int mouseY = m.getY();
for(int i = 0; i < PlayerBaseArray.length; i++){
if(getClickedObject(mouseX, mouseY) == PlayerBaseArray[i]){ // This is always false for some reason.
PlayerBaseArray[i].changeTroopCount(pressedBaseTroopCount);
PlayerBaseArray[pressedIndex].changeTroopCount(-pressedBaseTroopCount);
}
}
}
Method to see what object is clicked:
private GObject getClickedObject(int x, int y){
GObject clicked = getElementAt(x, y);
if(clicked == null) return null;
else return clicked;
}
For some reason the if statement in mouseReleased() is never true, even though it works properly in mousePressed(). Any idea on why the if statement in mouseReleased() does not work?
I've tried researching the problem to no avail, and instead of wasting another night on it I thought I would ask here. Thanks!
You shouldn't use the == operator to compare Objects. You should use the .equals() method:
if (getClickedObject(mouseX, mouseY).equals(PlayerBaseArray[i]))
Put simply, you can only use == only when comparing java primitives (eg int, long etc).
For all "normal" objects, always use objecta.equals(objectb).
This article discusses this issue in more detail.
Attention anal retentives:
Yes, you are right, under some circumstances you can safely use == between objects such as String contants, Integers between -128 and 127, etc, etc. But this guy needed a clear answer and not to be confused. Please resist the temptation to comment about this.

Categories