Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ Track whether the current scene has unsaved editor-authored changes.

Initial scope:

* [ ] Mark scene dirty when editor commands are executed.
* [ ] Mark scene dirty when inspector edits are committed.
* [ ] Mark scene dirty when authored objects are created, deleted, duplicated, moved, scaled, or reordered.
* [ ] Clear dirty state after a successful save.
* [ ] Keep runtime-only behaviour out of dirty tracking.
* [ ] Show dirty state somewhere lightweight in the editor UI.
* [x] Mark the scene dirty when a command modifies persistent, editor-authored scene data.
* [x] Mark scene dirty when inspector edits are committed.
* [x] Mark scene dirty when authored objects are created, deleted, duplicated, moved, scaled, or reordered.
* [x] Clear dirty state after a successful save.
* [x] Keep runtime-only behaviour out of dirty tracking.
* [x] Show dirty state somewhere lightweight in the editor UI.

Out of scope for the first pass:

Expand Down
94 changes: 67 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Different scene types can share the same editor shell while defining their own b
> Prune is still pre-release. Existing `.yml` scene files may break while the object model, scene descriptors, behaviour ids, concept metadata, and scene-specific save data are being shaped.
>
> Save compatibility will matter later. For now, the priority is getting the scene model and editor/runtime architecture right.
>
> Camera movements do not mark the scene as dirty but they are saved to the scene file. If you are testing save/load, be aware that camera movements will be persisted.

## Why this exists

Expand Down Expand Up @@ -110,17 +112,20 @@ Prune currently has:
- Shared editor camera and game camera foundations
- Grid rendering and snapping
- Live object selection
- Selected-object outline and first transform handle
- Handle-based object movement for authored movable objects
- Selected-object outlines and transform handles
- Handle-based movement for authored movable objects
- Handle-based scaling for single authored objects, with minimum-size constraints
- Runtime object protection by default
- Outliner and generic inspector panels
- Scene-specific inspector sections
- Scene-aware object concepts (scene roles) for selection, editability, movement, runtime-only objects, and collision meaning
- YAML scene save/load
- Dirty state tracking for authored scene data
- Scene factory creation and scene-type loading from save files
- Rectangle and sprite rendering
- Basic sprite resource map
- Shared scene renderer, interaction, camera, state, collision, and serialization pieces
- Basic audio playback and scene audio hooks, currently configured in code with no dedicated editor UI
- Shared scene renderer, interaction, camera, state, collision, serialization, and audio foundations
- Shared `WorldScene` foundation for scene types
- Platformer scene slice
- Artillery game slice
Expand Down Expand Up @@ -189,34 +194,69 @@ The immediate focus is proving that viewport tools can operate safely on scene o

Current priorities:

- [x] Command model for editor changes
- [x] Undo/Redo on top of the command model
- [x] Command history panel
- [x] Multi-select
- [x] Multi-select move/delete
- [x] Tool mode state
- [x] Scale tool
- [x] Basic audio hooks
- [x] Initial implementation of dirty-state tracking for persistent editor-authored changes.
- [ ] Ensure dirty state remains accurate through execution, undo, redo, save, and load.
- [ ] Add a Ctrl+S shortcut using the existing scene save workflow.
- [ ] Add explicit runtime reset and saved-scene reload behaviour.
- [ ] Add consistent reset, reload, pause, and resume controls to the editor.
- [ ] Add authored object z-index ordering.
- [ ] Persist and restore object ordering through scene save files.
- [ ] Add basic editor actions for moving selected objects forward or backward in render order.

My development plan is tracked in [NOTES.md](NOTES.md), check the file for more details on what each of these points mean as well as what is included - this is what I will be working on in the next development phase.
My development plan is tracked in [NOTES.md](NOTES.md), check the file for more details on what each of these points mean as well as what is included - this is what I will be working on in my next development phase.

## Ready for users when...

I will consider Prune ready for users when the below is ready, this is the minimum I think users expect from a live editor/runtime prototype:

