Skip to content

Data Driven UI

Ashton edited this page Nov 2, 2024 · 4 revisions

(For extra help, download the example pack)

Geometry Dash UIs are unable to be modified without separate mods by default. This mod aims to resolve that issue, such that any texture pack can change UI elements easily.

Within a texture pack, add a ui folder, this is where any UI modifications will go.

To help with the UI process, it is recommended to install the DevTools mod on Geode.

(Almost) every page on GD has an ID, you will want to create a JSON file with the format ID.json inside the ui folder to begin (if an ID contains a forward slash (/), replace it with a dollar sign ($) in the filename). Within DevTools, you can find this ID by going to the Tree tab, clicking on the CCScene dropdown to view the rest of the elements, and looking at IDs of the elements, in quotes, on that scene. For example, MenuLayer.json will be found on the main menu, which will be used as an example throughout the rest of this.

You can expand these elements in DevTools to find what you may want to modify. Example below

▼ [0] cocos2d::CCScene <1>
   ▼ [0] MenuLayer "MenuLayer" <13>
      🞂 [0] MenuGameLayer "main-menu-bg" <17>
      ▼ [1] cocos2d::CCMenu "main-menu" <3>
         🞂 [0] CCMenuItemSpriteExtra "icon-kit-button" <1>
         🞂 [1] CCMenuItemSpriteExtra "play-button" <1>
         🞂 [2] CCMenuItemSpriteExtra "editor-button" <1>
      🞂 [2] cocos2d::CCMenu "bottom-menu" <8>
      ...

Every node can have "children", there are three ways of accessing them, either with "node" to get them by name, "index" an array of objects to get them by their index in the node tree and optionally by type, and "all" which selects all the children to modify. For example:

{
    "children": {
        "node": {
            "example-node":{

            }
        },
        "index": [
            {
                "type": "CCSprite",
                "index": 0,
                "attributes": {
                    "..."
                }
            }
        ],
        "all": {

        }
    }
}

You can also get the parent from a child:

{
    "children": {
        "node": {
            "example-node":{
                "parent": {
                    "attributes":{
                        "..."
                    }
                }
            }
        }
    }
}

If you wish to add new nodes, you can add "new" to the children object, this contains an array of new nodes you want to add. You must specify a "type" and "id" value to each object. The type is the type of element you want to add, and the ID is the internal name you want to give the node (in case any other packs need to grab your node or you need to reference it in the future).

{
    "children": {
        "new": [
            {
                "type": "CCSprite",
                "id": "my-new-sprite",
                "attributes": {
                    "sprite": "happy-sprite.png",
                    "scale": 2
                }
            }
        ]
    }
}

The available types you can add are "CCSprite", "CCLabelBMFont", "CCMenu", "CCLayerColor", "CCMenuItemSpriteExtra", and "CCScale9Sprite".

Note that "CCMenuItemSpriteExtra" must be a child of a "CCMenu" to be clickable.

You can also create alerts and popups using this. Alerts require a "title" and "description" attribute. Popups are a blank slate and open like an alert, and you are required to add elements to it yourself. You can set a popup sprite, size, and title optionally. The sprite should be a scale 9 sprite such as "GJ_square01.png". When an alert or popup is created, they will only show on screen if you set "show" to true. It is best to throw these under a button's event to prevent annoying popups from opening whenever you switch scenes or when another popup appears.

Alerts optionally have the ability for you to change the "button-text" attribute, which changes it from the word "Okay" to whatever you specify.

{
    "children": {
        "new": [
            {
                "type": "Alert",
                "id": "my-new-alert",
                "attributes": {
                    "title": "Very Cool Alert",
                    "description": "This is a very cool alert!",
                    "button-text": "YAY!",
                    "show": true
                }
            }
        ]
    }
}
{
    "children": {
        "new": [
            {
                "type": "Popup",
                "id": "my-new-popup",
                "attributes": {
                    "sprite": "GJ_square02.png",
                    "popup-size": {
                        "width": 200,
                        "height": 200
                    },
                    "title": "Very Cool Popup"
                }
            }
        ]
    }
}

Within MenuLayer, we can see there is a menu, the main menu, which has three buttons. Any or all of these buttons can be modified within that MenuLayer.json file. We would first want to get the main menu by grabbing the children of the MenuLayer, and finding the "main-menu" ID:

