Video games are framed by the computer screen. The world of the game exists within that rectangle, and the ‘real’ world exists around it. What if we use connected lighting systems to extend the environment of the game beyond the screen?
For this project I have created a simple game of pong in which how well you are doing in the game is reflected by the world around you. Using a Philips Hue Bloom (or any “hue”, “sat”, and/or “bri” capable light), your progress in the game affects the brightness and colour of the room you are playing in.
Very simply, the longer you play, the more pleasantly cool and desaturated the light around you becomes. Whenever you ‘lose’ the light progresses some increment back towards a glaring, saturated red.
Hue Control Setup Function// Setup function modified from client-control example function setupHueControl() { var addressLabel, usernameLabel, connectButton; // set up the address and username fields: addressLabel = createSpan("IP address:"); addressLabel.position(10,10); addressField = createInput('text'); addressField.position(100, 10); addressField.value(url); usernameField = createInput('text'); usernameField.position(100, 40); usernameField.value(username); usernameLabel = createSpan("Username:"); usernameLabel.position(10,40); lightNumberField = createInput('text'); lightNumberField.position(100, 70); lightNumberField.value(4); lightNumberLabel = createSpan("Light No."); lightNumberLabel.position(10,70); targetColourField = createInput('text'); targetColourField.position(100, 100); targetColourField.value(44000); //A nice winning green targetColourLabel = createSpan("Target Hue"); targetColourLabel.position(10,100); connectButton = createButton("connect"); connectButton.position(100, 130); connectButton.mouseClicked(connect); resetButton = createButton("reset"); resetButton.position(180, 130); resetButton.mouseClicked(resetLight); }
At the moment this code works with one light, which the player can select.
Hue Connect//Connect function directly from client-control function connect() { this.html("refresh"); // change the connect button to 'refresh' for (control in controlArray) { controlArray[control].remove(); // clear the lights div on reconnect } controlArray = []; // clear the control array url = "http://" + addressField.value() + '/api/' + usernameField.value() + '/lights/'; httpDo(url, 'GET', getLights); }
Once we have all the lights on the hub, we find the one the player has selected:
Get Selected Lightfunction getLights(result) { var lights = JSON.parse(result); // parse the HTTP response let flag = false; for (thisLight in lights) { // iterate over each light in the response if(thisLight == lightNumberField.value()){ // This is the selected light setupLightController(lights[thisLight]); flag = true } } if(flag!=true){ alert("The light you entered is not available! Try use http://<hub.ip>/debug/clip.html to find a valid light"); } resetLight(); setupGame(); }
Now that we have the required light, use Tom’s technique to extract the useful properties. In my case I’m interested in whether the light has brightness control, saturation control, and/or hue control.
Setup Light Controller Objectfunction setupLightController(thisLight){ var state = thisLight.state; // state of this light for (property in state) { // iterate over properties in state object switch (property) { // handle the cases you care about case 'on': customLightControl.on = state.on; break; case 'bri': customLightControl.bri = state.bri; break; case 'hue': customLightControl.hue = state.hue; break; case 'sat': customLightControl.sat = state.sat; break; case 'ct': customLightControl.ct = state.ct; break; // break; } } // end of for-loop to create controls //console.log(customLightControl); }
My code has some helper functions to increase/decrease property values incrementally. Using a linear interpolation, we can slowly ease towards a target value for brightness, saturation, hue:
Property Value Change Functionsfunction increaseSaturation(target, amount){ //amount = lerp amount (1 max) for (property in customLightControl) { switch (property) { case 'sat': customLightControl.sat = lerp(customLightControl.sat, target, amount); break; } } } function increaseBrightness(target, amount){ //amount = lerp amount (1 max) for (property in customLightControl) { switch (property) { case 'bri': customLightControl.bri = lerp(customLightControl.bri, target, amount); break; } } } function increaseHue(target, amount){ //amount = lerp amount (1 max) for (property in customLightControl) { switch (property) { case 'hue': customLightControl.hue = lerp(customLightControl.hue, target, amount); break; } } } function fullBrightness(){ for (property in customLightControl) { switch (property) { case 'bri': customLightControl.bri = 254; break; } } } function resetLight(){ // Turns off, desaturates lights for (property in customLightControl) { switch (property) { case 'bri': customLightControl.bri = 150; break; case 'hue': customLightControl.hue = 0; break; case 'sat': customLightControl.sat = 0; break; } } updateLight(); //Always update light when resetting. } //https://codepen.io/ma77os/pen/KGIEh function lerp (start, end, amt){ return (1-amt)*start+amt*end }
Finally on the Hue Control side, send the request to change the Hue light:
Update Lights and Send Requestfunction updateLight(){ console.log(customLightControl); var payload = {}; // put the value for the given control into the payload: for (property in customLightControl) { switch (property) { case 'bri': payload['bri'] = Math.floor(customLightControl.bri); break; case 'hue': payload['hue'] = Math.floor(customLightControl.hue); break; case 'sat': payload['sat'] = Math.floor(customLightControl.sat); break; case 'on': payload['on'] = customLightControl.on; break; } } setLight(lightNumberField.value(), payload, 'state'); } function setLight(lightNumber, data, command) { var path = url + lightNumber + '/' + command; // assemble the full URL var content = JSON.stringify(data); // convert JSON obj to string httpDo( path, 'PUT', content, 'text', getResponse); //HTTP PUT the change } function getResponse(response) { // show response: console.log("res:"); console.log(response); }
I won’t go into detail on the pong side of this project, since it’s not too relevant to the subject. The important part of the pong code is where the lighting control functions are called.
There are two cases where a lighting control function is called. The first is when the ball hits the side or top of the screen. This marks successful progress in the game:
Handle Progressfunction onEdgeHit(){ ball.vel.mult(ball.speedMultiplier); // Increase difficulty over time increaseSaturation(minSat,0.1); // Decrease satureation over time increaseBrightness(255,0.05); // Increase brightness over time increaseHue(targetColourField.value(),0.1); //Progress towards users chosen colour temperature updateLight(); }
The other case is when the ball reaches the bottom. This is a negative progression:
Handle Setbackfunction onLose(){ increaseSaturation(maxSat,0.5); // Greatly increase saturation increaseBrightness(minBri,0.5); // Greatly decrease brightness increaseHue(0,0.5); //Greatly increment saturation towards red updateLight(); }