/** * 2次元ベクトルのクラス */ class Vec2 { /** * @param {number} x成分 * @param {number} y成分 */ constructor(x, y) { this.x = x; this.y = y; } /** * @param {Vec2} b 足したいベクトル */ add(b) { let a = this; return new Vec2(a.x+b.x, a.y+b.y); } /** * @param {Vec2} b 引きたいベクトル */ sub(b) { let a = this; return new Vec2(a.x-b.x, a.y-b.y); } /** * @param {number} s ベクトルにかけたい実数 */ mul(s) { return new Vec2(s*this.x, s*this.y); } /** * @param {number} s この実数でベクトルを割る */ div(s) { return new Vec2(this.x/s, this.y/s); } /** * @param {Vec2} v このベクトルとドット積をとる */ dot(b) { let a = this; return a.x*b.x + a.y*b.y; } /** * @returns ベクトルの大きさ(成分のユークリッド距離) */ mag() { return sqrt(this.x ** 2 + this.y ** 2); } /** * @param {number} s 大きさをsとしたベクトルを返す */ magSet(s) { return this.mul(s/this.mag()); } /** * @param {number} s 大きさにsを足したベクトルを返す */ magAdded(s) { return this.mul(1+s/this.mag()); } /** * @param {number} rad 回転させたい角度。単位はラジアン。 */ rotated(rad) { return new Vec2( this.x*cos(rad) - this.y*sin(rad), this.x*sin(rad) + this.y*cos(rad) ); } /** * @returns 正規化されたベクトル */ normalized() { return this.mul(1/this.mag()); } copy() { return new Vec2(this.x, this.y); } /** * @param {Vec2} b このベクトルと成分が等しいか否かを返す */ equals(b) { let a = this; return a.x === b.x && a.y === b.y; } } class Camera { constructor(pos, angle) { this.pos = pos; this.angle = angle; } } class Sankaku{ constructor(pos){ this.pos = pos; this.ft = []; this.fs = []; this.fr = []; this.fp = []; this.fc = []; this.V = [ [0, 0, 0], [1, 0, 0], [0, 0, 1], [0, -1, 0] ]; this.F = [ [0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2] ]; } draw(){ let pos = this.pos; let angle = this.angle; let animFrame = this.animFrame; /** 角度を0~2πに正規化する。0.0001足しているのは誤差によるバグを避けるため */ let normalized = a => ((a+0.0001+10000*PI) % (2*PI)); let isFacingUs = a => (a = normalized(a), (a >= -PI/36 && a <= 37*PI/36)); let isFacingUsNarrow = a => (a = normalized(a), (a >= PI/12 && a <= 11*PI/12)); angle = normalized(angle); this.ft = (A,x,y,z) => A.map(a => [a[0]+x, a[1]+y, a[2]+z]); this.fs = (A,x,y,z) => A.map(a => [x*a[0], y*a[1], z*a[2]]); this.fr = (A,r) => A.map( a => [ a[0]*cos(r) + a[2]*sin(r), a[1], -a[0]*sin(r) + a[2]*cos(r) ] ); this.fp = (A) => A.map(a => [a[0]/a[2], a[1]/a[2]]); this.fc = (A,w,h) => A.map(a => [width*a[0]+width/2, height*a[1]+height/2]); let t = millis()/1000; let S = this.V; S = this.fr(S, 2*t); S = this.ft(S, 0, 2, 3); S = this.ft(S, -game.camera.pos[0], -game.camera.pos[1], -game.camera.pos[2]); S = this.fp(S); S = this.fc(S, width, height); //background(200); strokeWeight(3); //noFill(); fill('orange'); for(let i=0; i ((a+0.0001+10000*PI) % (2*PI)); let isFacingUs = a => (a = normalized(a), (a >= -PI/36 && a <= 37*PI/36)); let isFacingUsNarrow = a => (a = normalized(a), (a >= PI/12 && a <= 11*PI/12)); angle = normalized(angle); let shake = new Vec2(0, ((animFrame % 20) > 10) ? -2 : 0); let bodyCenter = pos.add(new Vec2(0,-16)).add(shake); let assCenter = isFacingUsNarrow(angle+PI) ? bodyCenter.add(new Vec2(12*cos(angle+PI), -4)) : null; let headCenter = bodyCenter.add(new Vec2(6*cos(angle),-16)).sub(shake); let beakCenter = isFacingUs(angle) ? headCenter.add(new Vec2(12*cos(angle),0)) : null; let eyes = []; if (isFacingUs(angle-PI/3)) eyes.push(headCenter.add(new Vec2(8*cos(angle-PI/3),-4))); if (isFacingUs(angle+PI/3)) eyes.push(headCenter.add(new Vec2(8*cos(angle+PI/3),-4))); push(); stroke('black'); strokeWeight(2); fill('yellow'); let drawBody = _=>circle(bodyCenter.x, bodyCenter.y, 32); let drawHead = _=>circle(headCenter.x, headCenter.y, 24); if (isFacingUs(angle)) { drawBody();drawHead(); } else { drawHead();drawBody(); } fill('black'); strokeWeight(0); for(let eye of eyes){ circle(eye.x, eye.y, 3); } fill('orange') strokeWeight(1); if (beakCenter !== null) { circle(beakCenter.x, beakCenter.y, 8); } fill('black'); textAlign(CENTER, CENTER); textSize(10); strokeWeight(1); if (assCenter !== null) { text('x', assCenter.x, assCenter.y); } pop(); } } class Game { constructor() { this.pixelSize = 0; this.p=[]; this.briFilm=[]; this.briFloor=[]; this.x=0; this.y=0; this.z=0; this.a=0; this.b=0; this.l=0; this.m=0; //----- ゲームの舞台を生成 this.field = [ [1,1,1,0,1,0,1,0,1,1,0,0,1], [0,1,0,0,1,0,1,0,1,0,1,0,1], [0,1,0,0,0,1,0,0,1,1,0,0,1], [0,1,0,0,1,0,1,0,1,0,1,0,1], [0,1,0,0,1,0,1,0,1,1,0,0,1], ]; //----- ゲームの舞台を生成 this.sideField = [ [1,1,1,0,1,0,1,0,1,1,0,0,1], [0,1,0,0,1,0,1,0,1,0,1,0,1], [0,1,0,0,0,1,0,0,1,1,0,0,1], [0,1,0,0,1,0,1,0,1,0,1,0,1], [0,1,0,0,1,0,1,0,1,1,0,0,1], ]; // 地形のカメラ計算が不完全(y座標が0のときだけ正しく動く) // ので注意 this.camera = new Camera([2, 0, 6], 0); } /** * 毎フレーム行う処理 */ proc(){ //----- this.pixelSize = 6; let t = millis()/1000; //this.camera.pos = [0, 0, 0]; this.camera.angle = t; // プログラムを実行してから経過した秒数 //let t = millis()/1000; // 投影面上の1点 [l,m] の明るさを0~1の範囲で求める関数 this.briFilm = (l,m) => { // 床の平面上の点[x,z]の明るさを求める関数 this.briFloor = (x,z) => { this.a = floor(x); this.b = floor(z); if (this.a<0 || this.a>4 || this.b<0 || this.b>11){ //console.log(this.a); return 0.5;//ここのif文で道以外の部分の記述ができるかも・・・ } return this.field[this.a][this.b]; } // 床 y=2, 天井 y=-3 との交点 p を求める this.p = [1, -3].map(h => [l*h/m, h, h/m]); // 投影面より前方にある交点を採用 this.p = this.p.reduce((a,b) => a[2]>b[2] ? a : b); // 交点pの床の模様を、関数の値(計算結果)とする //----- カメラ情報に応じて交点を操作 // カメラの座標に応じて平行移動 this.p[0] += game.camera.pos[0]; this.p[2] += game.camera.pos[2]; // カメラの角度に応じて地形を回転 let r = game.camera.angle; //y軸に対する回転量(ラジアン) // カメラ座標を中心に回転したいので、カメラ座標を引く this.p[0] -= game.camera.pos[0]; this.p[2] -= game.camera.pos[2]; // 実際に回転を行う let x = this.p[0]; let z = this.p[2]; this.p[0] = z*sin(r) + x*cos(r); this.p[2] = z*cos(r) - x*sin(r); // さきほど引いたカメラ座標を足す(もどす) this.p[0] += game.camera.pos[0]; this.p[2] += game.camera.pos[2]; return this.briFloor(this.p[0], this.p[2]); }; } /** * 毎フレーム行う描画 */ draw(){ background(0); noStroke(); for(this.y=0; this.y