Edouard Boudet - UNote

Welcome to UNote!

Watch the demo video (on YouTube), see it in action.

Unote is a lightweight tool for Unreal Engine that keeps feedback directly inside the editor. Instead of spreading notes across chats or documents, notes can be created in the level, right where they are most useful.

Each note automatically stores the author, the level, and the time it was created. Notes can include tags for organization, replies for short discussions, and are stored in per-user assets inside developer folders to avoid conflicts. The editor panel provides an overview of all notes, with options to jump directly to their location in the scene, filter by author, tag, or note ID, and copy a note’s unique ID for sharing.

Unote is designed for day-to-day collaboration. It is not a replacement for production tracking tools, but a small companion that keeps feedback visible, contextual, and easy to revisit without leaving the editor.

Documentation is included in the plugin folder or you can find it here.

Find the example project for unreal 5.6 here.

_____________________________________

1 Like

Installing the plugin gives initialization errors on startup on 5.6.1.

LogClass: StrProperty FNoteEntry::NoteID is not initialized properly even though its struct probably has a custom default constructor. Module:UNote File:Public/Widget/Struct/NoteEntry.h

LogClass: StrProperty FNoteReply::ReplyID is not initialized properly even though its struct probably has a custom default constructor. Module:UNote File:Public/Widget/Struct/NoteReply.h

The issue seems to come from generating the FGuid in the constructor which triggers UE’s “uninitialized script struct members” test. Since two identically initialized structs can never match, this test assumes the memory is filled with garbage and thus uninitialized.

I resolved this in my project by replacing the default constructor and passing the FGuid in as a parameter

FNoteEntry() = default;
FNoteEntry(const FGuid& InNoteID)
    : NoteID(InNoteID.ToString())
{
    Timestamp = FDateTime::UtcNow();
    NoteTransform = FTransform::Identity;
}

and replacing each construction, passing in the FGuid instead

// Build the note entry
FNoteEntry NewNote{ FGuid::NewGuid() };
NewNote.Body = Body;
NewNote.Author = Username;

To be sure this didn’t break anything, I also added a data validation on UUnoteNoteDataAsset.

EDataValidationResult UUnoteNoteDataAsset::IsDataValid(class FDataValidationContext& Context) const
{
	EDataValidationResult Result = Super::IsDataValid(Context);

	for (int32 Index = 0; Index < Notes.Num(); ++Index)
	{
		const FNoteEntry& NoteEntry = Notes[Index];

		if (NoteEntry.NoteID.IsEmpty())
		{
			Context.AddError(FText::Format(NSLOCTEXT("UNote", "UNote.DataValidation.InvalidNoteID", "Note[{0}] has an invalid NoteID. {1}"), Index, FText::FromString(GetPathName())));
		}
	}

	[... same for the replies...]

	return Result;
}