Adding a mini game to my visual novel

Hi everyone!
I am working on a visual novel game project and I wanted to add a mini game(flappy birds) as a seperate challenge to enter the next chapter to the story. I am struggling with adding the game code that my teammate send me to my main code since they are 2 seperate classes.
We also wanted to change the some details for example setting a maximum score of 10 points and if that is reached the story is going to continue in the next chapter. I’ve tried to integrate the flappy birds code and the ideas into my main code a couple of times but i cant get it to work the way we imagined it. If you have any idea what i could change please let me know!
(we called flappy birds aquabounce since the game is supposed to be ocean themed also the rest of my chapters arent written yet)
Here is my main code:
Game game;

void setup()
{
size(1280, 720);
game = new Game();

}

void draw()
{
game.update();
}
/*void mousePressed()
{
game.mousePressed();
}
*/
void keyPressed()
{
game.keyPressed();
}

my game code:
class Game
{
//Einzelne Szenen
PImage qualle;
PImage meer;
boolean message1 = false;
boolean message2 = false;
boolean message3 = false;
boolean message4 = false;
boolean message5 = false;
boolean message6 = false;
boolean message7 = false;
boolean message8 = false;
boolean message9 = false;
boolean message10 = false;
private int gameState = 0;
private int messageCount=0;

//buttons
int rectX=1020, rectY=650;
int circleX=1100, circleY=50;
int rectSize=70;
int circleSize=70;
int currentColor;
color rectColor=color(165,137,193),circleColor=color(165,137,193);
color rectHighlight=color(221,212,232), circleHighlight=color(221,212,232);
boolean backHover = false;
boolean skipHover = false;
boolean homeHover = false;

//Sprechblasen
String nameL;
String nameR;
String messageL;
String messageR;
int index = 0;
int xL=200;
int yL=500;
int bubblewidthL=650;
int bubbleheightL=150;
int xR=1080;
int yR=500;
int bubblewidthR=-650;
int bubbleheightR=150;

Game()
{

qualle = loadImage("Qualle.png");
meer = loadImage("OffenesMeer.png");
for(int i = 0;i<3;i++){
p[i]=new pillar(i);

}
}
public void update()
{
background(#8BBCE5);

switch(this.gameState)
{
case 0:
  this.startScreen();
  break;
case 1:
  this.gameLoop();
  break;
  case 2:
  this.chapter1();
  break;
  case 3:
  this.aquabounce();
  break;
  case 4:
  this.chapter2();
  break;
  case 5:
  this.quiz();
  break;
  case 6:
  this.chapter3();
  break;
case 7:
  this.endScreen();
  break;
}

}

private void startScreen()
{
stroke(0);
textAlign(CENTER);
textSize(15);
fill(255);
text(“Willkommen bei Jellyfish!”, width/2, height/2);
text(“Spiel starten mit ‘s’”, width/2, height/2+30);
}

//Die BUTTONS//

private void homeButton(){
update(mouseX, mouseY);
if(homeHover){
cursor(HAND);
fill(circleHighlight);
} else {
fill(circleColor);
cursor(ARROW);
}
ellipse(circleX,circleY,circleSize,circleSize);
textSize(20);
fill(0);
text(“Home”,circleX,circleY+3);
if(mousePressed&& mouseoverHome(circleX, circleY, circleSize)==true){
setup();
}

}
private void backButton(){
update(mouseX, mouseY);
if (backHover) {
cursor(HAND);
fill(rectHighlight);
} else {
cursor(ARROW);
fill(rectColor);
}
rect(rectX,rectY,rectSize,rectSize-30);
textSize(25);
fill(0);
text(“Back”,rectX+30,rectY+27);
if(mousePressed&& mouseoverBack(rectX,rectY,rectSize,rectSize)==true){
messageCount–;
index=0;
}
}

private void skipButton(){
update(mouseX, mouseY);
if (skipHover) {
fill(rectHighlight);
cursor(HAND);
} else {
cursor(ARROW);
fill(rectColor);
}
rect(rectX+100,rectY,rectSize,rectSize-30);
textSize(25);
fill(0);
text(“Skip”,rectX+130,rectY+27);
if(mousePressed&& mouseoverSkip(rectX,rectY,rectSize,rectSize)==true){
messageCount=+2;
index=0;
}
}

//Die TEXTBUBBLES//

private void textbubbleLeft(String nameL, String messageL, int xL, int yL, int bubblewidthL, int bubbleheightL)
{
noStroke();
fill(255);
rectMode(CORNER);
rect(xL,yL, bubblewidthL, bubbleheightL);
stroke(0);
fill(0);
textSize(20);
text(nameL,xL +30, yL+30);
textSize(15);
if(mousePressed){
index=messageL.length();
}else
{
text(messageL.substring(0,min(index,messageL.length())), xL + 200, yL + 60);
}
if (frameCount % 5 == 0) {
index = min(index+1, messageL.length());

}
}
private void textbubbleRight(String nameR, String messageR, int xR, int yR, int bubblewidthR, int bubbleheightR)
{
noStroke();
fill(255);
rectMode(CORNER);
rect(xR, yR, bubblewidthR, bubbleheightR);
stroke(0);
fill(0);
textSize(20);
text(nameR, xR-600,yR+30);
textSize(15);
if(mousePressed){
index=messageR.length();
}else
{
text(messageR.substring(0,min(index,messageR.length())), xR -400, yR + 50);
}
if (frameCount % 5 == 0) {
index = min(index+1, messageR.length());
}
}

//CHAPTERS//

private void chapter1()
{

background(meer);
if(messageCount==0){

image(qualle,30,350,250,250);
textbubbleLeft(“Jamz”,“Hi Frieda! Was schwimmt?”,xL,yL,bubblewidthL,bubbleheightL);
homeButton();
backButton();
skipButton();
}
else if(messageCount==1){
fill(255);
ellipse(1180,400,50,50);
textbubbleRight(“Frieda”,“Oh ja…Du glaubst nicht, was hier passiert ist…”,xR, yR, bubblewidthR, bubbleheightR);
homeButton();
backButton();
skipButton();
}
else if(messageCount==2){
image(qualle,30,350,250,250);
textbubbleLeft(“Jamz”,“Hmmm…Hau raus!Was liegt dir auf dem Herzen?”,xL,yL,bubblewidthL,bubbleheightL);
homeButton();
backButton();
skipButton();
}
else if(messageCount==3){

fill(255);
ellipse(1180,400,50,50);
textbubbleRight("Frieda","Irgendein Raudi randaliert bei Tonys Haus!",xR, yR, bubblewidthR, bubbleheightR);
 homeButton();
backButton();
skipButton();

}
else if(messageCount==4){

fill(255);
ellipse(1180,400,50,50);
textbubbleRight("Frieda","Komm schnell! Wir brauchen deine Hilfe!!",xR, yR, bubblewidthR, bubbleheightR);
}
 else if(messageCount==5){

image(qualle,30,350,250,250);
textbubbleLeft(“Jamz”,“Alles klar! Ich mache mich auf den Weg!”,xL,yL,bubblewidthL,bubbleheightL);
homeButton();
backButton();
skipButton();
}
else if(messageCount==6){
fill(255);
ellipse(1180,400,50,50);
textbubbleRight(“Frieda”,“Astrein! Bis denne Antenne!”,xR, yR, bubblewidthR, bubbleheightR);
homeButton();
backButton();
skipButton();
}
else if(messageCount==7){
stroke(255);
textAlign(CENTER);
textSize(25);
fill(255);
text(“Nächstes Kapitel mit ‘s’”, width/2, height/2-50);

}
}

//AQUABOUNCE//

private void aquabounce()
{
background(0);
if(end){
b.move();
}
b.drawBird();
if(end){
b.drag();
}
b.checkCollisions();
for(int i = 0;i<3;i++){
p[i].drawPillar();
p[i].checkPosition();
}
fill(0);
stroke(255);
textSize(32);
if(end){
rect(20,20,100,50);
fill(255);
text(score,30,58);
}else{
rect(150,100,200,50);
rect(150,200,200,50);
fill(255);
if(intro){
text(“Flappy Code”,155,140);
text(“Click to Play”,155,240);
}else{
text(“game over”,170,140);
text(“score”,180,240);
text(score,280,240);
}
}

     if(endEnd==true){
    background(0,255,255);
    textAlign(CENTER);
    textSize(20);
    text("Congratulations!!!", width/2, height/2);
  
  }

  if(mousePressed){
 b.jump();
 intro=false;
 if(end==false){
   reset();
 }
}
if(keyPressed){
 b.jump(); 
 intro=false;
 if(end==false){
   reset();
 }
}

}

private void chapter2()
{
background(meer);
if(messageCount==0){
fill(255);
ellipse(120,530,50,50);
textbubbleLeft(“Jelly”,“kennt man sich”,xL,yL,bubblewidthL,bubbleheightL);
}
}

private void quiz()
{
background(255);
}

private void chapter3()
{
background(0);
}

private void endScreen()
{
stroke(255);
textAlign(CENTER);
textSize(15);
text(“GAME OVER!”, width/2, height/2);
text(“Spiel neustarten mit ‘r’”, width/2, height/2+30);
}

private void gameLoop()
{

}

//BUTTONS MOUSE INTERAKTION//

void update(int x, int y) {
if ( mouseoverHome(circleX, circleY, circleSize) ) {
homeHover = true;
backHover = false;
skipHover = false;
} else if ( mouseoverBack(rectX, rectY, rectSize, rectSize) ) {
backHover = true;
skipHover = false;
homeHover = false;
} else if ( mouseoverSkip(rectX+100,rectY,rectSize,rectSize-30) ) {
backHover = false;
skipHover = true;
homeHover = false;
} else {
homeHover = backHover =skipHover= false;
}

}

boolean mouseoverHome(int x, int y, int diameter){
float distance = dist(x, y, mouseX, mouseY);
if (distance < diameter / 2) {
return true;
} else {
return false;
}
}

boolean mouseoverBack(int x, int y, int width, int height){
if (mouseX >= x && mouseX <= x+width &&
mouseY >= y && mouseY <= y+height) {
return true;
} else {
return false;
}
}
boolean mouseoverSkip(int x, int y, int width, int height){
if (mouseX >= x && mouseX <= x+width &&
mouseY >= y && mouseY <= y+height) {
return true;
} else {
return false;
}
}

//KEY CONTROLLS//

public void keyPressed()

{
if (key == ‘s’)
{
this.gameState++;
messageCount=0;
}

if(key==CODED&&keyCode==RIGHT)
{
  this.messageCount++;
  index=0;
}
 if(key==CODED&&keyCode==LEFT)
{
  this.messageCount--;
  index=0;
}


if (key == 'r' && this.gameState == 7)
{
  setup ();
  
 // messageCount++;
}

}

}

and the flappy birds code:
PImage qualle;

bird b = new bird();
pillar p = new pillar[3];
boolean end=false;
boolean endEnd = false;
boolean intro=true;
int score=0;

class bird{
 
