TMXLoader v0.5.0 released

Maybe you can show me the log, which it output in the console. I can’t help you without information.

The most possible reason is something wrong with your assets(*.tmx file and tileset images) path.

There’s nothing useful in the log at all, apart from what I put in and something about the audio device not being supported

So after a little experimentation, I’ve done the following:

  1. Created a “player” sprite - This is to test to make sure that the game is rendering as it should.

  2. Went through each step, testing each one.

  3. When I got to:

    baseApplication.getStateManager().attach(new TiledMapAppState());

It started to stop rendering the sprite (though making sure that the sprite was appearing to be rendered ahead of the map.

I’m hoping this wont make a difference but I’m doing this in an AbstractScreen class?

1 Like

Hello, im newb trying to learn monkey engine and java, could i ask you some questions about TmxLoader

yes plz

2 Likes

I understand correctly that map got initialized in 1 point? I cant udnerstand what is doing 2 point , where map data (Tiles) are stored and how to interact with it, also how to force repaint.


as far as I could understand in 1 point map getting loaded, TileLayer class contains the necessary methods for editing Tiles, in the same time object map not getting data about Gid

P.S Sorry if the questions seem stupid, I’m still a beginner and just learning Java

Are you saying you are having a problem or are you asking what is going on in step 2?

It you are wondering “what’s going on?” then you may want to read up on app states.

In the style of “composition is better than inheritance”, app states are a way to extend Application with additional behavior. In this case, rendering a tile map. It still has to be told what map to render.

1 Like

Im just trying to understand the code, I can’t figure out how to change tiles and draw them to change the state of the map

Update tile (0,0) in TileLayer “Ground”, using tiles in Tileset “Ortho1_32x32_32x32”.

package com.jme3.tmx.app;

import com.jme3.app.SimpleApplication;
import com.jme3.system.AppSettings;
import com.jme3.tmx.TiledMapAppState;
import com.jme3.tmx.TmxLoader;
import com.jme3.tmx.core.*;

/**
 * Test update tile
 * @author yanmaoyuan
 *
 */
public class TestUpdateTile extends SimpleApplication {
	private TileLayer tileLayer;

	private Tileset tileset;

	@Override
	public void simpleInitApp() {
		assetManager.registerLoader(TmxLoader.class, "tmx", "tsx");

		TiledMap tiledMap = (TiledMap) assetManager.loadAsset("Models/Examples/Orthogonal/01.tmx");
		assert tiledMap != null;

		TiledMapAppState tiledMapState = new TiledMapAppState();
		stateManager.attach(tiledMapState);

		tiledMapState.setMap(tiledMap);

		for (Layer layer : tiledMap.getLayers()) {
			if ("Ground".equals(layer.getName())) {
				tileLayer = (TileLayer) layer;
			}
		}

		for (Tileset tileset : tiledMap.getTileSets()) {
			if ("Ortho1_32x32_32x32".equals(tileset.getName())) {
				this.tileset = tileset;
			}
		}

	}

	private float time = 0.0f;
	private float coolDown = 0.1f;
	private int tileId = 1;
	private int tileMax = 360;
	public void simpleUpdate(float tpf) {
		time += tpf;
		if (time > coolDown) {
			time -= coolDown;

			tileLayer.setTileAt(0, 0, nextTile());
		}
	}

	private Tile nextTile() {
		tileId ++;
		if (tileId >= tileMax) {
			tileId = 1;
		}

		return tileset.getTile(tileId);
	}

	public static void main(String[] args) {
		AppSettings settings = new AppSettings(true);
		settings.setWidth(1280);
		settings.setHeight(720);
		settings.setSamples(4);

		TestUpdateTile app = new TestUpdateTile();
		app.setSettings(settings);
		app.start();
	}
}

I think I need to add some interface like getLayer(String name) or getTileset(String name)

When the method setTileAt(0, 0, nextTile()) is invoked, that layer will be marked as isNeedUpdate = true, the state will render that layer in next frame.

2 Likes

TiledMap holds data from the *.tmx file

<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" renderorder="left-up" width="20" height="12" tilewidth="32" tileheight="32" backgroundcolor="#9d9794" nextobjectid="27">
 <tileset firstgid="1" name="Ortho1_32x32_32x32" tilewidth="32" tileheight="32">
  <image source="01_32x32_32x32.png" width="256" height="1450"/>
 </tileset>
 <tileset firstgid="361" name="player" tilewidth="67" tileheight="67">
  <tile id="0">
   <image width="67" height="67" source="../goblin.png"/>
  </tile>
 </tileset>
 <layer name="Ground" width="20" height="12">
  <data encoding="base64" compression="zlib">
   eJyNkTsOwjAMhpNudIMbQCnv8hQL3IDHgsTCbYEJWBk7MkLpEfgtHMlN08LwyWkSf7HdUCkVOjiCk+AMbqAOGmDAkYhAwDmR2Jf3LuAqIFcLdEHPQZ99rjOiCdpgyPW2uAZtMQFTMGPfqgDqayTqDjjPZsEs2XcooK6yc+iIXBfG5/oXoTWnORj/6Qt0nlDn53QHWyzW+ht9/o5ptnyffDssNmBvOZ8qy1t9XQbyJeI9qoF8tsvkej8gZ8quCvl1tt8yj1/ipX6ryK8Jn+2ht+Xa4HGPD/Di81TM48jRt3JdLrMfi5lJb8K+DwYPRhI=
  </data>
 </layer>
 <layer name="Decoration" width="20" height="12">
  <data encoding="base64" compression="zlib">
   eJxjYICAtUC8joFyoMvIwKAHxFuB7G1EqBcCYmEoey6SuBmUNgWaZcaI4OMDIDVSQCwNxDxAvJRIN8PAGjR6OYn60cEWNJoYYIZEg7ADlC8G5VcCcRUQixAwJ4IRwQ4AYnYo3wMqJgfErEDcCMRNQCyDx6xwoN4ERkxxATQxbnR5IOYDYn4ou5eAm4kFYVB6KhY5fizuHKpAlAg1AB8GEGE=
  </data>
 </layer>
 <objectgroup color="#0000ff" name="overlap" opacity="0.5">
  <properties>
   <property name="showObject" value="0"/>
  </properties>
  <object id="20" name="1" x="0" y="96" width="192" height="64"/>
  <object id="21" name="4" x="384" y="192" width="64" height="32"/>
  <object id="22" name="5" x="480" y="128" width="128" height="106"/>
  <object id="24" name="2" x="32" y="352" width="160" height="32"/>
  <object id="25" name="3" x="192" y="320" width="192" height="64"/>
 </objectgroup>
 <layer name="Deco2" width="20" height="12">
  <data encoding="base64" compression="zlib">
   eJxjYBgFyyjQ68zIwODCiKBBAEYTAqVAXAbE5UBcQYEbYKAWiOuAuB6IG8g0gw2I2aHsViBuA+J2IO6g1HFA0AvEfUDcD8QTqGDeVCCeBsTTgXgGFcwjFewE4l1QvJsKeg8C8SEoPkykOQCBrxVl
  </data>
 </layer>
 <layer name="Deco3" width="20" height="12">
  <data encoding="base64" compression="zlib">
   eJxjYBgFxIJcIM4D4nwgLhhgt9AK+DEyMJgPtCNGAdEAAE5mAkE=
  </data>
 </layer>
 <objectgroup color="#ff8000" name="Character">
  <properties>
   <property name="showObject" value="0"/>
  </properties>
  <object id="5" name="player" gid="361" x="128" y="288" width="67" height="67"/>
 </objectgroup>
 <objectgroup color="#800080" name="block" opacity="0.5">
  <properties>
   <property name="showObject" value="0"/>
  </properties>
  <object id="9" x="0" y="0">
   <polygon points="0,0 0,96 192,96 192,224 352,224 352,192 608,192 608,0"/>
  </object>
  <object id="10" x="608" y="0" width="32" height="384"/>
  <object id="11" x="0" y="152" width="128" height="38"/>
  <object id="12" x="160" y="152" width="32" height="38"/>
  <object id="13" x="386" y="218" width="62" height="36"/>
  <object id="14" x="224" y="342" width="128" height="42"/>
  <object id="15" x="64" y="372" width="128" height="12"/>
  <object id="17" x="352" y="364" width="32" height="20"/>
  <object id="18" x="512" y="234" width="62" height="46">
   <ellipse/>
  </object>
 </objectgroup>
</map>

1 Like

naisu thanks!!

1 Like

TiledMap it self provides a convenient api, you can just update the TileLayer with tileId
public void setTileAtFromTileId(TileLayer ml, int y, int x, int tileId)

	private TileLayer tileLayer;
	private TiledMap tiledMap;
	private float time = 0.0f;
	private float coolDown = 0.1f;
	private int tileId = 0;
	private int tileMax = 361;

	public void simpleUpdate(float tpf) {
		time += tpf;
		if (time > coolDown) {
			time -= coolDown;

			tileId ++;
			if (tileId >= tileMax) {
				tileId = 1;
			}
			tiledMap.setTileAtFromTileId(tileLayer, 0, 0, tileId);// <--- this line changed the layer
		}
	}

	@Override
	public void simpleInitApp() {
		assetManager.registerLoader(TmxLoader.class, "tmx", "tsx");

		tiledMap = (TiledMap) assetManager.loadAsset("Models/Examples/Orthogonal/01.tmx");
		assert tiledMap != null;

		TiledMapAppState tiledMapState = new TiledMapAppState();
		stateManager.attach(tiledMapState);

		tiledMapState.setMap(tiledMap);

		for (Layer layer : tiledMap.getLayers()) {
			if ("Ground".equals(layer.getName())) {
				tileLayer = (TileLayer) layer;
			}
		}
	}
2 Likes

I understand correctly that x y coordinates from
tileLayer.getTileAt(0,0).getY()
tileLayer.getTileAt(0,0).getX()
shows coordinates for Ortho1_32x32_32x32.png to draw? But is there any way to get X Y coordinates for tile i want to manipulate with them by mouse click

com.jme3.tmx.render.MapRenderer has some methods to do the math.

	/******************************
	 * Coordinates System Convert *
	 ******************************/

	public abstract Vector2f pixelToScreenCoords(float x, float y);

	public abstract Point pixelToTileCoords(float x, float y);

	public abstract Vector2f tileToPixelCoords(float x, float y);

	public abstract Vector2f tileToScreenCoords(float x, float y);

	public abstract Vector2f screenToPixelCoords(float x, float y);

	public abstract Point screenToTileCoords(float x, float y);

The problem is the different coordinate systems, and the scaled resolution.

TiledMap coordinates, this is how tiles and pixels are aligned.

  O------- X
  |
  |
  |
  Y

jme3 camera coordinates, this is where you get the mouse position.

  Y
  |
  |
  |
  O------- X

TiledMapAppState coordinates, this is how I generate the tile mesh.

 O------- X
 |
 |
 |
 Z

this is a example.

package com.jme3.tmx.app;

import com.jme3.app.SimpleApplication;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Vector2f;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.tmx.TiledMapAppState;
import com.jme3.tmx.TmxLoader;
import com.jme3.tmx.core.TileLayer;
import com.jme3.tmx.core.TiledMap;
import com.jme3.tmx.math2d.Point;

/**
 * Test mouse pick
 * @author yanmaoyuan
 *
 */
public class TestMousePick extends SimpleApplication {
    private TileLayer tileLayer;

    private TiledMap tiledMap;

    private int tileId = 0;
    private static final int TILE_MAX = 361;

    public void click() {
        TiledMapAppState state = stateManager.getState(TiledMapAppState.class);
        Spatial spatial = tiledMap.getVisual();

        // Screen width = 1280
        // Tile size = 32, view columns = 12, columns length = 32 * 12 = 384
        // scale = 1280 / 384 = 3.3333333
        float mapScale = spatial.getLocalScale().x;

        // in XOZ plane, origin at left-up corner
        Vector2f mapOrigin = new Vector2f(spatial.getLocalTranslation().x, spatial.getLocalTranslation().z);

        // cursor in XOY plane, origin at left-down corner
        Vector2f cursor = inputManager.getCursorPosition();
        // convert origin to left-up corner
        cursor = new Vector2f(cursor.x, cam.getHeight() - cursor.y);

        // map pixel = (cur.pos - map.pos) / map.scale
        Vector2f mapPixel = cursor.subtract(mapOrigin).divide(mapScale);

        Point point = state.getMapRenderer().screenToTileCoords(mapPixel.x, mapPixel.y);
        if (tiledMap.contains(point.x, point.y)) {
            // next tile
            tileId++;
            if (tileId >= TILE_MAX) {
                tileId = 0;
            }
            tiledMap.setTileAtFromTileId(tileLayer, point.y, point.x, tileId);
        }
    }

    public void initInput() {
        inputManager.setCursorVisible(true);
        inputManager.addMapping("CLICK", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(new ActionListener() {
            @Override
            public void onAction(String name, boolean isPressed, float tpf) {
                if (isPressed) {
                    click();
                }
            }
        }, "CLICK");
    }

    @Override
    public void simpleInitApp() {
        assetManager.registerLoader(TmxLoader.class, "tmx", "tsx");

        tiledMap = (TiledMap) assetManager.loadAsset("Models/Examples/Orthogonal/01.tmx");
        assert tiledMap != null;
        tileLayer = (TileLayer) tiledMap.getLayer("Ground");

        TiledMapAppState tiledMapState = new TiledMapAppState();
        stateManager.attach(tiledMapState);

        tiledMapState.setMap(tiledMap);

        initInput();
    }

    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setWidth(1280);
        settings.setHeight(720);
        settings.setSamples(4);

        TestMousePick app = new TestMousePick();
        app.setSettings(settings);
        app.start();
    }
}

I think I should provide a method in TiledMapAppState to do the math. :rofl:

1 Like

I add two method to TiledMapAppState.

	/**
	 * Get the cursor tile coordinate in the map
	 *
	 * @return The tile coordinate of the cursor
	 */
	public Point getCursorTileCoordinate() {
		if (inputManager == null) {
			throw new IllegalStateException(
					"inputManager is null. Please initialize TiledMapAppState first.");
		}
		Vector2f cursor = getCursorPixelCoordinate();
		return getMapRenderer().screenToTileCoords(cursor.x, cursor.y);
	}

	/**
	 * Get the cursor pixel coordinate in the map
	 *
	 * @return The pixel coordinate of the cursor
	 */
	public Vector2f getCursorPixelCoordinate() {
		if (inputManager == null) {
			throw new IllegalStateException(
					"inputManager is null. Please initialize TiledMapAppState first.");
		}
		Vector2f cursor = inputManager.getCursorPosition();
		cursor = new Vector2f(cursor.x, screenDimension.y - cursor.y);
		return cursor.subtractLocal(mapTranslation.x, mapTranslation.z).divideLocal(mapScale);
	}

the example now

    public void click() {
        TiledMapAppState state = stateManager.getState(TiledMapAppState.class);
        Point point = state.getCursorTileCoordinate();
        if (tiledMap.contains(point.x, point.y)) {
            // next tile
            tileId++;
            if (tileId >= TILE_MAX) {
                tileId = 0;
            }
            tiledMap.setTileAtFromTileId(tileLayer, point.y, point.x, tileId);
        }
    }
2 Likes

thanks everything working well, im trying to upgrade zoomCamera in TiledMapAppState class to fixthat the screen move when zooming, what do u think about this

	public void zoomCamera(float value) {

		if((viewColumns > maxZoom && value > 0) || (viewColumns < minZoom && value < 0)) return; //maximum and minimum zoom level 

		viewColumns += value*zoomSpeed;
		System.out.println("zoom "+viewColumns);
		// at less see 1 tile on screen
		if (viewColumns < 1f) {
			viewColumns = 1f;
		}
		setViewColumn(viewColumns);


			// zoom with saving screen position 
		if(value<0) {
			mapTranslation.set(mapTranslation.x - (40-(viewColumns*1.7f)), 0, +mapTranslation.z);
			map.getVisual().setLocalTranslation(mapTranslation);
		} else if (value>0) {
			mapTranslation.set(mapTranslation.x + (40-(viewColumns*1.7f)), 0, +mapTranslation.z);
			map.getVisual().setLocalTranslation(mapTranslation);
		}
	}

There is a moveToTile(float x, float y) method in TiledMapAppState, but I didn’t finished it. It should be like this.

	public void moveToTile(float x, float y) {
		Vector2f tilePos = mapRenderer.tileToScreenCoords(x, y).multLocal(getMapScale());
		Vector2f camPos = new Vector2f(cam.getLocation().x, screenDimension.y - cam.getLocation().y);
		camPos.subtract(tilePos, tilePos);
		mapTranslation.set(tilePos.x, 0, tilePos.y);
		map.getVisual().setLocalTranslation(mapTranslation);
	}

if you want to sticked to some tile when zoom the map,

    public void simpleUpdate(float tpf) {
        TiledMapAppState state = getStateManager().getState(TiledMapAppState.class);
        if (state != null) {
            state.moveToTile(12.5f, 5.5f);
        }
    }

I suggest you do not use that zoom or move function, unregister the input.

inputManager.deleteMapping(TiledMapAppState.DRAG);
inputManager.deleteMapping(TiledMapAppState.ZOOMIN);
inputManager.deleteMapping(TiledMapAppState.ZOOMOUT);

or just unregister all the default input and write your own.

tiledMapAppState.unregisterInput()

I only use them for debug purpose, don’t use in production.

In real game, map would always be scaled to a proper resolution. Like in most RPGMaker games, the real resolution is 816x624, which means 22.5x19.5 tiles (size=32) in screen. when player moves, the camera moves (or the map moves in opposite direction) with then player.

1 Like

im trying to make strategic game btw is that good idea?

Then I think this 3 method would be useful

  1. before zoom: Point tilePos = getCursorTileCoordinate()

  2. zoom: zoomCamera(zoom)

  3. after zoom: moveToTile(tilePos.x, tilePos.y)

1 Like

thanks !i will try this