Hello!
You are probably wondering how do you make projectiles like the birds in angry birds or the arrows in Minecraft or Bowmasters. This shows how to make throwable bouncy balls and an arrow shooting game!
Throwable Bouncy Balls
Let us start with this simple code:
var Ps = [];
var shootmode = 0;
var d = {x:0,y:0};
draw = function() {
background(255, 255, 255);
noStroke();
fill(230);
rect(0, 300, width, height);
ellipseMode(CENTER);
for(var i in Ps){
fill(0);
ellipse(Ps[i].x, Ps[i].y, 20, 20);
if(Ps[i].y+10 > 300){
Ps[i].y = 300-10;
Ps[i].ys = 0;
}
Ps[i].x +=Ps[i].xs/Ps[i].mass;
Ps[i].y +=Ps[i].ys/Ps[i].mass;
}
if(shootmode === 1){
stroke(0, 0, 0, 150);
strokeWeight(10);
line(d.x, d.y, mouseX, mouseY);
noStroke();
fill(0);
ellipse(mouseX, mouseY, 20, 20);
}
};
mouseClicked = function(){
if(shootmode === 0){
d = {x:mouseX,y:mouseY};
shootmode = 1;
return;
}
if(shootmode === 1){
Ps.push({xs:(d.x-mouseX),ys:(d.y-mouseY),x:mouseX,y:mouseY,mass:10});
shootmode = 0;
return;
}
};
There we go, try clicking and dragging so it aims towards the floor, right now it just slides with no bounce or anything. launch it towards the air, it just keeps going up, like in space. Let’s fix that.
First off, add this code after line 18, if you are following along, this code changes our y velocity by earth gravity acceleration divided by the frames per second and then multiplying by 10.
Ps[i].ys +=9.8/60*10;
Try launching the ball now! You will notice quite the difference. Try changing the mass on now line 37 (When it adds a new ball to the scene) and see what happens!
The ball still goes off the screen, it is easily fixable! Put this code after line 16.
if(Ps[i].x+10 > width){
Ps[i].x = width-10;
Ps[i].xs = -Ps[i].xs/1.5;
}
if(Ps[i].x-10 < 0){
Ps[i].x = 10;
Ps[i].xs = -Ps[i].xs/1.5;
}
if(Ps[i].y-10 < 0){
Ps[i].y = 10;
Ps[i].ys = -Ps[i].ys/1.5;
}
Now the ball will stay on the screen, but there is more! We still want the ground to have effects on the ball! replace the now line 15 with this:
Ps[i].xs *=0.8;
Ps[i].ys = -Ps[i].ys/2;
Try hitting the ball on the ground now! it will bounce half as high, and then again until there is no bounce at all. It will also slow down the x velocity when it rolls on the ground!
One last thing, the balls go thru each other! I have an interesting and cool solution!
Put this after line 32.
for(var j in Ps){
if(dist(Ps[i].x, Ps[i].y, Ps[j].x, Ps[j].y) < 20/2 && i !== j){
var R = atan2(Ps[j].x - Ps[i].x, Ps[j].y - Ps[i].y);
for(var k = 0;k < 10;k ++){
if(dist(Ps[i].x, Ps[i].y, Ps[j].x, Ps[j].y) < 20/2){
Ps[i].xs +=-sin(R+90)*(dist(Ps[i].x, Ps[i].y, Ps[j].x, Ps[j].y)-10/5);
Ps[i].ys -=-cos(R+90)*(dist(Ps[i].x, Ps[i].y, Ps[j].x, Ps[j].y)-10/5);
Ps[j].xs +=sin(R+90)*(dist(Ps[i].x, Ps[i].y, Ps[j].x, Ps[j].y)-10/5);
Ps[j].ys -=cos(R+90)*(dist(Ps[i].x, Ps[i].y, Ps[j].x, Ps[j].y)-10/5);
}
}
}
}
Be sure to use the tab key to space out your code so it doesn’t get messy! Try throwing some balls around, they start an endless chain reaction! How does this work you ask? Well… We go thru the balls again, check the distance between them and make sure that we aren’t calculating the same ball. Then find the direction that is opposite of the balls and move both balls in opposite directions of that direction! Whew, that was a lot to say, and our first project is done! Congrats! Give yourself a pat on the back! Okay, too much patting, BACK TO WORK! 😀
Arrow Shooter Game
Since there is a lot of code, I’ll put it all here.
var Scores = [
["the 1# planet proponent", 476],
];
Scores.sort(function(b, a){return a[1] - b[1];});
var Ps = [];
var shootmode = 0;
var d = {x:0,y:0};
var cameraX = 0;
var target = {x:random(500, 1000), y:random(40, 260), gx: 0, gy: 0};
target.gx = target.x;
target.gy = target.y;
var score = 0;
var scoreadd = 0;
var arrows = 10;
var movingaround = false;
var scoreY = 25;
var scoreTX = width*2;
var sS = 1;
var Keys = [];
keyPressed = function(){
Keys[keyCode] = true;
};
keyReleased = function(){
Keys[keyCode] = false;
};
draw = function() {
background(255, 255, 255);
noStroke();
fill(230);
rect(0, 300, width, height);
for(var i = -2;i < 5;i ++){ fill(200); if(i%2 === 0){ rect(-cameraX%200+i*100, 300, 100, height); } } if(shootmode === 2){ if(Ps[Ps.length-1].x-cameraX > 150){
cameraX +=((Ps[Ps.length-1].x-150)-cameraX)/5;
}
}
else{
if(Keys[LEFT] || Keys[RIGHT]){
movingaround = true;
}
if(!movingaround){cameraX +=(0-cameraX)/10;}
else{
if(Keys[LEFT]){
cameraX -=15;
}
if(Keys[RIGHT]){
cameraX +=15;
}
cameraX = cameraX < 0 ? 0 : cameraX; } } pushMatrix(); translate(-cameraX, 0); fill(0, 0, 0, 50); rect(0, 0, 250, height); pushMatrix(); translate(target.x+20, target.y); fill(207, 48, 48); rect(-10, -40, 22, 80); fill(235, 52, 52); rect(-10, -40, 16, 80); fill(245, 245, 245); rect(-8, -25, 12, 50); fill(235, 52, 52); rect(-7, -10, 8, 20); popMatrix(); for(var i in Ps){ if(Ps[i].m === 0){ Ps[i].r = 90-atan2((Ps[i].x+Ps[i].xs) - Ps[i].x, (Ps[i].y+Ps[i].ys) - Ps[i].y); } pushMatrix(); translate(Ps[i].x, Ps[i].y); rotate(Ps[i].r); fill(0); rect(-20, -2, 40, 4); triangle(20, -5, 30, 0, 20, 5); popMatrix(); if(Ps[i].y+10 > 300){
Ps[i].y = 300-10;
Ps[i].m = 1;
shootmode = 0;
}
if(Ps[i].x+10 > target.x && Ps[i].x+10 < target.x+20 && Ps[i].y > target.y-40 && Ps[i].y < target.y+40 && Ps[i].m === 0){
scoreadd +=50-floor(abs(Ps[i].y-target.y));
Ps[i].x = target.x-10;
Ps[i].m = 1;
shootmode = 0;
Ps[i].ontarget = true;
Ps[i].ty = -(target.y-Ps[i].y);
target.gx = random(500, 1000);
target.gy =random(40, 260);
}
if(Ps[i].ontarget){
Ps[i].x = target.x-10;
Ps[i].y = target.y+Ps[i].ty;
}
if(Ps[i].m === 0){
Ps[i].x +=Ps[i].xs/Ps[i].mass;
Ps[i].y +=Ps[i].ys/Ps[i].mass;
Ps[i].ys +=9.8/60*10;
}
}
if(shootmode === 1){
stroke(0, 0, 0, 150);
strokeWeight(10);
//line(d.x, d.y, mouseX, mouseY);
noStroke();
var R = 90-atan2(d.x - mouseX, d.y - mouseY);
pushMatrix();
translate(mouseX, mouseY);
rotate(R);
fill(0);
rect(-20, -2, 40, 4);
triangle(20, -5, 30, 0, 20, 5);
popMatrix();
var D = {
x: mouseX,
y: mouseY,
xs: (d.x-mouseX),
ys: (d.y-mouseY),
};
for(var i = 0;i < 6;i ++){
for(var j = 0;j < 10;j ++){
D.x +=D.xs/10;
D.y +=D.ys/10;
D.ys +=9.8/60*10;
}
noStroke();
fill(0, 0, 0, 50);
ellipse(D.x, D.y, 7, 7);
}
}
popMatrix();
fill(0);
textFont(createFont("Century Gothic Bold"));
pushMatrix();
translate(width/2, scoreY);
scale(sS);
textSize(50);
textAlign(CENTER, CENTER);
text(score, 0, 0);
popMatrix();
textSize(20);
textAlign(CENTER, TOP);
textFont(createFont("Verdana"));
text("Post your score in the tips and thanks!\nTop Scores:", scoreTX, 200);
for(var i = 0;i < Scores.length;i ++){
textSize(18);
text(Scores[i][1] + " - " + Scores[i][0], scoreTX, 252+i*20);
}
if(arrows <= 0){
scoreY +=(150-scoreY)/20;
sS +=(2-sS)/20;
scoreTX +=(width/2-scoreTX)/20;
}
for(var i = 0; i < arrows;i ++){ fill(0, 0, 0); rect(8+i*20, height-50, 4, 40); triangle(5+i*20, height-50, 10+i*20, height-60, 15+i*20, height-50); } if(scoreadd > 0){
score +=1;
scoreadd --;
}
target.x +=(target.gx-target.x)/10;
target.y +=(target.gy-target.y)/10;
};
mouseClicked = function(){
if(movingaround){movingaround = false;return;}
if(shootmode === 0 && mouseX < 250 && arrows > 0){
d = {x:mouseX,y:mouseY};
shootmode = 1;
return;
}
if(shootmode === 1 && mouseX < 250){
Ps.push({xs:(d.x-mouseX),ys:(d.y-mouseY),x:mouseX,y:mouseY,mass:10,r:0,m:0,ontarget:false,ty:0});
shootmode = 2;
arrows --;
return;
}
};
Man, that’s a lot to go thru!
First, we create and sort our scores!
var Scores = [
["the 1# planet proponent", 476],
];
Scores.sort(function(b, a){return a[1] - b[1];});
Then we set our variables
var Ps = [];
var shootmode = 0;
var d = {x:0,y:0};
var cameraX = 0;
var target = {x:random(500, 1000), y:random(40, 260), gx: 0, gy: 0};
target.gx = target.x;
target.gy = target.y;
var score = 0;
var scoreadd = 0;
var arrows = 10;
var movingaround = false;
var scoreY = 25;
var scoreTX = width*2;
var sS = 1;
We check if the keys are pressed
var Keys = [];
keyPressed = function(){
Keys[keyCode] = true;
};
keyReleased = function(){
Keys[keyCode] = false;
};
And then we have our backdrop and start of the draw function, with a moving ground
draw = function() {
background(255, 255, 255);
noStroke();
fill(230);
rect(0, 300, width, height);
for(var i = -2;i < 5;i ++){
fill(200);
if(i%2 === 0){
rect(-cameraX%200+i*100, 300, 100, height);
}
}
We have our camera movements
if(shootmode === 2){
if(Ps[Ps.length-1].x-cameraX > 150){
cameraX +=((Ps[Ps.length-1].x-150)-cameraX)/5;
}
}
And being able to look around.
else{
if(Keys[LEFT] || Keys[RIGHT]){
movingaround = true;
}
if(!movingaround){cameraX +=(0-cameraX)/10;}
else{
if(Keys[LEFT]){
cameraX -=15;
}
if(Keys[RIGHT]){
cameraX +=15;
}
cameraX = cameraX < 0 ? 0 : cameraX;
}
}
We have our shoot area and our target graphic:
pushMatrix();
translate(-cameraX, 0);
fill(0, 0, 0, 50);
rect(0, 0, 250, height);
pushMatrix();
translate(target.x+20, target.y);
fill(207, 48, 48);
rect(-10, -40, 22, 80);
fill(235, 52, 52);
rect(-10, -40, 16, 80);
fill(245, 245, 245);
rect(-8, -25, 12, 50);
fill(235, 52, 52);
rect(-7, -10, 8, 20);
popMatrix();
Our Arrows, We changed the ball to an arrow and made it follow it’s path. We took the bouncing, collisions and stuck to walls out of the code and make it stop instead only on the target or the floor. We also move the arrows with the target but only if they are attached. then we also move the targets goto spot and add points to our score based on how well we did.
for(var i in Ps){
if(Ps[i].m === 0){
Ps[i].r = 90-atan2((Ps[i].x+Ps[i].xs) - Ps[i].x, (Ps[i].y+Ps[i].ys) - Ps[i].y);
}
pushMatrix();
translate(Ps[i].x, Ps[i].y);
rotate(Ps[i].r);
fill(0);
rect(-20, -2, 40, 4);
triangle(20, -5, 30, 0, 20, 5);
popMatrix();
if(Ps[i].y+10 > 300){
Ps[i].y = 300-10;
Ps[i].m = 1;
shootmode = 0;
}
if(Ps[i].x+10 > target.x && Ps[i].x+10 < target.x+20 && Ps[i].y > target.y-40 && Ps[i].y < target.y+40 && Ps[i].m === 0){
scoreadd +=50-floor(abs(Ps[i].y-target.y));
Ps[i].x = target.x-10;
Ps[i].m = 1;
shootmode = 0;
Ps[i].ontarget = true;
Ps[i].ty = -(target.y-Ps[i].y);
target.gx = random(500, 1000);
target.gy =random(40, 260);
}
if(Ps[i].ontarget){
Ps[i].x = target.x-10;
Ps[i].y = target.y+Ps[i].ty;
}
if(Ps[i].m === 0){
Ps[i].x +=Ps[i].xs/Ps[i].mass;
Ps[i].y +=Ps[i].ys/Ps[i].mass;
Ps[i].ys +=9.8/60*10;
}
}
Then we also show our arrow and path when we are about to launch, we also end our translation of objects on the screen.
if(shootmode === 1){
stroke(0, 0, 0, 150);
strokeWeight(10);
//line(d.x, d.y, mouseX, mouseY);
noStroke();
var R = 90-atan2(d.x - mouseX, d.y - mouseY);
pushMatrix();
translate(mouseX, mouseY);
rotate(R);
fill(0);
rect(-20, -2, 40, 4);
triangle(20, -5, 30, 0, 20, 5);
popMatrix();
var D = {
x: mouseX,
y: mouseY,
xs: (d.x-mouseX),
ys: (d.y-mouseY),
};
for(var i = 0;i < 6;i ++){
for(var j = 0;j < 10;j ++){
D.x +=D.xs/10;
D.y +=D.ys/10;
D.ys +=9.8/60*10;
}
noStroke();
fill(0, 0, 0, 50);
ellipse(D.x, D.y, 7, 7);
}
}
popMatrix();
Now we show our score and the game over stuff if visible. We also show the high scores when needed.
fill(0);
textFont(createFont("Century Gothic Bold"));
pushMatrix();
translate(width/2, scoreY);
scale(sS);
textSize(50);
textAlign(CENTER, CENTER);
text(score, 0, 0);
popMatrix();
textSize(20);
textAlign(CENTER, TOP);
textFont(createFont("Verdana"));
text("Post your score in the tips and thanks!\nTop Scores:", scoreTX, 200);
for(var i = 0;i < Scores.length;i ++){
textSize(18);
text(Scores[i][1] + " - " + Scores[i][0], scoreTX, 252+i*20);
}
Then we show how many arrows we have left
if(arrows <= 0){
scoreY +=(150-scoreY)/20;
sS +=(2-sS)/20;
scoreTX +=(width/2-scoreTX)/20;
}
for(var i = 0; i < arrows;i ++){
fill(0, 0, 0);
rect(8+i*20, height-50, 4, 40);
triangle(5+i*20, height-50, 10+i*20, height-60, 15+i*20, height-50);
}
And change our score by one very quickly to make a cool effect. We also move our target nicely towards the goto point.
if(scoreadd > 0){
score +=1;
scoreadd --;
}
target.x +=(target.gx-target.x)/10;
target.y +=(target.gy-target.y)/10;};
And finally, not much has changed here except we have a few extra definitions and we change the number of arrows we have.
mouseClicked = function(){
if(movingaround){movingaround = false;return;}
if(shootmode === 0 && mouseX < 250 && arrows > 0){
d = {x:mouseX,y:mouseY};
shootmode = 1;
return;
}
if(shootmode === 1 && mouseX < 250){
Ps.push({xs:(d.x-mouseX),ys:(d.y-mouseY),x:mouseX,y:mouseY,mass:10,r:0,m:0,ontarget:false,ty:0});
shootmode = 2;
arrows --;
return;
}
};
Whew, that makes more sense! Thanks for watching or reading the tutorial! 😀