Markdown Support adds capabilities for the .md
and .markdown
file extensions inside
Unity. Markdown is a text formatting standard that is easy to read in plain text,
and can be rendered into HTML for web clients. Markdown Support converts plain text
markdown files into Unity UIToolkit panels that are viewable in both Editor and Runtime.
In Unity, open the Create menu to make a "Markdown File". This will create a
.markdown
file in your project. The Unity Inspector window will render the
markdown. You can also use the .md
file extension, but Unity will recognize it
as a TextAsset
, so the icon of the file will be the standard TextAsset
icon.
Markdown Support has a small SDK that you can use to integrate Markdown into
your workflow. For a demonstration, see the two sample projects, Markdown/Samples/RuntimeDemo
and
Markdown/Samples/EditorDemo
.
The SDK is inside the BrewedInk.MarkdownSupport
assembly, which is not set to be
automatically referenced. If you need access to the Editor specifics, use the BrewedInk.MarkdownSupport.Editor
assembly. This means that you will need to create your own assembly
definition and add a reference to BrewedInk.MarkdownSupport
to use the SDK.
Alternatively, you can change the autoReferenced
property to true
in the
Markdown Support assembly definition.
The entrypoint to the SDK is the UMarkdown.Parse
function.
// somehow, get some markdown text
string markdownText = @"
# Example
*hello* __world__, its `nice` to meet you.
";
// then convert it into a `MarkdownVisualElement`
MarkdownVisualElement markdownElement = UMarkdown.Parse(
markdown: markdownText,
context: UMarkdownContext.GetDefault(false));
The Parse()
method takes 2 parameters, a string
of markdown text, and a UMarkdownContext
,
which is discussed in the configuration section.
The output of the method is a MarkdownVisualElement
, which can be inserted into whatever
document you like. The Parse()
method will not create a ScrollView
automatically, so if
your markdown is long, you need to make sure a ScrollView
exists in the document where the
MarkdownVisualElement
is inserted. In most Editor use cases, Unity itself will inject a
ScrollView
into the parent panel. However, in Runtime, please don't forget to handle the
ScrollView
.
The MarkdownVisualElement
is a sub class of VisualElement
(the base type of most UIToolkit
components). A small SDK is available beyond the standard UIToolkit methods.
The ScrollTo
method will find the ScrollView
in the parent lineage of the element, and focus
the scroll position around the desired markdown element.
string headingAnchor = "#example";
markdownElement.ScrollTo(headingAnchor);
The method takes a single input, either a string
or a VisualElement
.
If a string
is given,
it must start with a "#"
symbol, and be a valid heading anchor link. Heading anchors are lowercased,
hyphenated versions of the headings in the markdown. For example, here is a table of example headings
and their corresponding anchors.
markdown | anchor |
---|---|
# Example |
#example |
# Hello World |
#hello-world |
# Tuna Truck |
#tuna-truck |
By default, only headings have anchor tags. However, Markdown Support includes the ability to specify
custom element ids, classes, and attributes. Check the docs
for more information. Below is an example of how to attach an id, #example
to paragraph in markdown.
# hello
This paragraph has a custom id, and can be selected. {#example}
If a VisualElement
is given the ScrollTo()
function, then the markdown document will scroll so that
the given VisualElement
is within view. The element must be a child of the markdown document.
It is possible to add custom attributes to markdown elements, but UIToolkit does not have a way to let you interact with those attributes by default. The following markdown sample will add an attribute, "tuna" with a value of "stinky".
this paragraph has an attribute, "tuna", and another, "fish" set to "stinky". {tuna=stinky}
Then, the element can be identified via the QAttribute
function.
var elem = content.QAttribute("fish", "stinky");
This could be useful for running a procedural operation on the generated markdown.
Warning, this method uses Reflection to access the attributes on the element, because Unity does not publically expose them. Reflection may be acceptable in your use case, but you should never call Reflection based code in a hot loop, or ideally, even in an Update loop. Also, with any Reflection based code, support for attribute filtering may break with future releases of Unity.
Everytime Markdown Support renders a markdown file, it needs to contextualize the rendering with
some extra data. There are 4 pieces of data, the configuration context, the isRuntime
field, the the rootFilePath
field,
and the textureLoader
field.
These pieces of information are stored in the UMarkdownContext
, which can be obtained in a standard
set of ways.
- the context can be created manually, using constructors.
- the context can be fetched using
UMarkdownContext.ForFile()
, which will create a context using the standard set of configuration, andisRuntime
androotFilePath
must be specified. - the context can be fetched using
UMarkdownContext.Runtime()
, which will create a context using the standard set of configuration, assume thatisRuntime
istrue
, and sets a blank string to therootFilePath
.
The properties of the context are defined in detail in the following sections.
Loading images in markdown can be done with URLs, or relative file paths. When images are loaded with relative file paths, then the markdown rendering must know where the file path is relative to.
In the Editor, the default Inspectors use the ForFile()
method and pass the file location of the markdown
file being rendered, which means the file paths are relative to the markdown file itself. The images are
then loaded with the AssetDatabase
.
However, in Runtime, AssetDatabase
is not available, and in a built game, images do not retain their
source path information anyway. Images are loaded via Resources
, which means that the filepath is
also relative to the Resources
folder regardless of where the original markdown file was located.
It is possible to override the image load behaviour, see the textureLoader
property below.
Images in Runtime are loaded with the Resources
SDK, and images in Editor are loaded with the AssetDatabase
.
However, there is no obvious way for code in Unity to know if its intended to be executed in Editor, or in Runtime.
Markdown Support's SDK can be used in either case, so the decision of which image loading technique to use
is left as a contextual requirement.
By default, images are loaded from the AssetDatabase
when in the Editor, and from Resources
folders
when in Runtime. As mentioned, it would be too overbearing to generalize an image loading
approach for all possible use cases in the Runtime. Instead, the Markdown Support library takes the approach
of a "less is more", and offers only Resources
loading out of the box.
However, within the Markdown Support library, all images are loaded through the textureLoader
delegate.
The delegate must be of the UMarkdownTextureLoader
type, which takes 3 parameters,
- the
UMarkdownContext
instance - the
MarkdownLinkUri
instance that is being invoked - a callback
Action<Texture2D>
function
The only requirement is that the given Action<Texture2D>
callback is executed with a Texture2D
instance.
The Markdown/Samples/RuntimeDemo/Files/Logic.cs
file has an example showing a custom loader function that
takes the link content and matches it against a set of Texture2D
asset names attached to the game object.
Markdown Support uses a configuration everytime plain text is rendered into markdown. Normally, a single global configuration is used, but it is possible to create and use customized configurations for custom workflows.
The Inspector for .markdown
and .md
files will always use the global configuration settings.
To change the settings, you must create a UMarkdownConfig
scriptable object in a Resources
folder,
and the file must be named, MarkdownSettings
, otherwise it will not be loaded.
The configuration controls the visual theming of the rendered markdown, the root file path for any relative file paths given in the markdown document, and a few behavioural items.
The useCodeCopyButtons
option is set to true
by default. When true
, any code fence will appear
with a "copy" button in the upper-right corner of the code block. While this is a nice tool for Editor
use cases, it may not be helpful in a Runtime setting.
The validTextAssetExtensions
option is an array of string
, that specify which file extensions
among the Unity Text Asset extensions should be rendered as markdown. From Unity's documentation,
these are the Text Asset extensions in 2022.
.txt
.html
.htm
.xml
.bytes
.json
.csv
.yaml
.fnt
.md
Markdown Support provides a custom Inspector for Text Assets, but only renders files with extensions
included in the validTextAssetExtensions
array as markdown. Otherwise, the file content is rendered
similarly to how Unity normally renders the content. By default, the validTextAssetExtensions
field only contains the .md
extension, which is the conventional extension for markdown documents.
Markdown Support uses Unity Style Sheets
to control most of the rendering of the markdown document. By default, there is a single .uss
file
that handles all of the styling, MarkdownStyle.uss
. However, this can be removed, or additional style
sheets can be added to override specific portions of the document.
This could be used to completely change the default appearance of markdown in your Unity workspace.
Unfortunately, there are some things that Markdown Support cannot style through .uss
. These remaining
items can be controlled through the richTextStyleAsset
field. There can be only one of these assets
referenced. You can create a RichTextStyleAsset
through the Create/Markdown Support
menu.
The sub properties of the rich text style asset are below.
Code blocks often have a different background color than the rest of the markdown document, and as
such, have a different text highlight color when the user selects code in the block. The codeSelectionColor
option allows you to control the color of the highlighted text in a code block.
Markdown Support uses Rich Text to handle a lot of the emphasis in markdown. Specifically, then
you use the single ` character to emphasis text, it shows up this way
. The codeMarkupColor
will control the background color of this text
.
When code is rendered, Rich Text is used to inline a new font into the paragraph element. Be aware that font assets must be placed in a "Resources/Font & Materials"folder (or whatever the given folder is in the TMP Settings).
Unfortunately, there are a few known limitations of the Markdown Support tool.
Extended Markdown is full of wonderful extensions that this asset does not support. Below is a comptability table.
Feature | Supported |
---|---|
Tables | yes |
Alignment | no |
Fenced Code | yes |
Footnotes | no |
Definition List | no |
Strike Through | yes |
Task list | no |
Emoji | no |
Highlight | no |
The following markdown should produce a link in italics, but Markdown Support will only render the link, without any italic text.
this _[link](https://brewed.ink)_ should have been italic, but it won't be in MarkdownSupport.
Support for Unity Lightmode is not ideal.
Unity UIToolkit's Label
class is not selectable in Unity 2021. In 2022, it is selectable, but selection
cannot go between multiple elements, making the selection of all text in a markdown document impossible.
Markdown Support relies on two excellent existing code libraries, and one font.
The font is Roboto, and is licensed under Apache 2.0.
There are two code libraries...
- Markdig is under BSD-2 license, https://github.com/xoofx/markdig/blob/master/license.txt.
- Highlight is under MIT license, https://github.com/thomasjo/highlight/blob/master/LICENSE.md . I made a private fork of the project and built my own
.dll
.
Without these excellent libraries, Markdown Support would not be possible in Unity.