Media Art ProgrammingWeek 07 · Period 02 Handout

p5.js / Media Art Programming

7주차 2교시 — 객체 배열과 클래스 확장

Classes and Objects 주제의 학생용 강의 자료입니다.

WEEK 07 PERIOD 02 P5.JS MEDIA ART

7주차 2교시 — 객체 배열과 클래스 확장

강의 아웃라인: week-07-outline 이전 교시: week-07-period1-student (왜 클래스가 필요한가 + class 기본 문법) 다음 교시: week-07-period3-student (학생 주도 실습)

항목 내용
대상 P5.js 입문자 (테크아트 전공)
소요 시간 45분
사전 준비 1교시 class 기본 문법 (constructor·this·메서드)
학습 목표
  • 객체 배열을 활용하여 수십~수백 개의 객체를 동시에 생성·관리할 수 있습니다
  • 클래스에 바운싱, 색상 등 기능을 확장할 수 있습니다
  • 마우스/키보드 인터랙션으로 객체를 동적으로 추가할 수 있습니다

1교시 복습

빠른 퀴즈

퀴즈 1

클래스 안에서 자기 자신의 x좌표를 참조하려면?

: this.x입니다. this는 ’이 객체 자신’이라는 뜻입니다.

퀴즈 2

설계도(클래스)로 실제 객체를 만드는 키워드는?

: new입니다. new Ball(200, 100) 이렇게 사용합니다.

퀴즈 3

ball.move()에서 move()는 뭐라고 부르나요?

: 메서드입니다. 클래스 안에 정의된 함수입니다.

2교시 전환

1교시에서 공 1개를 만들어 보았습니다. 실제로 작품을 만들려면 공이 수십 개, 수백 개 필요합니다. let ball1, ball2, ball3, ..., ball100처럼 변수 100개를 선언할 수는 없습니다.

6주차에서는 이런 문제를 배열로 해결했습니다. 오늘은 숫자 대신 객체를 배열에 넣습니다.


심화 개념 1 — 객체 배열: 단계적으로 만들어보기

Step A: 먼저 공 3개를 변수로 만들어보기

w07 fig05 object array steps

1교시에서 공 1개는 let ball = new Ball(...)로 만들었습니다. 이를 3개로 늘려 보겠습니다.

let ball1 = new Ball(100, 200, 2, 3);
let ball2 = new Ball(250, 150, -1, 2);
let ball3 = new Ball(350, 300, 3, -2);

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  ball1.move(); ball1.display();
  ball2.move(); ball2.display();
  ball3.move(); ball3.display();
}

3개까지는 이렇게 관리할 수 있습니다. 하지만 10개, 50개가 되면 ball1부터 ball50까지 변수를 만들고, draw()에서도 50줄을 써야 합니다.

이럴 때 배열을 사용합니다. 6주차에서 했던 것처럼 동일한 원리입니다.

Step B: 변수 3개를 배열 1개로 바꾸기

위 코드를 배열로 바꾸면 다음과 같습니다.

let balls = [];

function setup() {
  createCanvas(400, 400);
  balls[0] = new Ball(100, 200, 2, 3);
  balls[1] = new Ball(250, 150, -1, 2);
  balls[2] = new Ball(350, 300, 3, -2);
}

function draw() {
  background(220);
  for (let i = 0; i < balls.length; i++) {
    balls[i].move();
    balls[i].display();
  }
}

let balls = []; — 빈 배열 선언입니다. 6주차에서 했던 것과 똑같습니다.

balls[0], balls[1], balls[2]에 Ball 객체를 넣었습니다. 6주차에서 x[0] = 100;처럼 숫자를 넣었던 것과 같은 원리입니다. 숫자 대신 객체가 들어갈 뿐입니다.

balls[i].move() — ‘i번째 공아, 움직여라.’ for문으로 0번째부터 마지막까지 순회하면서 모든 공을 움직이고 그립니다.

