This commit is contained in:
asrael 2025-11-01 12:39:59 -05:00
parent e862b02019
commit e49fbede9a
21 changed files with 1280 additions and 148 deletions

21
tools/aseprite/package.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/bash
set -e
EXTENSION_NAME="tile-props"
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)/$EXTENSION_NAME"
ZIP_FILE="$(cd "$(dirname "$0")" && pwd)/${EXTENSION_NAME}.aseprite-extension"
echo "Creating extension package: ${EXTENSION_NAME}.aseprite-extension"
cd "$(dirname "$SOURCE_DIR")"
rm -f "$ZIP_FILE"
zip -q -r "$ZIP_FILE" "$EXTENSION_NAME"
echo "✓ Extension package created: $ZIP_FILE"
echo ""
echo "To install in Aseprite:"
echo "1. Open Aseprite"
echo "2. Go to Edit → Preferences → Extensions"
echo "3. Click 'Add Extension'"
echo "4. Select: $ZIP_FILE"
echo "5. Restart Aseprite"

View file

@ -0,0 +1,41 @@
# pxl8 Tile Properties Extension
Aseprite extension for editing custom tile properties that are exported to pxl8 engine.
## Installation
1. Copy this directory to your Aseprite extensions folder:
- **Windows**: `%APPDATA%\Aseprite\extensions\tile-properties`
- **macOS**: `~/Library/Application Support/Aseprite/extensions/tile-properties`
- **Linux**: `~/.config/aseprite/extensions/tile-properties`
2. Restart Aseprite
## Usage
1. Open a tilemap sprite in Aseprite (File → New → Tilemap)
2. Create your tileset with tiles
3. Select a tile in the Tileset panel
4. Go to **Edit → Edit Tile Properties** (menu will be enabled when a tile is selected)
5. Add/edit custom properties:
- **Name**: Property key (e.g., `solid`, `terrain`, `move_cost`)
- **Type**: `boolean`, `number`, or `string`
- **Value**: The property value
6. Click **Apply** to save
## Example Properties
For a grass tile in a tactical RPG:
- `solid` (boolean): `false`
- `terrain` (string): `grass`
- `move_cost` (number): `1`
- `defense_bonus` (number): `0`
For a wall tile:
- `solid` (boolean): `true`
- `terrain` (string): `wall`
- `blocks_sight` (boolean): `true`
## How It Works
Properties are saved directly into the Aseprite file using the tile properties API. When pxl8 loads the tilemap, these properties are automatically extracted and made available in your Fennel/Lua code via `pxl8.tilemap_get_tile_data()`.

View file