{
    "children": {
        "node": {
            "main-menu":{
                "..."
            }
        }
    }
}

Let's say we want to edit the play button, and scale it to twice it's original size, we would get the "play-button" child of the main-menu and then edit it's "attributes":

{
    "children": {
        "node": {
            "main-menu": {
                "children": {
                    "node":{
                        "play-button": {
                            "attributes": {
                                "scale": 2
                            }
                        }
                    }
                }
            }
        }
    }
}

There are a lot of attributes you can use, and they will be listed at the end of this page.

Some attributes only work if you are selecting a CCSprite or a CCMenuItemSpriteExtra. These attributes are "sprite" and "sprite-frame" which take in any image or a frame of an existing spritesheet.

When it comes to using the position attribute, you can change the x and y coordinates, which will place them at those exact coordinates on the screen no matter what, or you can choose a "relative" position, which makes x and y modifiy that. A relative position can be something such as "center" to place something directly in the center of the screen, or "top-right" to place something at the top right of the screen.

Full example of a MenuLayer UI modification

{
    "children": {
        "node": {
            "social-media-menu": {
                "attributes": {
                    "visible": false
                }
            },
            "profile-menu": {
                "attributes": {
                    "position": {
                        "anchor": "bottom-left",
                        "x": 140,
                        "y": 50
                    }
                }
            },
            "player-username": {
                "attributes": {
                    "position": {
                        "anchor": "bottom-left",
                        "x": 94,
                        "y": 84
                    }
                }
            },
            "main-menu": {
                "children": {
                    "all":{
                        "attributes": {
                            "actions": [
                                {
                                    "type": "RotateBy",
                                    "repeat": true,
                                    "value": 180,
                                    "duration": 1
                                }
                            ]
                        }
                    }
                }
            },
            "bottom-menu": {
                "attributes": {
                    "layout": {
                        "flip-axis": false,
                        "gap": 0,
                        "axis": "column"
                    },
                    "content-size": {
                        "width": 30,
                        "height": 250
                    },
                    "position": {
                        "anchor": "center-left",
                        "x": 25,
                        "y": -15
                    }
                }
            }
        }
    }
}

You can also reference the parent of a node. In the following example, the child accesses the parent node and sets it's scale. It is useful for when you need to modify a parent on a button event.

{
    "children": {
        "node": {
            "main-menu": {
                "parent": {
                    "attributes": {
                        "scale": 1.2
                    }
                }
            }
        }
    }
}

Available Attributes