  float xPos,yPos,ySpeed;
bird(){
xPos = 250;
yPos = 400;
}
void drawBird(){
   
  stroke(255);
  noFill();
  strokeWeight(2);
  ellipse(xPos,yPos,20,20);

}
void jump(){
 ySpeed=-10; 
}
void drag(){
 ySpeed+=0.4; 
}
void move(){
 yPos+=ySpeed; 
 for(int i = 0;i<3;i++){
  p[i].xPos-=3;
 }
}
void checkCollisions(){
 if(yPos>800){
  end=false;
 }
for(int i = 0;i<3;i++){
if((xPos<p[i].xPos+10&&xPos>p[i].xPos-10)&&(yPos<p[i].opening-100||yPos>p[i].opening+100)){
 end=false; 
}
}
} 
}
class pillar{
  float xPos, opening;
  boolean cashed = false;
 pillar(int i){
  xPos = 100+(i*200);
  opening = random(600)+100;
 }
 void drawPillar(){
   line(xPos,0,xPos,opening-100);  
   line(xPos,opening+100,xPos,800);
 }
 void checkPosition(){
  if(xPos<0){
   xPos+=(200*3);
   opening = random(600)+100;
   cashed=false;
  } 
  if(xPos<250&&cashed==false){
   cashed=true;
   score++; 
  
 }
 }
}
void reset(){
 end=true;
 score=0;
 b.yPos=400;
 for(int i = 0;i<3;i++){
  p[i].xPos+=550;
  p[i].cashed = false;
 }
}

void endEnd(){
   if (score >= 2) {
endEnd = true; 

}

}

with kind regards,
madita

1 Like

The flappy birds game might be a challenge to the reader but creating a multi-chapter video novel game that incorporates actual games is a serious challenge for the programmer.

Many programmers start by creating an application and then decide - oh wouldn’t it be nice to add that functionality only to realise that to do that

