So I am currently deciding on what route is best to take before I start adding lots more tiles to my game. Would it be best to use individual tile classes (GrassTile.java, MudTile.java, WaterTile.java) or have all tile decelerations made within the Tile.java class?
E.g. Individual Tile Class:
public class GrassTile extends Tile {
private static final long serialVersionUID = 1L;
public GrassTile(int type, int x, int y) {
super(type, x, y);
// Used to set the texture
setOffset(0, 0);
// Sets the name for use in-game
setName("Grass");
}
public void tick() {
super.tick();
}
public void render(Graphics g) {
super.render(g);
}
@Override
public void onDestroyTile(Level level, int x, int y) {
// Grass tile becomes Mud tile when destroyed
level.setTile(x, y, new MudTile(Tile.mud, x * Globals.tileSize, y * Globals.tileSize));
}
@Override
public ItemDrop[] onDestroy(Level level, int x, int y) {
// When destroyed we drop grass as an item
return new ItemDrop[] { new ItemDrop(new GrassItem(Item.grass, x, y), 1) };
}
}
How I would imagine stacked methods would look:
public static Tile grass = new Tile("Grass").setOffset(0, 0).onDestroy(Tile.mud).dropsItem(Item.grass);
What method would you use and what would you change (if anything)?
Storing Tile Data
So for my next question, how would one store tile data? Currently I have a “setMetadata” and “getMetadata” which basically allows you to assign a number to each tile. Is this the best way of storing individual data about a tile? It’s not very friendly in terms of readability since a boolean in metadata would have to be 0 and 1.
One thing I would definitely think about at this stage would be how many tiles is your game going to have?..If this is a huge amount, the more limited amount of memory
your tile class uses the better. My tile class uses 3 bytes of information, each tile is created via an abstract factory class now.
Try the Minecraft style of tiles: the individual classes. Each block has a tile class that is just re-referenced over and over again, saving lots of memory. The only extra data you need is metadata, and Minecraft uses 4 bit integers for basic metadata, but they’re working on combining it all into one form, which is the Tile Entity.
Tile entities are just a form of entity that has a block position. Either a tile has a tile entity or not. The tile entity is created by the Block when it is placed and removed when it is destroyed.
This way you save memory by having one set of actual Blocks, and when you need extra data it’ll only be on a block-per-block basis.
steveyg90’s way is by creating a Tile for every block. This solves the metadata tile entity thing but is a bit wasteful on memory as most blocks don’t need metadata. His factory class merely instantiates the tiles.
Coloured wool in Minecraft currently uses metadata but they’re working on making it a block state. You may as well just make it a separate block for each colour at that point all sub-classing wool.
Oh I see now, thanks for your help! Having multiple tiles instead of states should be OK to do since I’ll be re-using the same instance of the tiles.
I’m tempted to do the stacked methods rather than doing the seperate classes but can’t think of the pros and cons of either way other than classes are cleaner and stacked methods would be basically looking at a wall of text when editing any of the tile information.
Actually, Minecraft uses the flyweight design pattern for its blockdata.
The whole thing with the ‘strings as IDs’ is simply an abstraction on top of the whole thing;
Minecraft will forever use this exact system for managing its blocks.
So… Here’s a simplified version of the entire thing:
class BlockStorage {
int[] blocks = new int[width * height * length]; // this is where your block are actually stored
List<TileEntity> tileEntities = new ...; // this is where tile-entities are stored (can be a hashmap)
// Returns the 'raw' data of the block as a integer at the given position.
public int getBlockRaw(x, y, z) {
return (y * width * length) + (z * width) + (x);
}
// Returns a flyweight Block-object for the block at the given position.
public Block getBlock(x, y, z) {
return BlockList.blockList[getBlock(x,y,z)];
}
}
// Contains a list (/array) of all flyweight Block objects.
class BlockList {
public static final Block[] blockList = new Block[...];
static {
blockList[0] = new BlockAir();
blockList[1] = new BlockStone();
blockList[2] = new BlockOven();
}
}
// This class represents a simple abstract flyweight Block with metadata.
abstract class Block {}
// This is a block that can have a tile-entity.
abstract class BlockContainer extends Block {
abstract TileEntity createNewTileEntity();
}
// The three classes represent actual Block types.
class BlockAir extends Block {}
class BlockStone extends Block {}
class BlockOven extends BlockContainer {
TileEntity createNewTileEntity() {
return new OvenTileEntity();
}
}
Thanks for the reply and although that seems overkill for my use-case I do appreciate the help.
I am currently figuring out the best way to nest methods since I’ve never done this before and “returning this;” in every next seems wrong?
public class Scenery extends Camera {
private static final long serialVersionUID = 1L;
// new Scenery(NAME).setTexture(SPRITE_ID).setTurnsInto(SCENERY TURNS INTO...)
public static final Scenery bush = new Scenery("Tree").setTexture(1).setTurnsInto(null);
protected String name;
protected int texture;
public Scenery(String name) {
setBounds(x, y, Globals.tileSize, Globals.tileSize);
this.name = name;
}
public void tick() { }
public void render(Graphics g) {
g.drawImage(Images.othertiles.getSprite(texture), x, y, Images.othertiles.getSprite(1).getWidth() * Globals.scale, Images.othertiles.getSprite(1).getHeight() * Globals.scale, null);
}
private Scenery setTexture(int id) {
texture = id;
return this;
}
public Scenery setPosition(int x, int y) {
setBounds(x * Globals.tileSize, y * Globals.tileSize, Globals.tileSize, Globals.tileSize);
return this;
}
}
I currently have the following (below) and am trying to re-use the same instance across the entire level, but it seems to only be drawing a single tile, any ideas on what I’m going wrong?
It’s probably something simple, but it’s late here and I really wanted to get this done before bed.
// Tile initiated in Tile.java
public static Tile grass = new Tile().setName("Grass").setOffset(0, 0);
// Example method for setting name
public Tile setName(String name) {
this.name = name;
return this;
}
// How it is used in Level.java
for (int i = 0; i < level.length; i++) {
for (int j = 0; j < level[0].length; j++) {
level[i][j] = Tile.grass.setPosition(i * Globals.tileSize, j * Globals.tileSize);
level[i][j].setTransitionId(getId(i, j));
}
}
Your tile classes only hold logical data. They hold absolutely no rendering data, if anything they could get an abstract render method which you call with position parameters.
Unsure what you mean by that. Do you mean I shouldn’t have rendering in Tile (I currently have a render method in Tile) or that I should be rendering the “Tile.grass.render” rather than the “Tile.render”?
Possibly due to using static for the tile…the position would be the same for the tiles as this is shared possibly…
I have a draw method in my tile class where child objects override it in order to draw correct texture.
I store now a few more things in main tile class:
I have id (byte), light value(byte), original light value(byte), amount of hits to break the tile(byte) and a draw method.
Id is used for when I do some chunking stuff for when I want to place some random types of rocks inside my caves.
Snippet of factory class:
class EntityFactory {
static BlankEntity getEntity(String type, int x, int y) {
if (type.equals("."))
return new BlankEntity(x, y);
if (type.equalsIgnoreCase("L"))
return new LandscapeEntity(x, y);
return null;
}
}
Correct, basically for every tile type you’ll have to implement a render method for each (Tile.grass.render). Anything like setPosition is bad because that implies the tiles are holding position data, which they shouldn’t be if you’re using the same instance for every type of block in the world. Passing a position to the render method is ideal.
// Tile.java -> grass -> offset is texture position in tilesheet
public static Tile grass = new Tile("Grass").setOffset(0, 0);
// Creating the level
for (int i = 0; i < level.length; i++) {
for (int j = 0; j < level[0].length; j++) {
setTile(i, j, Tile.grass);
getTile(i, j).setTransitionId(getId(i, j));
}
}
// Rendering the level (sorry about the long for loops)
for (int x = (Game.cX / Globals.tileSize); x < (Game.cX / Globals.tileSize) + (Main.WIDTH / Globals.tileSize) + 2; x++) {
for (int y = (Game.cY / Globals.tileSize); y < (Game.cY / Globals.tileSize) + (Main.HEIGHT / Globals.tileSize) + 2; y++) {
if (x >= 0 && y >= 0 && x < mapSize && y < mapSize) {
if (getTile(x, y) != null) {
getTile(x, y).render(g, x * Globals.tileSize, y * Globals.tileSize);
}
}
}
}
Edit because I forgot to include camera/movement code…
// This code is in the game tick and moves the tiles within the on-screen viewing area
// Every entity/object/etc will have a moveWith() in the game tick (apart from player/something that should move with the camera
for (int x = (Game.cX / Globals.tileSize); x < (Game.cX / Globals.tileSize) + (Main.WIDTH / Globals.tileSize) + 2; x++) {
for (int y = (Game.cY / Globals.tileSize); y < (Game.cY / Globals.tileSize) + (Main.HEIGHT / Globals.tileSize) + 2; y++) {
if (x >= 0 && y >= 0 && x < Level.mapSize && y < Level.mapSize) {
if (level.getTile(x, y) != null) {
level.getTile(x, y).moveWith();
}
}
}
}
// Here is the Camera code (each Tile extends Camera)
public class Camera extends Rectangle {
private static final long serialVersionUID = 1L;
private Rectangle original;
public void setBounds(int x, int y, int width, int height) {
original = new Rectangle(x, y, width, height);
super.setBounds(x, y, width, height);
moveWith();
}
public void moveWith() {
x = original.x - Game.cX;
y = original.y - Game.cY;
}
public void setX(double d) {
original.x += d;
}
public void setY(double y) {
original.y += y;
}
public Rectangle getBounds() {
return original;
}
}
I’m thinking it’s because I’m modifying the tiles position, yet they are all located at (0, 0) because they are simply being rendered at their specific position (handing the x, y vars to render).
Edit again because this is getting super annoying to work with. A single static tile variable rather than a class seems to not be working out as well as I’d hoped. I can’t figure out a way to handle positions as well as transition id’s on a per tile basis because setting the transition of Tile.grass will set the same transition id for all grass elements.
Final edit since I’m going back to having tiles individually classed (GrassTile, MudTile, etc.) rather than them all being in the same Tile.java file. This seems to be the the best way of handling multiple tiles doing different things (for example, some tiles will have quite a log of logic) and the Tile.java class may get messy with tons of tiles having large amounts of code.
Not so final edit due to the fact I’m giving each tile an ID and I have absolutely no idea why?! I don’t think I did it for nothing?
// Tile.java
public static final int grass = 0;
public static final int stone = 1;
public static final int power = 2;
// When creating a new GrassTile.java inside Level.java
level[x][y] = new GrassTile(Tile.grass, x * tilesize, y * tilesize);
I like how you are going about this, but you should keep a table with only integers, not objects. When a block has metadata, you know away from being the default, you should create an objects and utilize this data. Therefore you don’t create ints for no reason, which may not change at all.
I suggest you take a break, reset and then start again.
A tile ain’t no camera, and a camera ain’t no rectangle.
You do not have to move tiles.
Take a piece of paper, draw a sketch of your tile map, then cut a paper window as camera, put it on the map, move it, and think what you need to do to draw a portion of the whole map.
Forget about fluent interfaces for a while.
Think if and where tile coordinates need to be stored and passed as parameter.
Texture Coordinates and parameters such as width and height of image, index of the tile, and any offset data. Though, I might be digressing here for the intention you’re trying to incur.