Attribute Description Example 1 Example 2
scale Set the scale of a node, can either be a number or an object containing x and y scale separately.
"scale": 2
"scale": {
    "x": 2,
    "y": 1.5
}
rotation Set the rotation of a node, can either be a number or an object containing x and y rotation separately.
"rotation": 90
"rotation": {
    "x": 90,
    "y": 45
}
skew Set the skew of a node, an object containing x and y skew separately.
"skew": {
    "x": 20,
    "y": 10
}
anchor-point Set the anchor of a node, an object containing x and y, values must be between 0 and 1.
"anchor-point": {
    "x": 0.5,
    "y": 0.5
}
content-size Set the content size of a node, an object containing width and height.
"content-size": {
    "width": 50,
    "height": 60
}
visible Set if a node should be visible.
"visible": false
ignore-anchor-pos Set if a node should ignore the anchor point for it's position.
"ignore-anchor-pos": false
color Set the color of a node (Only works for some nodes), uses r, g, b values between 0 and 255. Use the text "reset" to reset it back to its initial color (CCMenuItemSpriteExtra).
"color": {
    "r": 128,
    "g": 59,
    "b": 199
}
"color": "reset"
opacity Set the opacity of a node (Only works for some nodes), uses values between 0 and 255.
"opacity": 128
sprite Set the sprite of a node (Only works for CCSprite and CCMenuItemSpriteExtra).
"sprite": "path/to/my/sprite.png"
sprite-frame Set the sprite frame of a node, has to be in an existing spritesheet (Only works for CCSprite and CCMenuItemSpriteExtra).
"sprite-frame": "frameName.png"
text Set the text of a label or button.
"text": "example"
scale-multiplier Set the scale multiplier for when you click on a button.
"scale-multiplier": 1.4
base-scale Set the base scale of a button (scaling a button returns to this rather than 1 when clicked).
"base-scale": 2
z-order Set the z order of a node.
"z-order": 2
font Set the bitmap font of a label or button.
"font": "chatFont.fnt"
blending Set the blending of a node, see OpenGL documentation for how to use blending modes.
"blending": {
    "source": "GL_ONE_MINUS_SRC_COLOR",
    "destination": "GL_ONE_MINUS_SRC_ALPHA"
}
sound Set a sound to play on node attribute change. Useful for on-hover and on-click events.
"sound": "effect.ogg"
link Set a link to open on node attribute change. Useful for on-click events. Can be a URL or link to a "profile" or "level" with the type attribute.
"link": "https://google.com"
"link": {
    "type": "profile",
    "id": 123456,
}
position Set the position of a node, uses x and y coordinates from the bottom left by default, set the anchor position with "anchor". You can choose the relative node using `"relative"` in the anchor object and then using `"to"` to chose the position, the relative nodes are `"parent"` and `"screen"`. Available anchor positions are mentioned later.
"position": {
    "x": 100,
    "y": 150
}
"position": {
    "x": 30,
    "y": -30,
     "anchor": {
        "to": "top-right",
        "relative": "screen"
    }
}
layout Set layout attributes to a node (creates a new layout if the node does not have one already) Available layout attributes are mentioned later.
"layout": {
    "axis": "column",
    "flip-axis": false,
    "flip-cross-axis": false,
    "auto-scale": true,
    "grow-cross-axis": true,
    "allow-cross-axis-overflow": false,
    "gap": 10,
    "axis-alignment": "center",
    "cross=axis-alignment": "start",
    "cross=axis-line-alignment": "end",
    "ignore-invisible": true
}
update-layout Set if a node should update it's parent's layout after being modified.
"update-layout": true
disable-pages Disable pages on menus if the Pages API mod is enabled.
"disable-pages": true
remove Marks the element for removal. (Try not to use on vanilla nodes, only custom ones you have made)
"remove": true
actions Set an array of animation actions that a node will follow when modified. Available actions and action attributes are mentioned later.
"actions": [
    {
        "type": "RotateBy",
        "repeat": true,
        "value": 180,
        "duration": 1
    }
]
"actions": [
    {
        "type": "Sequence",
        "actions": [
            {
                "type": "RotateBy",
                "value": 360,
                "duration": 1
            },
            {
                "type": "FadeOut",
                "duration": 1
            }
        ]
    }
]

Button Events

Buttons (CCMenuItemSpriteExtra) can have events that occur when they are clicked, released, activated, hovered, and exited.

These events work just as everything else, within them, you can grab children, add children, and edit attributes.

