I have a web page which encloses a few sketches, all written in P5.JS
Each sketch uses its own name space, so that it runs independently from the others.
I noticed that, for each sketch, the level of performance is lower than the one I get when it runs alone in a separate web page.
My question : what can I do to prevent all the sketches to run all at once ? Is it possible, for example, to activate a sketch only when the mouse hovers its canvas ? It would probably spare ressources.
Thank you for your help.
You can call noLoop() and loop() to stop and restart a sketch. There aren't any built in p5.js events to help you trigger noLoop() when the mouse leaves the sketch or when the sketch is scrolled off screen, however there are a couple of ways you can do it which rely on using the underlying browser functionality:
The built in mouseenter and mouseleave events
Checking winMouseX and winMouseY against the sketch canvas getBoundingClientRect() in each call to draw()
function makeSketch(...colorArgs) {
return (p) => {
let bgColor;
let black;
let c;
p.setup = () => {
c = p.createCanvas(p.windowWidth, p.windowHeight / 3);
bgColor = p.color(...colorArgs);
black = p.color(0);
c.elt.addEventListener('mouseenter', () => {
p.loop();
});
c.elt.addEventListener('mouseleave', () => {
p.noLoop();
});
let bounds = c.elt.getBoundingClientRect();
// Just in case the mouse is already over the canvas when it is created.
// This is also how you would use getBoundingClientRect from the draw()
// and mouseMoved() functions instead of the mouseenter/mouseleave events.
if (p.winMouseX < bounds.left ||
p.winMouseX > bounds.right ||
p.minMouseY < bounds.top ||
p.winMouseY > bounds.bottom) {
p.noLoop();
}
};
p.draw = () => {
p.background(p.lerpColor(
bgColor,
black,
p.abs((p.frameCount % 240 - 120) / 120)
));
let bounds = c.elt.getBoundingClientRect();
p.fill('white');
p.noStroke();
p.text(`${p.winMouseX}, ${p.winMouseY} :: ${bounds.left}, ${bounds.top}, ${bounds.right}, ${bounds.bottom}`, 10, 10);
}
};
}
let sketch1 = new p5(makeSketch('red'));
let sketch2 = new p5(makeSketch(0, 255, 0));
let sketch3 = new p5(makeSketch('blue'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
You might also find that it is sufficient to pause sketches that are off screen:
function makeSketch(...colorArgs) {
return (p) => {
let bgColor;
let black;
let c;
let isLooping;
p.setup = () => {
c = p.createCanvas(p.windowWidth, p.windowHeight);
bgColor = p.color(...colorArgs);
black = p.color(0);
let bounds = c.elt.getBoundingClientRect();
isLooping = true;
if (bounds.bottom < 0 ||
bounds.top > p.windowHeight) {
p.noLoop();
isLooping = false;
}
// Might need to check this on resize as well.
document.addEventListener('scroll', () => {
let bounds = c.elt.getBoundingClientRect();
// Note this only checks verticle scrolling, but you could check horizontal as well
if (bounds.bottom > 0 &&
bounds.top <= p.windowHeight) {
if (!isLooping) {
isLooping = true;
console.log(`sketch ${colorArgs.join(',')}: loop`);
p.loop();
}
} else if (isLooping) {
isLooping = false;
console.log(`sketch ${colorArgs.join(',')}: noLoop`);
p.noLoop();
}
});
};
p.draw = () => {
p.background(p.lerpColor(
bgColor,
black,
p.abs((p.frameCount % 240 - 120) / 120)
));
p.fill('white');
p.noStroke();
p.text(`${p.frameCount}`, 10, 10);
}
};
}
let sketch1 = new p5(makeSketch('red'));
let sketch2 = new p5(makeSketch(0, 255, 0));
let sketch3 = new p5(makeSketch('blue'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Related
In p5.js, I am using "new p5.FFT(x,y)" to analyze the amplifer mp3 file.
But this has a little problem that if you set the mp3's volume to 0(by using .setVolume(x)) the song file cannot be analyzed maybe because you set volume to 0 so there's no input.
So i want to know how to analyze songfile even when i set the volume to 0.
The trick is you need to connect your FFT at a point where the volume is still non-zero, and then have a node down stream where you control the volume. Here's an example where I've used the p5.EQ effect to control the volume of one part of the audio graph. The "Tada" sound is connected to an FFT and to the "mute" p5.EQ effect. This makes it so that the FFT visualizes the sound at full volume, but the slider controls how loud the sound actually is. The "Ding" sound on the other hand is connected directly to the output, no FFT, no volume control.
let tada, ding;
let tadaBtn, dingBtn;
let volSlider;
let fft;
let mute;
function preload() {
tada = loadSound('https://www.paulwheeler.us/files/TADA.WAV');
ding = loadSound('https://www.paulwheeler.us/files/DING.WAV');
}
function setup() {
createCanvas(windowWidth, windowHeight);
let controls = createElement('div');
controls.style('display', 'flex');
controls.position(10, 10);
tadaBtn = createButton('Tada');
tadaBtn.mouseClicked(() => {
if (!tada.isPlaying()) {
tada.play();
tadaBtn.html('Stop');
} else {
tada.stop();
}
});
tadaBtn.parent(controls);
tada.onended(() => {
tadaBtn.html('Tada');
});
dingBtn = createButton('Ding');
dingBtn.mouseClicked(() => {
if (!ding.isPlaying()) {
ding.play();
dingBtn.html('Stop');
} else {
ding.stop();
}
});
dingBtn.parent(controls);
ding.onended(() => {
dingBtn.html('Ding');
});
volSlider = createSlider(0, 1, 0, 0);
volSlider.input(() => {
mute.amp(volSlider.value());
});
volSlider.parent(controls);
tada.disconnect();
fft = new p5.FFT();
fft.setInput(tada);
mute = new p5.EQ();
mute.amp(volSlider.value());
tada.connect(mute);
mute.connect();
}
function draw() {
background(0);
drawSpectrumGraph(0, 0, width, height);
}
// Graphing code adapted from https://jankozeluh.g6.cz/index.html by Jan Koželuh
function drawSpectrumGraph(left, top, w, h) {
let spectrum = fft.analyze();
stroke('limegreen');
fill('darkgreen');
strokeWeight(1);
beginShape();
vertex(left, top + h);
for (let i = 0; i < spectrum.length; i++) {
vertex(
left + map(log(i), 0, log(spectrum.length), 0, w),
top + map(spectrum[i], 0, 255, h, 0)
);
}
vertex(left + w, top + h);
endShape(CLOSE);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
I am trying to add a simple countdown timer to a Phaser game. When the timer ends I want the game to restart. After adding the relevant code there are no errors in the console but I can't get the timer to appear on the screen. I am new to Phaser and am still learning Javascript. Where I am going wrong please? I have posted only the relevant code below, and the code used for already existing text in the game that is working fine (text to count coins collected).
PlayState = {};
PlayState.init = function () {
//....
};
PlayState.preload = function () {
this.game.load.image('font:numbers', 'images/numbers.png'); //for the
//HUD coin count - not the timer
};
PlayState.create = function () {
//TIMER CODE:
this.timeInSeconds = 120;
this.timeText = this.game.add.text(this.game.world.centerX,
this.game.world.centerY, "0:00",{font: '15px Arial', fill: '#FFFFFF', align:
'center'});
this.timeText.anchor.set(0.5, 0.5);
this.timer = this.game.time.events.loop(Phaser.Timer.SECOND,
this.updateTimer, this);
};
PlayState.update = function () {
this.coinFont.text = `x${this.coinPickupCount}`; //for HUD coin count not
//the timer
};
//TIMER CODE:
PlayState.updateTimer = function() {
this.timeInSeconds--;
var minutes = Math.floor(this.timeInSeconds / 60);
var seconds = this.timeInSeconds - (minutes * 60);
var timeString = this.addZeros(minutes) + ":" + this.addZeros(seconds);
this.timeText.text = timeString;
if (this.timeInSeconds == 0) {
this.game.state.restart();
}
};
//add leading zeros to any number less than 10
//for example turn 1 to 01
PlayState.addZeros = function(num) {
if (num < 10) {
num = "0" + num;
}
return num;
};
//BELOW IS CODE FOR THE COIN COUNT NOT THE TIMER
PlayState._createHud = function () {
this.keyIcon = this.game.make.image(0, 30, 'icon:key');
this.keyIcon.anchor.set(0, 0.5);
const NUMBERS_STR = '0123456789X ';
this.coinFont = this.game.add.retroFont('font:numbers', 20,
26,NUMBERS_STR, 6);
let coinIcon = this.game.make.image(this.keyIcon.width + 7,
0, 'icon:coin');
let coinScoreImg = this.game.make.image(coinIcon.x +
coinIcon.width, coinIcon.height / 2, this.coinFont);
coinScoreImg.anchor.set(0, 0.5);
this.hud = this.game.add.group();
this.hud.add(coinIcon);
this.hud.position.set(10, 10);
this.hud.add(coinScoreImg);
this.hud.add(this.keyIcon);
this.hud.fixedToCamera = true;
};
window.onload = function () {
let game = new Phaser.Game(1280, 800, Phaser.CANVAS, 'game');
game.state.add('play', PlayState);
game.state.start('play');
};
I have finally solved the issue. The text was not showing because it was being rendered AFTER the background image in create. So it was there but being hidden by the background image. I simply moved the timer code to the end of create and it now works.
PlayState.create = function () {
this.game.world.setBounds(0, 0, 2560, 800);
background1 = this.game.add.sprite(0, 0, 'background');
background2 = this.game.add.sprite(1280, 0, 'background2');
this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.game.scale.setMinMax(480,320,1280,800);
this.game.scale.windowConstraints.bottom = 'visual';
this.game.add.image(0, 0, 'background');
this._loadLevel(this.game.cache.getJSON('level:1'));
this._createHud();
//TIMER CODE SHOULD GO HERE AND NOT AT THE BEGINNING OF CREATE
this.timeInSeconds = 120;
this.timeText = this.game.add.text(220, 30, "0:00",{font: '30px Arial', fill:
'#FFFFFF', align: 'center'});
this.timeText.anchor.set(0.5, 0.5);
this.timer = this.game.time.events.loop(Phaser.Timer.SECOND, this.updateTimer,
this);
};
You set the initial text to "0:00" and even that doesn't show on screen? First thing I would do is look at the coordinates where the text is located, maybe it's not visible off screen. Instead of this.game.world.centerX, this.game.world.centerY try something like 100,100, does it show up then? Also try to set very long initial text, so something like "blah test ABC 123" instead of "0:00" maybe makes some difference.
Secondly, maybe the Arial font is not available for some reason. If you leave out the {font: '15px..'center'} part it will use a default font, does that change anything?
Thirdly, you say you didn't post all your code here, but maybe you accidentally overwrite the variable this.timeText somewhere in you code? So check that you don't set that variable to be something else.
Finally, I would add a console.log to the updateTimer function, just to see if it is being called. so:
PlayState.updateTimer = function() {
console.log('updateTimer was called: ' + this.timeInSeconds);
this.timeInSeconds--;
// etc.
im currently building a website using fullpage js and lottie animations. Now im trying to trigger an animation when the user scrolls to the section with the animation. Here is what i tried:
(please note that im very new to js)
$(document).ready(function($) {'use strict';
$('#fullpage').fullpage({
sectionsColor: ['white', '#004E8A', 'white','#004E8A', 'white', '#004E8A',
'white','#004E8A', 'white'],
anchors:['startseite','pers_vermittler','team','konzept','rechner','mod_portfolio','sicherheit','absatz'],
onLeave: function(index, nextIndex, direction) {
if( index == 3 && direction == 'down' ) {
lottie.play('k2an');
}
(at the end of the body section ->)
<script>
var params = {
container: document.getElementById('k2an'),
renderer: 'svg',
loop: true,
autoplay: false,
path: 'k2an.json',
};
anim = lottie.loadAnimation(params);
You should be using fullPage.js callbacks to fire your JS animations.
See the example:
$('#fullpage').fullpage({
anchors: ['firstPage', 'secondPage', 'thirdPage', 'fourthPage', 'lastPage'],
afterLoad: function(anchorLink, index){
var loadedSection = $(this);
//using index
if(index == 3){
alert("Section 3 ended loading");
}
//using anchorLink
if(anchorLink == 'secondSlide'){
alert("Section 2 ended loading");
}
}
});
Feel free to also check my video tutorial on how to create animations using fullPage.js state classes.
Right now im using this approach on a couple production sites. It plays the animation as the user scrolls.
I basically check how much of the animation objects box is visible in the viewport, calculate the total length of the animation (in frames) and then project the percentage to a frame where i gotoAndStop().
var anim = <YOUR LOTTIE ANIMATION OBJECT>
// play around with these
var speed = 1; // speed of animation
var scrollOffset = 0; // start animation sooner / later
function scrollHandler() {
if (!anim.isLoaded) return;
p = percentageSeen(e) / 100 - (scrollOffset || 0);
if (p <= 0 || p >= 1) return
var length = anim.totalFrames / anim.frameModifier;
var pos = length * p * (speed || 1);
anim.goToAndStop(pos);
}
$(window).on('scroll', scrollHandler);
/**
* returns percentage of scrolling element through viewport
* 0% until element-middle is at bottom of viewport
* 100% if element-middle is at top of viewport
*
* #param id
* #returns {number}
*/
function percentageSeen(idOrElement) {
var $element;
if (typeof idOrElement === 'object') {
$element = idOrElement;
} else {
$element = $('#' + id);
if (!$element[0]) return 0;
}
var $win = $(window), viewportHeight = $(window).height(),
scrollTop = $win.scrollTop(),
elementOffsetTop = $element.offset().top,
elementHeight = $element.height();
if (elementOffsetTop > (scrollTop + viewportHeight)) {
return 0;
} else if ((elementOffsetTop + elementHeight) < scrollTop) {
return 100;
} else {
var distance = (scrollTop + viewportHeight) - elementOffsetTop - (elementHeight / 2);
if (distance < 0) return 0;
var percentage = distance / (viewportHeight / 100);
if (percentage > 100) return 100;
return percentage;
}
}
If you want to only start the animation and let it run (independently of further user-scrolling-behaviour), just use the jquery inview plugin, disable autoplay on your animation and trigger the play() once like this:
$(".animation-container").one("inview", function() {
anim.play()
});
Now I use interval make it come true, but it is very incoherence.
If I can just change the method (scroll) speed, it well be nice.
this.interval = setInterval(()=>{
if(!_scroll){
_this.interval && clearInterval(_this.interval);
}
if(totalWide+ScreenWidth >= width ){
_scroll.scrollWithoutAnimationTo();
totalWide=0;
i=0;
}else{
_scroll.scrollTo({x:eachWide*i,animate:true});
totalWide = totalWide + eachWide;
i= i+1;
}
},250)
use decelerationRate property of ScrollView
<ScrollView decelerationRate={0.5}>
</ScrollView>
I got this working by having setInterval call a function(in which you define the logic or the pace at which the scroll should move).
this.interval= setInterval(this.scrollwithSpeed, 100); // Set the function to this timer
scrollwithSpeed() {
position = this.state.currentPosition + x; // x decides the speed and
currentPosition is set to 0 initially.
this.scrollObject.scrollTo(
{ y: position, animated: true }
);
this.setState({ currentPosition: position });
}
Make sure you call clearInterval(this.interval) after it is done.
I would suggest to attach to js requestAnimationFrame (from how far I know it is supported in React Native).
Bellow example will scroll linearly from top to bottom. If You need to scoll to different offset just change distance variable.
startingPoint variable is redundant in scrolling from top to bottom but will stay in example.
scroll() {
if (this.scrollAnimationFrame) {
cancelAnimationFrame(this.scrollAnimationFrame);
}
this.listRef.scrollToOffset({offset: 0, animated: false}); // remove if You don't start scroll from top
const duration = this.scrollTime,
startingPoint = 0, // change if You don't start scroll from top
distance = Scrolling.LINE_HEIGHT * Scrolling.ITEMS_COUNT;
let startTimestamp, progress;
const frameCallback = (timestamp) => {
if (!startTimestamp) {
startTimestamp = timestamp;
}
progress = timestamp - startTimestamp;
this.listRef.scrollToOffset({
offset: distance * (progress / duration) + startingPoint,
animated: false,
});
if (progress < duration) {
this.scrollAnimationFrame = requestAnimationFrame(frameCallback);
}
};
this.scrollAnimationFrame = requestAnimationFrame(frameCallback);
}
You can use reanimated to make it work.
const offsetY = useSharedValue(0);
const animatedProps = useAnimatedProps<FlatListProps<unknown>>(() => {
return {
contentOffset: {
x: 0,
y: offsetY.value,
},
};
});
const handleScroll = () => {
offsetY.value = withTiming(targetIndex * CARD_HEIGHT, {
duration: YOUR_DURATION_HERE,
});
}
return <Animated.FlatList animatedProps={animatedProps} ... />
I'm trying to print out text when a user touches a sprite, however, even though I get no errors when building and running the code, the code refuses to printout the text and I cannot understand what I am doing wrong. Please help me understand why it does not print.
It prints the debug text when I touch the sprite, but not the GUI text with total.
This is the code:
#pragma strict
function Start () {
OnGUI();
}
function OnGUI(){
//var total = 0;
//GUI.Label( myRect, total.ToString() ); // Displays "10".
//GUI.Label( myRect, "" + bullets ); // Displays "10".
// GUI.Label(Rect(0,0,Screen.width,Screen.height),"Total:"+total);
}
//function Update () {
//}
var platform : RuntimePlatform = Application.platform;
function Update(){
if(platform == RuntimePlatform.Android || platform == RuntimePlatform.IPhonePlayer){
if(Input.touchCount > 0) {
if(Input.GetTouch(0).phase == TouchPhase.Began){
checkTouch(Input.GetTouch(0).position);
}
}
}else if(platform == RuntimePlatform.WindowsEditor){
if(Input.GetMouseButtonDown(0)) {
checkTouch(Input.mousePosition);
}
}
}
function checkTouch(pos){
var total = 0;
var wp : Vector3 = Camera.main.ScreenToWorldPoint(pos);
var touchPos : Vector2 = new Vector2(wp.x, wp.y);
var hit = Physics2D.OverlapPoint(touchPos);
if(hit){
GUI.Label(Rect(0,0,Screen.width,Screen.height),"Total:");
Debug.Log(hit.transform.gameObject.name);
hit.transform.gameObject.SendMessage('Clicked',0,SendMessageOptions.DontRequireReceiver);
//total = total +1;
}
}
You cannot put a gui item outside the OnGUI Method. You need to set a boolean to turn on the label.
Something along these lines.
OnGUI(){
if(labelbool)
GUI.Label ....
}
check(){
labelbool = true;
}