CClab Project B: Beyond the Stellaris

Project link: https://n-a-e-s.github.io/CCLab-Strix-Local/Star_Field/index

1) Process: Design and Composition:

Inspiration:

The inspiration for this project comes from an art style called NASApunk, exemplified by the video game “Starfield” developed by Bethesda Studios. At the same time, other artistic works have also inspired me during the development process, such as David Bowie’s “Space Oddity” and “Starman,” as well as the movie “The Martian.”

Interface:

When starting the design, I referred to the space exploration design in “Starfield,” hoping to enable players to explore across three dimensions: the galaxy, star systems, and planetary surfaces. Additionally, I aimed to populate this galaxy with randomly generated planets and star systems. For the early user interaction methods, I opted for the traditional WASD controls for forward, backward, left, and right movements, with the spacebar and CTRL keys controlling upward and downward movements. In terms of camera control, I initially chose the built-in `orbitControl` function. However, I soon discovered that this function causes the camera to move when rotating the view, which was not ideal for the desired user experience. Consequently, I wrote my own `manualOrbitControl()` function to rotate the view. This function utilizes `cam.pan(dx);` and `cam.tilt(dy);` to adjust the camera’s orientation, providing more precise and controlled movement.Entering a selected star system and landing on a planet are primarily accomplished using the ‘M’ key on the keyboard. This feature, along with the compass-like galaxy map, owes much to the inspiration from Paradox Interactive’s game “Stellaris.”、

Additionally, in the version updated after the presentation, I experimentally added support for Xbox controller inputs for spaceship flight controls within the star system. This version has been updated on GitHub, and now the flight within the star system supports dual input from both keyboard and mouse as well as a game controller.

Ship design: In the early versions, I did not include spaceship designs, but merely allowed the camera to fly through the boundless space. Of course, this design was quickly scrapped. I then turned to designing a bespoke spaceship for this project. By utilizing the export function of Jundroo Studio’s sandbox game SimplePlanes, I was able to design and construct the spaceship within the game. I then exported it to Blender, which allowed for further importing into the project.

2) Process: Technical

Code: Since the code is a bit too long to put inside this post, I will not paste the whole code inside. Instead, I will provide the Github link toward the project along with several parts of it.

https://github.com/N-A-E-S/CCLab-Strix-Local/tree/main/Star_Field

The web layout of this project is designed to be quite simple: it includes a cover page, a main project body, and a section for reading materials. Additionally, I created a transitional animation for the webpage, inspired by the hyperspace travel effects seen in science fiction movies like Star Wars. This animation simulates the effect of moving through space at superluminal speeds.

 

https://editor.p5js.org/N-A-E-S/sketches/zPjvSiRkH(p5js link to the animation)

Returning to the p5.js script for this project, in the `preload` function, I loaded 20 possible planet textures, sun textures, star system names, and the spaceship model. As mentioned earlier, the origin of the spaceship model has already been discussed and will not be reiterated here. The planet textures were created using another segment of p5.js code where I utilized Perlin noise to draw random 2D terrain maps. This technique allows for the generation of diverse and visually interesting planetary surfaces.

https://editor.p5js.org/N-A-E-S/sketches/2KQPUw7-v

function setup() {

  createCanvas(windowWidth, windowHeight, WEBGL);

  noStroke();

  while (syslis.length < 50) {

    let x = random(width);

    let y = random(height);

    if (dist(width / 2, height / 2, x, y) < 400 && dist(width / 2, height / 2, x, y) > 100) {

      avaliable = true;

      for (let i = 0; i < syslis.length; i++) {

        if (dist(syslis[i].x, syslis[i].y, x, y) < 30) {

          avaliable = false;

          break;

        }

      }

      if (avaliable) {

        syslis.push(new system(x, y, sysname[syslis.length]));

      }

    }

  }

  for (i in syslis) {

    for (j in syslis) {

      if (i != j) {

        if (dist(syslis[i].x, syslis[i].y, syslis[j].x, syslis[j].y) < 150 && !syslis[i].road.includes(j) && !syslis[j].road.includes(i)) {

          syslis[i].road.push(j);

          syslis[j].road.push(i);

        }

      }

    }

  }

  for (i in syslis) {

    syslis[i].solarsize = random(0.5, 1.5);

    syslis[i].planetlist = [];

    let planetnum = int(random(4, 7));

    let baseDistance = 200 + 300 * syslis[i].solarsize;

    let distanceIncrement = 250;

    for (let j = 0; j < planetnum; j++) {

      let radius = baseDistance + j * distanceIncrement + j * (10, 25);

      let angle = j * 2 * PI / planetnum + random(-PI / 4, PI / 4);

      let planetPosition = createVector(

        radius * cos(angle),

        random(-250, -150),

        radius * sin(angle)

      );

      let img = eval('img' + str(int(random(1, 21))));

      let rotateSpeed = random(0.001, 0.005);

      let size = random(0.5, 0.75) + j * 0.15;

      let np = new planet(planetPosition, size, img, rotateSpeed, 0);

      syslis[i].planetlist.push(np);

      featurenum = int(random(0, 6));

      for (let k = 0; k < featurenum; k++) {

        let feature = random(featurelib);

        np.feature.push(feature);

        let featurePosition = createVector(random(-4000, 4000), -5, random(-4000, 4000));

        np.featurePositions.push(featurePosition);

      }

    }

  }

  currentSystem = syslis[0];

  currentSystem.current = true;

  cam = createCamera();

  cam.move(200, -100, 200);

  prevMouseX = mouseX;

  prevMouseY = mouseY;

  canvas.addEventListener('mousedown', function (event) {

    event.preventDefault();

  });

  document.addEventListener('wheel', function (event) {

    handleMouseWheel(event);

  });

  window.addEventListener("gamepadconnected", function (e) {

    console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",

      e.gamepad.index, e.gamepad.id,

      e.gamepad.buttons.length, e.gamepad.axes.length);

  });

}

Now Looking at the `setup` function , this is where I create the star systems within the galaxy, randomly assigning each one a type and number of planets. I use two classes, `System` and `Planet`, to facilitate this organization. These classes will be discussed in more detail later as they play crucial roles in structuring the simulation and enhancing the interaction within the virtual galaxy.

Next, I created three `div` elements on the webpage responsible for displaying information and tutorials, thanks in large part to guidance from my professor, Moon. Following this, in the `draw` function, I added the ability to switch views, allowing users to navigate between three layers: the galaxy, the star system, and the planetary surface. This section mainly involves calling functions from the `System` and `Planet` classes, along with other functions to handle the drawing and dynamics for each level.

For the second map (star system), I integrated a section of code related to the gamepad to test control functionalities with an Xbox controller. This was primarily to assess the feasibility and responsiveness of gamepad inputs within this environment.

In the third map (planetary surface), I added a function to draw planetary terrains, which also uses Perlin noise, similar in principle to the texture drawing mentioned earlier. This function is crucial for creating realistic and engaging landscapes on the planetary surfaces that players explore.

texture(curPlanet.img);

          for (let x = -4000; x < 4000 - step; x += step) {

            for (let z = -4000; z < 4000 - step; z += step) {

              beginShape(TRIANGLES);

              let v1 = createVector(x, -curPlanet.mapheight[x + 4000][z + 4000], z);

              let v2 = createVector(x + step, -curPlanet.mapheight[x + 4000 + step][z + 4000], z);

              let v3 = createVector(x + step, -curPlanet.mapheight[x + 4000 + step][z + 4000 + step], z + step);

              let v4 = createVector(x, -curPlanet.mapheight[x + 4000][z + 4000 + step], z + step);

              vertex(v1.x, v1.y, v1.z);

              vertex(v2.x, v2.y, v2.z);

              vertex(v3.x, v3.y, v3.z);

              vertex(v1.x, v1.y, v1.z);

              vertex(v3.x, v3.y, v3.z);

              vertex(v4.x, v4.y, v4.z);

              endShape();

            }

          }

Additionally, I implemented a landing sequence within the program. This sequence ensures that the spaceship gradually descends to the planet’s surface, providing a realistic approach and touchdown experience. This feature enhances the immersion, allowing players to experience a smooth transition from space to planetary exploration.

Following that, I introduced a series of functions designed to facilitate the control of the spaceship and camera movements using the keyboard and mouse.

