dfsafsadfasdfdsf
🧩 Syntax:
/**
* 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 SankakuTry{
//sankakuX,sankakuY,sankakuZ,sankakuColor
constructor(){
this.sankakuX = 0;
this.sankakuY = 1;//この場合は1以外はだめ
this.sankakuZ = 0;
//this.pos=[0,0,0];
this.sankakuColor='red';
this.V = [
[0, 0, 0],
[1, 0, 0],
[0, 0, 1],
[0, -1, 0]
];
this.S = [];
this.vertsInCamCoord = [];
this.F = [
[0, 2, 1],
[0, 1, 3],
[1, 2, 3],
[0, 3, 2]
];
}
getZPosInCamCoord() {
return this.vertsInCamCoord[0][2];
}
proc(){
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,this.sankakuX,this.sankakuY,this.sankakuZ);//S = ft(S, 0, 1, 1); 5,1,1//this.sankakuX,this.sankakuY,this.sankakuZ
// カメラに応じて平行移動&回転
S = ft(S, -game.camera.pos[0], -game.camera.pos[1],-game.camera.pos[2]);
S = fr(S, -game.camera.angle);
this.vertsInCamCoord = ft(S, 0, 0, 0);
// 投影面の後ろにあるときは描画しない
if (S[0][2] < 1) return;
// 投影
S = fp(S);
S = fc(S, width, height);
this.S = S;
}
draw() {
fill(this.sankakuColor);
let S = this.S;
if (S.length === 0) return;
for(let i=0; i<this.F.length; i++) {
triangle(
...S[this.F[i][0]], ...S[this.F[i][1]], ...S[this.F[i][2]]
);
}
}
}
class ChickenDrawer{
/**
* ひよこ?を描画するコマンド
* @param {Vec2} pos 足元の位置ベクトル
* @param {number} angle Z軸の角度
* @param {number} animFrame アニメーションの何フレーム目を再生するか
* @param {number} depth
*/
constructor(pos, angle, animFrame, depth) {
this.pos = pos;
this.angle = angle;
this.animFrame = animFrame;
this.depth = depth;
this.zPosInCamCoord = 1;
}
getZPosInCamCoord() {
return this.zPosInCamCoord;
}
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);
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 Actor {
constructor() {
this.x = 0;
this.y = 0;
this.z = 0;
this.angle = 0;
}
}
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,2,1,0,0,1,1,0,0,0,0,0,0],
[0,1,3,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,2,1,1,0,1,0,2,1,0],
[0,1,1,1,1,3,0,0,0,1,1,3,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
];
// 地形のカメラ計算が不完全(y座標が0のときだけ正しく動く)
// ので注意
this.camera = new Camera([3, 0, -0.75], 15*PI*2/16);
this.hiyoko = new Actor();
this.hiyoko.x = 1.5;
this.hiyoko.y = 1;
this.hiyoko.z = 1.5;
}
/**
* 毎フレーム行う処理
*/
proc(){
// プログラム再生してからの経過秒数
let t = millis()/1000;
// WSADで操作
if (key === 'a') {
game.hiyoko.angle -= 1/10;
}else if (key === 'd') {
game.hiyoko.angle += 1/10;
}else{
let inputVec = (
!keyIsPressed ? [0, 0]
: key === 'w' ? [0, 1]
: key === 's' ? [0, -1]
: [0, 0]
);
let walkVec = inputVec;
let x = inputVec[0];
let z = inputVec[1];
let r = game.hiyoko.angle;
walkVec[0] = z*sin(r) + x*cos(r);
walkVec[1] = z*cos(r) - x*sin(r);
game.hiyoko.x += walkVec[0] / 5;
game.hiyoko.z += walkVec[1] / 5;
}
// カメラをひよこの後ろへ
let hiyokoPosX = game.hiyoko.x;
let hiyokoPosZ = game.hiyoko.z;
let hiyokoGyakuX = -3*sin(game.hiyoko.angle);
let hiyokoGyakuZ = -3*cos(game.hiyoko.angle);
let cameraPosX = hiyokoPosX + hiyokoGyakuX;
let cameraPosZ = hiyokoPosZ + hiyokoGyakuZ;
game.camera.pos = [cameraPosX, 0, cameraPosZ];
game.camera.angle = game.hiyoko.angle;
let colorDarkgreen = [22, 100, 45];
let colorGreen = [22, 150, 45];
let colorRoad = [236, 231, 195];
// 投影面上の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];
if(tile === 1) {
return colorRoad;
}else if (tile === 2) {
let j = x%1;
let k = z%1;
if (j+k-1 > 0) return colorRoad;
}else if (tile === 3) {
let j = x%1;
let k = z%1;
if (j+k-1 < 0) return colorRoad;
}
return colorGreen;
}
// フィールドの範囲外
return colorDarkgreen;
}
// 床 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<height; y+=pixelSize) {
for(let x=0; x<width; x+=pixelSize) {
let l = x/width - 1/2;
let m = y/height - 1/2;
fill(...briFilm(l,m));
rect(x, y, pixelSize, pixelSize);
}
}
// 三角錐の配列
let sankakuList = [];
// 三角錐の配列
let sankakuSitaList = [];
// 影の配列
let shadowList = [];
for(let i=0; i<5; i++){
let color;
if(i%2==0){
color='orange'
}else{
color='yellow'
}
// 三角錐を生成
let sankaku = new SankakuTry();
sankaku.sankakuX = 2 + 1.5*i;
sankaku.sankakuY = 0 + sin(2*t);
sankaku.sankakuZ = 3;
sankaku.sankakuColor = color;
sankaku.proc();
sankakuList.push(sankaku);
// 影を生成
let shadow = new SankakuTry();
shadow.sankakuX = 2 + 1.5*i;
shadow.sankakuY = 1;
shadow.sankakuZ = 3;
shadow.sankakuColor = 'black';
shadow.V = shadow.V.map(pos => [pos[0], 0, pos[2]]);
shadow.proc();
shadowList.push(shadow);
// 三角錐下側_Aを生成
let sankakuSitaA = new SankakuTry();
sankakuSitaA.sankakuX = 2 + 1.5*i;
sankakuSitaA.sankakuY = 1;//-1
sankakuSitaA.sankakuZ = 9;
sankakuSitaA.sankakuColor = color;
sankakuSitaA.proc();
sankakuList.push(sankakuSitaA);
}
//赤い三角錐
let sankakuSitaC = new SankakuTry();
sankakuSitaC.sankakuX = 2 + 1.5*3;
sankakuSitaC.sankakuY = 1;
sankakuSitaC.sankakuZ = 15 - t;
sankakuSitaC.sankakuColor = 'red';
//衝突判定のテスト
for(let sankaku of sankakuList) {
// 球の中心座標
let v = [
sankakuSitaC.sankakuX,
sankakuSitaC.sankakuY,
sankakuSitaC.sankakuZ
];
let w = [
sankaku.sankakuX,
sankaku.sankakuY,
sankaku.sankakuZ,
];
// 半径(仮)
let a = 0.5;
let b = 0.5;
// 2つの球の中心間の距離
let d = sqrt((v[0]-w[0])**2 + (v[1]-w[1])**2 + (v[2]-w[2])**2);
// 半径の和より小さいなら衝突している
if (d < a+b) {
sankakuSitaC.sankakuColor = 'blue';
break;
}
}
sankakuSitaC.proc();
sankakuList.push(sankakuSitaC);
// ひよこの描画コマンドを生成
let x1 = game.hiyoko.x;
let y1 = game.hiyoko.y;
let z1 = game.hiyoko.z;
// カメラの平行移動
let x2 = x1 - this.camera.pos[0];
let y2 = y1 - this.camera.pos[1];
let z2 = z1 - this.camera.pos[2];
// カメラの回転
let r = -game.camera.angle;
let x3 = z2*sin(r) + x2*cos(r);
let y3 = y2;
let z3 = z2*cos(r) - x2*sin(r);
// 投影面の前方にあるときだけ描画
if (z3 > 1) {
let playerDrawer = new ChickenDrawer(
new Vec2(width * x3/z3 + width/2, height * y3/z3 + height/2),
game.hiyoko.angle - game.camera.angle - 2*PI / 4,
0
);
playerDrawer.zPosInCamCoord = z3;
sankakuList.push(playerDrawer);
}
// Zソートで三角錐の描画順を決定
// カメラ座標系でのz座標でソートする
sankakuList.sort(
(san1, san2) => {
let z1 = san1.getZPosInCamCoord();
let z2 = san2.getZPosInCamCoord();
return z2 - z1;
}
);
// すべての影を描画
for(let shadow of shadowList) {
shadow.draw();
}
// すべての三角錐を描画
for(let sankaku of sankakuList) {
sankaku.draw();
}
}
}
//中途半端な衝突判定
/*
function hitThreeCircle() {
background(220);
let x0 = 120;
let y0 = 180;
let r0 = 60;
let x1 = mouseX;
let y1 = mouseY;
let r1 = 120;
let d = sqrt((x1-x0)**2+(y1-y0)**2);
let hit = d < r0+r1;
noFill();
strokeWeight(4);
circle(x0,y0,2*r0);
circle(x1,y1,2*r1);
stroke(0);
textSize(32);
text('d = '+nfc(d,2),30,420);
}
*/
let game;
function setup() {
createCanvas(640,640);
frameRate(15);
game = new Game();
}
function draw() {
game.proc();
}