Skip to content

Commit

Permalink
Add relativePath custom property as an option for determining res path
Browse files Browse the repository at this point in the history
  • Loading branch information
eleniums committed Jul 15, 2022
1 parent 59bab05 commit 540ee8e
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 48 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,42 @@ If you prefer watching check the video in YouTube:

[How to export from Tiled To Godot 3.2](https://youtu.be/4jSFAXIa_Lo)

The exporter needs to know how to set resource paths correctly for Godot before exporting. There are several ways to do this:

- If your Tiled files exist in your Godot project structure alongside the exported files, the exporter can automatically determine the resource paths. There is also the option to use the `projectRoot` custom property if needed.
- If your Tiled files exist outside your Godot project structure, you will need to set the `relativePath` custom property so the exporter can determine resource paths.

**_!!! Pay attention to the "/" instead of standard windows "\\". Single Backslash "\\" is used for escaping special symbols._**

### Setting relativePath

The custom property `relativePath : string` contains the relative path to the exported files from the Godot project root.
The value of `relativePath` is transformed to a resource path which Godot uses.

* When placed on a Tiled TileMap (.tmx) object, it will define where the Tileset objects (.tres) are located in the exported TileMap (.tscn).
* When placed on a Tiled TileSet (.tsx) object, it will define where the Image is located in the exported TileMap (.tres).

Example:
If the location of an exported tileset is: `C:/project/maps/level1/tileset.tres`
Then the `relativePath` custom property would be: `/maps/level1`

### Setting projectRoot `res://`

The exporter needs to know where `res://` is so when you export to a subfolder in your Godot project all the relative paths for the resources `(res://)` are set correctly and relative to the project root. `res://` is equivalent to the location of `project.godot` in your Godot project.

**By default, the exporter expects the Tiled files to be saved in a subfolder of your Godot project and will determine the project root automatically. Setting `projectRoot` is no longer required.**
**By default, the exporter expects the Tiled files to be saved in a subfolder of your Godot project next to the exported files and will determine the project root automatically. Setting `projectRoot` is no longer required in this scenario.**

If needed, you can override this path with a custom property `projectRoot : string` that is either relative to the file you are exporting (starting with a `.`) or an absolute path.
Either way, the value of `projectRoot` is transformed to the `res://` resource path which Godot uses and should be equivalent to the location of `project.godot`.

* When placed on a Tiled TileMap (.tmx) object it will be used as the root to determine the relative resource path to the Tileset objects (.tres) used in the exported TileMap (.tscn).
* When placed on a Tiled TileSet (.tsx) object it will be used as the root to determine the relative resource path to the Image used in the exported TileMap (.tres).

**_!!! Pay attention to the "/" instead of standard windows "\\".
Single Backslash "\\" is used for escaping special symbols._**
Example:
If the location of an exported tileset is: `C:/project/maps/level1/tileset.tres`
Then the `projectRoot` custom property would be: `C:/project`

### Exporting to Godot

If everything is fine when you go to _File -> Export As_, a new option should exist:

Expand Down
9 changes: 4 additions & 5 deletions export_to_godot_tilemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ class GodotTilemapExporter {
constructor(map, fileName) {
this.map = map;
this.fileName = fileName;
// noinspection JSUnresolvedFunction
this.projectRoot = getResPath(this.map.property("projectRoot"), fileName);
this.tileOffset = 65536;
this.tileMapsString = "";
this.tilesetsString = "";
Expand Down Expand Up @@ -76,8 +74,8 @@ class GodotTilemapExporter {
const tileset = this.map.tilesets[index];
this.extResourceId = index + 1;
this.tilesetsIndex.set(tileset.name, this.extResourceId);
// noinspection JSUnresolvedVariable
let tilesetPath = tileset.asset.fileName.replace(this.projectRoot, "").replace('.tsx', '.tres');
// noinspection JSUnresolvedFunction
let tilesetPath = getResPath(this.map.property("projectRoot"), this.map.property("relativePath"), tileset.asset.fileName.replace('.tsx', '.tres'));
this.tilesetsString += this.getTilesetResourceTemplate(this.extResourceId, tilesetPath, "TileSet");
}

Expand Down Expand Up @@ -132,7 +130,8 @@ class GodotTilemapExporter {
this.extResourceId = this.extResourceId + 1;
textureResourceId = this.extResourceId;
this.tilesetsIndex.set(tilesetsIndexKey, this.extResourceId);
let tilesetPath = object.tile.tileset.image.replace(this.projectRoot, "");
// noinspection JSUnresolvedFunction
let tilesetPath = getResPath(this.map.property("projectRoot"), this.map.property("relativePath"), object.tile.tileset.image);
this.tilesetsString += this.getTilesetResourceTemplate(this.extResourceId, tilesetPath, "Texture");
} else {
textureResourceId = this.tilesetsIndex.get(tilesetsIndexKey);
Expand Down
6 changes: 1 addition & 5 deletions export_to_godot_tileset.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ class GodotTilesetExporter {
this.tileset = tileset;
this.fileName = fileName;
// noinspection JSUnresolvedFunction
this.projectRoot = getResPath(this.tileset.property("projectRoot"), fileName);
this.spriteImagePath = this.tileset.image.replace(this.projectRoot, "");

// Strip leading slashes to prevent invalid triple slashes in Godot res:// path:
this.spriteImagePath = this.spriteImagePath.replace(/^\/+/, '');
this.spriteImagePath = getResPath(this.tileset.property("projectRoot"), this.tileset.property("relativePath"), this.tileset.image);
this.shapesResources = "";
this.shapes = "";
this.navpolyMap = [];
Expand Down
87 changes: 52 additions & 35 deletions utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,64 @@ function logk(data) {
}

/**
* Determines the project root of a Godot project, which is the equivalent of 'res://' within Godot.
* There are three possible inputs for projectRoot:
* - absolute: This defines the projectRoot. Ex: getResPath('C:/project', 'C:/project/map/tileset.tres') => 'C:/project'
* - relative: This defines the projectRoot relative to the outputPath. Ex: getResPath('./..', 'C:/project/map/tileset.tres') => 'C:/project'
* - undefined: Attempts to automatically determine the projectRoot. Ex: getResPath(undefined, 'C:/project/map/tileset.tres') => 'C:/project'
* Returns a full res path to a file.
*
* If relativePath is defined, uses relativePath to determine the res path.
* If relativePath is undefined, uses projectRoot to determine the res path.
* If relativePath is undefined and projectRoot is undefined, automatically determines the res path.
*
* Information on file paths in Godot: https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html
*
* @param {string} projectRoot desired project root path, which can be an absolute or relative path
* @param {string} outputPath full path and name of destination file
* @return {string} project root path, which is the equivalent of 'res://'
* @param {string} projectRoot desired project root path, which can be an absolute or relative path to the outputPath. Ex: 'C:/project' or './../..'
* @param {string} relativePath relative path to file. Ex: '/maps/level1'
* @param {string} outputPath full path and name of destination file. Ex: 'C:/project/maps/level1/tileset.tres'
* @returns {string} full relative path to file to be included in a 'res://' path. Ex: 'maps/level1/tileset.tres'
*/
function getResPath(projectRoot, outputPath) {
const p = outputPath.split('/').slice(0, -1)
function getResPath(projectRoot, relativePath, outputPath) {
let fullResPath = ''
if (relativePath) {
// Replace all backslashes with forward slashes
relativePath = relativePath.replace(/\\/g, '/');

// If projectRoot is not set, attempt to automatically determine projectRoot by searching for godot project file
if (!projectRoot) {
const out = p
outputPath.split('/').every(_ => {
var godotProjectFile = FileInfo.joinPaths(out.join('/'), 'project.godot');
if (!File.exists(godotProjectFile)) {
out.pop()
return true;
}
return false;
})
projectRoot = out.join('/')
}
projectRoot = projectRoot.replace(/\\/g, '/')
fullResPath = FileInfo.joinPaths(relativePath, FileInfo.fileName(outputPath));
} else {
const p = outputPath.split('/').slice(0, -1)

// If projectRoot is not set, attempt to automatically determine projectRoot by searching for godot project file
if (!projectRoot) {
const out = p
outputPath.split('/').every(_ => {
let godotProjectFile = FileInfo.joinPaths(out.join('/'), 'project.godot');
if (!File.exists(godotProjectFile)) {
out.pop()
return true;
}
return false;
})
projectRoot = out.join('/')
}

// Use projectRoot as absolute if it doesn't start with ".", relative if it does
if (projectRoot[0] === '.') {
const out = p
projectRoot.split('/').forEach((segment) => {
if (segment === '..') {
out.pop()
}
})
projectRoot = out.join('/')
// Replace all backslashes with forward slashes
projectRoot = projectRoot.replace(/\\/g, '/');

// Use projectRoot as absolute if it doesn't start with ".", relative if it does
if (projectRoot[0] === '.') {
const out = p
projectRoot.split('/').forEach((segment) => {
if (segment === '..') {
out.pop()
}
})
projectRoot = out.join('/')
}

fullResPath = outputPath.replace(projectRoot, "");
}

return projectRoot
// Strip leading slashes to prevent invalid triple slashes in Godot res:// path
fullResPath = fullResPath.replace(/^\/+/, '');

return fullResPath
}

/**
Expand Down Expand Up @@ -88,7 +105,7 @@ function splitCommaSeparated(str) {
* @param {object} nodeProperties pair key/values for the "node" properties
* @param {object} contentProperties pair key/values for the content properties
* @param {object} metaProperties pair key/values for the meta properties
* @return {string} TSCN scene node like so :
* @returns {string} TSCN scene node like so :
* ```
* [node key="value"]
* content_key = AnyValue
Expand Down

0 comments on commit 540ee8e

Please sign in to comment.