@ -0,0 +1,283 @@
-- pxl8 tile properties editor
-- provides a ui for editing custom properties of tilemap tiles
local DEBUG = false
local function log(msg)
if DEBUG then
print("[tile-properties] " .. msg)
end
end
local function getSelectedTile()
log("getSelectedTile() called")
if not app then
log("ERROR: app is nil!")
return nil
end
local sprite = app.sprite
log("sprite: " .. tostring(sprite))
if not sprite then
log("No sprite selected")
return nil
end
local layer = app.layer
log("layer: " .. tostring(layer))
if not layer or not layer.isTilemap then
log("Layer is not a tilemap")
return nil
end
local tileset = layer.tileset
log("tileset: " .. tostring(tileset))
if not tileset then
log("No tileset in layer")
return nil
end
local tileIndex = app.fgTile
log("tileIndex: " .. tostring(tileIndex))
if not tileIndex or tileIndex < 0 then
log("Invalid tile index")
return nil
end
log("Selected tile: tileset=" .. tostring(tileset) .. ", index=" .. tostring(tileIndex))
return tileset, tileIndex
end
local function getTileProperties(tileset, tileIndex)
log("getTileProperties() called for tile index: " .. tostring(tileIndex))
local tile = tileset:tile(tileIndex)
if not tile then
log("Could not get tile object")
return {}
end
if not tile.properties then
log("Tile has no properties")
return {}
end
local props = {}
for key, value in pairs(tile.properties) do
local propType = "string"
if type(value) == "boolean" then
propType = "boolean"
elseif type(value) == "number" then
propType = "number"
end
table.insert(props, {
key = key,
type = propType,
value = value
})
end
log("Found " .. #props .. " properties")
return props
end
local function setTileProperties(tileset, tileIndex, props)
log("setTileProperties() called for tile index: " .. tostring(tileIndex))
local tile = tileset:tile(tileIndex)
if not tile then
log("Could not get tile object")
return
end
tile.properties = {}
local count = 0
for _, prop in ipairs(props) do
if prop.key and prop.key ~= "" then
local value = prop.value
if prop.type == "boolean" then
value = (value == true or value == "true")
elseif prop.type == "number" then
value = tonumber(value) or 0
end
tile.properties[prop.key] = value
count = count + 1
end
end
log("Set " .. count .. " properties")
end
local function showPropertyEditor(existingProps)
log("showPropertyEditor() called")
local tileset, tileIndex = getSelectedTile()
if not tileset then
log("No tileset selected, showing alert")
app.alert("Please select a tile in the tileset")
return
end
log("Getting properties for tile")
local properties = existingProps or getTileProperties(tileset, tileIndex)
local dlg = Dialog("Tile Properties - Tile #" .. tileIndex)
dlg:label{ text="Properties:" }
for i, prop in ipairs(properties) do
dlg:separator()
dlg:entry{
id = "key_" .. i,
label = "Name:",
text = prop.key
}
dlg:combobox{
id = "type_" .. i,
label = "Type:",
option = prop.type,
options = { "boolean", "number", "string" },
onchange = function()
-- Save all current field values and apply type conversions
for j = 1, #properties do
properties[j].key = dlg.data["key_" .. j] or properties[j].key
local newType = dlg.data["type_" .. j] or properties[j].type
local oldType = properties[j].type
-- Apply default values when type changes
if newType ~= oldType then
if newType == "boolean" then
properties[j].value = false
elseif newType == "number" then
properties[j].value = 0
else -- string
properties[j].value = ""
end
else
properties[j].value = dlg.data["value_" .. j] or properties[j].value
end
properties[j].type = newType
end
dlg:close()
showPropertyEditor(properties)
end
}
if prop.type == "boolean" then
dlg:check{
id = "value_" .. i,
text = "",
selected = prop.value
}
elseif prop.type == "number" then
dlg:number{
id = "value_" .. i,
label = "Value:",
text = tostring(prop.value),
decimals = 0
}
else
dlg:entry{
id = "value_" .. i,
label = "Value:",
text = tostring(prop.value)
}
end
dlg:button{
id = "delete_" .. i,
text = "Delete",
onclick = function()
-- Save current field values before deleting
for j = 1, #properties do
properties[j].key = dlg.data["key_" .. j] or properties[j].key
properties[j].type = dlg.data["type_" .. j] or properties[j].type
properties[j].value = dlg.data["value_" .. j] or properties[j].value
end
table.remove(properties, i)
dlg:close()
showPropertyEditor(properties)
end
}
end
dlg:separator()
dlg:button{
text = "Add Property",
onclick = function()
-- Save current field values before adding new property
for i = 1, #properties do
properties[i].key = dlg.data["key_" .. i] or properties[i].key
properties[i].type = dlg.data["type_" .. i] or properties[i].type
properties[i].value = dlg.data["value_" .. i] or properties[i].value
end
table.insert(properties, {
key = "",
type = "string",
value = ""
})
dlg:close()
showPropertyEditor(properties)
end
}
dlg:separator()
dlg:button{
text = "Apply",
onclick = function()
local newProps = {}
for i = 1, #properties do
local key = dlg.data["key_" .. i]
local propType = dlg.data["type_" .. i]
local value = dlg.data["value_" .. i]
if key and key ~= "" then
table.insert(newProps, {
key = key,
type = propType,
value = value
})
end
end
setTileProperties(tileset, tileIndex, newProps)
dlg:close()
end
}
dlg:button{ text = "Cancel" }
dlg:show()
end
function init(plugin)
log("=== PLUGIN INIT START ===")
if not plugin then
print("[tile-properties] ERROR: plugin is nil!")
return
end
-- Register in Sprite menu
plugin:newCommand{
id = "TilePropertiesEditor",
title = "Tile Properties",
group = "sprite_properties",
onenabled = function()
local tileset, tileIndex = getSelectedTile()
return tileset ~= nil and tileIndex ~= nil
end,
onclick = showPropertyEditor
}
log("Command registered in Sprite menu")
log("=== PLUGIN INIT COMPLETE ===")
end
function exit(plugin)
log("=== PLUGIN EXIT ===")
end

View file

@ -0,0 +1,17 @@
{
"name": "tile-props",
"displayName": "pxl8 tile props",
"description": "Edit custom properties for tilemap tiles",
"version": "1.0.0",
"author": {
"name": "pxl8.org"
},
"categories": ["Scripts"],
"contributes": {
"scripts": [
{
"path": "./main.lua"
}
]
}
}