Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

osu! markup language #30

Closed
smoogipoo opened this issue Sep 5, 2016 · 20 comments
Closed

osu! markup language #30

smoogipoo opened this issue Sep 5, 2016 · 20 comments
Labels

Comments

@smoogipoo
Copy link
Contributor

smoogipoo commented Sep 5, 2016

Initial draft spec of OML. It's changed a little bit since the last iteration in the transitional repository so you'd best go over this again and point out any inconsistencies/points of clarification or just general comments. Topic for the next pair programming session.


OML - osu! markup language

What is OML

OML is a markup language very similar to QML, oriented towards designing layouts for games that are built upon the osu!framework. The language is YAML-Validated and generates appropriate C# code upon compilation.

Basic syntax

The following is an object that represents a 100x100 red rectangle:

Rectangle:
  Width: 100
  Height: 100
  Colour: Color.Red

Width, Height and Colour are general properties of the Rectangle. These are applied when the object is constructed.

Special Properties

Objects have special properties:
Extends
States
Transitions
Events
Properties
Children

The Extends Property

Objects extend Drawable by default and inherit all of Drawable's properties. It is possible to change this behaviour where this is not intended by specifying the Extends property of the object:

Rectangle:
  Extends: Box  # Rectangle will now inherit all of Box's properties

The States Property

A state defines how an object looks at a point in time by modifying the value of the general properties of the object. This special property may be used to define named states which may be referred to by multiple places in further code.
The property named "Default" is optional but will be applied when the object is first constructed.

Rectangle:
  States:
    - Name: Hovered
      Colour: Color.Red
    - Name: Default       # Optional state to be applied when the object is first constructed
      Colour: Color.Blue

The Transitions Property

A transition defines how an object should transition towards a state. This special property may be used to define named transitions which may be referred to by multiple places in further code. These can be used to transition to either named or anonymous states.

Rectangle:
  States:
    - Name: Hovered
      Colour: Color.Red
  Transitions:
    - Name: HoveredTransition
      State: Hovered           # Named state
      Duration: 500
    - Name: ScaleTransition
      State:                   # Anonymous state
        Scale: 1.5

The Events Property

An event defines the transitions that should be applied to an object when the event is called. Events are exposed in the generated C# code. These can be used to apply either named or anonymous transitions.

Rectangle:
  States:
    - Name: Hovered
      Colour: Color.Red
  Transitions:
    - Name: HoveredTransition
      State: Hovered
      Duration: 500
    - Name: ScaleTransition
      State:
        Scale: 1.5
  Events:
    - Name: OnHover
      Transitions:
        - HoveredTransition        # Named transition
        - ScaleTransition          # Named transition
    - Name: MyTransition
      Transitions:
        -                          # Anonymous transition
          State:
            Alpha: 0.5
          Duration: 100
          Easing: EasingTypes.Out
        -                          # Anonymous transition
          State:
            Rotation: 90
          Duration: 100

Todo: Reserved events

The Properties Property

An object may define properties as private members for it to use.

Rectangle:
  Properties:
    - Name: FocusColour
      Type: Color
      Value: Color.Blue    # Default value is optional
    - Name: NormalColour
      Type: Color
      Value: Color.White
  States:
    - Name: Focused
      Colour: FocusColour
    - Name: Default
      Colour: NormalColour

The Children Property

An object may be composited by several children that are directly affected by the containing object. In this way it is possible to transition an object and all of its children together by applying the transition to the containing object.
Children may be either named - allowing them to be referenced as private members from generated code, or anonymous.

Rectangle:
  Width: 100
  Height: 100

  Children:
    - Type: Box                         # Specifies the type of the child
      SizeMode: InheritMode.XY          # Setting the child's property
      Colour: Color.Gray
      Depth: -1
    - Type: FlowContainer
      Direction: FlowDirection.Vertical
      Children:                          # Can define subchildren for children
        - Type: SpriteText
          Text: Hello World!
        - Type: SpriteText
          Name: SubText                  # This child can be referenced to by `SubText` in code
@ddevault
Copy link
Contributor

ddevault commented Sep 5, 2016

generates appropriate C# code upon compilation.

How about parsing it at runtime instead of complicating the build process with additional tools? We can use Reflection.Emit when we compile YAML so that the runtime cost is low.

