Hi everyone, I'm new to Java and a relative newbie when it comes to programming in general. I've been learning a bit from some Youtube tutorials dealing with Java basics. They've been very helpful and I've been able to create a basic game setup with my new knowledge, with a randomly generated map of tiles, a "camera" that allows the user to look around the map, and a character that the use can move using the arrow keys. I have a few problems/questions though.

1) Regarding classes/superclasses. I have an abstract class, "Tile", which contains methods such as getImage and getPassable, with subclasses "Grass" and "Rock". However, I seem to have to write the methods from "Tile" in the subclasses as well to get them to work, which was not how I thought it worked. Am I doing something wrong? Tile class below:

package main;

import java.awt.Image;

public abstract class Tile {

	private Image tileImage;
	private boolean tilePassable;
	private int x,y;
		
	public Image getImage(){
		return tileImage;
	}
	
	public boolean getPassable(){
		return tilePassable;
	}
	
	public int getHeight(){
		return tileImage.getHeight(null);
	}
	public int getWidth(){
		return tileImage.getHeight(null);
	}
	
	public void setX(int x){
		this.x = x;
	}
	public void setY(int y){
		this.y = y;
	}
	public int getX(){
		return x;
	}
	public int getY(){
		return y;
	}
	
}

2) Character movement. I want my character to move along the tiles, so it should not be able to stop when it's half way between two tiles. I use the following methods:

//sets player movement, is called when users presses an arrow key
public void playerMove(int d, int m, float vx, float vy){
		if(player.getMoveDistance() == 0){
			player.setDirection(d);
			player.setMoveDistance(m);
			player.setVelocityX(vx);
			player.setVelocityY(vy);
		}
	}
//update method in the sprite, is called in the main game loop
public void update(long timePassed) {
    	if(moveDistance > 0){
    		x += vx * 32;
    		y += vy * 32;
    		if(Math.round(x) % 32 == 0 && Math.round(y) % 32 == 0){
    			moveDistance--;
    		}
    	}
    }

Basically, moveDistance specifies the number of tiles left to move, and the velocity cannot be changed if this is not 0. However, this seems to generate quite a large wait if the player wishes to change direction - the sprite reaches the end of the tile, then does nothing for around half a second before taking the new direction. Is there a better method to achieve the movement I want or am I stuck with this delay?

Thanks in advanced for any help.

I seem to have to write the methods from "Tile" in the subclasses as well to get them to work,

Which methods? What changes need to be made?

Hard to make suggestions with these short pieces of code.

All the methods. Rock and Grass are both Tiles, but I also want them to have their own methods along with all the methods in Tile. However they don't seem to be inheriting them. I can post up all the relevant code if necessary but it's probably hugely messy at this stage.

Grass class:

package main;

import java.awt.Image;

import javax.swing.ImageIcon;

public class Grass extends Tile {
	
	private int x,y;
	boolean tilePassable = true;
	Image tileImage = new ImageIcon("C:\\Users\\Angus\\workspace\\dwarf\\bin\\main\\grass.png").getImage();
	
	public Grass(int x, int y){
		this.x = x;
		this.y = y;
	}
		
	public boolean getPassable(){
		return tilePassable;
	}
	
	public Image getImage(){
		return tileImage;
	}
	
	public int getHeight(){
		return tileImage.getHeight(null);
	}
	public int getWidth(){
		return tileImage.getHeight(null);
	}
	
	public void setX(int x){
		this.x = x;
	}
	public void setY(int y){
		this.y = y;
	}
	public int getX(){
		return x;
	}
	public int getY(){
		return y;
	}

	
}

Core class:

package main;

import java.awt.*;

public abstract class Core {
	
	public static DisplayMode modes1[] = {
		new DisplayMode(800,600,32,0),
		new DisplayMode(800,600,24,0),
		new DisplayMode(800,600,16,0),
		new DisplayMode(640,480,32,0),
		new DisplayMode(640,480,24,0),
		new DisplayMode(640,480,16,0),		
	};
	private boolean running, moving;
	protected ScreenManager s;
	
	//stop method
	public void stop(){
		running = false;
	}
	
	public void moving(boolean m){
		moving = m;
	}
	
	
	//call init and gameloop
	public void run(){
		try{
			init();
			gameLoop();
		}finally{
			s.restoreScreen();
		}
	}
	
	//set to full screen
	public void init(){
		s = new ScreenManager();
		DisplayMode dm = s.findFirstCompatibleMode(modes1);
		s.setFullScreen(dm);
		
		Window w = s.getFullScreenWindow();
		w.setFont(new Font("Arial", Font.PLAIN, 20));
		w.setBackground(Color.GREEN);
		w.setForeground(Color.WHITE);
		
		running = true;
		moving = false;
	}
	
	//main game loop
	public void gameLoop(){
		long startTime = System.currentTimeMillis();
		long cumTime = startTime;
		
		while(running){
			long timePassed = System.currentTimeMillis() - cumTime;
			cumTime += timePassed;
			
			update(timePassed);
			
			Graphics2D g = s.getGraphics();
			draw(g);
			g.dispose();
			s.update();
			
			try{
				Thread.sleep(20);
			}catch(Exception ex){}
		}
	}
	
	
	
	//update animation
	public void update(long timePassed){
	}
	
	//draw to screen
	public abstract void draw(Graphics2D g);
}

Main class:

package main;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;

public class Main extends Core implements KeyListener, MouseMotionListener {
	public static void main(String[] args){
		new Main().run();
	}
	
	public ArrayList<Tile> tileArray = new ArrayList<Tile>();
	public Sprite player;
	private Camera camera;
	private Robot robot;
	private Point mouse;
	private Point center;
	private Point image;
	private boolean centering;
	private int key;
	
	//init
	public void init(){
		super.init();
		camera = new Camera();
		mouse = new Point();
		center = new Point();
		image = new Point();
		centering = false;
		
		try{
			robot = new Robot();
			recenterMouse();
			mouse.x = center.x;
			mouse.y = center.y;
		}catch(Exception ex){
			System.out.println("Exception 1");
		}
		
		try{
			loadImages();
		}catch(Exception ex){
			System.out.println("Exception 2");
		}
		
		Window w = s.getFullScreenWindow();
		w.setCursor(null);
		w.setFocusTraversalKeysEnabled(false);
		w.addKeyListener(this);
		w.addMouseMotionListener(this);
		
		int x = 0;
		int y = 0;
		Random rng = new Random();
		
		while(y < (w.getHeight() * 4)){
			while(x < (w.getWidth() * 4)){
				if(rng.nextInt(20) == 0){
					tileArray.add(new Rock(x,y));
				}else{
					tileArray.add(new Grass(x,y));
				}
				x += tileArray.get(tileArray.size() - 1).getWidth();
			}
			x = 0;
			y += tileArray.get(tileArray.size() - 1).getHeight();			
		}
	}	
	
	//load images
	private void loadImages(){
		Image pfs = new ImageIcon("C:\\Users\\Angus\\workspace\\dwarf\\bin\\main\\playerforwardstill.png").getImage();
		Image pbs = new ImageIcon("C:\\Users\\Angus\\workspace\\dwarf\\bin\\main\\playerbackwardstill.png").getImage();
		Image prs = new ImageIcon("C:\\Users\\Angus\\workspace\\dwarf\\bin\\main\\playerrightstill.png").getImage();
		Image pls = new ImageIcon("C:\\Users\\Angus\\workspace\\dwarf\\bin\\main\\playerleftstill.png").getImage();
		
		Animation a[] = {new Animation(), new Animation(), new Animation(), new Animation()};
		
		a[0].addScene(pfs, 250);
		a[1].addScene(pbs, 250);
		a[2].addScene(prs, 250);
		a[3].addScene(pls, 250);
		
		player = new Sprite(a);
		player.setDirection(0);
		player.setX(32);
		player.setY(32);
	}
	
	//draw
	public synchronized void draw(Graphics2D g){
		int tw = getTotalWidth(tileArray.get(tileArray.size()-1));
		int th = getTotalHeight(tileArray.get(tileArray.size()-1));
		int sw = s.getWidth();
		int sh = s.getHeight();
		
		if(image.x > 0){
			image.x = 0;
		}
		if(image.x < -(tw-sw)){
			image.x = -(tw-sw);
		}
		if(image.y < -(th-sh)){
			image.y = -(th-sh);
		}
		if(image.y > 0){
			image.y = 0;
		}
		
		int x = image.x;
		int y = image.y;
		
		
		for(Tile t: tileArray){
			g.drawImage(t.getImage(), x + t.getX(), y + t.getY(), null);
		}
			
		g.drawImage(player.getImage(player.getDirection()), x + Math.round(player.getX()), y + Math.round(player.getY()), null);
		
	}
	