  • it requires major changes to existing code
  • the code becomes a rats nest of ugly code fixes to get the new functionality to work
  • multiple dependencies between unrelated parts of the code make writing safe code almost impossible.

What you really need is some code framework to act as scaffolding for your functionality so that you can add chapters and games without worrying that it will break code you have already created.

The sketch below demonstrates just that and creates 2 pages that you can move between. Note each ‘page’ is its own class so it can create its own fields/variables isolating itself.

Although this code uses OO (object orientation) which you might not be familiar with I strongly recommend you experiment with this code by adding your own simple pages. The time taken to master this code will save you a lot of time and heartache later.

If you have questions about the code just ask here. :grin:

Scene s1, s2, s3;
Scene currScene;

public void setup() {
  size(640, 480, P2D);
  surface.setTitle("Welcom to my video book");
  surface.setResizable(true);
  // Create the scenes
  s1 = new BookStart();
  s2 = new Chapter1();
}

public boolean setScene(Scene scene){
  if(scene != null && scene != currScene){
    if(currScene != null) currScene.exit();
    currScene = scene;
    currScene.enter();
    return true;
  }
  return false;
}

/*
These standard Processing methods simply forward actions and events to the current
scene if it exists. If you add other event handlers then make sure you updtae the
class Scene with a matching empty
*/
public void draw() { 
  // The next line is a fix for using with P2D provided by @glv
  // but leave it in even for JAVA2D
  if(frameCount == 1) setScene(s1);
  if(currScene != null) currScene.draw();
}

public void mouseMoved() { if(currScene != null) currScene.draw(); }
public void mouseClicked() { if(currScene != null) currScene.mouseClicked(); }
public void mousePressed() { if(currScene != null) currScene.mousePressed(); }
public void mouseReleased() { if(currScene != null) currScene.mouseReleased(); }
public void mouseDragged() { if(currScene != null) currScene.mouseDragged(); }

public void keyPressed(){ if(currScene != null) currScene.keyPressed(); }
public void keyReleased(){ if(currScene != null) currScene.keyReleased(); }
public void keyTyped(){ if(currScene != null) currScene.keyTyped(); }

/*
This is the base class for all scenes and simply provides empty functions 
and event handlers for scenes that do not need them.
*/
abstract class Scene {
  public Scene(){}         // Equivalent to setup methid i.e. executed once
  public void enter() { }  // initialise any data for this scene
  public void exit() { }   // cleanup code when we leave this scene

