/** * 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(){ 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 ft = (A,x,y,z) => A.map(a => [a[0]+x, a[1]+y, a[2]+z]); let fs = (A,x,y,z) => A.map(a => [x*a[0], y*a[1], z*a[2]]); let fr = (A,r) => A.map( a => [ a[0]*cos(r) + a[2]*sin(r), a[1], -a[0]*sin(r) + a[2]*cos(r) ] ); let fp = (A) => A.map(a => [a[0]/a[2], a[1]/a[2]]); let 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 = fr(S, 2*t); S = ft(S, 0, 1, 1); // カメラに応じて平行移動&回転 S = ft(S, -game.camera.pos[0], -game.camera.pos[1],-game.camera.pos[2]); S = fr(S, -game.camera.angle); // 投影面の後ろにあるときは描画しない if (S[0][2] < 1) return; // 投影 S = fp(S); S = 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.field = [ [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,1,1,1,0,0,0,0,0,0,0,0], [0,0,1,0,0,1,1,0,0,0,0,0,0], [0,1,0,0,0,0,0,1,1,1,0,0,0], [0,1,0,0,0,0,0,0,0,0,1,1,0], [0,1,0,0,0,0,0,1,0,0,0,1,0], [0,1,0,0,0,1,1,0,1,0,0,1,0], [0,1,1,1,1,0,0,0,0,1,1,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0], ]; // 地形のカメラ計算が不完全(y座標が0のときだけ正しく動く) // ので注意 this.camera = new Camera([2, 0, 6], 0); } /** * 毎フレーム行う処理 */ proc(){ // プログラム再生してからの経過秒数 let t = millis()/1000; // マウスで回る this.camera.angle = 2*PI * mouseX / width; // WSADで移動 let inputVec = ( !keyIsPressed ? [0, 0] : key === 'w' ? [0, 1] : key === 'd' ? [1, 0] : key === 's' ? [0, -1] : key === 'a' ? [-1, 0] : [0, 0] ); let walkVec = inputVec; let x = inputVec[0]; let z = inputVec[1]; let r = game.camera.angle; walkVec[0] = z*sin(r) + x*cos(r); walkVec[1] = z*cos(r) - x*sin(r); this.camera.pos[0] += walkVec[0] / 4; this.camera.pos[2] += walkVec[1] / 4; // 投影面上の1点 [l,m] の明るさをの範囲で求める関数 let briFilm = (l,m) => { // 床の平面上の点[x,z]のRGBを求める関数 let briFloor = (x,z) => { let a = floor(x); let b = floor(z); if (a >= 0 && a < this.field.length && b >= 0 && b < this.field[0].length) { // フィールドの範囲内 let tile = this.field[a][b]; return tile === 0 ? [22, 190, 45] : [236, 231, 195]; } // フィールドの範囲外 return [22, 150, 45]; } // 床 y=1 との交点 p を求める let h = 1; let p = [l*h/m, h, h/m]; //----- カメラ情報に応じて交点を操作 let r = game.camera.angle; //y軸に対する回転量(ラジアン) // 回転させてから let x = p[0]; let z = p[2]; p[0] = z*sin(r) + x*cos(r); p[2] = z*cos(r) - x*sin(r); // 平行移動 p[0] += this.camera.pos[0]; p[2] += this.camera.pos[2]; //----- // 交点pの床の模様を、関数の値(計算結果)とする return briFloor(p[0], p[2]); }; //----- 描画 background(12, 178, 227); noStroke(); let pixelSize = 6; for(let y=height/2; y 1) { let playerDrawer = new ChickenDrawer( new Vec2(width * x3/z3 + width/2, height * y3/z3 + height/2), frameCount/2, 0 //floor(player.pos.x) + floor(player.pos.y) ); playerDrawer.draw(); } let sankakuDrawer = new Sankaku(); let sankakuDrawerTypeB = new Sankaku(); sankakuDrawer.draw(); sankakuDrawerTypeB.draw(); } } let game; function setup() { createCanvas(640,640); frameRate(15); game = new Game(); } function draw() { game.proc(); } /* 回転前の座標X,Y,ZをY軸にr回転させると 回転後の座標X',Y',Z'は X'=Zsin(r)+Xcos(r) Y'=Y Z'=Zcos(r)-Xsin(r) となる。 */