【p5.js×自己効力感】「しなやかな心の復元力」をバネ運動(Hookeの法則)とイッテンの色彩対比でコードに翻訳する【WSN : L-017】

深いダークグレーの空間の中央で黄金の光を放つ黄色い円と、周囲を漂う無数の細い直線群を描いたp5.jsの実行画面
  • URLをコピーしました!
今回の記事の目的
  • 心理学の概念である「自己効力感(Self-efficacy)」を、ヨハネス・イッテンの造形理論(形態と色彩の対比)を用いて視覚言語へと翻訳する
  • 外部の圧力を受け止めつつ元の形へ戻る「心のしなやかさ(弾性)」を、フックの法則(Spring Physics)の数理モデルで実装する
  • 能動的なアクション(クリック)によって内部パラメータが成長し、周囲の無秩序なノイズを美しい調和へと変えていくプロセスをp5.jsで体感する
ワタタク

どうもクリエイティブコーダーのワタタクです。

前回の記事『【自己効力感ってなに?】「きっとできる!」という心のエネルギーが生まれるしくみと美しい形』で、自己効力感とは「自分が動けば、世界はちゃんと応えてくれるという手応え」であり、造形の視点で見れば「外部からの圧力に対して、内側から押し返す『張力』と『重心』の確立」であると言語化しました。

今回はその実践編として、この「しなやかな復元力と自己効力感の成長」を、p5.jsの物理演算(バネ運動)と色彩対比を使って動くジェネラティブアートへと翻訳していきます。

この記事を書いた人

クリエイティブコーダー Watataku のプロフィール写真
ワタタク(Watataku)

クリエイティブコーダー

「才能」ではなく「発見」で描く。文系・36歳からの上達ログ。

  • ゼロから独学でクリエイティブコーディングに挑戦し、以下の実績を達成
    • 開始1年8ヶ月目までに公募6つに挑戦し、3つ入選(勝率50%)
      • KITTE大阪の18メートルあるAxCROSS大型スクリーン
      • 虎ノ門ヒルズステーションタワーB2F TOKYO NODE エントランスサイネージで展示
      • UN:O(東京大手町)にある会員限定のサテライトオフィスにて2作品選出・常設展示
  • 開始1年9ヶ月目に、虎ノ門ヒルズ49階、地上250mのインフィニティプールへ作品提供・展示
目次

視覚的イメージと造形秩序(イッテンの形態・色彩対比)

「自己効力感」を画面上の視覚言語として構築するにあたり、バウハウスの予備課程を率いたヨハネス・イッテンの造形理論における「形態の対比(円と直線)」と「明暗・寒暖の色彩対比」を設計の核に据えます。

形態の秩序:無数の直線群 vs ひとつの正円

画面全体を覆う無数の「細く鋭い直線群(外部環境・ノイズ・課題)」に対し、画面の中央に存在する「ひとつの正円(自己・主体)」を対置させます。

円はイッテンが「絶え間ない運動と精神性、内なる集中」の象徴とした基本形態です。外部の直線やマウスカーソルが円の領域に干渉しようとするとき、円はただ硬直して跳ね返すのではなく、一度わずかに凹み(衝撃を受け止め)、次の瞬間に元の真円へと美しく復元する「弾性」を見せます。

