.png)
Link: https://editor.p5js.org/StonesGate604/sketches/Z9gIyIwkA
To be honest, I don't have any particularly good ideas; I'm not really a musically gifted person. But I happened to play a rhythm game yesterday, and I was thinking about whether I could use p5js with a music library to recreate it. So, it's still not in the creative stage yet. I hope to build a rough framework and then use that framework to do some expansion.
// ICM week 10 Assessment Zekai Wang
let lanes = 4;
let laneWidth;
let hitLineY = 650;
let notes = [];
let spawnInterval = 45;
let noteSpeed = 4;
let hitWindow = 50;
let oscillators = [];
let freqs = [261.63, 329.63, 392.0, 523.25]; // C4, E4, G4, C5
let keyToLane = {
D: 0,
F: 1,
J: 2,
K: 3,
};
function setup() {
createCanvas(600, 800);
laneWidth = width / lanes;
for (let i = 0; i < lanes; i++) {
let osc = new p5.Oscillator("sine");
osc.freq(freqs[i]);
osc.amp(0);
osc.start();
oscillators.push(osc);
}
textAlign(CENTER, CENTER);
textSize(24);
}
function draw() {
background(20);
drawLanes();
if (frameCount % spawnInterval === 0) {
spawnRandomNote();
}
for (let i = notes.length - 1; i >= 0; i--) {
notes[i].update();
notes[i].show();
if (notes[i].y > height + 50) {
notes.splice(i, 1);
}
}
stroke(255, 200, 0);
strokeWeight(3);
line(0, hitLineY, width, hitLineY);
noStroke();
fill(255);
textSize(20);
text("D", laneWidth * 0.5, hitLineY + 20);
text("F", laneWidth * 1.5, hitLineY + 20);
text("J", laneWidth * 2.5, hitLineY + 20);
text("K", laneWidth * 3.5, hitLineY + 20);
}
function drawLanes() {
stroke(80);
strokeWeight(2);
for (let i = 0; i < lanes; i++) {
let x = i * laneWidth;
line(x, 0, x, height);
}
}
function spawnRandomNote() {
let laneIndex = floor(random(lanes));
let x = laneIndex * laneWidth + laneWidth / 2;
let y = -20;
notes.push(new Note(x, y, laneIndex));
}
class Note {
constructor(x, y, laneIndex) {
this.x = x;
this.y = y;
this.laneIndex = laneIndex;
this.speed = noteSpeed;
this.hit = false;
}
update() {
this.y += this.speed;
}
show() {
noStroke();
if (this.hit) {
fill(0, 255, 0);
} else {
fill(0, 150, 255);
}
ellipse(this.x, this.y, laneWidth * 0.6, laneWidth * 0.6);
}
}
function keyPressed() {
if (key === "s") saveCanvas("myPicture", "png");
let keyUpper = key.toUpperCase();
console.log("keypressed");
if (keyUpper in keyToLane) {
let laneIndex = keyToLane[keyUpper];
handleHit(laneIndex);
}
}
function handleHit(laneIndex) {
let closestNote = null;
let closestDistance = Infinity;
for (let n of notes) {
if (n.laneIndex === laneIndex && !n.hit) {
let d = abs(n.y - hitLineY);
if (d < closestDistance) {
closestDistance = d;
closestNote = n;
}
}
}
if (closestNote && closestDistance <= hitWindow) {
closestNote.hit = true;
console.log("hitted");
playLaneSound(laneIndex);
}
}
function playLaneSound(laneIndex) {
let osc = oscillators[laneIndex];
osc.amp(0.4, 0.01);
osc.amp(0, 0.2);
}
This code was completed with the help of chatGPT. Because I didn't have relevant development skills before, I asked chat to provide me with a rhythm game framework, and then I made some modifications to this framework. However, I made quite a few changes, so I won't indicate which parts of the code were provided by chatGPT. Next, I will introduce the basic principles and structure of this code.
function draw() {
background(20);
drawLanes();
if (frameCount % spawnInterval === 0) {
spawnRandomNote();
}
for (let i = notes.length - 1; i >= 0; i--) {
notes[i].update();
notes[i].show();
if (notes[i].y > height + 50) {
notes.splice(i, 1);
}
}
function spawnRandomNote() {
let laneIndex = floor(random(lanes));
let x = laneIndex * laneWidth + laneWidth / 2;
let y = -20;
notes.push(new Note(x, y, laneIndex));
}
Then, each note's class indicates which row it belongs to, and its vertical coordinate position is updated in each draw.
class Note {
constructor(x, y, laneIndex) {
this.x = x;
this.y = y;
this.laneIndex = laneIndex;
this.speed = noteSpeed;
this.hit = false;
}
update() {
this.y += this.speed;
}
show() {
noStroke();
if (this.hit) {
fill(0, 255, 0);
} else {
fill(0, 150, 255);
}
ellipse(this.x, this.y, laneWidth * 0.6, laneWidth * 0.6);
}
}
Finally, and most importantly, is determining whether a note has been hit. Chat's method is to trigger it by pressing a key. When I press a key, the program binds it to the track that matches that key and calculates the distance between the nearest note on the track and the strike line. If the distance is less than the value I set, then it has been hit, and a sound is emitted.
function keyPressed() {
if (key === "s") saveCanvas("myPicture", "png");
let keyUpper = key.toUpperCase();
console.log("keypressed");
if (keyUpper in keyToLane) {
let laneIndex = keyToLane[keyUpper];
handleHit(laneIndex);
}
}
function handleHit(laneIndex) {
let closestNote = null;
let closestDistance = Infinity;
for (let n of notes) {
if (n.laneIndex === laneIndex && !n.hit) {
let d = abs(n.y - hitLineY);
if (d < closestDistance) {
closestDistance = d;
closestNote = n;
}
}
}
As for how to develop it next, I really haven't thought it through. Maybe we'll introduce more buttons and tracks to make the notes generate and fall faster? Or maybe we'll just make it look better visually? Or perhaps we'll add background music and make the notes match the beat of the background music.
let oscillators = [];
let freqs = [261.63, 329.63, 392.0, 523.25];
Store four oscillator objects and the pitches of the four tracks.
let osc = new p5.Oscillator("sine");
osc.freq(freqs[i]);// set Hz
osc.amp(0);//set sound volume
osc.start();
oscillators.push(osc);