function handleMouseWheel(event) {

  let zoomSensitivity = 0.1;

  if (event.deltaY < 0) {

    camDistance -= zoomSensitivity * 100;

  } else {

    camDistance += zoomSensitivity * 100;

  }

  camDistance = constrain(camDistance, 100, 2000);

}

function displayPlanetInfo(info) {

  push();

  fill(255);

  textSize(16);

  textAlign(RIGHT, BOTTOM);

  text(info, width - 50, height - 50);

  pop();

}

function manualOrbitControl() {

  let dx = (pmouseX - mouseX) * 0.005;

  let dy = (pmouseY - mouseY) * -0.005;

  cam.pan(dx);

  cam.tilt(dy);

}

function keyboardCamControl() {

  let speed = 3;

  if (mouseIsPressed) {

    manualOrbitControl();

  }

  if (keys['w']) {

    cam.move(0, 0, -speed);

  }

  if (keys['s']) {

    cam.move(0, 0, speed);

  }

  if (keys['a']) {

    cam.move(-speed, 0, 0);

  }

  if (keys['d']) {

    cam.move(speed, 0, 0);

  }

  if (keys[' ']) {

    cam.move(0, -speed, 0);

  }

  if (keys['control']) {

    cam.move(0, speed, 0);

  }

}

function keyboadControl() {

  let speed = 3;

  if (keys['w']) {

    shipZ -= speed * cos(modelRotationY);

    shipX -= speed * sin(modelRotationY);

  }

  if (keys['s']) {

    shipZ += speed * cos(modelRotationY);

    shipX += speed * sin(modelRotationY);

  }

  if (keys['a']) {

    modelRotationY += 0.02;

  }

  if (keys['d']) {

    modelRotationY -= 0.02;

  }

  if (keys[' ']) {

    shipY -= speed;

  }

  if (keys['control']) {

    shipY += speed;

  }

}

function updateCam() {

  if (mouseIsPressed) {

    cameraAngleY += (mouseX - pmouseX) * 0.01;

    cameraAngleX += (mouseY - pmouseY) * 0.01;

  }

  let camX = shipX + camDistance * sin(cameraAngleY) * cos(cameraAngleX);

  let camY = shipY - camDistance * sin(cameraAngleX);

  let camZ = shipZ - camDistance * cos(cameraAngleY) * cos(cameraAngleX);

  cam.setPosition(camX, camY, camZ);

  cam.lookAt(shipX, shipY, shipZ);

}

Finally, I wrote two classes representing the star systems and planets. The rendering of planets within these classes primarily involves applying the previously mentioned textures to spherical models. Given the extensive length of the code, I won’t display it here. Please visit the GitHub page to view the full codebase.

Actually, I am very dissatisfied with the performance of this project after landing. The content on the planets is too homogeneous, with almost nothing to explore other than color variations and a few different elements. However, the webpage is already very sluggish due to the excessive content, and it can even occupy up to 2GB of memory when running, hence the plan to add more interactive content has been shelved.

(A short video to show all that functions)

3) Reflection and Future Development:

During the process of creating this project, the first thing I learned was not to set overly ambitious goals. The final product of this project can almost only be described as a skeleton compared to the objectives I set in the proposal. Additionally, many of the goals set at that time now seem almost impossible to achieve. Additionally, choosing the right tools is crucial. Out of personal interest, I chose to work on a project in a 3D environment, but the 3D capabilities of p5.js are not that… convenient. I often spent several hours trying to implement features that could be handled in a matter of minutes with a professional 3D engine like Unity. Of course, the process was still filled with interest. However, creating a work in this way makes it difficult to fully convey the main purpose of the project to users, which is obviously not the desired outcome. 

During my presentation, the guest professor Leon suggested that I should incorporate more conceptual content into the exploration of planetary surfaces, such as having players search for ancient ruins or other collectibles. This is exactly the direction I hope to take in future development.However, due to the limitations of the webpage, future development might not be entirely based on p5.js. Perhaps in the future, after optimizing the performance of this project, I will further advance the development in the direction of playability.

4) Credits & References

Cover: Starfield developed by Bethesda Studios. Steam Link: https://store.steampowered.com/agecheck/app/1716740/

Assistant on exporting the model: SimplePlane by Jundroon. Steam Link:https://store.steampowered.com/app/397340/SimplePlanes/

Special thanks to professor Moon, who is the instructor of my CClab section, and Carrot Liu, who is the fellow for this course.

CCLab ProjectB_Web Draft

Project Title: Beyond the Stellaris

Link:https://n-a-e-s.github.io/CCLab-Strix-Local/Star_Field/index

Descripition: The project mainly aimed to provide a web layout draft for Project B. Thu I built the layout with three main sites and a transferring CG. The first layer is set for the cover(which comes from the game Star Field.) and menu choices. The Second layer lies in the performance of the game, which is currently still in processing. Thus, I simply used a relative demo as a placeholder for the real game.  The same works for the reading material part in the third layer where there shall be a letter and operation instruction for those who live a thousand years later.  After those three layers is a transferring CG that connects all the layers. The CG is designed as a set of stars flashing towards the user,  imitating that they are going through an FTL travel.

Code:

For the whole project’s code, please visit my GitHub to accesshttps://github.com/N-A-E-S/CCLab-Strix-Local/tree/main/Star_Field

The Code mainly consists of 5 HTML files and a CSS file. Due to the concern of adjustability, I moved several style codes from independent CSS files to combine them with their HTML files. The major issue here I encountered was how to count the time and jump to a new layer after the transferred CG was played. Eventually, I used codes like

function redirect() {
window.location.href = ‘gamepage.html’;
}

setTimeout(redirect, 3000);

to solve the problem. 

Also, I attached some degree of flex CSS to the project.

Takeaway/Future improvement

  1. The second layer is a bit too pale and needs more decoration and instruction.
  2. The third layer needs more real content.
  3. The transfer between layers and transfer CG is way too straight, a little fade-in/fade-out would be nice. 
  4. The position of buttons can be optimized.

CCLab Project B Proposal

Project Working Title: Beyond the Stellaris—— An Odyssey of Major Tom

Project Description: Beyond the Stellaris is a virtual game in which the player is allowed to explore various unvisited planets in a mysterious stellar system or even more. The game aimed to provide users with 2 layers of interface: the stellar system, and the planet after landed. In general, the player may choose to land on a particular planet in the current system or initiate the FTL jump to a new system. After landing, the player may get a chance to see the breathtaking outer-space landscape, and fascinating new life forms,  which are all randomly generated via a pre-setted library of potential elements.

As planned, the system layer of the interface will be set from the perspective of the spaceship while the player serves as the pilot. The character will be under the players’ control once the ship lands on one of the planets.

The project is inspired by the so-called “NASA Punk” art style, which is promoted by the game Star Field produced by Bethesda Studio and includes a wide range of artworks, such as Space Oddity by David Bowie and movies like Interstellar and The Martian. A major characteristic of these kinds of art is the eulogizing of the exploration spirit of mankind, which is partly owing to the infection of space racing during the Cold War era. Such a “golden age” where all superpowers on the earth focused on pushing the frontier of human beings seems quite attractive to Sci-Fi lovers, especially in an era where everyone is living in a total mass of the vortex of postmodernism. Anyway, space might have already become a familiar place for our descendants after 1k years. Still, the further one goes, the less joy of first sight one may have. Pure enthusiasm for exploration may face its challenge along with the commercialization of space flight. That’s the reason why I would like to send those living in 1k years later a relatively naive imagination of outer space, to invoke their desire towards exploration and remind them of their old days when they stared at the night sky, imagining what was beyond the sky of the SOL-III.

Link to the slide:https://docs.google.com/presentation/d/1nU1OxAUMt4VBZ3k9gWSgpO9dl21iXUXAHsaJQPm1lSI/edit?usp=sharing

 

CCLab project- Particle world

Project title: Bubble Shooting

Link to the project:https://n-a-e-s.github.io/CCLAB-particle-homework/index.html

Brief Description and Concept: The project is a small game in which the player controls a character to shoot down falling bubbles from the ceiling to earn points while keeping the character away from the bubbles. The major operation users conduct is moving the character via using WASD buttons and shooting different types of bullets.

Visual Documentation: 

Coding: 

// CCLab Mini Project - 9.R Particles Template

let NUM_OF_PARTICLES = 30; // Decide the initial number of particles.
let bullets = [];
let particles = [];
var total_points = 0;
fireBreak = false;
fireBreakTime = 0;
let yoff = 0.0;
function generateNoiseColor() {
  let r = noise(yoff) * 255;
  let g = noise(yoff + 5) * 255;
  let b = noise(yoff + 10) * 255;
  yoff += 0.005;
  return color(r, g, b);
}
class Player {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.xspd = 0;
    this.yspd = 0;
    this.dir = 1;
  }
  display() {
    push();
    translate(this.x, this.y);
    scale(this.dir, 1);
    noStroke();
    rectMode(CENTER);
    fill(30);
    rect(-7.5, 0, 55, 50);
    rectMode(CORNER);
    fill(60);
    rect(-20, -10, 20, 30);
    rect(-20, -10, 25, 10);
    rect(-20, -10, 30, 5);
    rect(-50, -5, 15, 15);
    rect(-45, -15, 10, 15);
    rect(20, -5, 15, 15);
    rect(20, -15, 10, 15);
    rect(-35, -45, 5, 10)
    fill(65);
    rect(-30, -50, 55, 30);
    fill('gold');
    rect(-30, -20, 5, 5);
    rect(-25, -15, 5, 5);
    rect(-20, -10, 5, 5);
    rect(-15, -5, 5, 5);
    rect(-10, 0, 10, 5);
    rect(0, 5, 10, 5);
    rect(10, 10, 5, 5);
    rect(15, 15, 5, 5);
    rect(-50, 10, 15, 5);
    rect(20, 10, 15, 5);
    fill(0);
    rect(-5, 25, 5, 5);
    rect(0, 30, 5, 5);
    rect(-10, 30, 5, 5);
    rect(5, 35, 5, 20);
    rect(-15, 35, 5, 20);
    rect(10, 50, 40, 5);
    rect(-50, 50, 40, 5);
    rect(45, 40, 5, 10);
    rect(35, 35, 10, 5);
    rect(25, 30, 10, 5);
    rect(20, 25, 10, 5);
    rect(-50, 40, 5, 10);
    rect(-45, 35, 10, 5);
    rect(-40, 30, 10, 5);
    rect(-35, 25, 5, 5);
    rect(-35, -25, 5, 5);
    rect(-45, -20, 10, 5);
    rect(-50, -15, 5, 10);
    rect(-55, -5, 5, 20);
    rect(-50, 15, 15, 5);
    rect(-35, 0, 5, 15);
    rect(-40, 0, 5, 5);
    rect(-25, -20, 5, 5);
    rect(-20, -15, 30, 5);
    rect(10, -20, 20, 5);
    rect(30, -20, 5, 15);
    rect(35, -5, 5, 20);
    rect(20, 15, 15, 5);
    rect(15, 0, 5, 10);
    rect(20, 0, 5, 5);
    rect(-35, -50, 5, 5);
    rect(-30, -60, 5, 10);
    rect(-25, -65, 5, 5);
    rect(-20, -70, 15, 5);
    rect(-10, -75, 15, 5);
    rect(5, -70, 5, 5);
    rect(10, -65, 5, 5);
    rect(15, -60, 5, 5);
    rect(20, -55, 5, 10);
    fill(30);
    rect(10, 40, 35, 10);
    rect(10, 35, 25, 5);
    rect(10, 25, 10, 5);
    rect(-45, 40, 30, 10);
    rect(-35, 35, 20, 5);
    rect(-30, 25, 10, 5);
    rect(-40, -15, 5, 5);
    rect(20, -15, 5, 5);
    rect(-30, -50, 50, 5);
    rect(-25, -55, 45, 5);
    rect(-25, -60, 40, 5);
    rect(-20, -65, 30, 5);
    rect(-5, -70, 10, 5);
    fill(80);
    rect(-25, -60, 5, 5);
    rect(-20, -65, 10, 5);
    fill(60);
    rect(5, 30, 20, 5);
    rect(0, 25, 10, 5);
    rect(-30, 30, 20, 5);
    rect(-20, 25, 15, 5);
    rect(-20, -60, 5, 5);
    rect(-10, -65, 5, 5);
    rect(-5, -70, 5, 5);
    fill('gold');
    rect(-15, -45, 15, 5);
    rect(-10, -40, 15, 10);
    rect(-15, -30, 15, 5);
    rect(15, -45, 10, 5);
    rect(10, -40, 10, 10);
    rect(15, -30, 10, 5);
    pop();
  }
  update() {
    if (keyIsPressed) {
      switch (key) {
        case 'w':
          //if (this.y > 50) {
          this.yspd = -10
          break;
        //}
        case 's':
          this.yspd = 1;
          break;
        case 'a':
          this.xspd = -5;
          this.dir = -1;
          break;
        case 'd':
          this.xspd = 5;
          this.dir = 1;
          break;
      }
    }
    if (this.y >= 600 && !(keyIsPressed && key == 'w')) {
      this.yspd = 0;
      this.y = 600;
    }
    else {
      if (this.y < 600) { this.yspd += 1; } } if (this.x >= 0 && this.x <= width) {
      this.x += this.xspd;
      this.y += this.yspd;
    }
    this.xspd = 0;
  }
  death_dectection() {
    for (let i = 0; i < particles.length; i++) {
      if (dist(this.x, this.y, particles[i].x, particles[i].y) < particles[i].dia / 2 + 50) { return true; } } return false; } } class bullet { constructor(x, y, color) { this.x = x; this.y = y; this.startX = x; this.startY = y; this.xspd = 0; this.yspd = 0; this.color = color; } update() { this.x += this.xspd; this.y += this.yspd; } display() { push(); translate(this.x, this.y); if (this.color == 'blue') { noStroke(); fill(0, 0, 255); rect(0, 0, 10, 10); if (dist(this.x, this.y, this.startX, this.startY) > 60) {
        for (let i = 1; i <= 10; i++) { fill(i * 20, i * 20, 255) rect(-i * this.xspd * 0.8, -i * this.yspd * 0.8, 10 - 0.2 * i, 10 - 0.2 * i); } } } else { if (this.color == 'red') { noStroke(); fill(255, 0, 0); rect(0, 0, 5, 5); if (dist(this.x, this.y, this.startX, this.startY) > 60) {
          for (let i = 1; i <= 10; i++) {
            fill(255, i * 20, i * 20)
            rect(-i * this.xspd * 0.8, -i * this.yspd * 0.8, 5 - 0.2 * i, 5 - 0.2 * i);
          }
        }
      }
    }
    pop();
  }
  hit_detection() {
    if (this.color == 'blue') {
      this.range = 10;
    }
    else {
      this.range = 3;
    }
    for (let i = 0; i < particles.length; i++) {
      if (dist(this.x, this.y, particles[i].x, particles[i].y) < particles[i].dia / 2 + this.range) {
        point = particles[i].value;
        particles.splice(i, 1);
        i--;
        total_points += point;
      }
    }
  }
}

class Particle {
  // constructor function
  constructor(startX, startY) {
    // properties: particle's characteristics
    this.x = startX;
    this.y = startY;
    this.xspd = random(0, 0.8);
    this.yspd = random(0.5, 2);
    this.dia = random(10, 30);
    this.dir = 1;
    this.value = int(map(this.dia, 10, 30, 0, 10));
  }