@peppy
Copy link
Sponsor Member

peppy commented Sep 5, 2016

it will have runtime parsing support, but we need c# code so we can intellisense and use stuff it creates, i believe.

@ddevault
Copy link
Contributor

ddevault commented Sep 5, 2016

I see. Sounds good.

@wobbol
Copy link

wobbol commented Sep 7, 2016

Is it a correct assumption that Type may hold the name of any class that is drawable?

Can a top level node be cut and pasted into a Children list?

@smoogipoo
Copy link
Contributor Author

  1. My idea for Type was to act as inheritance, i.e. Type: Container translates to class MyLayout : Container, or Type: FlowContainer translates to class MyLayout : FlowContainer.

  2. While the top level node is a class construction, the plan is for children being object instantiations and not subclass constructions as it would get messy on all fronts real quick. Imo these should be really simple data structures, so you could define something like...

Button:
  Type: Box
  Width: 100
  Height: 100

  Properties:
    - Name: HoverColour
      Type: Color

  States:
    - Name: Hovered
      Colour: HoverColour
    - Name: Default
      Colour: Color.White

  Events:
    - Name: OnHover
      Transition:
        State: Hovered
    - Name: OnHoverLost
      Transition:
        State: Default

ButtonSystem:
  Children:
    - Type: FlowContainer
      Children:
      - Type: Button
        HoverColour: Color.Red
      - Type: Button
        HoverColour: Color.Green
      - Type: Button
        HoverColour: Color.Blue

In one file. Can you give a use case for needing to copy-paste top-level nodes?

@itsMapleLeaf
Copy link

If it acts as inheritance, maybe renaming it to Extends would be more appropriate?

@smoogipoo
Copy link
Contributor Author

Funny that, I had extends in my original proposal. I totally agree.

@wobbol
Copy link

wobbol commented Sep 7, 2016

Can you give a use case for needing to copy-paste top-level nodes?

I was thinking about ease of definition for children. Sounds like you got it handled. 👍

@MuresanSergiu
Copy link

MuresanSergiu commented Sep 12, 2016

Are states defined by the objects themselves or just the behaviour of each state for that object? Is there a set of predefined states and we are just defining how they should behave in this state? If that's the case are user defined states allowed?

Are transitions defining behaviour by default?

- Name: HoveredTransition
  State: Hovered           # Named state
  Duration: 500
- Name: ScaleTransition
  State:                   # Anonymous state
    Scale: 1.5

This is what I understand: HoveredTransition is basically saying when this is applied transition between the original state to the hovered state in 500 time units. But then ScaleTransition simply says that when you apply this scale the object by this amount, directly changing the state with no additional information provided. (Equivalent to something like: object.setState(scaleState);).

What I am asking is: Is there some sort of predefined behaviour when a transition doesn't provide any information about how it should transition? If there isn't any (which I assume it is the case) then you can implicitly define empty transitions for states. Example:

Object:
  States: 
    - Name: Scale
      Scale: 1.5

This will implicitly have a "ScaleTransition" that would be the equivalent of writing:

Transitions:
  - Name: ScaleTransition
    State: Scale

@WorldSEnder
Copy link

WorldSEnder commented Sep 17, 2016

There should be a way to define the transition type, i.e. CubicOut, Exponential, etc.. Possibly defaulting to some value if not set

@smoogipoo
Copy link
Contributor Author

Easing: EasingTypes.InOut or whatever we have in osu! currently.

@ddevault
Copy link
Contributor

It seems that this isn't going to be necessary.

@peppy peppy reopened this Jan 24, 2019
@peppy
Copy link
Sponsor Member

peppy commented Jan 24, 2019

Reopened at request of @phosphene47. We still need this for (at very least) skinning – cases where we want to accept runtime-parsed drawables and a secure way.

@jorolf
Copy link
Contributor

jorolf commented Jan 25, 2019

We could also use JSON, right? I think JSON.net would allow us to parse this data at runtime and directly populate an existing container (+ we already have the parser included in the nuget packages).

@luaneko
Copy link
Contributor

luaneko commented Jan 26, 2019