	private int getTotalWidth(Tile t){
		int x = t.getX();
		int w = t.getWidth();
		return x+w;
	}
	
	private int getTotalHeight(Tile t){
		int y = t.getY();
		int h = t.getHeight();
		return y+h;
	}
	
	//update
	public void update(long timePassed){
		player.update(timePassed);
	}
	
	//set player movement
	public void playerMove(int d, int m, float vx, float vy){
		if(player.getMoveDistance() == 0){
			player.setDirection(d);
			player.setMoveDistance(m);
			player.setVelocityX(vx);
			player.setVelocityY(vy);
			moving(true);
		}
	}
	
	//recenter mouse using robot
	private synchronized void recenterMouse(){
		Window w = s.getFullScreenWindow();
		if(robot != null && w.isShowing()){
			center.x = w.getWidth() / 2;
			center.y = w.getHeight() / 2;
			SwingUtilities.convertPointToScreen(center, w);
			centering = true;
			robot.mouseMove(center.x, center.y);
		}
	}
	
	//mouse motion listener interface
	public void mouseDragged(MouseEvent e){
		mouseMoved(e);
	}
	
	public synchronized void mouseMoved(MouseEvent e){
		if(centering && center.x == e.getX() && center.y == e.getY()){
			centering = false;
		}else{
			int dx = e.getX() - mouse.x;
			int dy = e.getY() - mouse.y;
			image.x += dx / camera.getSpeed();
			image.y += dy / camera.getSpeed();
			recenterMouse();
		}
		
		mouse.x = e.getX();
		mouse.y = e.getY();
	}
		
	
	public void keyPressed(KeyEvent e){
		int keyCode = e.getKeyCode();
		key = keyCode;
		if(keyCode == KeyEvent.VK_ESCAPE){
			stop();
		}else{
			keyReact(keyCode);
			e.consume();
		}
	}
	public void keyReleased(KeyEvent e){
		if(e.getKeyCode() == key){
			moving(false);
			//player.setVelocityX(0);
			//player.setVelocityY(0);
		}
		e.consume();
	}
	public void keyTyped(KeyEvent e){
		e.consume();
	}
	public void keyReact(int i){
		switch(i){
			case KeyEvent.VK_UP:
				playerMove(1, 1, 0, -0.1f);
				//	System.out.println("Moving.");
				break;
			case KeyEvent.VK_DOWN:
				playerMove(0, 1, 0, 0.1f);
				break;
			case KeyEvent.VK_LEFT:
				playerMove(3, 1, -0.1f, 0);
				break;
			case KeyEvent.VK_RIGHT:
				playerMove(2, 1, 0.1f, 0);
				break;
		}
		
	}
}

Sprite class:

package main;

import java.awt.Image;

public class Sprite {

	private Animation animationArray[] = new Animation[4];
	private float x, y, vx, vy;
	private int direction, moveDistance;
	
	
	public Sprite(Animation[] a){
		for(int i = 0; i < a.length; i++){
			this.animationArray[i] = a[i];
		}
	}
	
	//set forward animation
	/*public void setForwardAnimation(Animation a){
		this.fw = a;
	}
	
	//set forward animation
	public void setBackwardAnimation(Animation a){
		this.bw = a;
	}
		
	//set forward animation
	public void setLeftAnimation(Animation a){
		this.lw = a;
	}
	
	//set forward animation
	public void setRightAnimation(Animation a){
		this.rw = a;
	}*/
	
    //Change position
    public void update(long timePassed) {
    	if(moveDistance > 0){
    		x += vx * 32;
    		y += vy * 32;
    		if(Math.round(x) % 32 == 0 && Math.round(y) % 32 == 0){
    			moveDistance--;
    		}
    	}
        //System.out.println(x+","+y+" mod: "+Math.round(x) % 32+","+Math.round(y) % 32);
    }

    //get x position
    public float getX() {
        return x;
    }

    //get y position
    public float getY() {
        return y;
    }

    //set x position
    public void setX(float x) {
        this.x = x;
    }

    //set y position
    public void setY(float y) {
        this.y = y;
    }

    // get sprite width
    public int getWidth(Animation a) {
        return a.getImage().getWidth(null);
    }

    // get sprite height
    public int getHeight(Animation a) {
        return a.getImage().getHeight(null);
    }

    //get horizontal velocity
    public float getVelocityX() {
        return vx;
    }

    //get vertical velocity
    public float getVelocityY() {
        return vy;
    }