{
    "children": {
        "new": [
            {
                "type": "CCMenu",
                "id": "my-very-cool-menu",
                "attributes": {
                    "ignore-anchor-pos": false
                },
                "children": {
                    "new": [
                        {
                            "type": "CCMenuItemSpriteExtra",
                            "id": "my-very-cool-button",
                            "attributes": {
                                "sprite": "happy_textures.png",
                                "position": {
                                    "anchor": "center",
                                    "x": 45,
                                    "y": 0
                                }
                            },
                            "event": {
                                "on-activate": {
                                    "children": {
                                        "new": [
                                            {
                                                "type": "Alert",
                                                "id": "alert-on-activate",
                                                "attributes": {
                                                    "title": "Example Alert",
                                                    "description": "This happened when I activated a button!",
                                                    "button-text": "Yay!",
                                                    "show": true
                                                }
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        ]
    }
}
{
    "children": {
        "new": [
            {
                "type": "CCMenu",
                "id": "my-very-cool-menu",
                "attributes": {
                    "ignore-anchor-pos": false
                },
                "children": {
                    "new": [
                        {
                            "type": "CCMenuItemSpriteExtra",
                            "id": "my-very-cool-button",
                            "attributes": {
                                "sprite": "happy_textures.png",
                                "position": {
                                    "anchor": "center",
                                    "x": 45,
                                    "y": 0
                                }
                            },
                            "event": {
                                "on-hover": {
                                    "attributes" : {
                                        "actions": [
                                            {
                                                "type": "RotateBy",
                                                "easing": "ElasicIn",
                                                "value": 180,
                                                "duration": 1
                                            }
                                        ],
                                    }
                                },
                                "on-exit": {
                                    "attributes" : {
                                        "actions": [
                                            {
                                                "type": "RotateBy",
                                                "easing": "ElasicIn",
                                                "value": -180,
                                                "duration": 1
                                            }
                                        ],
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        ]
    }
}

Event Types

Event What affects it
on-click Clicking on the button
on-release Releasing the button
on-activate Clicking then releasing the button (how buttons in GD work)
on-hover Hovering over a button
on-exit No longer hovering over a button

Anchor Positions

Position Location
top-left
⬛⬜⬜
⬜⬜⬜
⬜⬜⬜
top-center
⬜⬛⬜
⬜⬜⬜
⬜⬜⬜
top-right
⬜⬜⬛
⬜⬜⬜
⬜⬜⬜
center-left
⬜⬜⬜
⬛⬜⬜
⬜⬜⬜
center
⬜⬜⬜
⬜⬛⬜
⬜⬜⬜
center-right
⬜⬜⬜
⬜⬜⬛
⬜⬜⬜
bottom-left
⬜⬜⬜
⬜⬜⬜
⬛⬜⬜
bottom-center
⬜⬜⬜
⬜⬜⬜
⬜⬛⬜
bottom-right
⬜⬜⬜
⬜⬜⬜
⬜⬜⬛
self Original Location

Layout Attributes

Attribute Values
axis
  • column
  • row
alignment
  • start
  • center
  • end
  • even
  • between

Action Attributes

All Actions have attributes that go along with them, some more than others. The available attributes are easing, easing-rate, repeat, duration, value.

Attribute Type Example
easing string (See "Action Easing" for types)
"easing": "EaseInOut"
easing-rate float
"easing-rate": 2
repeat bool or int (true for forever, int for amount of repeats)
"repeat": true

or

"repeat": 5
duration float
"duration": 5.2
value float, or object of "x" and "y", see types below for which is used
"value": {
    "x": 10,
    "y": 20
}

or

"value": 5.5

Action Types

Attribute Description Has Position Object Has Float Value
MoveBy Move the node by a set amount of units ⬜️
MoveTo Move the node to a specific position ⬜️
SkewBy Skew the node by a set amount of units ⬜️
SkewTo Skew the node to a specific point ⬜️
FadeIn Fade the node to full opacity ⬜️ ⬜️
FadeOut Fade the node to zero opacity ⬜️ ⬜️
FadeTo Fade the node to a specific opacity ⬜️ ⬜️
ScaleBy Scale the node by a specific scale ⬜️ ⬜️
ScaleTo Scale the node to a specific scale
RotateBy Rotate the node by a specific rotation
RotateTo Rotate the node to a specific rotation

Another type of action can contain actions in itself, which are activated in order, this is an example of a sequence action that rotates a node by 180, then back on repeat:

"actions": [
    {
        "type": "Sequence",
        "actions": [
            {
                "type": "RotateBy",
                "easing": "ElasticIn",
                "value": 180,
                "duration": 1
            },
            {
                "type": "RotateBy",
                "easing": "ElasticIn",
                "value": -180,
                "duration": 1
            }
        ],
        "repeat": true
    }
]

If you ever want to stop an exisitng action, just create a "Stop" action:

"actions": [
    {
        "type": "Stop",
    }
]

Action Easing

A list of easings provided are as follows:

  • EaseInOut
  • EaseIn
  • EaseOut
  • ElasticInOut
  • ElasticIn
  • ElasticOut
  • BounceInOut
  • BounceIn
  • BounceOut
  • ExponentialInOut
  • ExponentialIn
  • ExponentialOut
  • SineInOut
  • SineIn
  • SineOut
  • BackInOut
  • BackIn
  • BackOut

EaseInOut, EaseIn, and EaseOut require you to set the "easing-rate" parameter.

For more info on easings, check https://easings.net/

An example of this is:

{
    "type": "MoveBy",
    "value": {
        "x": 20,
        "y": 30
    },
    "easing": "EaseInOut",
    "easing-rate": 2
}