yaml is a superset of json so we automatically get json support from yaml parsers (https://stackoverflow.com/questions/1726802/what-is-the-difference-between-yaml-and-json-when-to-prefer-one-over-the-other). no need for json.net

we can't simply directly populate existing Containers. what if a custom Drawable derives from an abstract class? we can't simply instantiate the base class to populate the properties, we have to emit the class using reflection at runtime. this is also necessary to implement Transitions.

the only drawback to emitting at runtime is cross-platform incompatibility (specifically iOS).

@peppy
Copy link
Sponsor Member

peppy commented Jan 26, 2019

It's definitely a consideration we need to keep in mind, since skins still need to work on iOS.

@holly-hacker
Copy link
Contributor

I may look into doing this, got some questions. Some of these are implementation details so I'm not sure if I'm supposed to chose these myself or not.

  1. Are values of properties just the code representation of them? The examples show Color.Red in Transitions, does that mean defining a Vector2 would be done as Vector2.One and new Vector2(0.5f, 1f)? If this is the case, it would be hard to provide runtime support, since we'd have to interpret the C# code somehow.

  2. From what I can tell, the goal is to both generate C# code (for coding against them) and to dynamically make them at runtime (skinning, I assume).
    My idea was to generate a skeleton class (containing properties, events) which at runtime gets populated through an object holding all the information from the yaml file. Would this be a good way, or was another method planned?

  3. Are properties always private?

  4. A Property has a Type field, are these pre-defined (in some Dictionary<string, Type>) or resolved at runtime? osu-framework does not use a Color class but ColourInfo, this had me confused.

@peppy
Copy link
Sponsor Member

peppy commented May 2, 2019

Since this was written, the scope has definitely changed. The primary use case is not for skins and such, where we want to allow users the ability to customise without the ability to break. They would only be able to touch properties exposed by a specific interface or attribute marking (and we would need to define these interfaces for clases which wish to expose them to the user).

  1. We don't plan on providing direct code interpretation (dangerous). It should be a custom representation (check how other yaml usages do it in other projects).
  2. That sounds like a good start, definitely. The case of generating code is likely a secondary use case we can support at a later point in time. The main goal is to allow skins access to all public properties exposed to them by the game (and the flexibility of moving children around, removing them, etc.)
  3. Not too sure what you mean here.
  4. Again, I think it would be good to focus on population rather than code generation, which may make this point unnecessary to answer.

@miterosan
Copy link
Contributor

miterosan commented Jun 9, 2019

I personally would prefer a markup language that allows following yaml code:

# Skin usecase: Backbutton
Target: Backbutton

Box:
  Width: 200
  Height: 50
  Colour: Red
  Alpha: 0.7
  Origin: LeftBottom
  Anchor: LeftBottom
  OnHover:
    - Scale: 1
      To: 1.5
      Duration: 300ms
      Easing: InQuint
    - FadeTo: 1
      Duration: 300ms
      Easing: OutQuint
  OnClick:
    - FadeTo: 0
      Duration: 100ms
  Children:
    - SpriteText:
      RelativeSizeAxes: Both
      Width: 0.6
      Height: 0.6
      Anchor: Centre
      Origin: Centre
      FontColour: White
      Text: "Go Back!"

---

# Skin Usecase: Healthbar
# Predefined variable:
#   $healthValue
Target: Healthbar

Box:
  RelativeSizeAxes: Width
  Width: 0.3
  Height: 100
  Anchor: TopLeft
  Colour: Black
  Alpha: 0.9
  OnUpdate:
    - FadeColourTo: Red
      Condition: $healthValue < 0.3
      Trigger: Condition
    - FadeColourTo: White
      Condition: $healthValue >= 0.3
      Trigger: Condition
  Children:
    - Box:
      RelativeSizeAxes: Both
      Width: 0.9 * $healthValue
      Height: 0.3
      Origin: Center
      Anchor: Center
      Colour: White

Each yaml document targets an ingame element.
The elements may have variables that are inlined using $variableName.
Everything can have children.
And I would expect that transformations can have conditions.

The Trigger can have following values:

  • Condition
    • The transformation only happenes when the condition's status changed.
  • Once
  • Every n seconds

@smoogipoo
Copy link
Contributor Author

Gonna close this issue to start afresh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests