The Happening type lets you create a variety of visuals and more using JavaScript.
There's different types of shapes like circles, rectangles, particles and more which you can freely setup and change
over time. You can also draw all the cells of the item itself in various ways, and multiple times in one go.
Happenings can be placed in the world and are then started when walking over, or triggered as personal boosts (not visible by others).
What Happening code looks like
To program Happenings, you need to know some JavaScript (there's many great resources available). Here's a sample code structure:
// An example script var particles = [ { x: 0, y: -30, thickness: 5 } ]; function update(my) { particles[0].x += my.tick * 10; return {particles: particles}; }
First near the top, we're setting a persistent particles array. Now every Happening script has a main function called update, which is called multiple times a second. In it, you can now change the particles if you want. Movement and more should be multiplied by my.tick, which ensures constant speeds even at different frames per seconds. Finally, you'll return the particles and they will be drawn. Happenings run for up to 2 hours.
To try this yourself, you can login, hit the Create button, select Happening, then draw something and save. You can now place the block in a created area where you're listed as editor, and walk over it. Because you've kept the code field empty during saving, an editor box will now appear (you can also tick the attribute 'Offers Editing').
Tip: If you're looking for a good font, try our Doid.ttf, which is a spin on Droid especially suited for programming. After saving locally and installing as font on your machine, you can adjust your browser settings to use this as default monospace font.
Shapes data you can return
You can prepare the following shapes data to return:
- particles[] // an array of up to 100 particles
- x // the relative center x position from -1000 to 1000
- y // the relative center y position from -1000 to 1000
- thickness // optionally, the thickness from 1 to 10
- rgba[] // optionally, red-Green-Blue-Alpha, e.g. [0,255,0,1] for green
- mode // optionally, a compositing mode
- lines[] // like others, an array of up to 100
- x1
- y1
- x2
- y2
- thickness
- rgba[]
- mode // optionally, a compositing mode
- rectangles[]
- x1
- y1
- x2
- y2
- thickness
- rgba[]
- mode // optionally, a compositing mode
- filled // true or false
- polygons[]
- points[] // Up to 50 points, like {x: ..., y: ...}, {x: ..., y: ...}, ...
- thickness
- rgba[]
- mode // optionally, a compositing mode
- filled // true or false
- circles[]
- x
- y
- radius
- thickness
- rgba[]
- mode // optionally, a compositing mode
- filled // true or false
- items[] // An array of up to 100 items to draw cells from the item image
- x // the center x
- y // the center y
- cell // the cell number of the item you want to draw, like 1 (default)
- alpha // the alpha opacity from 0-1
- mode // optionally, a compositing mode
- rotation // optionally, a rotation angle in degrees, like 180 to rotate upside down
- flip {x: false, y: false} // optionally, containing an x and y bool of whether to flip this
- pivot {x: 10, y: 5} // optionally, setting of the rotation center pivot
- texts[]
- x
- y
- content // a string like 'hello world' with a maximum of 250 characters
- alpha // the alpha opacity from 0-1
- font // optionally, you can use enumFont.whiteSmall (default), enumFont.white, enumFont.blackSmall, or enumFont.black
- align // optionally, you can use enumAlign.center (default), enumAlign.left, or enumAlign.right
- mode // optionally, a compositing mode
- doPlaySound // true/ false. This will play an associated sound (use Add Sound from the menu during creation), at most once per 2 seconds
- back // {particles: ..., lines: ..., ...} Holds shape data like above, but for the background (e.g. behind background walls)
- front // {particles: ..., lines: ..., ...} Holds shape data like above, but for the foreground (e.g. above solids)
- itemAlpha // optional, a value from 0-1 to set the opacity of the Happening block in the world (like maybe you want to hide it while the Happening script runs)
- doEnd // optional, you can set this to true to end the Happening
Tip: On desktop, you can pick an rgba color value during creation by selecting a palette index, then pressing i.
Please note: Using a high number of shapes or items may make things slower.
Compositing modes
On any shape, you can optionally use the mode parameter to determine how it will be drawn. The following values are available:
- enumMode.default
- enumMode.bright
- enumMode.dark
- enumMode.multiply
- enumMode.screen
- enumMode.overlay
- enumMode.difference
- enumMode.exclusion
- enumMode.hue
- enumMode.saturation
- enumMode.color
- enumMode.luminosity
Checking the surrounding
Happenings can optionally react to the specific surrounding blocks and entities. If your code is found to contain references to '.sight', 'my.x', 'my.y' and so on, then the following data is passed with the my object as well:
- sight
- placements
- entities
- scope
- x
- y
- width
- height
For details on these properties, please see the Brain type's explanations.
Native functions
The following functions are available in addition to JavaScript's native functions:
// True or false, depending on a chance of 0 - 100% chance(percent); // Returns a random integer from minInt to maxInt getRandomInt(minInt, maxInt); // For use with the Changer, // converts pixels from palette indexes to rgba: convertToFullColor(creation); // Returns a copy of the object so that // changes won't affect the original: cloneObject(object); // Limits a value into the numbers, // sets to minimum if not numeric: toLimit(value, min, max);
Note: In Happenings, Math.random is overloaded with a seedable randomizer that will result in consistently same randomizations, dependent on the hour of the day, the item position and more.
Logging
You can use the following functions to log debug information when the script editor is opened (you can pass as many parameters as you like):
log('label', someValue); logOnce(someValue); // only logs once per run logFresh(someValue); // clears the log and logs something new logFresh(); // clears the log
Examples
Example: Drawing a variety of shapes
var data; function update(my) { if (!data) { initializeData(); } var line = data.lines[0]; if (line.y2 <= 100) { line.y2 += 25 * my.tick; } data.circles[0].radius += 2.5 * my.tick; data.particles[0].x += 15 * my.tick; data.particles[1].y -= 15 * my.tick; data.particles[2].y -= 15 * my.tick; return data; } function initializeData() { data = {}; data.particles = [ {x: 0, y: -70, thickness: 5, rgba: [0,255,128,.5], mode: enumMode.bright}, {x: 0, y: -60} ]; data.lines = [ { x1: -50, y1: 50, x2: 50, y2: -50, rgba: [0,255,0,1], mode: enumMode.bright } ]; data.rectangles = [ { x1: 10, y1: -50, x2: 50, y2: -40, rgba: [255,0,255,1], filled: true } ]; data.items = [ {cell: 2, x: -10, y: 0, alpha: .75, flip: {x: true} } ]; data.polygons = [ { points: [ {x: 10, y: 10 - 30}, {x: 30, y: 20 - 30}, {x: 20, y: 50 - 30} ], rgba: [255,0,0,.8], filled: true, mode: enumMode.luminosity } ]; data.circles = [ { x: 0, y: -50, rgba: [0,255,255,.2], thickness: 3, radius: 30, filled: true } ]; for (var i = 0; i < 30; i++) { data.particles.push( { x: getRandomInt(-150, 150), y: getRandomInt(-100, 100), thickness: 10, rgba: [0,0,0,.5] } ); }; }
Example: Using the Back and Front layers
In this example, all layers (default plus back and front) are drawn to:
var particles = [ { x: -10, y: -30, thickness: 10 } ]; var particlesFront = [ { x: -30, y: -30, rgba: [0,255,255,1], thickness: 10 } ]; var particlesBack = [ { x: -10, y: -40, rgba: [0,255,0,1], thickness: 6 } ]; function update(my) { particlesBack[0].x -= my.tick * 20; particles[0].x += my.tick * 10; particlesFront[0].x -= my.tick * 10; return { particles: particles, back: {particles: particlesBack}, front: {particles: particlesFront} }; }
Example: Many particles
Here we are moving a variety of particles upwards:
var inited = false; var particles = []; var tick = 0; function update(my) { if (!inited) { init(); } tick = my.tick; moveParticles(); return {particles: particles}; } function moveParticles() { for (var i = 0; i < particles.length; i++) { particles[i].y -= 50 * tick; } } function init() { for (var i = 0; i < 100; i++) { var particle = {}; particle.x = getRandomInt(-50, 50); particle.y = getRandomInt(-50, 50); particle.rgba = [getRandomInt(0,255), 0, 0, 1]; particles.push(particle); } inited = true; }
Example: Lines in the background
The following fades in a wobbling net of lines in the background:
var lines = []; var alpha = 0; function update(my) { if (lines.length == 0) { initLines(); } alpha += .005; if (alpha > 1) { alpha = 1; } changeLines(); return { back: {lines: lines} }; } function changeLines() { var fuzzy = .25; for (var i = 0; i < lines.length; i++) { var line = lines[i]; line.x1 += getRandomInt(-1, 1) * fuzzy; line.y1 += getRandomInt(-1, 1) * fuzzy; line.x2 += getRandomInt(-1, 1) * fuzzy; line.y2 += getRandomInt(-1, 1) * fuzzy; line.rgba = [255, 255, 255, alpha * .6]; } } function initLines() { var fuzzy = 400; for (var i = 0; i < 100; i++) { var line = {}; line.x1 = getRandomInt(-fuzzy, fuzzy); line.y1 = getRandomInt(-fuzzy, fuzzy); line.x2 = getRandomInt(-fuzzy, fuzzy); line.y2 = getRandomInt(-fuzzy, fuzzy); if ( chance(20) ) { line.thickness = 2; } lines.push(line); } }
Example: Electricity between metal balls
In this example, we're drawing electricity lines between objects around named 'metal ball' (this works best if you add an electricity sound to the Happening during creation):
var soundCounter = 0; function update(my) { var lines = []; var doPlaySound = false; var balls = getPositionsByName(my, 'metal ball'); if (balls.length >= 2) { lines = getEletricLines(balls); } if (++soundCounter <= 1) { doPlaySound = true; } return {lines: lines, doPlaySound: doPlaySound}; } function getEletricLines(balls) { var lines = []; var maxLines = 100; var tileSize = 19; var fuzzy = 1; for (var ballI = 0; ballI < balls.length; ballI++) { for (var i = 3; i >= 1; i--) { var offX = -tileSize, offY = -tileSize; var ball1 = balls[ballI]; var ball2 = ballI == balls.length - 1 ? balls[0] : balls[ballI + 1]; var line = { x1: ball1.x + offX, y1: ball1.y + offY, x2: ball2.x + offX, y2: ball2.y + offY }; var alpha = getRandomInt(1, 10) * .1; line.rgba = [ 200, 200, getRandomInt(200,255), alpha ]; line.thickness = i * 1; line.x1 += getRandomInt(-fuzzy, fuzzy); line.y1 += getRandomInt(-fuzzy, fuzzy); line.x2 += getRandomInt(-fuzzy, fuzzy); line.y2 += getRandomInt(-fuzzy, fuzzy); line.mode = enumMode.bright; lines.push(line); if (lines.length > maxLines) { return lines; } } } return lines; } function getPositionsByName(my, name) { var placements = my.sight.placements; var scope = my.sight.scope; var centerX = my.x; var centerY = my.y; var positions = []; var scope = 10; for (var y = -scope; y <= scope; y++) { for (var x = -scope; x <= scope; x++) { var block = placements[x + scope][y + scope]; if (block && block.name == name) { positions.push( {x: block.x - centerX, y: block.y - centerY} ); } } } return positions; }
More...
Also check out the main introductory help and the Interacting help. Got a question which isn't answered here, or new ideas and feedback? Have a look here please.