After tweeting about the multitouch bug to the author of p5.js, I received the following reply:
I therefore filed the bug on the p5.js GitHub.
Wendy forwarded me a video that she had shot of someone using the original version of Reactickles that I am now in the process of porting to the web. I’ve embedded it below:
The three Reactickles that I am aiming to port initially are at the following points in the video:
- 2:41, which I am calling KeyboardScalingCircleGrid.
- 3:57, which I am calling KeyboardBouncingCircleGrid.
- 0:37, which I am calling KeyboardSnake.
So lets start with the development of KeyboardScalingCircleGrid. I began by duplicating the code I wrote yesterday (KeyboardToScreen) and renaming the folder to KeyboardScalingCircleGrid.
I knew that I would have to create an array of ScalingCircles and instantiate them with certain values, so I had a look at the p5.js examples page to see if there were any sketches that might be useful. Sure enough, the Array of Objects example and the Objects 2 example looked perfect.
I started by creating a new ScalingCircle object:
function ScalingCircle(aKey, aCircleRadius){ //ScalingCircle object
this.key = aKey;
this.circleRadius = aCircleRadius;
this.position = createVector(-1,-1);
this.position = getCanvasPositionFromKey(aKey);
this.colour = color(random(100),50,100); //random hue, saturation 50% and brightness 100%
this.display = function(){
var translatedX = this.position.x * windowWidth;
var translatedY = this.position.y * windowHeight;
fill(this.colour);
ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius);
}; //don't forget to close your method!
}
And creating an array of those objects inside the setup method of the sketch:
var characterSize = 50;
var circles = []; //array of ScalingCircle objects
var allTheKeys = "1234567890qwertyuiopasdfghjklzxcvbnm";
var circleRadius = 100;
function setup() {
createCanvas(windowWidth,windowHeight); //make a fullscreen canvas, thanks to: http://codepen.io/grayfuse/pen/wKqLGL
textSize(characterSize);
colorMode(HSB, 100);// Use HSB with scale of 0-100, see https://p5js.org/reference/#/p5/color
for (var i=0; i < allTheKeys.length; i++) {
circles.push(new ScalingCircle(allTheKeys[i],circleRadius));
}
console.log("The size of the circles array is " + circles.length);
}
Finally, I looped through the array of circles inside the draw method of the sketch:
function draw() {
background(255); //white background
noStroke();
for (var i=0; i<circles.length; i++) {
circles[i].display();
}
}
This resulted in the following output:
The next step is to add scaling when keyboard buttons are pressed.
I added a scaleUp() method to the ScalingCircle, and changed the display method to ease towards the new targetCircleRadius (The Processing Easing example was useful for this):
function ScalingCircle(aKey, aCircleRadius){ //ScalingCircle object
this.key = aKey;
this.actualCircleRadius = aCircleRadius;
this.targetCircleRadius = aCircleRadius;
this.position = createVector(-1,-1);
this.position = getCanvasPositionFromKey(aKey);
this.colour = color(random(100),50,100); //random hue, saturation 50% and brightness 100%
this.display = function(){
var differenceInRadius = this.targetCircleRadius - this.actualCircleRadius;
var changeThisFrame = differenceInRadius*easing;
this.actualCircleRadius += changeThisFrame;
var translatedX = this.position.x * windowWidth;
var translatedY = this.position.y * windowHeight;
fill(this.colour);
ellipse(translatedX, translatedY, this.actualCircleRadius, this.actualCircleRadius);
}; //don't forget to close your method!
this.scaleUp = function(){
this.targetCircleRadius = this.actualCircleRadius+10;
}
}
Then added code to check which button was pressed, and to call the scaleUp() method on the correct ScalingCircle in the circles array:
function keyTyped(){
var lowerCaseKey = key.toLowerCase(); //key is a system variable via https://p5js.org/reference/#/p5/key
for (var i=0; i<circles.length; i++) {
if(lowerCaseKey == circles[i].key){
circles[i].scaleUp();
}
}
return false; //https://p5js.org/reference/#/p5/keyTyped preventing default behaviour
}
This resulted in a pleasing scaling animation, but it wasn’t quite as fast as the video, so I altered the easing ratio to 0.3 from 0.1, as well as setting a fixed number for the target radius of each circle – initially 100 pixels, then 200 pixels on keypress. Finally, I changed the colour of the circle to be 50% transparent to match the blending effect that was visible on the Reactickles 1 demonstration video.
However, in the video I could see that the circles not only scale up, but scale back to their original size – and worse than that they don’t scale up in a linear way, but seem to “bounce” around their full size – overshooting initially and then scaling back. From previous experience in Actionscript and C++ I knew of the existence of Robert Penner’s Easing functions, and that it was very likely that they had already been implemented in p5.js. I Googled “easing functions p5.js” and found p5.ijeoma.js:
A p5.js addon for ijeoma.js, a JS library for creating animations.
I downloaded the library and added it and its dependencies to my libraries folder. Looking at the examples and documentation I started by creating a tween for the scaling up of circles:
function ScalingCircle(aKey){ //ScalingCircle object
this.key = aKey;
this.startCircleRadius = 100;
this.endCircleRadius = 200;
this.circleRadius = this.startCircleRadius; //start with the start
this.scaleUpDuration = 0.5; //take half of a second to scale up
this.scaleDownDelay = this.scaleUpDuration; //wait until the scale up is down to scale down
this.scaleDownDuration = 0.25; //take quarter of a second to scale down
this.position = createVector(-1,-1);
this.position = getCanvasPositionFromKey(aKey);
this.colour = color(random(100),50,100,50); //random hue, saturation 50% and brightness 100%, alpha 50%
this.display = function(){
var translatedX = this.position.x * windowWidth;
var translatedY = this.position.y * windowHeight;
fill(this.colour);
ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius);
}; //don't forget to close your method!
this.scaleUpAndThenDown = function(){
//syntax for tweens is createTween(object property, end, duration, [delay], [easing])
//see https://github.com/ekeneijeoma/p5.ijeoma.js
var scaleUpTween = createTween('this.circleRadius', this.endCircleRadius, this.scaleUpDuration).easing(Quad.In).play();
var scaleDownTween = createTween('this.circleRadius', this.startCircleRadius, this.scaleDownDuration, this.scaleDownDelay).easing(Quad.Out).play();
}
}
Unfortunately, this didn’t work, resulting in the JavaScript errors:
[Log] p5 had problems creating the global function "frames", possibly because your code is already using that name as a variable. You may want to rename your variable to something else. (p5.js, line 9429)
[Log] p5 had problems creating the global function "stop", possibly because your code is already using that name as a variable. You may want to rename your variable to something else. (p5.js, line 9429)
[Log] The size of the circles array is 36 (sketch.js, line 14)
[Warning] Only numbers, p5.colors and p5.vectors are supported. (p5.ijeoma.js, line 142)
[Error] TypeError: undefined is not an object (evaluating 'this._properties[i].update')
_updateProperties (ijeoma.js:1170)
dispatchChangedEvent (ijeoma.js:1297)
seek (ijeoma.js:194)
play (ijeoma.js:148)
scaleUpAndThenDown (sketch.js:59:122)
keyTyped (sketch.js:31)
_onkeypress (p5.js:16261)
(anonymous function)
I duplicated the code into an example, Tweeted at the developer, filed a bug on the library GitHub and rolled back to my previous code.
I decided to try getting elements to scale up and then down, before worrying about “bouncing”.
function ScalingCircle(aKey){ //ScalingCircle object
this.key = aKey;
this.circleBigRadius = 200;
this.circleSmallRadius = 100;
this.circleRadius = this.circleSmallRadius;
this.position = createVector(-1,-1);
this.position = getCanvasPositionFromKey(aKey);
this.colour = color(random(100),50,100,50); //random hue, saturation 50% and brightness 100%, alpha 50%
this.millisToScaleUp = 50;
this.millisToScaleDown = 200;
this.startScale = 0;
this.endScale = 0;
this.scaling = false;
this.display = function(){
if(this.scaling){
this.scale();
}
var translatedX = this.position.x * windowWidth;
var translatedY = this.position.y * windowHeight;
fill(this.colour);
ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius);
}; //don't forget to close your method!
this.scale = function(){
var now = millis();
var millisElapsed = now-this.startScale;
if(millisElapsed < this.millisToScaleUp){
var howFarAlongScaleUp = millisElapsed/this.millisToScaleUp;
this.scaleUp(howFarAlongScaleUp);
}else{
var howFarAlongScaleDown = (millisElapsed-this.millisToScaleUp)/this.millisToScaleDown;
this.scaleDown(howFarAlongScaleDown);
}
if(now >= this.endScale){
this.scaling = false;
}
}
this.scaleUp = function(howFarAlongScale){
var differenceInRadius = this.circleBigRadius - this.circleSmallRadius;
var newRadius = this.circleSmallRadius+(howFarAlongScale*differenceInRadius);
this.circleRadius = newRadius;
}
this.scaleDown = function(howFarAlongScale){
var differenceInRadius = this.circleBigRadius - this.circleSmallRadius;
var newRadius = this.circleBigRadius-(howFarAlongScale*differenceInRadius);
this.circleRadius = newRadius;
}
this.scaleUpandDown = function(){
this.scaling = true;
this.startScale = millis();
this.endScale = this.startScale+this.millisToScaleUp+this.millisToScaleDown;
}
}
This resulted in a scaling up and down, but without the bounce that I could see in the video.