aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriamcheeseman <[email protected]>2026-03-13 15:25:03 -0400
committeriamcheeseman <[email protected]>2026-03-13 15:25:03 -0400
commit332d00e92bf620b6dd11dc10096ead7a5badd7f4 (patch)
treed0e557afe65949d0d7de2f7aa97a8327f1537360
parentb47490aea2f63d881a2cc69a326b03abae1a46c0 (diff)
Add basic entity editor
-rw-r--r--main.lua2
-rw-r--r--src/im.lua3
-rw-r--r--src/init.lua5
-rw-r--r--src/objs/muntik.lua5
-rw-r--r--src/objs/room_editor.lua65
-rw-r--r--src/room_editor.lua283
-rw-r--r--src/utils.lua2
7 files changed, 296 insertions, 69 deletions
diff --git a/main.lua b/main.lua
index 2dfda3d..44809b5 100644
--- a/main.lua
+++ b/main.lua
@@ -14,6 +14,8 @@ function love.load()
event_bind(scn.on_update, "Room_Editor", room_editor_ui_sys)
event_bind(scn.on_ui, "Room_Editor", tile_place_sys)
+ event_bind(scn.on_draw, "Editor_Entity", draw_editor_entity_sys)
+ event_bind(scn.on_ui, "Editor_Entity", editor_entity_ui_sys)
set_scene(scn)
diff --git a/src/im.lua b/src/im.lua
index cb583ff..d48d4bc 100644
--- a/src/im.lua
+++ b/src/im.lua
@@ -281,7 +281,7 @@ function im.slider(val, min, max, step, h)
rect_cmd(x, y, w, h, unpack(col))
p = handle / max_w
- return clamp(p * (max - min) + min, min, max)
+ return snap(clamp(p * (max - min) + min, min, max), step)
end
function im.begin_window(title, x, y, w, h, opts)
@@ -422,6 +422,7 @@ function im.draw()
for _, cmd in ipairs(win.cmds) do
cmd.fn(unpack(cmd.args))
end
+ win.cmds = {}
end
lg.setStencilTest()
end
diff --git a/src/init.lua b/src/init.lua
index a8d106b..246147a 100644
--- a/src/init.lua
+++ b/src/init.lua
@@ -6,6 +6,7 @@ require "src.textures"
require "src.phys"
require "src.sprite"
require "src.im"
+require "src.room_editor"
SCR_WIDTH = 320
SCR_HEIGHT = 180
@@ -16,8 +17,8 @@ register_input("Right", {{"key", "right"}, {"key", "d"}})
register_input("Down", {{"key", "down"}, {"key", "s"}})
register_input("Up", {{"key", "up"}, {"key", "w"}})
-register_input("Right_Click", {{"mouse", 1}})
-register_input("Left_Click", {{"mouse", 2}})
+register_input("Right_Click", {{"mouse", 2}})
+register_input("Left_Click", {{"mouse", 1}})
lg = love.graphics
lf = love.filesystem
diff --git a/src/objs/muntik.lua b/src/objs/muntik.lua
new file mode 100644
index 0000000..3db7d2b
--- /dev/null
+++ b/src/objs/muntik.lua
@@ -0,0 +1,5 @@
+define_entity("Muntik", "res/img/muntik.ase", {
+ {"face_dir", 1, min=-1, max=1, step=1}
+}, function()
+end)
+
diff --git a/src/objs/room_editor.lua b/src/objs/room_editor.lua
deleted file mode 100644
index 70f0a86..0000000
--- a/src/objs/room_editor.lua
+++ /dev/null
@@ -1,65 +0,0 @@
-register_input("Next_Tileset", {{"key", "t"}})
-register_input("Prev_Tileset", {{"key", "r"}})
-
-register_comp("Room_Editor", function(ent)
- ent.room_editor = {
- tile = 1,
- }
-end)
-
-function tile_place_sys(ent)
- local room_editor = ent.room_editor
-
- if not im.has_focus() then
- if is_input_pressed("Right_Click") then
- local scn = get_current_scene()
- assert(scn, "no scene set.")
-
- local mx, my = get_mouse_pos()
- local tx, ty = to_tile_coords(mx, my)
- set_tile(scn.tilemap, tx, ty, room_editor.tile)
- end
- if is_input_pressed("Left_Click") then
- local scn = get_current_scene()
- assert(scn, "no scene set.")
-
- local mx, my = get_mouse_pos()
- local tx, ty = to_tile_coords(mx, my)
- remove_tile(scn.tilemap, tx, ty)
- end
- end
-
- if is_input_just_pressed("Next_Tileset") then
- room_editor.tile = math.min(room_editor.tile + 1, get_tileset_count())
- end
- if is_input_just_pressed("Prev_Tileset") then
- room_editor.tile = math.max(room_editor.tile - 1, 1)
- end
-end
-
-function room_editor_ui_sys(ent)
- local room_editor = ent.room_editor
- im.begin_window("Room Editor", 120, 5, 180, 320, {})
- im.text("Tile: " .. tostring(room_editor.tile))
- im.separator()
- im.layout({0.1, 0.6, 1})
-
- for tileset_id=1, get_tileset_count() do
- local text = " "
- if tileset_id == room_editor.tile then
- text = "*"
- elseif tileset_id == room_editor.tile - 1 then
- text = "r"
- elseif tileset_id == room_editor.tile + 1 then
- text = "t"
- end
- im.text(text)
- if im.button("select") then
- room_editor.tile = tileset_id
- end
- im.image(TILE_TEX, get_tileset_quad(tileset_id))
- end
-
- im.layout()
- im.end_window()
-end
diff --git a/src/room_editor.lua b/src/room_editor.lua
new file mode 100644
index 0000000..01d5c90
--- /dev/null
+++ b/src/room_editor.lua
@@ -0,0 +1,283 @@
+register_input("Next_Tileset", {{"key", "v"}})
+register_input("Prev_Tileset", {{"key", "c"}})
+register_input("Tile_Brush", {{"key", "t"}})
+register_input("Entity_Brush", {{"key", "e"}})
+register_input("Delete", {{"key", "delete"}, {"key", "backspace"}})
+
+local basic_entity_properties = {
+ {"flip_h", false},
+ {"flip_v", false},
+}
+
+local entity_definitions = {}
+
+function define_entity(name, tex_name, properties, spawner)
+ table.insert(entity_definitions, name)
+ entity_definitions[name] = {
+ spawner = spawner,
+ tex_name = tex_name,
+ properties = properties,
+ idx = #entity_definitions,
+ }
+end
+
+register_comp("Room_Editor", function(ent)
+ ent.room_editor = {
+ -- Valid values are Tile, Entity
+ -- TODO:
+ -- * Prop (place stuff on top of tiles automatically)
+ brush = "Tile",
+ tile = 1,
+ entity = "Muntik",
+ entity_snap = true,
+ selected_ent = -1,
+ placed_entities = {},
+ }
+end)
+
+register_comp("Editor_Entity", function(ent, room_editor, ent_type, x, y)
+ ent.x = x
+ ent.y = y
+ local entity_def = entity_definitions[ent_type]
+ local properties = {}
+ for _, property in ipairs(basic_entity_properties) do
+ local name = property[1]
+ local default = property[2]
+ properties[name] = default
+ end
+ for _, property in ipairs(entity_def.properties) do
+ local name = property[1]
+ local default = property[2]
+ properties[name] = default
+ end
+ ent.editor_dat = {
+ type = ent_type,
+ room_editor = room_editor,
+ properties = properties,
+ }
+end)
+
+local function get_selected_entity(room_editor)
+ return room_editor.placed_entities[room_editor.selected_ent]
+end
+
+local function new_editor_entity(room_editor, ent_type, x, y)
+ local ent = new_entity()
+ add_comp(ent, "Editor_Entity", room_editor, ent_type, x, y)
+ add_comp(ent, "Sprite", entity_definitions[ent_type].tex_name, {})
+ return ent
+end
+
+function draw_editor_entity_sys(ent)
+ local editor_dat = ent.editor_dat
+ local selected_ent = get_selected_entity(editor_dat.room_editor)
+
+ if selected_ent == ent then
+ -- show selected mhmm
+ lg.rectangle("line", ent.x, ent.y, ent.sprite.width, ent.sprite.height)
+ end
+end
+
+local property_ui = {
+ ["number"] = function(name, editor_dat, property)
+ im.layout({0.5, 1})
+ im.text(name)
+ im.text(tostring(snap(editor_dat.properties[name], 0.01)))
+ im.layout()
+ editor_dat.properties[name] = im.slider(
+ editor_dat.properties[name],
+ property.min or 0,
+ property.max or 1,
+ property.step or 0.1
+ )
+ end,
+ ["boolean"] = function(name, editor_dat, property)
+ im.layout({0.25, 0.5, 1})
+ im.text(name)
+ im.text(tostring(editor_dat.properties[name]))
+ if im.button("Toggle") then
+ editor_dat.properties[name] = not editor_dat.properties[name]
+ end
+ end,
+}
+
+local function property_list(ent, properties)
+ local editor_dat = ent.editor_dat
+ for _, property in ipairs(properties) do
+ local name = property[1]
+ local prop_type = type(property[2])
+ property_ui[prop_type](name, editor_dat, property)
+ im.layout()
+ end
+end
+
+function editor_entity_ui_sys(ent)
+ local editor_dat = ent.editor_dat
+ if editor_dat.room_editor.brush ~= "Entity" then
+ return
+ end
+
+ local selected_ent = get_selected_entity(editor_dat.room_editor)
+
+ if selected_ent == ent then
+ local entity_def = entity_definitions[editor_dat.type]
+ im.begin_window("Entity Properties", 190, 5, 180, 340, {})
+ im.text("Base Properties")
+ im.separator()
+ property_list(ent, basic_entity_properties)
+ im.separator()
+ im.text("Type Properties")
+ im.separator()
+ property_list(ent, entity_def.properties)
+ im.end_window()
+ end
+end
+
+local function get_snapped_to_grid(room_editor, x, y)
+ if room_editor.entity_snap then
+ return snap(x, TILESIZE), snap(y, TILESIZE)
+ else
+ -- Still snap to the pixel cause yes
+ return round(x), round(y)
+ end
+end
+
+local brushes = {
+ ["Tile"] = function(room_editor)
+ if is_input_pressed("Left_Click") then
+ local scn = get_current_scene()
+ assert(scn, "no scene set.")
+
+ local mx, my = get_mouse_pos()
+ local tx, ty = to_tile_coords(mx, my)
+ set_tile(scn.tilemap, tx, ty, room_editor.tile)
+ end
+ if is_input_pressed("Right_Click") then
+ local scn = get_current_scene()
+ assert(scn, "no scene set.")
+
+ local mx, my = get_mouse_pos()
+ local tx, ty = to_tile_coords(mx, my)
+ remove_tile(scn.tilemap, tx, ty)
+ end
+ end,
+ ["Entity"] = function(room_editor)
+ local mx, my = get_mouse_pos()
+ if is_input_just_pressed("Left_Click") then
+ -- first check if we're selecting an entity
+ local selected = false
+ for i, ent in ipairs(room_editor.placed_entities) do
+ if point_in_rect(
+ mx, my,
+ ent.x, ent.y,
+ ent.sprite.width, ent.sprite.height
+ ) then
+ room_editor.selected_ent = i
+ selected = true
+ break
+ end
+ end
+
+ -- Place one frfr
+ if not selected then
+ mx, my = get_snapped_to_grid(room_editor, mx, my)
+ local ent = new_editor_entity(room_editor, room_editor.entity, mx, my)
+ table.insert(room_editor.placed_entities, ent)
+ room_editor.selected_ent = #room_editor.placed_entities
+ end
+ end
+ end
+}
+
+function tile_place_sys(ent)
+ local room_editor = ent.room_editor
+
+ if not im.has_focus() then
+ brushes[room_editor.brush](room_editor)
+ end
+
+ if is_input_just_pressed("Next_Tileset") then
+ room_editor.tile = math.min(room_editor.tile + 1, get_tileset_count())
+ end
+ if is_input_just_pressed("Prev_Tileset") then
+ room_editor.tile = math.max(room_editor.tile - 1, 1)
+ end
+
+ if is_input_just_pressed("Tile_Brush") then
+ room_editor.brush = "Tile"
+ end
+ if is_input_just_pressed("Entity_Brush") then
+ room_editor.brush = "Entity"
+ end
+end
+
+function room_editor_ui_sys(ent)
+ local room_editor = ent.room_editor
+
+ im.begin_window("Room Editor", 5, 5, 180, 180, {})
+ im.text("Brushes")
+ im.layout({0.1, 1})
+ im.text("t")
+ if im.button("Tile") then
+ room_editor.brush = "Tile"
+ end
+ im.text("e")
+ if im.button("Entity") then
+ room_editor.brush = "Entity"
+ end
+ im.layout()
+ im.end_window()
+
+ if room_editor.brush == "Tile" then
+ im.begin_window("Tile Editor", 5, 190, 180, 340, {})
+ im.text("Tile: " .. tostring(room_editor.tile))
+ im.separator()
+ im.layout({0.1, 0.6, 1})
+
+ for tileset_id=1, get_tileset_count() do
+ local text = " "
+ if tileset_id == room_editor.tile then
+ text = "*"
+ elseif tileset_id == room_editor.tile - 1 then
+ text = "c"
+ elseif tileset_id == room_editor.tile + 1 then
+ text = "v"
+ end
+ im.text(text)
+ if im.button("select") then
+ room_editor.tile = tileset_id
+ end
+ im.image(TILE_TEX, get_tileset_quad(tileset_id))
+ end
+
+ im.layout()
+ im.end_window()
+ elseif room_editor.brush == "Entity" then
+ im.begin_window("Entity Editor", 5, 190, 180, 340, {})
+ im.layout({0.2, 1})
+
+ im.text(room_editor.entity_snap and "On" or "Off")
+ if im.button("Position Snapping") then
+ room_editor.entity_snap = not room_editor.entity_snap
+ end
+
+ im.layout({0.1, 0.6, 1})
+
+ for _, entity_name in ipairs(entity_definitions) do
+ local entity_def = entity_definitions[entity_name]
+
+ local text = " "
+ if entity_name == room_editor.entity then
+ text = "*"
+ end
+ im.text(text)
+ if im.button("select") then
+ room_editor.entity = entity_name
+ end
+ im.image(get_tex(entity_def.tex_name))
+ end
+
+ im.layout()
+ im.end_window()
+ end
+end
diff --git a/src/utils.lua b/src/utils.lua
index ec5d16e..55a9d35 100644
--- a/src/utils.lua
+++ b/src/utils.lua
@@ -54,7 +54,7 @@ function clamp(a, min, max)
end
function snap(x, step)
- return math.floor(x / step) * step
+ return round(x / step) * step
end
function frac(x)