Midterm Project – “Ice&Glasses” by Marcos Hernández

Introduction

I think I might have liked challenges since I was a kid, and the idea of trying to do something different and challenging was very compelling.

During the weeks that passed in the I.M. lab, even though some of my works seemed a bit lacking in art, I was mostly focused on the technicality of things. For example, in one assignment I was experimenting with using the mouse as a medium of interactivity, and in the other I was experimenting with the concept of “gravity and physics”. When I noticed that what I was trying to do was possible, then I focused myself on delivering the best I could in terms of what I had learned so far. Thus, presenting “Ice&Glasses”, an interactable piece of art, and also a game that focuses on delivering the player with a good auditory and visual experience with the use of nostalgic-like art and a heavy focus on the use of the mouse.

Full-screen link:  Ice&Glasses in fullscreen

Concept and Inspiration

As mentioned, thanks to the experimentation I did in previous assignments, I came to the realization that, most likely, everything that I imagined was possible to apply to it, but obviously, it would require a lot of time to apply.

The whole concept of the game is not inspired in reality by other mediums, but rather by something that came into my mind while I was doing Assignment #4, in which I wanted to simulate some diamonds falling as to represent some data; in reality, it looked more like ice cubes… Sadly, that simulation was an illusion since it only took into account some distance and then stopped when it reached it.

When I thought about ice cubes, I had a weird realization. When I thought about ice cubes, the first things that came to mind were ice cubes and glasses. I searched for it on Google and came across this A.I. picture:

oil painting of two empty transparent cups on a table

After seeing this, I knew what I wanted to do.

With this idea in my mind, I wanted to actually create the illusion of physics and proper gravity in the game with the use of ice cubes and cup glasses. I had the code; it was only a matter of experimenting enough to find a way to implement this without causing massive slowdowns in the computer due to the amount of “if” conditions it would require.

The code

a. Overview
In summary, it is a lot of code.

It is easily the largest code I have ever created at the moment, and I am really proud of the outcome. Some of the highlights are the following:

1. It has a system to use the mouse without it going too spammy.

function mouseReleased() {
  if (mouse.called == 1) {
    mouse.called = 0;
    if (cubes[mouse.grabbing] == null) {
    } else {
      cubes[mouse.grabbing].isgrabbed = 0;
    }
  }

  //Allows to exit the game and avoid array issues.
  mouse.grabbing = "nothing";
}

2. A collision system.