Step C: push()로 50개 대량 생성

3개는 수동으로 넣었지만, 50개를 만들 때는 for문 + push()를 사용하면 됩니다.

let balls = [];

function setup() {
  createCanvas(400, 400);
  for (let i = 0; i < 50; i++) {
    balls.push(new Ball(
      random(width), random(height),
      random(-3, 3), random(-3, 3)
    ));
  }
}

function draw() {
  background(220);
  for (let i = 0; i < balls.length; i++) {
    balls[i].move();
    balls[i].display();
  }
}

실행 결과: - 50개의 파란 원이 각각 다른 위치에서 시작, 각각 다른 방향과 속도로 이동 - 전부 화면 밖으로 나감 (바운싱 아직 없음)

03 push objects 50 balls result

balls.push(new Ball(...))push()로 배열에 추가하는 방식은 6주차와 같습니다. random(width), random(-3, 3)처럼 매번 다른 랜덤 값이 들어가므로 각 공이 다른 위치와 속도를 가집니다.

6주차 배열 6개 vs 7주차 객체 배열 비교

w07 fig06 object array structure
6주차: 배열 6개 방식 7주차: 객체 배열 방식
let x = [], y = [], speedX = [], speedY = [], size = [], c = []; let balls = [];
변수가 6줄 변수가 1줄
x[i] += speedX[i]; balls[i].move();
데이터가 흩어져 있음 데이터가 하나로 묶여 있음
속성 추가 시 배열 추가 필요 클래스에 프로퍼티만 추가

배열 6개가 배열 1개로 줄었습니다. 공 하나의 모든 데이터가 하나의 객체 안에 들어 있기 때문입니다.

확인 질문

공에 투명도(alpha) 속성을 추가하고 싶으면 배열 방식과 클래스 방식은 각각 어떻게 다를까요?

: 배열 방식에서는 alpha[] 배열을 하나 더 만들어야 하고, 클래스 방식에서는 constructor에 this.alpha = 255; 한 줄만 추가하면 됩니다.


심화 개념 2 — 바운싱 + 색상: 클래스 확장

지금 공들이 전부 화면 밖으로 나가고, 다 같은 파란색이라 심심합니다. 클래스에 기능을 추가해서 한 번에 해결할 수 있습니다.

완성된 Ball 클래스

5주차 바운싱 코드를 bounce() 메서드로, 색상과 크기를 constructor에 추가합니다.

class Ball {
  constructor(x, y, speedX, speedY) {
    this.x = x;
    this.y = y;
    this.speedX = speedX;
    this.speedY = speedY;
    this.size = random(20, 50);                            // 랜덤 크기
    this.c = color(random(255), random(255), random(255)); // 랜덤 색상
  }

  move() {
    this.x += this.speedX;
    this.y += this.speedY;
  }

  bounce() {
    if (this.x > width || this.x < 0) {
      this.speedX *= -1;
    }
    if (this.y > height || this.y < 0) {
      this.speedY *= -1;
    }
  }

  display() {
    fill(this.c);
    noStroke();
    ellipse(this.x, this.y, this.size);
  }
}

this.size = random(20, 50) — 20에서 50 사이 랜덤 크기입니다. new Ball()을 할 때마다 random()이 실행되어 매번 다른 크기가 됩니다.

this.c = color(random(255), random(255), random(255)) — 랜덤 RGB 색상입니다. display()에서 fill(this.c)자기 자신의 색상으로 그립니다.

bounce() — 5주차 바운싱 코드가 그대로 메서드 안에 들어갔습니다. xthis.x로, speedthis.speedX로 바뀌었을 뿐입니다.

draw()에서 사용

function draw() {
  background(220);
  for (let i = 0; i < balls.length; i++) {
    balls[i].move();
    balls[i].bounce();
    balls[i].display();
  }
}

실행 결과: - 50개의 공이 각각 다른 크기, 다른 색상으로 벽에 튕기며 움직임