色彩の設計:冷たいダークグレー vs 発光する黄金のイエロー

  • 背景・外部環境: 彩度を極限まで落とした冷たい寒色系のダークグレー(#1A1D24 〜 #2E3440 相当)を配置し、冷静さや不確実性を表現します。
  • 中心の円(自己効力感の核): イッテンが最も強い発光とエネルギーを認め、暖色の極みとした「純粋な黄(Yellow)」や高明度のゴールドを与えます。

外部からの干渉を乗り越えるたびに、円の中心から外側へ向かって黄金色のグラデーションが脈動し、暗い空間に確かな光の領域(テリトリー)を押し広げていきます。

コード・物理挙動への落とし込み(バネ運動と成長の数理)

「外部の圧力を受け止めつつ、自らの力で元の形と位置を復元し、周囲の空間に波紋を広げる」という挙動は、フックの法則に基づくバネ運動(Spring Physics)によって数理的に記述できます。

Spring Physics(弾性復元力)の実装

円の重心および輪郭を構成する60個の頂点に対し、本来の理想的な位置へ戻ろうとする復元力(F)を与えます。

F = -k * (x – x0) – c * v

  • k(バネ定数): 心のしなやかさ・芯の強さ
  • c(減衰係数): 感情の揺れを鎮める落ち着き
  • v(速度ベクトル): 動きの勢い

マウスカーソル(外部からのストレスや課題)が近づくと斥力が働いて一時的に形が歪みますが、カーソルが離れると美しい減衰振動を描きながら元の調和へと戻ります。

自己効力感の蓄積(学習パラメータ efficacy)

画面をクリック(=能動的な行動を起こす)するたびに、内部変数 efficacy(0.1 〜 1.0)が少しずつ上昇します。

この値が高まるにつれ、バネ定数 k が最適化されて復元スピードが上がり、同時に円が放つ光の半径(影響力)が拡大します。これにより、「小さな行動と成功体験を積むほど、環境に対してより大きく安定した影響を与えられるようになる」という心理プロセスをアルゴリズムとして可視化しています。

実装プロトタイプ(p5.jsコードと解説)

以下は、p5.jsを用いた実装プロトタイプです。

マウスを近づけると外部の圧力によって中心の円が押されて凹みますが、画面をクリック(能動的なアクション)することで内側から黄金の波動が放たれ、周囲のノイズを美しい同心円状の軌道へと整列させながら、円自身の輝きと復元力が増していく様子を体験できます。

深いダークグレーの空間の中央で黄金の光を放つ黄色い円と、周囲を漂う無数の細い直線群を描いたp5.jsの実行画面
【WSN : L-017】
/**
 * Self-Efficacy (自己効力感) - Generative Prototype
 * Concept: Inner Tension & Elastic Resilience based on Johannes Itten's Theory
 */
let core;
let particles = [];
const NUM_PARTICLES = 180;

function setup() {
  createCanvas(800, 800);
  colorMode(HSB, 360, 100, 100, 100);
  
  // 中心の核(自己)の初期化
  core = new ResilientCore(width / 2, height / 2, 70);
  
  // 外部環境(ノイズ・課題)のパーティクル群
  for (let i = 0; i < NUM_PARTICLES; i++) {
    particles.push(new EnvironmentLine());
  }
}

function draw() {
  // 深い寒色系の背景(残像を残して時間の経過と軌跡を可視化)
  background(222, 28, 12, 25);
  
  // マウス位置からの外部圧力を計算
  let mousePos = createVector(mouseX, mouseY);
  let distToMouse = p5.Vector.dist(core.pos, mousePos);
  
  // 外部からの干渉(マウスが近づくと押される)
  if (distToMouse < 220 && mouseX > 0 && mouseY > 0) {
    let pushForce = p5.Vector.sub(core.pos, mousePos);
    let strength = map(distToMouse, 0, 220, 2.5, 0);
    pushForce.setMag(strength);
    core.applyForce(pushForce);
    core.disturb(strength * 8);
  }
  
  // 核の物理演算と描画
  core.update();
  core.display();
  
  // 外部環境の線群の更新と描画(核の効力感が高いほど整列していく)
  for (let p of particles) {
    p.interactWithCore(core);
    p.update();
    p.display(core.efficacy);
  }
}

// クリック(能動的な行動)で自己効力感の波動を放つ
function mousePressed() {
  core.triggerAction();
}

// --------------------------------------------------
// 中心の核(自己効力感を持つ主体)クラス
// --------------------------------------------------
class ResilientCore {
  constructor(x, y, baseRadius) {
    this.anchor = createVector(x, y); // 本来の重心
    this.pos = createVector(x, y);
    this.vel = createVector(0, 0);
    this.acc = createVector(0, 0);
    
    this.baseRadius = baseRadius;
    this.vertices = 60;
    this.offsets = new Array(this.vertices).fill(0);
    this.offsetVels = new Array(this.vertices).fill(0);
    
    // 自己効力感パラメータ (0.1 〜 1.0)
    this.efficacy = 0.25;
    this.pulseRadius = 0;
    this.pulseAlpha = 0;
  }

  applyForce(force) {
    this.acc.add(force);
  }

  // 外部からの衝撃による輪郭の歪み
  disturb(amount) {
    for (let i = 0; i < this.vertices; i++) {
      let angle = (TWO_PI / this.vertices) * i;
      let dir = p5.Vector.fromAngle(angle);
      let toMouse = createVector(mouseX - this.pos.x, mouseY - this.pos.y).normalize();
      let alignment = max(0, dir.dot(toMouse));
      this.offsetVels[i] -= alignment * amount * 0.15;
    }
  }

  // 能動的なアクション(波紋の放出と効力感の成長)
  triggerAction() {
    this.efficacy = min(1.0, this.efficacy + 0.12);
    this.pulseRadius = this.baseRadius;
    this.pulseAlpha = 90;
    
    // 輪郭を内側から外側へ力強く押し広げる
    for (let i = 0; i < this.vertices; i++) {
      this.offsetVels[i] += 6.0 * this.efficacy;
    }
  }

  update() {
    // 1. 重心へのSpring Physics(フックの法則)
    // 自己効力感が高いほど、しなやかで強い復元力(k)を持つ
    let k = map(this.efficacy, 0.1, 1.0, 0.03, 0.12);
    let damping = 0.86;
    
    let springForce = p5.Vector.sub(this.anchor, this.pos);
    springForce.mult(k);
    this.applyForce(springForce);
    
    this.vel.add(this.acc);
    this.vel.mult(damping);
    this.pos.add(this.vel);
    this.acc.mult(0);

    // 2. 輪郭の弾性復元(各頂点のバネ運動)
    for (let i = 0; i < this.vertices; i++) {
      let spring = -k * 1.8 * this.offsets[i];
      this.offsetVels[i] += spring;
      this.offsetVels[i] *= 0.85;
      this.offsets[i] += this.offsetVels[i];
    }

    // 3. 波紋の拡大
    if (this.pulseAlpha > 0) {
      this.pulseRadius += 8 + this.efficacy * 6;
      this.pulseAlpha -= 2.2;
    }
  }

  display() {
    push();
    translate(this.pos.x, this.pos.y);

    // 行動によって放たれる黄金の波動(影響力の可視化)
    if (this.pulseAlpha > 0) {
      noFill();
      stroke(45, 75, 100, this.pulseAlpha);
      strokeWeight(2);
      ellipse(0, 0, this.pulseRadius * 2);
    }

    // 自己効力感に応じたオーラ(暖色・黄の光)
    let glowSize = this.baseRadius * (1.8 + this.efficacy * 2.2);
    for (let r = glowSize; r > this.baseRadius; r -= 12) {
      let alpha = map(r, this.baseRadius, glowSize, 18 * this.efficacy, 0);
      fill(44, 80, 98, alpha);
      noStroke();
      ellipse(0, 0, r * 2);
    }

    // 弾性を持つ円の輪郭描画
    fill(45, 65 + this.efficacy * 25, 98, 95);
    stroke(48, 30, 100, 100);
    strokeWeight(2);
    
    beginShape();
    for (let i = 0; i < this.vertices; i++) {
      let angle = (TWO_PI / this.vertices) * i;
      let r = this.baseRadius + this.offsets[i];
      let x = cos(angle) * r;
      let y = sin(angle) * r;
      vertex(x, y);
    }
    endShape(CLOSE);
    
    // 中心の不動のコア(純白)
    fill(45, 10, 100, 90);
    noStroke();
    ellipse(0, 0, 12 + this.efficacy * 10);
    pop();
  }
}

// --------------------------------------------------
// 外部環境(不確実性・ノイズ)を表す直線クラス
// --------------------------------------------------
class EnvironmentLine {
  constructor() {
    this.pos = createVector(random(width), random(height));
    this.angle = random(TWO_PI);
    this.length = random(15, 35);
    this.speed = random(0.5, 1.5);
  }

  interactWithCore(core) {
    let d = p5.Vector.dist(this.pos, core.pos);
    let influenceRange = 140 + core.efficacy * 280; // 効力感が高いほど広範囲に秩序をもたらす

    if (d < influenceRange) {
      // 核からの距離が近いほど、ランダムな動きから「核を中心とする同心円状の美しい秩序」へと整列する
      let angleToCore = atan2(this.pos.y - core.pos.y, this.pos.x - core.pos.x);
      let targetAngle = angleToCore + HALF_PI; // 接線方向(調和の軌道)
      let alignStrength = map(d, 0, influenceRange, 0.15 * core.efficacy, 0);
      this.angle = lerp(this.angle, targetAngle, alignStrength);
    } else {
      // 影響圏外ではノイズによって不規則に揺らぐ
      this.angle += random(-0.08, 0.08);
    }
  }

  update() {
    this.pos.x += cos(this.angle) * this.speed;
    this.pos.y += sin(this.angle) * this.speed;

    // 画面端でのループ
    if (this.pos.x < 0) this.pos.x = width;
    if (this.pos.x > width) this.pos.x = 0;
    if (this.pos.y < 0) this.pos.y = height;
    if (this.pos.y > height) this.pos.y = 0;
  }

  display(efficacy) {
    push();
    translate(this.pos.x, this.pos.y);
    rotate(this.angle);
    // 寒色系の直線(効力感が高まるとわずかに暖色の光を反射する)
    stroke(215 - efficacy * 30, 20, 65, 45);
    strokeWeight(1.2);
    line(-this.length / 2, 0, this.length / 2, 0);
    pop();
  }
}

パラメータ調整の要点

コード内の数値を書き換えることで、自分の理想とする「心の質感」や空間の雰囲気を自由にチューニングできます。

  1. バネ定数 k(復元力の強さ)
    • k = map(this.efficacy, 0.1, 1.0, 0.03, 0.12) の数値を小さくすると、外部の力を柔らかく受け止める受容的な印象になり、大きくすると跳ね返す力の強い凛とした緊張感が生まれます。
  2. 減衰係数 damping(余韻と落ち着き)
    • 0.85〜0.86 を 0.95 に近づけると、衝撃を受けたあとに長く余韻(振動)が残り、0.70 に近づけると重厚で落ち着いた静止へと素早く収束します。
  3. 影響範囲 influenceRange(環境との調和)
    • core.efficacy の上昇に伴い、周囲の無秩序な線分が円の接線方向(同心円状の美しい公転軌道)へと整列する範囲が広がります。自分の内なる秩序が確立されることで、結果として周囲の環境(世界)の風景そのものが調和していくプロセスを視覚化しています。

まとめ:言葉の理解を「動く体験」へ昇華させる

「自己効力感」という目に見えない心の働きも、バネの復元力や色彩のコントラストというルールに置き換えることで、手触りのある美しい作品へと生まれ変わります。

ぜひ実際にクリックを重ねて、中央の黄金の円が輝きを増し、周囲のノイズが美しい光の軌道へと変わっていく様子を眺めてみてください。

ワタタク

それでは今日もレッツワクワクコーディング。

ワタタク(Watataku)
クリエイティブコーダー
独学1年9ヶ月で虎ノ門ヒルズ49階、地上250mのインフィニティプールへ作品提供・展示

言葉×コード×手書き。三位一体の表現が成長する過程の記録

【発信】クリエイティブコーディング(2024.3~) / 絵(2025.11~) / モレスキン / 思考法 / 生き方

▼運営者について
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次