    //set horizontal velocity
    public void setVelocityX(float vx) {
        this.vx = vx;
    }

    //set vertical velocity
    public void setVelocityY(float vy) {
        this.vy = vy;
    }

    //get sprite/image
    public Image getImage(int i) {
        return this.animationArray[i].getImage();
    }
    
    //get current direction
    public int getDirection(){
    	return this.direction;
    }
    
    //set current direction
    public void setDirection(int d){
    	this.direction = d;
    }
    
    //set current move distance
    public void setMoveDistance(int d){
    	this.moveDistance = d;
    }
    
    //get current move distance
    public int getMoveDistance(){
    	return this.moveDistance;
    }
	
	
}

Those should be all the relevant classes.

also want them to have their own methods along with all the methods in Tile. However they don't seem to be inheriting them.

Are ALL the methods not being inherited or just some of them? Please list the ones you are having trouble with.

The getHeight and getWidth functions are definitely not being inherited, the program will not run in less I place them in the Grass class. It runs if I leave out the others, but does not draw any Grass tiles on the screen, so they are not working as they should. Also, I changed the variables in Tile to protected instead of private. (I think that was the correct thing to do, yes?)

the program will not run

Please explain what "not run" means".
Execution errors, invalid results returned? What happens?
Add some printlns to the Tile class code to show when they are called and what the values of the variables are that they are working with

Edited 5 Years Ago by NormR1: n/a

"Exception in thread "main" java.lang.NullPointerException
at main.Tile.getWidth(Tile.java:24)
at main.Main.init(Main.java:74)
at main.Core.run(Core.java:31)
at main.Main.main(Main.java:20)"

The only other method that's actually called at the moment is getImage I believe. This is called at the appropriate time, but does not return anything. It should be working with the tileImage variable, which is given a value in the Grass class BUT I had to create a new variable (used the same name as in the Tile class) in the Grass class to put it in, I could not use the variable declared in the Tile class, which might be where the problem is?

EDIT: this would also explain why the getHeight method was not working - there would be no image in the variable it was trying to get the height of. How do I go about fixing this?

Edited 5 Years Ago by AJG: n/a

When you subclass Tile you re-declare variables
private Image tileImage;
private boolean tilePassable;
private int x,y;
in each subclass. Now you have two versions of each variable, the subclass ones "hide" the inherited ones and you end up having to duplicate inherited methods to word with the duplicated variables.
Simple answer: get rid of the duplicated variable declarations and just use the inherited ones. Then your inherited methods will work also.

ps declaring them private doesn't help either, protected or default access will allow subclasses to see them
http://download.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html

Edited 5 Years Ago by JamesCherrill: ps

java.lang.NullPointerException
at main.Tile.getWidth(Tile.java:24)

What variable is null at line 24 in Tile? When you find the variable, backtrack in the code to see why it does NOT have a valid value?
Where is the variable given a value? Add a println following every place the variable is given a value that will printout the code that is setting it to null.
Or if none of them print out, then you will know that the code never gives the variable a value leaving it null.

James, if I remove the variable declarations, I can't then change the value of the variable, as it does not recgonise the variable as being previously declared. Not sure why this would be.

Norm, that's the tileImage variable, and it is null because the Tile's tileImage is never set, as James points out. It is supposed to be given a value in the Grass class, but I can't work out how to do that. What I had done before was create a new variable with the same name in the Grass class and set that instead.

I changed them to "protected" on the suggestion of a friend and that seemed to work for x and y but not the others. Shall I just remove the "visibility thing" (I don't know the technical name for it) altogether?

Provided all your code is in one package you can use protected, or just leave the visibility blank - its the same (see the link I sent earlier). It will work for all your variables, so if it appears to be just x and y then there's something else wrong as well.

They're all set to protected and I don't appear to be able to access them in the class body, however, access them inside methods in the Grass class, eg in the constuctor:

public Grass(int x, int y){
		this.x = x;
		this.y = y;
	}

I suppose I could just set the variables I want setting inside the constructor, which would make a lot of sense. Is this the way it's intended to work?

EDIT: I did that and it appears to be all working, thanks for the help you two. Still stuck on my other problem though.

Edited 5 Years Ago by AJG: n/a

Sorry for the double post, wouldn't let me edit my other one. I've solved my other problem my implementing a queue, so that if the sprite stops moving, it starts moving again in the direction last pressed. Not sure if this is the best solution but it works. Thanks for all the help.

This question has already been answered. Start a new discussion instead.