04 bounce method before after

balls[i].move() — 움직이고, balls[i].bounce() — 벽 확인하고, balls[i].display() — 그립니다. 세 줄이면 공 하나의 전체 동작입니다. 코드가 무엇을 하는지 바로 읽힙니다.

w07 fig07 concept integration
개념 통합

1~6주차의 모든 개념이 여기서 만납니다: 변수(프로퍼티), 조건문(바운스 if), 함수(메서드), 배열(객체 배열), 반복문(for문 순회). 오늘 배우는 클래스는 지금까지 배운 내용을 하나로 묶는 구조입니다.


심화 개념 3 — 마우스 인터랙션: 클릭으로 공 추가

지금은 setup()에서 50개를 미리 만들어 놓았습니다. 클릭할 때마다 새 공이 생기면 더 재미있어집니다.

mousePressed()에서 객체 추가

function mousePressed() {
  balls.push(new Ball(
    mouseX, mouseY,
    random(-3, 3), random(-3, 3)
  ));
}

실행 결과: - 마우스를 클릭할 때마다 클릭한 위치에 새로운 공이 생성 - 각 공은 랜덤 속도, 랜덤 크기, 랜덤 색상

05 mousepressed add ball at click

mousePressed() — 4주차에서 배운 클릭 이벤트입니다. balls.push() — 6주차에서 배운 배열 추가입니다. new Ball(mouseX, mouseY, ...) — 7주차 클래스로 객체 생성입니다.

4주차 인터랙션 + 6주차 배열 + 7주차 클래스가 한 코드 안에 결합됩니다. 세 줄에 세 주차의 개념이 모두 들어 있습니다.

바운스 시 색상 변경 보너스

벽에 부딪힐 때 색이 바뀌면 더 재미있습니다. bounce() 메서드에 한 줄만 추가하면 됩니다.

bounce() {
  if (this.x > width || this.x < 0) {
    this.speedX *= -1;
    this.c = color(random(255), random(255), random(255));  // 색 변경!
  }
  if (this.y > height || this.y < 0) {
    this.speedY *= -1;
    this.c = color(random(255), random(255), random(255));  // 색 변경!
  }
}
06 bounce color change sequence

바운싱 if문 안에 this.c = color(random(...)) 한 줄을 추가합니다. 벽에 부딪힐 때마다 색이 랜덤으로 바뀝니다.


미니 실습 — 코드 수정 체험

에디터에 아래 전체 코드를 복사해서 넣어 보세요. 그리고 다음을 바꿔 보세요.

class Ball {
  constructor(x, y, speedX, speedY) {
    this.x = x;
    this.y = y;
    this.speedX = speedX;
    this.speedY = speedY;
    this.size = random(20, 50);
    this.c = color(random(255), random(255), random(255));
  }

  move() {
    this.x += this.speedX;
    this.y += this.speedY;
  }

  bounce() {
    if (this.x > width || this.x < 0) {
      this.speedX *= -1;
      this.c = color(random(255), random(255), random(255));
    }
    if (this.y > height || this.y < 0) {
      this.speedY *= -1;
      this.c = color(random(255), random(255), random(255));
    }
  }

  display() {
    fill(this.c);
    noStroke();
    ellipse(this.x, this.y, this.size);
  }
}

let balls = [];

function setup() {
  createCanvas(400, 400);
  for (let i = 0; i < 20; i++) {
    balls.push(new Ball(
      random(width), random(height),
      random(-4, 4), random(-4, 4)
    ));
  }
}

function draw() {
  background(30);
  for (let i = 0; i < balls.length; i++) {
    balls[i].move();
    balls[i].bounce();
    balls[i].display();
  }
}

function mousePressed() {
  balls.push(new Ball(
    mouseX, mouseY,
    random(-4, 4), random(-4, 4)
  ));
}