- [x] Open app.
- [ ] Pick a scene type. (Deferred for now, initial scene type is hardcoded to Platformer, others in new dropdown.)
- [x] Select object in viewport.
- [x] Move with handle.
- [x] Resize with scale tool.
- [x] Duplicate it.
- [x] Delete it.
- [x] Undo/redo all of that.
- [x] Save.
- [x] Load.
- [x] Behaviour still works.
- [x] Runtime objects do not get accidentally edited or saved.
- [x] Basic audio hooks
The core editor/runtime loop is now working, but that does mean Prune is ready for you to invest any meaninful time in it just yet.

I will consider Prune ready for early external users when someone unfamiliar with the codebase can create, edit, run, save, reload, and understand a small scene without modifying C++ or relying on undocumented project knowledge.

### Core editor/runtime

- [x] Open the application.
- [x] Select objects in the viewport.
- [x] Move authored objects using the move tool.
- [x] Resize authored objects using the scale tool.
- [x] Duplicate authored objects.
- [x] Delete authored objects.
- [x] Undo and redo editor changes.
- [x] Save and load scenes.
- [x] Preserve scene behaviour after loading.
- [x] Protect runtime-only objects from accidental editing and persistence.
- [x] Provide basic code-driven audio playback and scene audio hooks.

### Editor workflow

- [ ] Select a scene type when creating a new scene.
- [ ] Provide reliable new, open, save, save-as, reload, and reset workflows.
- [ ] Track unsaved authored changes accurately, including through undo and redo (undo/redo missing right now).
- [x] Show the current dirty state clearly in the editor.
- [ ] Support Ctrl+S for normal scene saving.
- [ ] Warn before closing, reloading, or replacing a scene with unsaved changes.
- [ ] Provide consistent pause, resume, runtime reset, and scene reload controls.
- [ ] Add explicit authored-object render ordering.
- [ ] Apply grid snapping consistently across applicable editor tools.
- [ ] Provide object locking or protection for authored objects that should not be changed accidentally.

### Scene editing

- [ ] Provide a usable asset workflow that does not require editing source code.
- [ ] Allow sprites and audio resources to be selected and configured through the editor.
- [ ] Expose useful authored-object behaviour through editor controls.
- [ ] Provide editor-facing event and reaction configuration, including audio playback.
- [ ] Support basic scene-wide configuration such as background and music.
- [ ] Add sufficient text rendering support for practical scenes.
- [ ] Introduce scene file versioning before external users create files that need long-term compatibility.

### Proof Prune works and is usable

- [ ] Provide at least one polished example for each supported scene type.
- [ ] Ensure errors such as missing assets and invalid scene files are reported clearly.
- [ ] Document how to build, launch, create, edit, run, save, and reload a scene.
- [ ] Ensure a new user can complete the basic workflow without reading the engine source.
- [ ] Provide at least one practical way to package or share a playable scene. (This may be a simple zip of the scene file and assets, or a small standalone executable.)

*Until all or the majority of the above is checked, please consider Prune an active 2D game engine and editor prototype rather than something you can depend.

## Documentation stance

