'Transitioning from one scene to the next with p5.js
I'm currently using p5.js to create an interactive story. My main problem is that I can't seem to figure out how to write the logic for transitioning from the second scene to the third scene.
I have used p5.js's switching scenes example here as a reference guide. I haven't really quite grasped the logic behind the mousePressed()
function in order to use it to my advantage.
function mousePressed() {
if (scene1 == true) {
if (mouseX < width / 2) {
//do something
scene2 = true;
} else if (mouseX > width / 2) {
//do something else
scene3 = true;
}
// turn scene 1 off
scene1 = false;
}
}
The function divides the canvas into two locations for the mousePressed()
function to execute and transition to another scene.
I just want to press anywhere on the canvas as a starting point to transition from the first to the second scene, but later I would like to press on a specific object in order to transition from the second to third scene.
Snippet of current code:
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
easycam = new Dw.EasyCam(this._renderer, {
distance: lunarDistance * 1.3
});
}
function draw() {
if (scene1 == true) {
drawScene1();
} else if (scene2 == true) {
drawScene2();
} else if(scene3 == true){
drawScene3();
}
}
function drawScene1() {
...
}
function drawScene2() {
...
}
function drawScene3() {
...
}
function mousePressed() {
if (scene1 == true) {
if (mouseX == width) {
scene2 = true;
if (mouseX == width) {
scene3 = true;
}
}
scene1 = false;
}
}
Unfortunately this doesn't seem to work. I've tried modifying it further by removing mousePressed():
function draw(){
if (scene1 == true) {
drawScene1();
if(mouseIsPressed){
scene1 = false;
scene2 = true;
drawScene2();
}
}
}
This seems to work, but it disables my animations and messes it up completely.
How can I go about this?
Solution 1:[1]
MousePressed event: P5 reference
Language references should be the first place you should be scouring. The mouse pressed function is described here within the p5 reference
Here is a snippet of the event description from the reference.
The mousePressed() function is called once after every time a mouse button is pressed. The mouseButton variable (see the related reference entry) can be used to determine which button has been pressed. If no mousePressed() function is defined, the touchStarted() function will be called instead if it is defined.
Negation
Think about how you can apply negation to manage your events.
scene1 = !scene1; // This would turn your scene on or off
Mouse Pressed event
Your code snippet here would break because it will draw just 1 frame and since scene 1 is set to false no other conditionals would be accessible after the first frame.
function draw(){
if (scene1 == true) {
drawScene1();
if(mouseIsPressed){
scene1 = false;
scene2 = true;
drawScene2();
}
}}
What you want to do is first check where the mouse click is happening. Based upon the mouseclick change your scene. There are a plethora of ways to do this. Keeping track of the booleans and variables is too tedious and erorr prone. So, how do you get around this? Is there any collection that might be able to help you? Yes!
You can use lists or any other flavor of collections to help track scenes easily. To do this, keep track of the current scene using a pointer and only draw that scene if the pointer is on that scene.
i.e: if (pointer==1){drawScene1();}
I believe you can solve the rest of the problem from this.
Solution 2:[2]
The approach of using if
s inside of draw()
to figure out which scene to render might be fine for small sketches, but isn't especially scalable, involving booleans, hard-to-reason-about conditions, shared state and/or the program having to know about magic/hardcoded variable names.
Whenever you find yourself dealing with thing1
, thing2
, thing3
, ..., thingN
, the path forward is almost always an object or an array. For long if
-else
or switch
chains, the common refactor is to use an array or object of functions.
Scenes are essentially state machines, which are a perfect use-case for keyable/indexable objects or arrays of functions. If you're proceeding through your scenes in a stepwise manner, arrays are probably best since they're ordered and can be stepped through sequentially with an index or shift()
. See this answer for an approach to this.
If your scenes aren't quite so linear, giving them names rather than numbers might be a better way to navigate through them. Here's an example:
const scenes = {
loading: () => {
let ticks = 0;
frameRate(3);
mousePressed = () => {};
draw = () => {
clear();
text(`loading sceen, please wait${".".repeat(ticks % 4)}`, 50, 50);
if (++ticks > 10) {
frameRate(60);
scenes.menu();
}
};
},
menu: () => {
mousePressed = () => {
scenes.gamePlay();
};
draw = () => {
clear();
text("menu scene. click to play", 50, 50);
};
},
gamePlay: () => {
mousePressed = () => {
fill(0);
scenes.gameOver();
};
let x = 0;
fill(50, 50, 160);
draw = () => {
clear();
text("gameplay scene. click to go to game over", cos(x) * 20 + 50, 50);
x += 0.1;
};
},
gameOver: () => {
mousePressed = () => {
scenes.menu();
};
draw = () => {
clear();
text("game over scene. click to go to menu", 50, 50);
};
},
// ... more scenes ...
};
function setup() {
createCanvas(500, 100);
textSize(20);
}
function draw() {
scenes.loading();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
Naturally, you'll have more complex conditions for switching scenes and you probably won't be using the mouse to make the switch in many cases (game state or buttons are just as likely), but this example illustrates the idea that each scene will replace p5's window library functions with their own implementation in an event-driven manner, avoiding any if
s and keeping the code readable and modular. You can break the scenes into separate files quite easily.
The "setup" logic for each scene is baked in the top-level scenes.yourScene
function, which provides a nice closure for state that should persist between draw
calls without polluting other scenes. You can move variables out to a global or shared scope as necessary to persist data between scenes, such as an overall top score.
Although the relationships between scenes in the above example are simple, scenes can test different conditions to transition to any scene, such as separate win/loss scenes. The condition block that triggers the transition represents the cleanup/teardown logic for the exiting state. For very complex games and animations, nesting scenes and using combinations of arrays and objects for stepwise and state machine transitions respectively should be pretty workable.
It's often useful to draw a diagram showing the scenes in your app and which conditions trigger transitions. Optionally, write down which actions should be taken to set up and tear down each scene:
For example, the above app's scenes could be visualized like this:
.---------.
| loading |
`---------`
|
loaded
|
v
.------. .-----------.
| menu |--click-->| game play |
`------` `-----------`
^ |
| click
| |
click v
| .-----------.
+--------------| game over |
`-----------`
Lastly, if it bothers you to overwrite the draw
function that p5 knows about, you can always add a layer of indirection, flipping a local function between a few different options, then calling it from draw
in a way that's less invasive of p5:
const menuScene = () => {
if (someCondition) {
renderScene = gameScene;
}
// update and draw menu stuff
};
const gameScene = () => {
if (someCondition) {
renderScene = menuScene;
}
// update and draw game stuff
};
let renderScene = menuScene;
function draw() {
renderScene();
}
Without the closures, we lose the scene-specific setup code and local variables, but that's easy enough to reintroduce using a pattern similar to the one shown in the first example.
The same strategy works for setting handlers such as mouse and keyboard per scene.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |