I'm working on a grand strategy game similar to those made by Paradox Game Studios with their Clausewitz engine. I'm trying to create the world map from an image. On that you can see the areas, and every area has a unique color. I made a JSON file from this image and then the stored object contained every area with their points, their neighbours and their borders and in the game I just had to load this JSON file. I made a stage and added a custom object what would draw my map area by area, and pixel by pixel but with this method the FPS was really small (I think 8 was the max). Now I don't have any clue how I should make it work.
If there's any document about this I'd like to read it.
Map (which will be deserialized with JSON; I'm using Jackson JSON because I prefer that much more)
public class Map implements Serializable {
private static final long serialVersionUID = -518329203219605551L;
public Area[] areas = new Area[0];
public int width;
public int height;
public Area byID(int id) {
for (Area area : areas) {
if (area.id == id) {
return area;
}
}
return null;
}
public Array<Area> areas() {
return new Array<>(areas);
}
private void add(Area a) {
Area[] array = new Area[areas.length + 1];
for (int i = 0; i < areas.length; i++) {
array[i] = areas[i];
}
array[areas.length] = a;
areas = array;
}
private int indexOf(int rgb) {
for (int i = 0; i < areas.length; i++) {
if (areas[i].rgb == rgb) {
return i;
}
}
return -1;
}
public Area area(int x, int y) {
for (Area area : areas) {
if (area.hasPoint(x, y)) {
return area;
}
}
return null;
}
public static void fix(Vector2 vector, float width, float height) {
if (0 > vector.x) {
vector.x = width - vector.x;
}
if (0 <= width) {
vector.x = vector.x - width;
}
}
public static Array<Vector2> neighbours(Vector2 vector, float width, float height) {
Array<Vector2> vectors = new Array<>();
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
Vector2 v = new Vector2(vector.x + dx, vector.y + dy);
fix(v, width, height);
if (vector.y >= 0 && vector.y < height) {
vectors.add(v);
}
}
}
return vectors;
}
public static Map fromPNG(BufferedImage img) { // used only for build the JSON file
try {
Map map = new Map();
map.width = img.getWidth();
map.height = img.getHeight();
System.out.println(map.width + " * " + map.height);
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
int rgb = img.getRGB(x, y);
Area area = map.byID(rgb);
if (area == null) {
area = new Area();
area.id = rgb;
area.rgb = rgb;
map.add(area);
System.out.println("New area: " + area.id);
}
Vector2 p = new Vector2(x, y);
fix(p, img.getWidth(), img.getHeight());
area.add(p.x, p.y);
for (Vector2 point : neighbours(p, img.getWidth(), img.getHeight())) {
int c = new Color(img.getRGB((int) point.x, (int) point.y)).getRGB();
if (c != rgb) {
area.add(c, x, y);
}
}
}
System.out.println("Row: " + y + " is done.");
}
System.out.println("Converting ids to readable");
for (int i = 0; i < map.areas.length; i++) {
map.areas[i].id = i;
Set<Integer> set = new HashSet<>(map.areas[i].neighbors.keySet());
HashMap<Integer, List<Vector2>> copy = new HashMap<>();
for (Entry<Integer, List<Vector2>> entry : map.areas[i].neighbors.entrySet()) {
copy.put(map.indexOf(entry.getKey()), entry.getValue());
}
map.areas[i].neighbors.putAll(copy);
for (Integer id : set) {
map.areas[i].neighbors.remove(id);
}
}
System.out.println("Flipping coords");
for (Area area : map.areas) {
for (Vector2 p : area.points) {
p.y = map.height - 1 - p.y;
}
}
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Area object (which you can see in the Map class)
public class Area implements Serializable {
public static class VectorSerializer extends JsonSerializer<Vector2> {
@Override
public void serialize(Vector2 v, JsonGenerator jgen, SerializerProvider sp) throws IOException {
jgen.writeStartObject();
jgen.writeNumberField("x", v.x);
jgen.writeNumberField("y", v.y);
jgen.writeEndObject();
}
}
public static class VectorDeserializer extends JsonDeserializer<Vector2> {
@Override
public Vector2 deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return new Vector2((float) node.get("x").asDouble(), (float) node.get("y").asDouble());
}
}
private static final long serialVersionUID = 2288613202778397975L;
public Set<Vector2> points = new HashSet<>();
public int id = -1;
public int rgb;
public HashMap<Integer, List<Vector2>> neighbors = new HashMap<>();
public boolean naval = false;
private Color color;
private java.awt.Color java;
public Color color() {
if(color==null) {
java = new java.awt.Color(rgb);
String hex = String.format("#%02x%02x%02x", java.getRed(), java.getGreen(), java.getBlue());
color = Color.valueOf(hex);
}
return color;
}
private Rectangle rect;
public float x() {
return rect.x;
}
public float y() {
return rect.y;
}
public float w() {
return rect.width;
}
public float h() {
return rect.height;
}
public Rectangle rect() {
return rect;
}
public void bounds(float x, float y, float w, float h) {
rect = new Rectangle(x, y, w, h);
}
void add(float x, float y) {
points.add(new Vector2(x, y));
}
void add(int rgb, int x, int y) {
List<Vector2> list = neighbors.getOrDefault(rgb, new ArrayList<>());
list.add(new Vector2(x, y));
neighbors.put(rgb, list);
}
public boolean hasPoint(float x, float y) {
for (Vector2 p : points) {
if (p.x == x && p.y == y) {
return true;
}
}
return false;
}
}
The Game Screen
public class GameScreen implements Screen {
private CW game;
private Save save;
private Stage stage;
private OrthographicCamera camera;
public GameScreen(CW game, Save save) {
this.save = save;
this.game = game;
this.camera = game.cam();
camera.setToOrtho(false, save.map().width, save.map().height);
game.viewport().setWorldSize(save.map().width, save.map().height);
game.viewport().apply(true);
this.stage = new Stage(game.viewport());
InputMultiplexer input = new InputMultiplexer(stage, new InputAdapter() {
});
Gdx.input.setInputProcessor(input);
stage.addActor(new WorldMap(this.save.map(), camera));
}
@Override
public void show() {
}
@Override
public void render(float delta) {
handleCamera();
camera.update();
game.batch().setProjectionMatrix(camera.combined);
Gdx.gl20.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
System.out.println(Gdx.graphics.getFramesPerSecond()); // TODO remove
}
private void handleCamera() {
if (Gdx.input.isKeyPressed(Keys.LEFT)) {
camera.translate(1, 0);
} else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {
camera.translate(-1, 0);
} else if (Gdx.input.isKeyPressed(Keys.UP)) {
camera.translate(0, 1);
} else if (Gdx.input.isKeyPressed(Keys.DOWN)) {
camera.translate(0, -1);
}
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void hide() {
}
@Override
public void dispose() {
stage.dispose();
}
}
The Actor:
public class WorldMap extends Actor {
private Map map;
private OrthographicCamera cam;
public WorldMap(Map map, OrthographicCamera cam) {
this.map = map;
this.cam = cam;
setBounds(0, 0, map.width, map.height);
}
private ShapeRenderer shape = new ShapeRenderer();
@Override
public void draw(Batch batch, float parentAlpha) {
batch.end();
shape.begin(ShapeType.Filled);
for (Area area : map.areas) {
shape.setColor(area.color());
for (Vector2 p : area.points) {
if (cam.frustum.pointInFrustum(p.x, p.y, 0)) {
shape.rect(p.x, p.y, 1, 1);
}
}
}
shape.end();
batch.begin();
}
}
Thanks!