  // methods (functions): particle's behaviors
  update() {
    // (add) 
    this.x += this.xspd * this.dir;
    this.y += this.yspd;
  }
  display() {
    // particle's appearance
    push();
    translate(this.x, this.y);
    noStroke();
    fill(generateNoiseColor());
    circle(0, 0, this.dia);
    pop();
  }
  applyWind() {
    if (mouseX < width / 2) { this.dir = 1; } else { this.dir = -1; } } stop() { if (this.y >= height - 10) {
      this.yspd = 0;
      this.xspd = 0;
    }
  }
}
function preload() {
  myfont = loadFont('MZPXorig.ttf');
}
function setup() {
  let canvas = createCanvas(800, 800);
  canvas.parent("canvasWrapper");
  pl = new Player(width / 2, height / 2);
  // generate particles
  for (let i = 0; i < NUM_OF_PARTICLES; i++) {
    particles[i] = new Particle(random(width), random(-height, 0));
  }
}
function draw() {
  background(255, 100, 100);
  textFont(myfont);
  // update and display
  for (let i = 0; i < particles.length; i++) { let p = particles[i]; p.applyWind(); p.stop(); p.update(); p.display(); if (p.y >= height - 10) {
      particles.splice(i, 1);
      i--;
      p.y = random(-height, 0);
    }
  }
  if (particles.length == 0) {
    for (let i = 0; i < NUM_OF_PARTICLES; i++) { particles[i] = new Particle(random(width), random(-height, 0)); } } if (mouseIsPressed && !fireBreak) { n_bullet = new bullet(pl.x, pl.y, 'blue'); n_bullet.xspd = 10 * (mouseX - pl.x) / dist(pl.x, pl.y, mouseX, mouseY); n_bullet.yspd = 10 * (mouseY - pl.y) / dist(pl.x, pl.y, mouseX, mouseY); bullets.push(n_bullet); fireBreak = true; } if (keyIsPressed && key == ' ' && !fireBreak) { n_bullet = new bullet(pl.x, pl.y, 'red'); n_bullet.xspd = 10 * (mouseX - pl.x) / dist(pl.x, pl.y, mouseX, mouseY); n_bullet.yspd = 10 * (mouseY - pl.y) / dist(pl.x, pl.y, mouseX, mouseY); bullets.push(n_bullet); spd = createVector(mouseX - pl.x, mouseY - pl.y).normalize().mult(10); spd.rotate(PI / 6); n_bullet2 = new bullet(pl.x, pl.y, 'red'); n_bullet2.xspd = spd.x; n_bullet2.yspd = spd.y; bullets.push(n_bullet2); bullet3 = new bullet(pl.x, pl.y, 'red'); spd.rotate(-PI / 3); bullet3.xspd = spd.x; bullet3.yspd = spd.y; bullets.push(bullet3); fireBreak = true; } if (fireBreak) { fireBreakTime++; if (fireBreakTime >= 20) {
      fireBreak = false;
      fireBreakTime = 0;
    }
  }
  for (let i = 0; i < bullets.length; i++) { bullets[i].update(); bullets[i].display(); if (bullets[i].x > width || bullets[i].x < 0 || bullets[i].y > height || bullets[i].y < 0) {
      bullets.splice(i, 1);
      i--;
    }
    else {
      bullets[i].hit_detection();
    }
  }
  pl.update();
  pl.display();
  if (pl.death_dectection()) {
    textSize(50);
    text("Game Over", width / 2 - 100, height / 2);
    textSize(40);
    noLoop();
  }
  textSize(40);
  text("Score: " + total_points, 10, 50);
}

  

Coding Description : The code can be divided into several parts:

Initialization: The code initializes variables and constants, such as the number of particles, an array to store bullets, and an array to store particles.

Player class: In this part, the code creates a character that represents the player. The class mainly consists of three parts: Drawing, moving, and collision detecting, which display the figure and allow the user to move it with the keyboard when it’s alive.

Bullet class: In this part, the code allows the player to shoot 2 kinds of differnt bullets: a bunch of big blue bullet like a rifle or 3 small bullet like a shot gun. They are reperesneted in different colors.

Particle class: In this part, the code generates falling bubbles with a random diameter. The value of such a object also depends on the size. Also the winds system is attached.

Setup Function: In the setup() function, the canvas is created, and an instance of the player character is initialized at the center of the canvas. It also generates particles at random positions above the canvas.

Draw Function: The draw() function is the main game loop. It updates and displays all the elements in the game, including particles, bullets, and the player character.

Score Display: The player’s score is displayed on the screen.

Reflection: 

  1. Weapon class: It seems quite strange to have a bullet comes directly from the character. Having a rifle that points to the mouse would be better.
  2. Background music: The origin design of the character comes from a game called The Finals, which is quite famous for it’s fantasic music. It can also served as the music for this project.
  3. More details: More details can be added. For example: the bubble can be somehow like a firework after being hit.

Artistic Inspiration Research2: Mark Nappier’s net.flag

  • Net Art: 

Forms of Art that can be presented on the Internet, especially those can be interacted online or make use of other nature of internet.

  • Introduction:

This project was created in 2002 in Java, and modified in JavaScript in 2019. It was created by Mark Napier, and supported by the Guggenheim. The website includes thousands of flags created by visitors to the website, based on the existing national flags. The website aims to manifest the different ideologies and concepts of people from different geographical areas. It is public, inclusive, and interactive. In 2002, when the internet began to popularize globally, this website was a public archive for people to share their thoughts through pictures.

  • Discussion: 

According to the reading, the nature of Net Art is process-based, interactive, and mostly ephemeral. This project fits the definition of Net Art in the reading, but though the heading flag will be replaced by any new flag made by a new visitor, the flags are well archived, so it is not so “ephemeral”. It is written in JavaScript, which is the same coding language used in CCLab. It draws our attention to the art of reassembly and collage, which can be done in JavaScript.

The most significant features of the internet for our research subject are its interactivity and publicity. This website can not be web art if there are no visitors who would visit and edit the flags. This project is purely made of code, which encourages us to express our opinions and concepts by coding.

  • Individual Conclusion*: 

Rebecca’s conclusion: This project shows an insight on how the audience can become a part of the artwork. Though it is not exclusive to web art, through the web’s unique specialty of interactivity and publicity, it can be done exceptionally well. It inspires me to create some content that can be further edited by the audience for Project B.

Sean’s conclusion This project explores the potential of establishing new artwork from the existing art. The project provides the user a chance to combine existing flags into a new piece of work. By utilizing different elements users could explore and extend the hypothesis ideology, social construction, nationality culture background, which inspired me to ensure user could conduct their exploration via various dimensions.

CClab Mini Project: OOP Dancer.

Project Title: Dancing Pumpkin Man

Link to project:https://n-a-e-s.github.io/oop-dancer/

https://github.com/N-A-E-S/oop-dancer

Description: When I was told that we were going to make a dancing object, the first image that came into my mind was a meme:https://www.bilibili.com/video/BV14y4y1g7Ex/(which is a meme generated from the movie: Mobile Suit Gundam: Hathaway’s Flash)

Still, the large amount of movement here makes it hard to complete all the steps inside. Thus, I decided to only conduct the first step, which mainly consists of the Pendulum motion of the body and the repetitive striking of two arms.

The final version of the work:

Coding:

/*
Check our the GOAL and the RULES of this exercise at the bottom of this file.

After that, follow these steps before you start coding:

1. rename the dancer class to reflect your name (line 35).
2. adjust line 20 to reflect your dancer’s name, too.
3. run the code and see if a square (your dancer) appears on the canvas.
4. start coding your dancer inside the class that has been prepared for you.
5. have fun.
*/

let dancer;

function setup() {
// no adjustments in the setup function needed…
let canvas = createCanvas(windowWidth, windowHeight);
canvas.parent(“p5-canvas-container”);

// …except to adjust the dancer’s name on the next line:
dancer = new SeanDancer(width / 2, height / 2);
}

function draw() {
// you don’t need to make any adjustments inside the draw loop
background(0);
drawFloor(); // for reference only

dancer.update();
dancer.display();
}

