Platformer Example
A proof of concept of a platformer game in Vega, made by @xyzrr to promote the Weights & Biases machine learning visualization IDE. Use WASD to move and jump, and press shift in the air to dash. Uses assets from the video game Celeste.
Vega JSON Specification <>
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 400,
"description": "A simple platformer. WASD to move, shift to dash.",
"background": "rgb(8, 3, 15)",
"data": [
{
"name": "wandb",
"url": "data/platformer-terrain.json",
"transform": [
{
"type": "formula",
"expr": "datum.x + '-' + datum.y",
"as": "key",
"initonly": true
}
]
}
],
"scales": [
{
"name": "pixels",
"type": "linear",
"domain": [0, 128],
"range": {
"signal": "[0, height]"
}
}
],
"signals": [
{
"name": "test",
"update": "[0, containerSize()[1], height]"
},
{
"name": "frame",
"init": "0",
"on": [
{
"events": {
"type": "timer",
"throttle": 17
},
"update": "frame + 1"
}
]
},
{
"name": "lastFrameOnGround",
"value": -99,
"update": "playerOnGround ? frame : lastFrameOnGround"
},
{
"name": "lastFrameDashing",
"value": -99,
"update": "dashing ? frame : lastFrameDashing"
},
{
"name": "playerCanJump",
"update": "frame - lastFrameOnGround < 5 && yVelocity >= 0"
},
{
"name": "playerCanDash",
"value": true,
"update": "dashing ? false : playerOnGround ? true : playerCanDash"
},
{
"name": "collidingBottom",
"update": "indata('wandb', 'key', rx + '-' + (ry+2)) || indata('wandb', 'key', (rx+1) + '-' + (ry+2)) || indata('wandb', 'key', (rx+2) + '-' + (ry+2)) || indata('wandb', 'key', (rx+3) + '-' + (ry+2)) || indata('wandb', 'key', rx + '-' + (ry+3)) || indata('wandb', 'key', (rx+1) + '-' + (ry+3)) || indata('wandb', 'key', (rx+2) + '-' + (ry+3)) || indata('wandb', 'key', (rx+3) + '-' + (ry+3))"
},
{
"name": "playerOnGround",
"update": "collidingBottom || indata('wandb', 'key', rx + '-' + (ry+4)) || indata('wandb', 'key', (rx+1) + '-' + (ry+4)) || indata('wandb', 'key', (rx+2) + '-' + (ry+4)) || indata('wandb', 'key', (rx+3) + '-' + (ry+4))"
},
{
"name": "collidingLeft",
"update": "indata('wandb', 'key', rx + '-' + (ry+0)) || indata('wandb', 'key', rx + '-' + (ry+1)) || indata('wandb', 'key', rx + '-' + (ry+2))"
},
{
"name": "collidingRight",
"update": "indata('wandb', 'key', (rx+3) + '-' + (ry+0)) || indata('wandb', 'key', (rx+3) + '-' + (ry+1)) || indata('wandb', 'key', (rx+3) + '-' + (ry+2))"
},
{
"name": "wallOnLeft",
"update": "collidingLeft || indata('wandb', 'key', ((rx-1) + '-' + (ry+0))) || indata('wandb', 'key', ((rx-1) + '-' + (ry+1))) || indata('wandb', 'key', ((rx-1) + '-' + (ry+2)))"
},
{
"name": "wallOnRight",
"update": "collidingRight || indata('wandb', 'key', ((rx+4) + '-' + (ry+0))) || indata('wandb', 'key', ((rx+4) + '-' + (ry+1))) || indata('wandb', 'key', ((rx+4) + '-' + (ry+2)))"
},
{
"name": "xVelocity",
"update": "max(min(keyRight ? dashing ? 2 : max(1, xVelocity-.04) : keyLeft ? (dashing ? -2 : min(-1, xVelocity + .04)) : 0, wallOnRight ? 0 : 999), wallOnLeft ? 0 : -999)"
},
{
"name": "yVelocity",
"value": 0,
"on": [
{
"events": {
"type": "timer",
"throttle": 17
},
"update": "dashing ? (keyJump ? -1.5 : -.6) :jumping ? -2 : playerOnGround ? min(0, yVelocity) : min(2.5, yVelocity + .15)"
}
]
},
{
"name": "playerX",
"value": 5,
"on": [
{
"events": {
"type": "timer",
"throttle": 17
},
"update": "(collidingRight && !collidingLeft) ? round(playerX - 1) : (collidingLeft && !collidingRight) ? round(playerX + 1) : playerX + xVelocity"
}
]
},
{
"name": "playerY",
"init": 15,
"on": [
{
"events": {
"type": "timer",
"throttle": 17
},
"update": "(collidingBottom && !(collidingRight ^ collidingLeft)) ? round(playerY + yVelocity - 1) : playerY + yVelocity"
}
]
},
{
"name": "jumping",
"on": [
{
"events": { "type": "timer", "throttle": 17 },
"update": "playerCanJump && keyJump"
}
]
},
{
"name": "dashing",
"on": [
{
"events": { "type": "timer", "throttle": 17 },
"update": "playerCanDash && keyDash"
}
]
},
{
"name": "rx",
"update": "round(playerX)"
},
{
"name": "ry",
"update": "round(playerY)"
},
{
"name": "scrollX",
"update": "max(playerX - 32, 0)"
},
{
"name": "keys",
"value": "",
"on": [
{
"events": "window:keydown",
"update": "indexof(keys, event.code) > -1 ? keys : keys + event.code + ','"
},
{
"events": "window:keyup",
"update": "replace(keys, event.code + ',', '')"
}
]
},
{
"name": "keyLeft",
"value": false,
"update": "indexof(keys, 'KeyA') > -1 || indexof(keys, 'ArrowLeft') > -1"
},
{
"name": "keyRight",
"value": false,
"update": "indexof(keys, 'KeyD') > -1 || indexof(keys, 'ArrowRight') > -1"
},
{
"name": "keyJump",
"value": false,
"update": "indexof(keys, 'KeyW') > -1 || indexof(keys, 'ArrowUp') > -1"
},
{
"name": "keyDash",
"value": false,
"update": "indexof(keys, 'KeyZ') > -1 || indexof(keys, 'ShiftLeft') > -1 || indexof(keys, 'ShiftRight') > -1"
}
],
"marks": [
{
"type": "group",
"name": "everything",
"clip": true,
"encode": {
"update": {
"x": {
"scale": "pixels",
"signal": "-scrollX"
}
}
},
"marks": [
{
"type": "image",
"encode": {
"enter": {
"url": {
"value": "https://i.ibb.co/ZBc5RZK/Screen-Shot-2020-12-13-at-3-36-09-PM.png"
},
"smooth": {
"value": false
}
},
"update": {
"opacity": {
"value": 0.25
},
"width": {
"scale": "pixels",
"signal": "350"
},
"y": {
"scale": "pixels",
"signal": "-40"
},
"x": {
"scale": "pixels",
"signal": "scrollX/2"
},
"height": {
"scale": "pixels",
"signal": "200"
}
}
}
},
{
"type": "group",
"encode": {
"update": {
"x": {
"scale": "pixels",
"signal": "playerX"
},
"y": {
"scale": "pixels",
"signal": "playerY"
}
}
},
"marks": [
{
"type": "rect",
"name": "trailingGhost",
"encode": {
"update": {
"x": {
"scale": "pixels",
"signal": "-xVelocity*2"
},
"y": {
"scale": "pixels",
"signal": "-yVelocity*2"
},
"fill": {
"value": "cyan"
},
"fillOpacity": {
"signal": "max(0, (lastFrameDashing + 20 - frame) / 40)"
},
"width": {
"scale": "pixels",
"signal": "4"
},
"height": {
"scale": "pixels",
"signal": "4"
}
}
}
},
{
"type": "rect",
"name": "player",
"encode": {
"update": {
"fill": {
"signal": "playerCanDash ? 'rgb(167, 66, 60)' : 'rgb(110, 184, 248)'"
},
"width": {
"scale": "pixels",
"signal": "4"
},
"height": {
"scale": "pixels",
"signal": "4"
}
}
}
}
]
},
{
"type": "rect",
"name": "terrainBlocks",
"from": {
"data": "wandb"
},
"encode": {
"update": {
"fill": {
"signal": "hsl(hsl(datum.color).h, hsl(datum.color).s + datum.saturation - .5, hsl(datum.color).l + datum.lumosity - .5)"
},
"x": {
"scale": "pixels",
"field": "x"
},
"y": {
"scale": "pixels",
"field": "y"
},
"width": {
"scale": "pixels",
"signal": "1"
},
"height": {
"scale": "pixels",
"signal": "1"
}
}
}
}
]
}
]
}