Expand Down
6 changes: 3 additions & 3 deletions src/prune/app/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@ namespace prune {
const std::filesystem::path sound_path =
std::filesystem::current_path() / "assets" / "sound-effects";

m_audio->load_sound(sound_ids::shoot, sound_path / "shoot.wav", error);
m_audio->load_sound(sound_ids::jump, sound_path / "jump.wav", error);
m_audio->load_sound(sound_ids::explosion, sound_path / "explosion.wav", error);
static_cast<void>(m_audio->load_sound(sound_ids::shoot, sound_path / "shoot.wav", error));
static_cast<void>(m_audio->load_sound(sound_ids::jump, sound_path / "jump.wav", error));
static_cast<void>(m_audio->load_sound(sound_ids::explosion, sound_path / "explosion.wav", error));

m_audio->map_event_to_sound(scene_events::player_fired, sound_ids::shoot);
m_audio->map_event_to_sound(scene_events::player_jumped, sound_ids::jump);
Expand Down
1 change: 1 addition & 0 deletions src/prune/editor/editor_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ namespace prune {
) {
EditorCommand command{};
command.type = EditorCommandType::MoveViewport;
command.makes_dirty = false;
command.label = std::string(label);
command.detail = std::string(detail);
command.before_camera = before;
Expand Down
1 change: 1 addition & 0 deletions src/prune/editor/editor_command.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace prune {

struct EditorCommand {
EditorCommandType type = EditorCommandType::MoveObject;
bool makes_dirty = true;
std::string label;
std::string detail;
GameObjectId object_id = k_invalid_game_object_id;
Expand Down
3 changes: 2 additions & 1 deletion src/prune/scene/scene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ namespace prune {
virtual void update_editor(float, const Input&) {}
virtual void render(SDL_Renderer* renderer) = 0;

[[nodiscard]] virtual bool save_to_file(std::string_view path, std::string& error) const = 0;
[[nodiscard]] virtual bool save_to_file(std::string_view path, std::string& error) = 0;
[[nodiscard]] virtual bool load_from_file(std::string_view path, std::string& error) = 0;
[[nodiscard]] virtual bool is_dirty() const noexcept = 0;
[[nodiscard]] virtual std::string_view default_file_path() const noexcept = 0;
[[nodiscard]] virtual std::string_view scene_type_id() const noexcept = 0;

Expand Down
1 change: 1 addition & 0 deletions src/prune/scene/scene_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ namespace prune {
};

struct SceneState {
bool dirty = false;
SceneViewport viewport{};
DragState drag_state{};
SceneOptions scene_options{};
Expand Down
14 changes: 13 additions & 1 deletion src/prune/scene/world_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ namespace prune {
}
}

bool WorldScene::save_to_file(std::string_view path, std::string& error) const
bool WorldScene::save_to_file(std::string_view path, std::string& error)
{
try {
YAML::Node root;
Expand All @@ -136,6 +136,7 @@ namespace prune {
return false;
}

m_state.dirty = false;
return true;
}
catch (const YAML::Exception& ex) {
Expand Down Expand Up @@ -180,6 +181,7 @@ namespace prune {
m_state.events.clear();
m_state.drag_state = {};
m_state.editor_commands.clear();
m_state.dirty = false;
m_camera = loaded_camera;
m_grid_options = loaded_grid_options;

Expand Down Expand Up @@ -228,6 +230,10 @@ namespace prune {

void WorldScene::record_editor_command(EditorCommand command)
{
if (command.makes_dirty) {
m_state.dirty = true;
}

m_state.editor_commands.record(std::move(command));
}

Expand All @@ -244,6 +250,9 @@ namespace prune {
}

apply_editor_command(*command, false);
if (command->makes_dirty) {
m_state.dirty = true;
}
return true;
}

Expand All @@ -255,6 +264,9 @@ namespace prune {
}

apply_editor_command(*command, true);
if (command->makes_dirty) {
m_state.dirty = true;
}
return true;
}

Expand Down
3 changes: 2 additions & 1 deletion src/prune/scene/world_scene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ namespace prune {
void render(SDL_Renderer* renderer) final;
void draw_viewport_overlays() final;

[[nodiscard]] bool save_to_file(std::string_view path, std::string& error) const final;
[[nodiscard]] bool save_to_file(std::string_view path, std::string& error) final;
[[nodiscard]] bool load_from_file(std::string_view path, std::string& error) final;
[[nodiscard]] bool is_dirty() const noexcept final { return m_state.dirty; }

void set_viewport(const SceneViewport& viewport) noexcept final;
[[nodiscard]] const SceneViewport& get_viewport() const noexcept final { return m_state.viewport; }
Expand Down
3 changes: 3 additions & 0 deletions src/prune/tooling/ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ namespace prune {
draw_scene_viewport(scene, renderer);

if (ImGui::BeginMainMenuBar()) {
ImGui::Text("%.*s%s", static_cast<int>(scene.scene_name().size()), scene.scene_name().data(), scene.is_dirty() ? " *" : "");
ImGui::Separator();

if (ImGui::BeginMenu("File")) {
if (ImGui::BeginMenu("New Scene")) {
for (const SceneDescriptor& descriptor : k_scene_descriptors) {
Expand Down
Loading