  public void draw() { }
  public void mouseMoved() { }
  public void mouseClicked() { }
  public void mousePressed() { }
  public void mouseReleased() { }
  public void mouseDragged() { }

  public void keyPressed(){ }
  public void keyReleased(){ }
  public void keyTyped(){ }

}

class BookStart extends Scene{
  
  String text;
  
  public BookStart(){
     this.text = "The Princess in the Tower\n by \nQuark";
  }
  
  public void enter() {
    surface.setTitle("Front Cover");
    windowResize(500,300);

    textSize(40);
   
  }
  
  public void draw(){
    background(255,255,200);
    fill(0,0,200);
    textAlign(CENTER, CENTER);
    textSize(40);
    text(this.text, 0, 0, width, height);
    textSize(16);
    text("Click for next scene", 0, height-40, width,40);
  }
  
  public void mouseClicked(){
    setScene(s2);
  }
}


class Chapter1 extends Scene{
  
  String text;
  
  public Chapter1(){
    this.text = "Once upon a time a beautiful pricess who \n";
    this.text += "was held captive in the tallest tower \nin a ";
    this.text += "far distant\n land.\n\nOf couse there is a happy ending.";
  }
  
  public void enter() {
    surface.setTitle("Chapter 1");
    windowResize(400, 600);
  }
  