실행되면 다음을 시도해 보세요.

  1. setup의 20100으로 바꿔보기 — 공이 100개로 늘어남
  2. random(-4, 4)random(-1, 1)로 바꿔보기 — 느리게 움직임
  3. this.size = random(20, 50)this.size = random(5, 80)으로 바꿔보기
  4. background(30)background(30, 30, 30, 50)으로 바꿔보기 — 잔상 효과!
잔상 효과

배경의 투명도를 낮추면 이전 프레임이 살짝 남아서 궤적이 보입니다.


실전 사례 시연

사례 1: 파티클 분수

class Particle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.speedX = random(-2, 2);
    this.speedY = random(-8, -3);
    this.size = random(5, 12);
    this.c = color(random(255), random(100, 255), 0);
  }

  update() {
    this.x += this.speedX;
    this.y += this.speedY;
    this.speedY += 0.15;  // 중력!
  }

  display() {
    fill(this.c);
    noStroke();
    ellipse(this.x, this.y, this.size);
  }
}

let particles = [];

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(0, 0, 0, 40);

  if (mouseIsPressed) {
    for (let i = 0; i < 3; i++) {
      particles.push(new Particle(mouseX, mouseY));
    }
  }

  for (let i = 0; i < particles.length; i++) {
    particles[i].update();
    particles[i].display();
  }

  // 화면 밖 파티클 제거 (성능)
  for (let i = particles.length - 1; i >= 0; i--) {
    if (particles[i].y > height) {
      particles.splice(i, 1);
    }
  }
}
07 particle fountain gravity trail

마우스를 누르면 파티클이 분수처럼 솟아올랐다가 떨어집니다.

핵심은 딱 한 줄입니다: this.speedY += 0.15 — 매 프레임 y속도에 0.15를 더합니다. 처음엔 위로 올라가지만, 속도가 점점 바뀌어 결국 아래로 떨어집니다. 이 효과를 중력으로 볼 수 있습니다. 10주차에서 정식으로 다룹니다.

splice()는 화면 밖 파티클을 지우는 코드입니다. 13주차(파티클 시스템)에서 다시 다루므로, 지금은 개념만 알아두면 됩니다.

사례 2: 다양한 도형 — 맛보기

Ball 클래스의 display()에서 ellipse() 대신 rect()를 쓰면 네모가 됩니다. 이름을 바꾸면 Square 클래스로 확장할 수 있습니다. 같은 화면에 공과 네모를 동시에 띄울 수도 있습니다.

Ball과 Square 코드는 구조가 비슷합니다. 공통 부분을 자동으로 물려받는 상속(inheritance)이라는 개념이 있지만, 이 수업에서는 다루지 않습니다.


3교시 실습 안내

핵심 한 줄

3교시 실습이 곧 실습과제 3입니다. 제출 완료 시 만점입니다. 중간고사(8주차) 전까지 P5.js 웹 에디터 링크로 제출합니다.

필수 요구사항 4개

# 요구사항 한 줄 설명
1 class 1개 이상 정의 constructor + 메서드 2개 이상
2 객체 배열로 10개 이상 관리 let balls = []; + for문
3 각 객체가 독립적으로 움직이기 move() + display()
4 인터랙션 1개 클릭 추가, 키보드 제어 등

이 네 가지만 지키면 됩니다.

세 가지 방향

방향 한 줄 설명
A. 바운싱 볼 월드 오늘 만든 코드에서 시작 — 가장 쉬움
B. 파티클 아트 마우스에서 파티클 생성 + 중력
C. 자유 주제 눈, 비, 별, 꽃잎 등 나만의 클래스
08 project directions a b c preview
방향 선택 팁

무엇을 골라야 할지 모르겠으면 A(바운싱 볼)부터 시작하세요. 2교시 미니 실습에서 이미 만들어 봤기 때문입니다.

3교시에서 자세한 스타터 코드와 단계별 가이드가 제공됩니다.