function checkCollision(i, c) {
  //Check collition in X axis.
  if (
    cubes[i].y + cubes[i].h > cubes[c].y &&
    cubes[i].y < cubes[c].y + cubes[c].h
  ) {
    if (cubes[i].type == "ice") {
      if (cubes[i].insidecup == 1) {
        if (
          cubes[i].x < cubes[c].x &&
          cubes[i].x > cubes[c].x - 25 &&
          cubes[c].type == "glass"
        ) {
          cubes[i].hit_x_r();
        }
        if (
          cubes[i].x + cubes[i].w > cubes[c].x + cubes[c].w &&
          cubes[i].x + cubes[i].w < cubes[c].x + cubes[c].w + 25 &&
          cubes[c].type == "glass"
        ) {
          cubes[i].hit_x_l();
        }
        if (
          cubes[i].x + cubes[i].w > cubes[c].x &&
          cubes[i].x + cubes[i].w < cubes[c].x + 8 &&
          cubes[c].type == "ice"
        ) {
          cubes[i].hit_x_l();
        }
        if (
          cubes[i].x < cubes[c].x + cubes[c].w &&
          cubes[i].x > cubes[c].x + cubes[c].w - 8 &&
          cubes[c].type == "ice"
        ) {
          cubes[i].hit_x_r();
        }
      } else if (cubes[i].type == "ice" && cubes[i].insidecup == 0) {
        if (cubes[c].type == "ice") {
          if (
            cubes[i].x + cubes[i].w >= cubes[c].x &&
            cubes[i].x + cubes[i].w <= cubes[c].x + 25
          ) {
            cubes[c].hit_x_r();
          } else if (
            cubes[i].x <= cubes[c].x + cubes[c].w &&
            cubes[i].x >= cubes[c].x + cubes[c].w - 25
          ) {
            cubes[c].hit_x_l();
          }
        } else if (cubes[c].type == "glass") {
          if (
            cubes[i].x + cubes[i].w >= cubes[c].x &&
            cubes[i].x + cubes[i].w <= cubes[c].x + 15
          ) {
            cubes[c].hit_x_r();
          } else if (
            cubes[i].x <= cubes[c].x + cubes[c].w &&
            cubes[i].x >= cubes[c].x + cubes[c].w - 15
          ) {
            cubes[c].hit_x_l();
          }
        }
      }
    }

    if (cubes[i].type == "glass") {
      if (cubes[c].type == "ice") {
        if (
          cubes[i].x + cubes[i].w >= cubes[c].x - 3 &&
          cubes[i].x + cubes[i].w <= cubes[c].x + 10 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_r();
        } else if (
          cubes[i].x <= cubes[c].x + cubes[c].w + 3 &&
          cubes[i].x >= cubes[c].x + cubes[c].w - 10 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_l();
        }
      } else if (cubes[c].type == "glass") {
        if (
          cubes[i].x + cubes[i].w >= cubes[c].x - 3 &&
          cubes[i].x + cubes[i].w <= cubes[c].x + 40 &&
          cubes[c].isgrabbed == 0 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_r();
        } else if (
          cubes[i].x <= cubes[c].x + cubes[c].w + 3 &&
          cubes[i].x >= cubes[c].x + cubes[c].w - 40 &&
          cubes[c].isgrabbed == 0 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_l();
        }
      }
    }
  }

  //Check collition in Y axis.
  if (
    cubes[i].x <= cubes[c].x + cubes[c].w &&
    cubes[i].x + cubes[i].w >= cubes[c].x
  ) {
    if (cubes[i].type == "ice") {
      if (
        cubes[i].y + cubes[i].h > cubes[c].y &&
        cubes[i].y + cubes[i].h < cubes[c].y + 15
      ) {
        cubes[c].hit_y_d();
      }
      if (cubes[c].type == "ice") {
        if (
          cubes[i].y > cubes[c].y &&
          cubes[i].y < cubes[c].y + cubes[c].h + 1
        ) {
          //To avoid any issues with clipping, this makes sure that every block that is inside the cup that is above it, it will share the "inside of" property.
          index2 = cubes.indexOf(cubes[c]);
          index3 = cubes.indexOf(cubes[i]);

          if (index2 > -1) {
            if (cubes[index2].insidecup == 1) {
              cubes[index3].insidecup = 1;
            }
          }

          cubes[c].hit_y_u();
        }
      }
      if (cubes[c].type == "glass") {
        if (
          cubes[i].y + 45 > cubes[c].y + cubes[c].h &&
          cubes[i].y < cubes[c].y + cubes[c].h &&
          cubes[c].x + cubes[c].w > cubes[i].x + 10 &&
          cubes[c].x < cubes[i].x + cubes[i].w - 10 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_y_u();
        }
      }
    }
    if (cubes[i].type == "glass") {
      if (
        cubes[i].y + cubes[i].h > cubes[c].y &&
        cubes[i].y + cubes[i].h < cubes[c].y + 15 &&
        cubes[c].insidecup == 0 &&
        cubes[c].type == "ice"
      ) {
        cubes[c].hit_y_d();
      }
      if (cubes[c].type == "ice") {
        if (
          cubes[c].y > cubes[i].y &&
          cubes[c].y + cubes[c].h < cubes[i].y + cubes[i].h - 90 &&
          cubes[c].x + cubes[c].w > cubes[i].x + 25 &&
          cubes[c].x < cubes[i].x + cubes[i].w - 25 &&
          cubes[c].insidecup == 0
        ) {
          //Find who cube index is inside. Also, this is really necessary to fix a bug with the cubes going out of the glass cup.
          index2 = cubes.indexOf(cubes[c]);

          if (index2 > -1) {
            cubes[index2].insidecup = 1;
          }
        } else if (
          ((cubes[c].y > cubes[i].y - 20 && cubes[c].y < cubes[i].y - 2) ||
            (cubes[c].y > cubes[i].y + cubes[i].h + 1 &&
              cubes[c].y < cubes[i].y + cubes[i].h + 10)) &&
          cubes[c].insidecup == 1
        ) {
          //Find who cube index is outside. Also, this is really necessary to fix a bug with the cubes going out of the glass cup.
          index2 = cubes.indexOf(cubes[c]);

          if (index2 > -1) {
            cubes[index2].insidecup = 0;
          }
        }

        if (
          cubes[i].y + cubes[i].h > cubes[c].y &&
          cubes[i].y + cubes[i].h - 30 < cubes[c].y + cubes[c].h &&
          cubes[c].insidecup == 1
        ) {
          cubes[c].hit_y_u();
        }
      }

      if (cubes[c].type == "glass") {
        if (
          cubes[c].y + cubes[c].h > cubes[i].y &&
          cubes[c].y + cubes[c].h < cubes[i].y + 150
        ) {
          cubes[c].hit_y_u();
        }
      }
    }
  }

  //Check collition with table.
  if (
    cubes[c].y + cubes[c].h > scenario_table.y &&
    cubes[c].y + cubes[c].h < scenario_table.y + 60 &&
    cubes[c].x + cubes[c].w > scenario_table.x &&
    cubes[c].x < scenario_table.x + scenario_table.w
  ) {
    cubes[c].hit_y_u();
  } else {
    cubes[c].tempy = 1000; //Send to deletion.
  }
}

