FlatRedBall Documentation

repository·NetStandard·Indexed 20 days ago

https://github.com/vchelaru/flatredball

A 2D game engine using C# and the .NET runtime, featuring a powerful editor (Glue) and a stable runtime. The engine supports multiple platforms including Windows GL, Windows 8, Windows Phone, iOS, MacOS, and Android via MonoGame. Documentation covers project organization, the Glue plugin architecture using MEF, the ReferenceService for object inheritance and usage, and sample projects like Beefball and ChickenClicker.

Tokens
10.3K
Snippets
17
Records
51
Agent score
69%

What's inside FlatRedBall

  1. Gum Standard Element Codegen Requirements

    NetStandard

    When working with Gum standard elements and their code generation, be aware of how different types handle properties:

    • Skia-based Elements: Arc, ColoredCircle, LottieAnimation, RoundedRectangle, Svg, and Canvas share SkiaGum.Renderables.RenderableSkiaObject. These support the full fill, stroke, gradient, and dropshadow surface.
    • Color Conversion: The universal Color property is XNA-Color typed, but the backing types use System.Drawing.Color. A version-gated conversion (ToXNA/ToSystemDrawing) is used based on FileVersion >= GluxVersions.GumUsesSystemTypes.
    • Known Element Behaviors:
      • Polygon: Uses a dedicated custom getter/setter for Red/Green/Blue/Alpha onto LinePolygon.Color. Points is a VariableListSave and is not iterated by the codegen pipeline.
      • NineSlice: The Animate property is version-gated. Ensure both the property pipeline and the state-pipeline use matching gates based on GluxVersions.GumNineSliceHasAnimate to avoid compilation errors.
      • Sprite: The RenderTargetTextureSource property is managed by SpriteCodeGenerator.GenerateIRenderTargetTextureReferencerProperties to ensure correct typing (e.g., IRenderableIpso?).
      • Container: Does not have a backing runtime type and does not support SourceShaderFile.
  2. How PluginManager handles UI-coupled components

    NetStandard

    The PluginManager contains some UI-specific statics that are normally provided by a live MainGlueWindow. When running in a headless test environment, these must be handled to avoid null references:

    • TabControlViewModel: A plain MVVM view model (GlueFormsCore.ViewModels.TabControlViewModel). In tests, GlueTestBootstrap.EnsureInitialized ensures this is populated via PluginManager.SetTabs(new TabControlViewModel()) if it is unset.
    • mMenuStrip: A menu strip reference. In headless tests, this will be null. The PluginManager includes a null-guard (mMenuStrip == null || mMenuStrip.IsDisposed) to ensure that synchronous plugin dispatch paths (like FixNamedObjectCollisionType) do not crash when no UI thread is available.
  3. Understand the Glue Plugin Architecture

    NetStandard

    Glue (the FlatRedBall Editor) uses the Managed Extensibility Framework (MEF) to separate functionality into plugins. This allows for modularity and extensibility.

    There are two types of plugins:

    • External plugins: Compiled as independent .dll files and loaded at runtime (e.g., Gum).
    • Official plugins: Located within the OfficialPlugins/ directory under the OfficialPlugins.csproj project. Each plugin resides in its own subfolder.

    Important: Cross-plugin method invocation Some plugins communicate by calling methods on other plugins using string-based invocation (by method name). Because of this:

    • Renaming a public method in a plugin can silently break other plugins.
    • The main plugin class of any plugin is the most likely target for these calls.
    • Warning: Before renaming or removing a public method, you must search the entire codebase for string references to that name, not just symbol/type references.
  4. How WizardViewModel handles project configuration

    NetStandard

    The WizardViewModel (located at OfficialPlugins/Wizard/ViewModels/WizardViewModel.cs) manages the state for the project creation wizard.

    • State Management: It uses a pure Get<T>/Set(value) pattern for state.
    • Visibility Logic: It uses [DependsOn] attributes on computed boolean properties to handle WPF visibility binding (e.g., showing or hiding specific checkboxes based on other selected options).
    • Application: The WizardProjectLogic.Apply(vm) method is used to apply the selected options from the WizardViewModel to a project (e.g., adding collision relationships, camera controllers, or Gum projects).
  5. Understand the IMainGlueWindow interface and MainGlueWindow.Self

    NetStandard

    The IMainGlueWindow interface (defined in Glue/Managers/IMainGlueWindow.cs) provides a decoupled way to interact with the main application window without requiring a concrete MainGlueWindow instance. This is essential for testing and plugin development.

    Key Members of IMainGlueWindow

    • Window Properties: Width, Height, Text, IsDisposed, Handle (via IWin32Window).
    • UI Marshalling: Invoke and BeginInvoke (used to run code on the UI thread).
    • Application State: HasErrorOccurred, Close, Components, NumberOfStoredRecentFiles.
    • UI Components: PropertyGrid (a System.Windows.Forms.PropertyGrid instance).
    • Theme/Styles: SyncMenuStripWithTheme, TryGenerateImplicitWindowStylesFor.

    Accessing the Window

    Access the current window instance via the static property MainGlueWindow.Self.

    Note on Lifecycle: In production, MainGlueWindow.Self is lazily assigned in the MainGlueWindow constructor. In test environments, it can be swapped with a FakeMainGlueWindow via GlueTestBootstrap.EnsureInitialized to avoid side effects like spinning up a System.Windows.Application or setting MSBuild environment variables.

    // Accessing the window via the interface
    IMainGlueWindow window = MainGlueWindow.Self;
    window.Invoke(new Action(() => {
        // Code to run on UI thread
    }));
  6. How SelectionLogic works and how to access it

    NetStandard

    The SelectionLogic class manages the selection state within the Glue editor. It has been transitioned from a static class to an instance class to improve testability and decouple it from specific UI controls.

    • Accessing the instance: Use the SelectionLogic.Current property to access the active selection logic instance from other components like NodeViewModel or RightClickHelper.
    • Decoupling from ViewModels: NodeViewModel no longer has a direct compile-time dependency on SelectionLogic. Instead, it uses internal static delegates (NodeSelected and NodeDeselected) which are wired up by the SelectionLogic instance during initialization.
    • UI Interaction: SelectionLogic interacts with the UI via the ITreeViewDisplay interface, allowing it to trigger menu refreshes, scrolling, and layout updates without being tightly coupled to the concrete MainTreeViewControl class.
  7. Understand the difference between ObjectFinder and FileReferenceManager

    NetStandard

    When querying file and element relationships, it is important to use the correct service based on the level of abstraction required:

    1. ObjectFinder / ReferenceService (Glue Project Model level): Use these to query the internal Glue project structure. They answer questions like "Which ReferencedFileSave objects exist in this project?" or "Which elements reference a specific RFS name?".
    2. FileReferenceManager (Disk/Content level): Use this to query the actual file system and content dependencies. It tracks which content files an asset file imports on disk (cached by write time via ContentParser).

    Note: While some plugin managers (like TileGraphicsPlugin) may have classes named FileReferenceManager, they are plugin-specific providers that feed dependencies into the core FileReferenceManager and operate in different namespaces.

  8. Understand the ProjectDiffPlan logic in UpdateReactor

    NetStandard

    The UpdateReactor.ReloadGlux method uses BuildProjectDiffPlan to determine if an external edit to .glux, .gluj, or per-element files (.glsj, .glej) requires a full project reload or a partial reload.

    Partial Reload Criteria:

    • A partial reload is possible if every difference between the in-memory project and the reloaded copy collapses to a single entry within Screens[i], Entities[i], or GlobalFiles[i].
    • If a difference is found in a top-level GlueProjectSave property (that is not whitelisted in DiffableTopLevelProperties), a FullReloadRequired outcome is returned.
    • If a list Count changes (add/remove/reorder), a full reload is required.

    Important Quirk: When a difference list contains an unresolvable entry (like a project-level property), the replacements resolved before that entry in iteration order are still applied to the project, even though the overall outcome is FullReloadRequired.

  9. Use IUiThreadMarshaller for UI thread dispatching

    NetStandard

    While IMainGlueWindow provides Invoke and BeginInvoke for window-specific operations, the project also provides IUiThreadMarshaller for a more generic, purpose-built way to marshal tasks onto the UI thread. This interface is not tied to WinForms-specific signatures and only accepts Action, Func<T>, or Task.

    Use IUiThreadMarshaller when you only need to run code on the UI thread and do not need access to window properties like Text or PropertyGrid. It is accessible via:

    • TaskManager.UiThreadMarshaller
    • TaskManager.OnUiThread
    • GlueTask.DoOnUiThread

    In production, the WinFormsUiThreadMarshaller implementation forwards these calls to MainGlueWindow.Self.Invoke or BeginInvoke.

    // Using the task manager to run code on the UI thread
    TaskManager.OnUiThread(() => {
        // UI logic here
    });
  10. How SearchMatcher calculates match weights

    NetStandard

    The SearchMatcher is a standalone utility used to determine how well a search term matches a specific name. It is used by the MainTreeViewViewModel to rank results in the flattened list.

    It supports various weight tiers to ensure relevant results appear first:

    • Exact matches
    • Case-sensitive prefixes
    • Case-insensitive exact matches
    • Camel case matches
    • Case-insensitive prefixes
    • Contains matches
    • No match (lowest weight)

    It also accounts for an isDefinedByBase penalty to adjust the ranking of inherited members.

    // Conceptual usage of the SearchMatcher
    float weight = SearchMatcher.GetMatchWeight(itemName, searchTerm);
  11. Understand the limitations of PluginManager.CallPluginMethod in tests

    NetStandard

    The PluginManager.CallPluginMethod and PluginManager.CallPluginMethodAsync methods are designed to find and execute methods on loaded plugins by their friendly name.

    Warning for Test Authors: In a test host, these methods will silently no-op and return null if no real plugin is registered. They do not throw exceptions. To test actual plugin behavior (like Collision Plugin logic), you should bypass the PluginManager and call the underlying static methods directly (e.g., calling CollisionRelationshipViewModelController.TryFixSourceClassType instead of routing through PluginManager).

  12. Avoid Semantic Shifts when Retyping Static Members

    NetStandard

    When refactoring, be cautious when changing a static member (e.g., Xyz.Self) from a concrete class to an interface. This can silently change overload resolution.

    For example, a delegate-typed call site like Self.Invoke((MethodInvoker)x) might have been resolving to a base-class overload that is only reachable via the concrete type. Once Self is typed as an interface, that specific overload becomes invisible to the compiler, potentially changing behavior without a compiler error.

    Mitigation: After retyping a member to an interface, grep the codebase for delegate casts (MethodInvoker, Action, Func) at call sites of that member and rebuild to ensure no semantic shifts occurred.