  public void draw(){
    background(0,255,255);
    fill(200, 0, 0);
    textAlign(LEFT, TOP);
    textSize(20);
    text(this.text, 30, 10, width-20, height-60);
    textAlign(CENTER, CENTER);
    textSize(16);
    text("Click to return to start", 0, height-40, width,40);
  }
  
  public void mouseClicked(){
    setScene(s1);
  }
}
2 Likes

Surprised here.

Typo or intentionally?

2 Likes

Thanks for spotting that @Chrisir - the cardinal sin of copy and paste LOL

Rather than re-post the code I have amended the code above and also made changes that came to me when I woke this morning.

The changes involve adding constructors to the scene classes to take the role of the setup method because they are both executed once only e.g. load images and other resources.

Now the enter method is simply used to initialise existing class fields when the scene is chosen for display.

2 Likes

Hello @quark,

Your example does not work as expected here…
I am using W10 and Processing 4.3.

Using:
size(640, 480);

It starts with this :

image

And ends with this on a mouseClicked():

A subsequent mouseClicked() does not change anything.

I did have some success with:
size(640, 480, P2D);

There was a long pause before the first screen… 6457 ms and a long winded error related to waiting over 5000:
java.lang.RuntimeException: Waited 5000ms < Not the entire console display!

Starting the scene in draw() instead of setup() (with P2D) worked with this and no delay:

if (frameCount == 1) 
  {
  setScene(s1);
  t1 = millis();
  println(t1-t0);
  }

I have seen the above delays before and that is my workaround.

Result:

image

And they toggled back and forth as expected.

:)

@glv thanks for finding that problem I must admit I never tested it in P2D - obviously :wink:

I have incorporated your fix with credit in the code above. Your solution works in JAVA2D and P2D so I recommend leaving it as a permanent part of the code.

Hello again!

It did not work with JAVA2D on my PC with W10 and Processing 4.3.

Adding a delay solved the issue here:

public boolean setScene(Scene scene){
  if(scene != null && scene != currScene){
    if(currScene != null) currScene.exit();
    currScene = scene;
    currScene.enter();
    delay(2);  // GLV added!
    return true;
  }

Note:
I kept the delay to a minimum.
I still see a flash of a frame (or more?) that is resized in the transition between scenes:

image

I increased to a delay(3000) to see these frames switching between scenes and to capture them.

Related topics:

:)

I have tested the code in my first post above on

  • iMac osx 14.2.1 with Processing 4.3
  • PC laptop Windows 10 with Processing 4.3

using JAVA2D (default), P2D and P3D renderers and in all cases the sketch performed as expected without error.

@glv found the original problem and fix which was great but I have not needed the delay mentioned in the last post.

I believe that the issue is related to resizing the sketch window in Windows OS. So if you are using Windows or want to export to windows then I suggest you test the code above on your machine before committing yourself to using it.

An alternative is to simply create the sketch with a fixed size window.

1 Like

Hello,

It seems to be just my PC at home that is doing this.

I did not have any issues on a W10 laptop at work and did not need a delay.

@quark Thanks for the code example!

:)

1 Like