3. A gravity and acceleration system.

function Gravity(i) {
  if (cubes[i].y + cubes[i].h < cubes[i].tempy) {
    cubes[i].gravity();
  }
}

The function is the following in Cubes.js:

gravity() {
  //14 seems more than enough to simulate gravity and acceleration.
  if (this.acceleration < 14) {
    this.acceleration++;
  }
  this.y += this.acceleration;
}

4. A code that helps keeps variety in the ice cubes (that means, from a single .png have different outcomes like different sizes and angles)

display() {
    //Push() and pop() isolates the figure properties.
    push();
    if (this.type == "ice") {
      //Different rotations for the same cube.
      if (this.rotated == 0) {
        translate(this.x, this.y);
        rotate(0);
      } else if (this.rotated == 1) {
        translate(this.x + 30, this.y + 2);
        rotate(90);
      } else if (this.rotated == 2) {
        translate(this.x + 30, this.y + 30);
        rotate(180);
      } else if (this.rotated == 3) {
        translate(this.x - 2, this.y + 30);
        rotate(270);
      }
      image(cubeimg, 0, 0, this.w, this.h);
    }

b. The separation of classes

The code is divided into the following way:

  • Cubes.js (Has the logic for the ice cubes and glasses).
  • Levels.js (Originally started as a set of levels, it is now a set of rules established in order to ensure that there are gameplay elements).
  • Menu.js (It contains the start menu, tutorial, credits, selections, information and winning or losing screens).
  • UI.js (It is the information the player will not only see but be able to interact with. For example, it displays how many ice cubes are left and an icon of a door to return to the start menu).
  • Scenario.js (it has the code that allows the display of the scenario and the logic of the spawners).
  • Mouse.js (Created with the sole purpose of allowing a better use of the mouse and its events, like also allowing to store data in its class).

I also divided the graphics and sounds into their respective folders, since there were a lot of them.

c. Difficulties

The collision system took a lot of time, easily around 10 hours, and I discovered a good method by just accident since I was still trying to make it in the way of finding the distance between two objects, when in reality, the best possible solution in this case is just to continuously apply force in specific directions according to whether an object is nearby or trying to clip through it. Not only that, but using the mouse as a medium to drag other objects and still be able to display physics was very complicated.

The graphics

This is one of the parts that were actually very fast to do, maybe either to already having the idea in my mind or due to having experience in using tools such as GIMP for photo editing.

Since I wanted to capture the essence of nostalgia (like in the crayon.ai image), I had to actually go in my way to “downgrade” some images I found on Google. Here are some screenshots of the process I went through in creating these images:

Here I was just starting

A bit more of progress.

The “ice cubes”

Some of the layouts

After I finished editing all these images, since I would look very bad in full screen due to the realism of these images, I applied a filter to make them more “nostalgic.”

The process was very similar to the rest of the images.

As an important note, the ice cubes are made yellow in order to stand out even more with the glass cups, as in to allow the best possible user engagement possible by using intuition.

The sound & music

Most of the sounds I obtained accomplished their purpose, but some of them were a bit delayed; thus, I had to use a tool called “Audacity” to cut some of the delay to allow the best possible feedback for the user.

Also, the music choice was still the same, although I decided to go with the second proposed option from my report, since it reflected a more nostalgic and calming ambient:

A problem… Where is the gameplay?

Indeed, there was only one problem: Where is the gameplay? I had only created an interactable piece of art, but it really conveyed gameplay elements. For the user, it was like there was gameplay, but it could not be seen anywhere; thus, I had to come up with a solution: Divide the game between “normal” gameplay and “sandbox,” which is more of what I envisioned.

In the normal mode, the player has the established challenges of aligning the cubes and the glass cups in a visible red highlight, under a time limit, and without losing any object. The sandbox mode is just complete freedom, and there are no penalties. Also, the player has the ability to use the keyboard in order to spawn more objects quicker or to delete all of them in an instant.

Trying to figure out the gameplay.

Improvements and Conclusion

I wanted to improve the collisions a bit more and add more effects for images, but due to time constraints, it was not possible. Also, the challenges could vary more; for example, move the ice cubes to the glass cup while avoiding the red-highlighted zones that will make you lose the game immediately.

Still, I am really proud of my work, and this allowed me to have more confidence in my skills as a programmer, and also as an artist, since I had to come up with proper solutions to create the best outcome possible, while also maintaining a good (possibly thoughtful) experience for the user.

Extra content

If you want to see some progress, here are some old versions of the midterm:

Have a good day! And thanks for reading my midterm report.

Leave a Reply