// You only code inside this class.
// Start by giving the dancer your name, e.g. LeonDancer.
class SeanDancer {
constructor(startX, startY) {
this.x = startX;
this.y = startY;
this.angle = 0;
this.rotationSpeed = 0.04;
this.radius = 50;
this.amplitude = 20;
this.action1 = 0;
this.action2 = 0;
this.timebreak = 0;
this.angle2 = 0;
// add properties for your dancer here:
//..
//..
//..
}
update() {
this.angle += this.rotationSpeed;
this.angle2 += 0.1
}
display() {
// the push and pop, along with the translate
// places your whole dancer object at this.x and this.y.
// you may change its position on line 19 to see the effect.
// ******** //
// ⬇️ draw your dancer from here ⬇️
//head
push();
translate(this.x, this.y);
rotate(0.35 * sin(this.angle));
//neck
fill(255, 204, 153);
rect(-10, -45, 20, 10);
fill(255, 153, 51);
ellipse(0, -65, 50, 50);
fill(“green”);
rect(-5, -95, 10, 5);
fill(0);
triangle(-15, -80, -5, -80, -10, -70);
triangle(15, -80, 5, -80, 10, -70);
triangle(0, -65, -5, -60, 5, -60);
strokeWeight(3);
line(0, -55, -6, -50);
line(0, -55, 6, -50);
line(-6, -50, -10, -55);
line(6, -50, 10, -55);
//body
fill(96, 96, 96);
noStroke();
rect(-20, -35, 40, 10);
beginShape();
vertex(-20, -25);
vertex(-30, -20);
vertex(30, -20);
vertex(20, -25);
endShape(CLOSE);
beginShape();
vertex(-30, -20);
vertex(-30, -15);
vertex(30, -15);
vertex(30, -20);
endShape(CLOSE);
beginShape();
vertex(-25, -15);
vertex(-18, 30);
vertex(18, 30);
vertex(25, -15);
endShape(CLOSE);
strokeWeight(1);
stroke(0);
line(-18, -5, -18, 30);
line(18, -5, 18, 30);
line(-25, -15, -18, -5);
line(25, -15, 18, -5);
fill(96, 96, 96);
//legs
if (sin(this.angle) > 0) {
beginShape();
vertex(-20, 30);
vertex(-20, 50);
vertex(-10, 50);
vertex(-10, 30);
endShape(CLOSE);
beginShape();
vertex(-20, 50);
vertex(-20 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10 + (50 * sin(0.35 * sin(this.angle))), 50 – tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10, 50 – tan(0.35 * sin(this.angle)));
endShape(CLOSE);
beginShape();
vertex(20, 30);
vertex(20, 50);
vertex(10, 50);
vertex(10, 30);
endShape(CLOSE);
beginShape();
vertex(20, 50);
vertex(20 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10 + (50 * sin(0.35 * sin(this.angle))), 50 – tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10, 50 – tan(0.35 * sin(this.angle)));
endShape(CLOSE);
//feet
beginShape();
vertex(-25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10 + (50 * sin(0.35 * sin(this.angle))), 50 – tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10 + (50 * sin(0.35 * sin(this.angle))), 50 – tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle))) – 10);
vertex(-25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle))) – 10);
endShape(CLOSE);
beginShape();
vertex(25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10 + (50 * sin(0.35 * sin(this.angle))), 50 – tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10 + (50 * sin(0.35 * sin(this.angle))), 50 – tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle))) – 10);
vertex(25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle))) – 10);
endShape(CLOSE);
//arms
strokeWeight(0.5);
stroke(0);
beginShape();
vertex(-30, -15);
vertex(-45, -5);
vertex(-45, 5);
vertex(-20, -10);
vertex(-20, -15);
endShape(CLOSE);
beginShape();
vertex(-45, -5);
vertex(-60 – 5 * sin(this.angle2), -5 – 5 * cos(this.angle2));
vertex(-60 – 5 * sin(this.angle2), 5 – 5 * cos(this.angle2));
vertex(-45, 5);
endShape(CLOSE);
beginShape();
vertex(30, -15);
vertex(0 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(0 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(30, -5);
endShape(CLOSE);
beginShape();
vertex(0 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(-20 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(-20 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(0 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
endShape(CLOSE);
fill(255, 204, 153);
beginShape();
vertex(-20 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(-20 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(-25 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(-25 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
endShape(CLOSE);
beginShape();
vertex(-60 – 5 * sin(this.angle2), -5 – 5 * cos(this.angle2));
vertex(-60 – 5 * sin(this.angle2), 5 – 5 * cos(this.angle2));
vertex(-65 – 5 * sin(this.angle2), 5 – 5 * cos(this.angle2));
vertex(-65 – 5 * sin(this.angle2), -5 – 5 * cos(this.angle2));
endShape(CLOSE);

}
else {
if (sin(this.angle) < 0) {
beginShape();
vertex(-20, 30);
vertex(-20, 50);
vertex(-10, 50);
vertex(-10, 30);
endShape(CLOSE);
beginShape();
vertex(-20, 50 – tan(0.35 * sin(this.angle)));
vertex(-20 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10, 50);
endShape(CLOSE);
beginShape();
vertex(20, 30);
vertex(20, 50);
vertex(10, 50);
vertex(10, 30);
endShape(CLOSE);
beginShape();
vertex(20, 50 – tan(0.35 * sin(this.angle)));
vertex(20 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10, 50);
endShape(CLOSE);
//feet
beginShape();
vertex(-25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(-10 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle))) – 10);
vertex(-25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle))) – 10);
endShape(CLOSE);
beginShape();
vertex(25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20));
vertex(10 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle))) – 10);
vertex(25 + (50 * sin(0.35 * sin(this.angle))), 50 + (50 * cos(0.35 * sin(this.angle))) – 10);
endShape(CLOSE);
//arms
strokeWeight(0.5);
stroke(0);
beginShape();
vertex(30, -15);
vertex(45, -5);
vertex(45, 5);
vertex(20, -5);
vertex(20, -15);
endShape(CLOSE);
beginShape();
vertex(45, -5);
vertex(60 – 5 * sin(this.angle2), -5 – 5 * cos(this.angle2));
vertex(60 – 5 * sin(this.angle2), 5 – 5 * cos(this.angle2));
vertex(45, 5);
endShape(CLOSE);
beginShape();
vertex(-30, -15);
vertex(0 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(0 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(-30, -5);
endShape(CLOSE);
beginShape();
vertex(0 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(20 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(20 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(0 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
endShape(CLOSE);
fill(255, 204, 153);
beginShape();
vertex(20 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
vertex(20 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(25 + 10 * sin(this.angle2), -5 + 10 * cos(this.angle2));
vertex(25 + 10 * sin(this.angle2), -15 + 10 * cos(this.angle2));
endShape(CLOSE);
beginShape();
vertex(60 – 5 * sin(this.angle2), -5 – 5 * cos(this.angle2));
vertex(60 – 5 * sin(this.angle2), 5 – 5 * cos(this.angle2));
vertex(65 – 5 * sin(this.angle2), 5 – 5 * cos(this.angle2));
vertex(65 – 5 * sin(this.angle2), -5 – 5 * cos(this.angle2));
endShape(CLOSE);
}
}
pop();

 

// ⬆️ draw your dancer above ⬆️
// ******** //

// the next function draws a SQUARE and CROSS
// to indicate the approximate size and the center point
// of your dancer.
// it is using “this” because this function, too,
// is a part if your Dancer object.
// comment it out or delete it eventually.
push();
translate(this.x, this.y);
this.drawReferenceShapes()
pop();
}
drawReferenceShapes() {
noFill();
stroke(255, 0, 0);
line(-5, 0, 5, 0);
line(0, -5, 0, 5);
stroke(255);
rect(-100, -100, 200, 200);
fill(255);
stroke(0);
}
}

 

/*
GOAL:
The goal is for you to write a class that produces a dancing being/creature/object/thing. In the next class, your dancer along with your peers’ dancers will all dance in the same sketch that your instructor will put together.

RULES:
For this to work you need to follow one rule:
– Only put relevant code into your dancer class; your dancer cannot depend on code outside of itself (like global variables or functions defined outside)
– Your dancer must perform by means of the two essential methods: update and display. Don’t add more methods that require to be called from outside (e.g. in the draw loop).
– Your dancer will always be initialized receiving two arguments:
– startX (currently the horizontal center of the canvas)
– startY (currently the vertical center of the canvas)
beside these, please don’t add more parameters into the constructor function
– lastly, to make sure our dancers will harmonize once on the same canvas, please don’t make your dancer bigger than 200×200 pixels.
*/
The coding mainly consists of two parts: update and display.

In the upload function, we update the two angles that determine the moving parament of the body and arms. 

In the display part, we draw the whole figure.

First, we draw the Pumpkinhead and body of the figure, then we rotate using sin(angle1) to achieve the “Pendulum motion” of the main body. Then comes the legs and arms. in this part, I used a lot of maths function to calculate the location, such a paraments like (-10 + (50 * sin(0.35 * sin(this.angle))), 50 + tan(0.35 * sin(this.angle)) + (50 * cos(0.35 * sin(this.angle)) – 20)), which contribute to the movement of legs.

Reflection:

OOP could be a powerful function, which I’ve already learned during the midterm process. Yet to make a project, especially combined with visual art would take a lot of effort. It may be a significant focus to find a efficient way to beautify the work.

 

 

ProjectA:Pixelime(part 2)

Process: Design and Composition:

This project’s inspiration and design have undergone multiple iterations. Its earliest version stemmed from a sketch inspired by Conway’s Game of Life while I was working on a project proposal (Project A). It simulated sexual reproduction of creatures, where their appearance was simplified to circles and squares, and they were differentiated by gender and behavioral patterns.

Link to this sketch:https://editor.p5js.org/N-A-E-S/sketches/nweZqmRem

In subsequent versions, I removed gender differentiation, unifying them under the image of the same kind of pixel slime. Then, I incorporated a food system completed in another sketch(Which takes a lot inspiration from the user testing part in the interaction day.).

To make them behave more like slimes, I replaced the original random movement with a bouncing motion to simulate the movement of living organisms. Additionally, to reflect the concept of ecological cycles, a unit of food is generated at the location where a pixelime dies.
In the final version, when two pixelimes come into contact and are mature enough, they produce a new offspring. The surname of the offspring is a blend of the surnames of the two parents, while the given name is randomly generated. Additionally, the color of the offspring is a blend of the colors of the parents.

Some other changes made include:

  1. Implementing a lifespan for food items.
  2.  Adding a new background image referenced from YouTube.
  3. Introducing gravity for slime individuals.
  4. Limiting the movement range of slimes.

 

Process: Technical 

Code:

//Reference of background image: https://www.youtube.com/watch?v=MIq1QW0ycI8&t=247s
var img;
var gameStarted = false; // Track game state
var startButton;
var startSlime; // Store the start slime
var foods = []; // Array to store food objects
var foodBreak = 0; // Track food deployment
var recentFood = false; // Track recent food deployment
function preload() {
print(“lo123123ed”);
img = loadImage(‘img.png’);
myfont = loadFont(‘MZPXorig.ttf’);
}
function setup() {
let canvas = createCanvas(800, 500);
canvas.parent(“p5-canvas-container”)
startButton = createButton(‘Start’);
startButton.size(100, 50); // Set button size
var canvasPosition = canvas.position();
startButton.position(canvasPosition.x + windowWidth / 2 – 50, canvasPosition.y + windowHeight / 2 – 25);
startButton.mousePressed(startGame);

}
function startGame() {
gameStarted = true;
startButton.hide(); // Hide the button when game starts
}
class creature//class for the creature, pixel slime shape
{
constructor(x, y, size, r, g, b, name) {
this.x = x;
this.y = y;
this.r = r;
this.g = g;
this.b = b;
this.size = size;
this.color = color;
this.hp = 100;
this.hunger = 100;
this.name = name;
this.lifetime = 0;
this.speedx = 0;
this.speedy = 0;
this.jumpduration = 0;
this.startheight = y;
this.jump = false;
this.jumpbreak = 0;
this.jumpdirection = 0;
this.jumpforceX = 0;
this.jumpforceY = 0;
this.reproductionbreak = 0;
this.lately_reproduction = false;

}
display() {
push();;
translate(this.x, this.y);
fill(0);
noStroke();
//text of name and hp
text(this.name, -10, -50);
fill(‘red’);
rect(-20, -40, this.hp * 0.4, 5);
fill(0)
//text(“Hunger: “+this.hunger,0,-10);
//border for the whole slime
rectMode(CENTER);
if (this.jumpdirection < 0)
scale(1, 1);
else
scale(-1, 1);
rect(0, 0, this.size * 75, this.size * 10);//bottom of the slime
rect(-1.25, 7.5, this.size * 72.5, this.size * 5);
rect(-2.5, 11.25, this.size * 70, this.size * 2.5);
rect(-2.5, 13.75, this.size * 65, this.size * 2.5);
rect(-2.5, 16.25, this.size * 55, this.size * 2.5);
rect(0, -7.5, this.size * 70, this.size * 5);//top of the slime
rect(0, -12.5, this.size * 65, this.size * 5);
rect(0, -17.5, this.size * 55, this.size * 5);
rect(0, -20, this.size * 50, this.size * 2.5);
rect(0, -22.5, this.size * 45, this.size * 2.5);
rect(0, -25, this.size * 35, this.size * 2.5);
rect(-2.5, -27.5, this.size * 22.5, this.size * 2.5);
//body
fill(this.r, this.g, this.b);
scale(0.95);
noStroke();
rect(0, 0, this.size * 75, this.size * 10.5);//bottom of the slime
rect(-1.25, 7.5, this.size * 72.5, this.size * 6);
rect(-2.5, 11.25, this.size * 70, this.size * 3);
rect(-2.5, 13.75, this.size * 65, this.size * 3);
rect(-2.5, 16.25, this.size * 55, this.size * 3);
rect(0, -7.5, this.size * 70, this.size * 5.75);//top of the slime
rect(0, -12.5, this.size * 65, this.size * 5.75);
rect(0, -17.5, this.size * 55, this.size * 5.75);
rect(0, -20, this.size * 50, this.size * 3.25);
rect(0, -22.5, this.size * 45, this.size * 3.25);
rect(0, -25, this.size * 35, this.size * 3.25);
rect(-2.5, -27.5, this.size * 22.5, this.size * 2.5);
//eyes
fill(“white”);
rect(-15, -10, this.size * 5, this.size * 10);
rect(10.5, -10, this.size * 10, this.size * 10);
//outline for the lefteyes
fill(“black”);
rect(-18.75, -10, this.size * 2.5, this.size * 10);
rect(-15, -3.75, this.size * 5, this.size * 2.5);
rect(-15, -16.25, this.size * 5, this.size * 2.5);
rect(-11.25, -10, this.size * 2.5, this.size * 15);
//outline for the righteyes
rect(4.25, -10, this.size * 2.5, this.size * 10)
rect(10.5, -3.75, this.size * 10, this.size * 2.5);
rect(10.5, -16.25, this.size * 10, this.size * 2.5);
rect(16.75, -10, this.size * 2.5, this.size * 10);
//mouth, “W” shape
fill(this.r – 30, this.g – 30, this.b – 30);
rect(-5, 5, this.size * 15, this.size * 2.5);
fill(10);
rect(-5, 5, this.size * 10, this.size * 2.5);
rect(-11.25, 2.5, this.size * 2.5, this.size * 2.5);
rect(1.25, 2.5, this.size * 2.5, this.size * 2.5);
rect(-5, 2.5, this.size * 2.5, this.size * 2.5);
pop();
}
move()//update the position of the slime
{
if (this.y < 250 || this.jump == true)
this.y += 3 + this.jumpduration * 2;
if (this.y + this.speedy < 500 && this.y + this.speedy > 0 && this.x + this.speedx < 800 && this.x + this.speedx > 0) {
this.x += this.speedx;
this.y += this.speedy;
}
}
step()//jumping function
{
if (this.jump == true) {
this.jumpduration++;

if (this.jumpdirection < 0) {
if (this.speedx > -5)
this.speedx -= this.jumpforceX;
this.speedy = this.jumpforceY;
}
if (this.jumpdirection > 0) {
if (this.speedx < 5)
this.speedx += this.jumpforceX;
this.speedy = this.jumpforceY;
}
if (this.jumpduration > 1 && this.y >= this.startheight) {
this.jump = false;
this.jumpduration = 0;
this.speedy = 0;
this.speedx = 0;
this.y = this.startheight;
}
}
}
print() {
console.log(this.name)
}
}
alllife = []
deployBreak = 0;
newdeploy = false
var alphabet = new Array(‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’, ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’)//alphabet for name generate
function name(len, inhen = null) {

let name = ”
if (inhen == null)
for (i = 0; i < len; i++) {
let w = int(random(1, 52))
name += alphabet[w]
}
else {
name += inhen
for (i = 0; i < len; i++) {
let w = int(random(1, 52))
name += alphabet[w]
}
}
return name
}
function findCloseSlimes() {
let closeSlimes = [];
for (let i = 0; i < alllife.length; i++) {
for (let j = i + 1; j < alllife.length; j++) {
let distance = dist(alllife[i].x, alllife[i].y, alllife[j].x, alllife[j].y);
if (distance < 50) {
closeSlimes.push(alllife[i]);
closeSlimes.push(alllife[j]);
}
}
}
return closeSlimes;
}
class Food {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 10;
this.lifetime = 0;
}

display() {
fill(“gold”); // Red color for food
noStroke();
// Draw pixel ball
for (let i = 0; i < this.radius * 2; i++) {
for (let j = 0; j < this.radius * 2; j++) {
if (dist(i, j, this.radius, this.radius) < this.radius) {
rect(this.x + i – this.radius, this.y + j – this.radius, 1, 1);
}
}
}
}

checkCollision(creature) {
let distance = dist(this.x, this.y, creature.x, creature.y);
if (distance < this.radius + creature.size * 75 / 2) {
creature.hp += 20; // Increase creature’s hp
return true;
}
return false;
}
}

function draw() {
background(255);
imageMode(CENTER);
image(img, 400, 250, 800, 500);
if (!gameStarted) {
textFont(myfont);
textSize(50);
fill(“gold”);
text(“Press b to create a slime”, 100, 80);
text(“Click mouse to provide food”, 100, 140);
text(“Observe how their family develop”, 50, 200);
//text(“New slimes will inherit the name and color from both of their parents”, 50, 220);
//startSlime = new Creature(400, 300, 1, 0, 255, 0, “PIXEL”);
//startSlime.display();
}
else {
textFont(myfont);
textSize(20);
fill(0);
text(“current slimes: ” + alllife.length, 15, 90);
//text(foodBreak, 15, 110);
if (keyIsPressed === true)
if (key == ‘b’ && newdeploy == false) {
r = random(255);
g = random(255);
b = random(255);
let new_name = name(3, null)
console.log(new_name)
alllife.push(new creature(mouseX, mouseY, 1, r, g, b, new_name));
newdeploy = true;
}
if (newdeploy == true) {
deployBreak++;
}
if (deployBreak > 50) {
newdeploy = false;
deployBreak = 0;
}
for (var i = 0; i < alllife.length; i++) {
alllife[i].display();
alllife[i].lifetime++;
alllife[i].hp -= 0.05 + alllife.length * 0.001;
alllife[i].hunger -= 0.05;
alllife[i].move();
alllife[i].step();
if (alllife[i].jump == false) {
alllife[i].jumpbreak++;
}
if (alllife[i].jumpbreak > 200) {
alllife[i].jump = true;
alllife[i].jumpdirection = random(-1, 1);
alllife[i].jumpforceX = random(0.5, 1.5);
alllife[i].jumpforceY = random(-20, -15);
alllife[i].startheight = alllife[i].y;
alllife[i].jumpbreak = 0;
}
if (alllife[i].lately_reproduction == true) {
alllife[i].reproductionbreak++;
}
if (alllife[i].reproductionbreak > 400) {
alllife[i].lately_reproduction = false;
alllife[i].reproductionbreak = 0;
}
if (alllife[i].hp <= 0) {
foods.push(new Food(alllife[i].x, alllife[i].y));
alllife.splice(i, 1);
}
}
let closeSlimes = findCloseSlimes();
for (let i = 0; i < closeSlimes.length; i += 2) {
if (closeSlimes[i].lately_reproduction == false && closeSlimes[i + 1].lately_reproduction == false && closeSlimes[i].lifetime > 600 && closeSlimes[i + 1].lifetime > 600) {
let new_name = name(2, closeSlimes[i].name[0] + closeSlimes[i + 1].name[0] + ‘.’)
alllife.push(new creature((closeSlimes[i].x + closeSlimes[i + 1].x) / 2, closeSlimes[i].y, 1, closeSlimes[i].r, closeSlimes[i + 1].g, (closeSlimes[i].b + closeSlimes[i + 1].b) / 2, new_name));
closeSlimes[i].lately_reproduction = true;
closeSlimes[i + 1].lately_reproduction = true;
}
}
// Deploy food
for (let i = foods.length – 1; i >= 0; i–) {
foods[i].display();
if (foods[i].y < 250) {
foods[i].y += 5;
}
foods[i].lifetime++;
if (foods[i].lifetime > 300) {
foods.splice(i, 1);
continue;
}
// Check collision with each creature
for (let j = 0; j < alllife.length; j++) {
if (foods[i].checkCollision(alllife[j])) {
foods.splice(i, 1); // Remove food if consumed
break; // Exit the loop after collision detected
}
}
}
}

}
function mouseClicked() {
if (gameStarted) {
// Add food at mouse click location
foods.push(new Food(mouseX, mouseY));
}
}

The whole code for this sketch can be divided into  several sections:

Initialization and Setup:

  • The code begins by loading a background image and a custom font in the preload() function.
  • In the setup() function, a canvas is created and attached to the position of the canvas, and a start button is added to initiate the game.
  • Creature Class:
    • The creature class represents individual pixelime creatures.
    • In this section, I utilized the skill of Object Oriented Programming(OOP) to deal with the task of multi objects issue.
    • Each creature has properties such as position (x and y), size, color, health points (hp), hunger, name, and various flags to control behavior.
    • The display() method draws the creature on the canvas using various rect() shapes for body parts and features like eyes and mouth.
    • The move() method updates the position of the creature, including a jumping motion controlled by the step() method.
    • There are methods for generating random names and finding close slimes for reproduction.

Food Class:

  • The Food class represents food items for the creatures.
  • Each food item has properties for position, radius, and lifespan.
  • The display() method draws the food item on the canvas, and the checkCollision() method checks if a creature consumes the food.

Game Loop:

  • The draw() function serves as the game loop.
  • It starts by displaying instructions if the game hasn’t started yet.
  • If the game has started, it updates and displays all creatures, checks for reproduction opportunities, and deploys food items.
  • Due to the requirement of iterating through each elements in the allife list and food list, this part can be quite time consuming, which sets a limit on the amount of slimes.

Challenges faced:

  • Implementing realistic creature behavior, such as jumping and movement.
  • Managing interactions between creatures and food items efficiently.
  • Ensuring smooth gameplay and avoiding performance issues with a large number of creatures and food items.(Which still required a lot of works todo)

Discoveries made:

  • Experimenting with different algorithms and approaches to simulate creature behavior and interactions.
  • Utilizing OOP in the management of various sorts of elements.

Reflection and Future Development

Although this project has achieved most of my expectations, there are still several areas for improvement:

  1. Lack of visual appeal: While the project has a significant amount of code to implement functionality, there is still a deficiency in visual artistry.
  2. UI design and interaction logic are not rational: The logic of pressing the ‘b’ key and mouse operations should have been reversed. Additionally, once the game starts, the instructional prompts disappear, which is not user-friendly.
  3. Failure to fully utilize OOP features: The distinctive features of the slimes are not prominent enough, indicating a missed opportunity to fully leverage object-oriented programming principles.

Takeaways:

  1. Setting appropriate goals is crucial. An overly ambitious proposal not only consumes significant energy but may also never be completed.
  2. While complex mechanisms are the core of a sketch, visually appealing presentation is also important to attract attention.
  3. A reasonable UI/UX interface can have a significant impact on the user-friendly aspects of the work.

Credits & References

Background Image:https://www.youtube.com/watch?v=MIq1QW0ycI8&t=247s

 
 

ProjectA-Pixelime(Part1)

Pixelime

——A story of how life expands and vanishes

Description: Pixelime is a hypothetical soft-bodied creature living in the wetlands of Southeast Asia. Please create your slimes in this simulator, feed them, witness their offspring, build families, and ultimately witness the end of life.

Abstruct: This project utilizes p5.js and HTML to construct a web simulator, simulating the evolution and life cycle of an interactive pixelime community. The inspiration for this work comes from the classic fantasy depiction of slimes and pixel art style found in various fantasy works. Users primarily use the mouse and keyboard to create new pixelime individuals and feed them. Afterward, users can observe the evolution of various slime communities.

A pixelime individual is primarily composed of appearance, color, name, and HP (health points). Individuals deployed directly by users have completely random names and colors. New individuals bred from existing ones inherit the family name from their parents and receive a random given name. Additionally, their color is a blend of their parents’ colors.

 

 

 

Mini Project 4. Purple Haze

Project Title: Purple Haze

Link:https://editor.p5js.org/N-A-E-S/sketches/9AoAeKusd

Brief Description: This project aims to create a self-generative smoke-like atmosphere for project A. The project used Perlin Noise to generate continuous random values to denote the different parts of the smoke area.

Visual Documentation: Purple Haze under different frames

Coding:

function setup() {
createCanvas(600, 600)
noStroke()
}

function draw() {
const noiseScale = 0.01
bscale=map(sin(frameCount*0.05),-1,1,5,10)
for (let i = 0; i < width; i += bscale) {
for (let j = 0; j < height; j += bscale) {
const noiseVal = noise(i * noiseScale, j * noiseScale,frameCount*0.05)
const currentColor = map(noiseVal, 0, 1, 0, 255)
fill(currentColor*0.75+map(sin(frameCount*0.05),-1,1,20,50),50,map(sin(frameCount*0.05),-1,1,120,170))
rect(i,j,bscale,bscale)
}
}
}

 

Mini Project3-Project A Proposal

Sketch1: Models of sexual reproduction of organisms

Link:https://editor.p5js.org/N-A-E-S/sketches/nweZqmRem

In Sketch 1 I explored a very basic part of living creatures: reproduction. In the simulation presentation, male creatures are marked as green circles with a name randomly picked, with the existing lifetime of individual creatures increasing, the color would go blue. A single male creature could live for 600 frames. When the male organism grows mature enough (more than 200 frames), it will search for unpregnant female organisms within 200 distance. If there is no qualified object in the corresponding range, it will swim randomly.

When a pair of creatures mate, the male swims towards his mate and mates. After a while the male leaves. The female then carries offspring after a short period of gestation. There is an 80% chance that breeding will produce male offspring and a 20% chance that it will produce female offspring. To mark the parent, male offspring inherits the first two characters of his name from his mother and female offspring inherits the first character of her name from her father.

I decided to develop this sketch into my project A because the sketch studied multiple creatures that interacted with each other. Also, this sketch could be self-developed into a self-sustained system.

Coding:

function setup() {
  createCanvas(600, 600);
  background(225)
}
class male_c{  //male creature
  constructor(name,x,y){
    this.name=name
    this.x=x
    this.y=y
    this.lifetime=0
    this.size=50
    this.r=0
    this.g=220
    this.b=0
    this.mate=null
    this.xspeed=random(-2,2)
    this.yspeed=random(-2,2)
    this.moveTimeCount=0
    this.mateTime=0
  }
  show(){
    noStroke()
    fill(this.r,this.g,this.b)
    ellipse(this.x,this.y,this.size,this.size)
    fill(0)
    text(this.name,this.x-15,this.y)
    
  }
  move(){
    if(this.mateTime>30){
      this.mate=null
      this.mateTime=0
        this.xspeed=random(-2,2)
      this.yspeed=random(-2,2)}
    if(this.mate!=null)
      this.mateTime+=1
    if(this.mate==null){
      this.moveTimeCount+=1
      if(this.moveTimeCount>=50)
        {
          this.xspeed=random(-2,2)
          this.yspeed=random(-2,2)
          this.moveTimeCount=0
        }
    }
    else{
      if(dist(this.x,this.y,this.mate.x,this.mate.y)>=50){
      this.xspeed=map(this.mate.x-this.x,300,-300,20,-20)
      this.yspeed=map(this.mate.y-this.y,300,-300,20,-20)
      }
      else{
        this.xspeed=0
        this.yspeed=0
      }
    }
    if(this.x>0 && this.y>0 && this.x<600 && this.y<600){ this.x+=this.xspeed this.y+=this.yspeed } } propose(fl){ this.mate=fl fl.pergnant=true } } class female_c{ //female creature constructor(name,x,y){ this.name=name this.x=x this.y=y this.lifetime=0 this.size=50 this.r=220 this.g=0 this.b=0 this.pergnant=false this.pergnant_break=0 } show(){ noStroke() fill(this.r,this.g,this.b) rect(this.x,this.y,this.size,this.size) fill(0) text(this.name,this.x+5,this.y+25) } birth(){ var gender=random(-1,1) if(gender>-0.5){
  this.new_name=this.name[0]+this.name[1]
  this.new_name+=alphabet[int(random(0,52))]
    mc.push(new male_c(this.new_name,this.x+random(10,30),this.y+random(10,30)))
  }
  else{
    this.new_name=this.name[0]
  this.new_name+=alphabet[int(random(0,52))]
  this.new_name+=alphabet[int(random(0,52))]
    fc.push(new female_c(this.new_name,this.x+(0.5+random(-1,1))*50*random(1,1.5),this.y+(0.5+random(-1,1))*50*random(1,1.5)))
  }
  this.pergnant=false
  }
}
var alphabet=new Array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z')//alphabet for name generate
var mc=new Array()
var fc=new Array()
function keyPressed(){
    if(key=='m')//male product
      {
        name=''
        wc=int(random(2,5))
        for(i=0;i<wc;i++)
          {
            w=int(random(1,52))
            name+=alphabet[w]
          }
        new_male=new male_c(name,mouseX,mouseY)
        mc.push(new_male)
        console.log(new_male.name)
      }
      if(key=='f')
      {
        name=''
        wc=int(random(2,5))
        for(i=0;i<wc;i++)
          {
            w=int(random(1,52))
            name+=alphabet[w]
          }
        new_female=new female_c(name,mouseX,mouseY)
        fc.push(new_female)
        console.log(new_female.name)
      }
  if(key=='c'){
    mc=new Array()
    fc=new Array()
  }
}
function draw() {
  background(255)
  for(i=0;i<mc.length;i++){ mc[i].b+=0.5 mc[i].show() mc[i].move() if(mc[i].mate==null && mc[i].lifetime>200){
      for(j=0;j<fc.length;j++){
        if(dist(mc[i].x,mc[i].y,fc[j].x,fc[j].y)<=200 && fc[j].pergnant==false) mc[i].propose(fc[j]) } } if(mc[i].mate!=null){ line(mc[i].x,mc[i].y,mc[i].mate.x,mc[i].mate.y) } mc[i].lifetime+=1 mc[i].size50=map(mc[i].lifetime,0,500,50,100) if(mc[i].lifetime>=600)
      mc.splice(i,1)
  }
  for(i=0;i<fc.length;i++){ fc[i].b+=0.5 fc[i].show() fc[i].lifetime+=1 if(fc[i].pergnant==true) fc[i].pergnant_break+=1 if(fc[i].pergnant_break>200 && fc[i].pergnant==true)
      {
        fc[i].pergnant_break=0        
        fc[i].pergnant=false
        fc[i].birth()
      }
        console.log(fc[i].pergnant_break,fc[i].pergnant)
    fc[i].size50=map(fc[i].lifetime,0,500,50,100)
    if(fc[i].lifetime>=2000)
      fc.splice(i,1)
  }
    
} 

Sketch2: Feed the loading ring

Link:https://editor.p5js.org/N-A-E-S/sketches/0Iwsggodt

This sketch focuses on exploring the feeding and hunting of food. The loading ring simulated a snake-shaped creature. The HP stands for the health standard of the creature, which will decrease over time.  A higher HP point means a larger size of the creature and a faster spinning speed. Pressing the mouse will deploy food in the mouse position. The creature will adjust its radius to eat the food. 

coding:function setup() {
createCanvas(400, 400);
}
var t=0
var hp=100
var r=50
var rp=50
class food{
constructor(hp,x,y){
this.x=x
this.y=y}
}
f=new food(0,200,200)
function draw() {
if(mouseIsPressed){
f.x=mouseX
f.y=mouseY
f.hp=random(10,15)
rp=dist(f.x,f.y,200,200)
}
t+=0.05*hp*0.01
if(hp>0)
hp-=0.05
background(0,20);
if(r<rp)
r+=1
if(r>rp)
r-=1
console.log(r,rp)
noStroke()
sv=r*sin(t)
cv=r*cos(t)
x=200+sv
y=200+cv
fill(255)
text('hp:'+hp,10,10,200,200)
circle(x,y,hp*0.25)
if(abs(f.x-x)<5 && abs(f.y-y)<5)
{
hp+=f.hp
f.hp=0
}
if(f.hp>1)
fill(255)
else
fill(0)
circle(f.x,f.y,10)
}

Sketch3: mountain drawing with Perlin Noise

 

Link:https://editor.p5js.org/N-A-E-S/sketches/MYnB4NKQM

In this sketch, I use two Perlin Noise functions to draw a multi-dimensional mountain. The color and altitude both decrease. With the Perlin Noise function, we could get the different mountain shapes.

Coding:

function setup() {
createCanvas(400, 400);
background(0);
stroke(100)
}
var x=1
var xspeed=2
var c1=100
var c2=50
var layers=0
function draw() {
let freq = frameCount * 0.02;
let amp = 250-0.05*layers;
let amp2=250-0.05*layers
x+=xspeed
if(x>400 || x<=0){
xspeed=-xspeed
layers+=50
c2=c1
c1+=50
}
let noiseValue = noise(freq) * amp; // noise
let noiseValue2= noise(freq)*amp2
let yNoise=50+noiseValue+layers
let yNoise2=100+noiseValue2+layers
noStroke()
ellipse(x, yNoise, 5, 5);
ellipse(x, yNoise2, 5, 5);
fill(c2)
rect(x,yNoise2,2,400-yNoise2)
fill(c1)
rect(x,yNoise,2,yNoise2-yNoise)
}