-
Notifications
You must be signed in to change notification settings - Fork 3
Data Driven UI
(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.
{
"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
}
}
}
}
}
}
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
}
]
}
] |
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 | 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 |
Position | Location |
top-left |
|
top-center |
|
top-right |
|
center-left |
|
center |
|
center-right |
|
bottom-left |
|
bottom-center |
|
bottom-right |
|
self | Original Location |
Attribute | Values |
axis |
|
alignment |
|
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 |
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",
}
]
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
}