/* ===== アバターの部品(数字で持つ。絵は1枚も要らない) ===== */ var HAIR_N = 8; /* 髪型8種 */ var HAIR_NM = ['短髪', 'ぼさ', 'ボブ', 'ロング', 'ポニー', 'アフロ', '三つ編み', 'つるり']; var HAIR_COL = ['#241a12', '#4a2f1c', '#7b4a22', '#b07a35', '#d9b95e', '#8b9099', '#e9e3d3', '#b4443a']; var SKIN_COL = ['#f7dcc2', '#eec49f', '#d29b6e', '#a97244']; var WEAR_COL = ['#3f6fa8', '#c0563f', '#4f8a5b', '#7a5aa0', '#d3a33c', '#2f3b46', '#cc7096', '#e3ded1']; function avDefault() { return { h: 0, hc: 0, sk: 0, w: 0 }; } function avFix(a) { a = a || {}; return { h: ((a.h | 0) % HAIR_N + HAIR_N) % HAIR_N, hc: ((a.hc | 0) % HAIR_COL.length + HAIR_COL.length) % HAIR_COL.length, sk: ((a.sk | 0) % SKIN_COL.length + SKIN_COL.length) % SKIN_COL.length, w: ((a.w | 0) % WEAR_COL.length + WEAR_COL.length) % WEAR_COL.length }; } /* ===== 人 ===== g … 描く先 / x,yHead … 頭の中心 / r … 頭の半径 / dir … +1で右向き(左の席) / av … アバター st … {mouth:0..1 口の開き, walk:0..1 歩いている, lift:0..1 腰を浮かす, gone:帰った} bottom … 体の下端(テーブルの高さ) */ function drawChar(g, x, yHead, r, dir, av, st, bottom) { av = avFix(av); st = st || {}; var skin = SKIN_COL[av.sk], wear = WEAR_COL[av.w], hair = HAIR_COL[av.hc]; var shY = yHead + r * 1.30; var bot = bottom != null ? bottom : shY + r * 2.4; g.save(); g.lineCap = 'round'; g.lineJoin = 'round'; /* 歩いている時だけ足が見える(座ったらテーブルに隠れる) */ if (st.walk) { var sw = Math.sin(st.walk * Math.PI * 6); g.strokeStyle = '#33323a'; g.lineWidth = r * 0.28; g.beginPath(); g.moveTo(x, bot - r * 0.2); g.lineTo(x + sw * r * 0.55, bot + r * 1.15); g.stroke(); g.beginPath(); g.moveTo(x, bot - r * 0.2); g.lineTo(x - sw * r * 0.55, bot + r * 1.15); g.stroke(); } /* 胴。上が狭く下が広い台形 */ g.fillStyle = wear; g.beginPath(); g.moveTo(x - r * 0.74, shY); g.lineTo(x + r * 0.74, shY); g.lineTo(x + r * 1.16, bot); g.lineTo(x - r * 1.16, bot); g.closePath(); g.fill(); /* 腕。テーブルの上に片手を置いている */ g.strokeStyle = wear; g.lineWidth = r * 0.34; g.beginPath(); g.moveTo(x + dir * r * 0.62, shY + r * 0.18); g.lineTo(x + dir * r * 1.30, shY + r * 0.95); g.stroke(); g.strokeStyle = skin; g.lineWidth = r * 0.26; g.beginPath(); g.moveTo(x + dir * r * 1.28, shY + r * 0.92); g.lineTo(x + dir * r * 1.80, bot - r * 0.10); g.stroke(); g.fillStyle = skin; g.beginPath(); g.arc(x + dir * r * 1.80, bot - r * 0.10, r * 0.20, 0, 7); g.fill(); /* 首 */ g.fillStyle = skin; g.fillRect(x - r * 0.26, yHead + r * 0.55, r * 0.52, r * 0.90); /* 頭 */ g.fillStyle = skin; g.beginPath(); g.arc(x, yHead, r, 0, 7); g.fill(); /* 耳(向いている反対側) */ g.beginPath(); g.arc(x - dir * r * 0.93, yHead + r * 0.06, r * 0.17, 0, 7); g.fill(); /* 髪 */ g.fillStyle = hair; var H = av.h; if (H !== 7) { /* 7 = つるり(髪なし) */ g.beginPath(); g.arc(x, yHead - r * 0.05, r * 1.03, Math.PI * 1.02, Math.PI * 2.04); g.fill(); } if (H === 1) { /* ぼさ */ for (var i = -3; i <= 3; i++) { g.beginPath(); g.arc(x + i * r * 0.30, yHead - r * 0.72 + Math.abs(i) * r * 0.12, r * 0.26, 0, 7); g.fill(); } } else if (H === 2) { /* ボブ */ g.fillRect(x - r * 1.04, yHead - r * 0.10, r * 0.30, r * 0.95); g.fillRect(x + r * 0.74, yHead - r * 0.10, r * 0.30, r * 0.95); } else if (H === 3) { /* ロング */ g.fillRect(x - r * 1.04, yHead - r * 0.10, r * 0.32, r * 2.30); g.fillRect(x + r * 0.72, yHead - r * 0.10, r * 0.32, r * 2.30); } else if (H === 4) { /* ポニーテール(後ろへ垂らす) */ var px = x - dir * r * 1.05; g.beginPath(); g.moveTo(px, yHead - r * 0.35); g.quadraticCurveTo(px - dir * r * 0.75, yHead + r * 0.55, px - dir * r * 0.32, yHead + r * 1.65); g.lineTo(px + dir * r * 0.22, yHead + r * 1.45); g.quadraticCurveTo(px - dir * r * 0.10, yHead + r * 0.45, px + dir * r * 0.28, yHead - r * 0.30); g.closePath(); g.fill(); } else if (H === 5) { /* アフロ */ for (var j = 0; j < 9; j++) { var an = Math.PI * (1.02 + j / 8 * 1.0); g.beginPath(); g.arc(x + Math.cos(an) * r * 0.92, yHead + Math.sin(an) * r * 0.92, r * 0.40, 0, 7); g.fill(); } } else if (H === 6) { /* 三つ編み(2026-09-09 K1指示でちょんまげから差し替え) */ var ss = [-1, 1]; for (var q = 0; q < 2; q++) { var sd = ss[q], sx = x + sd * r * 0.94; for (var k = 0; k < 4; k++) { g.beginPath(); g.arc(sx + sd * r * 0.06 * ((k % 2) ? 1 : -1), yHead + r * (0.18 + k * 0.34), r * 0.19, 0, 7); g.fill(); } } } else if (H === 0) { /* 短髪 */ g.fillRect(x - r * 1.02, yHead - r * 0.28, r * 2.04, r * 0.22); } /* 顔。向いている側に寄せる */ var ex = x + dir * r * 0.40, ey = yHead - r * 0.10; g.fillStyle = '#2b2620'; g.beginPath(); g.arc(ex, ey, r * 0.115, 0, 7); g.fill(); g.beginPath(); g.arc(x + dir * r * 0.02, ey - r * 0.02, r * 0.10, 0, 7); g.fill(); /* 眉 */ g.strokeStyle = hair; g.lineWidth = r * 0.09; g.beginPath(); g.moveTo(ex - r * 0.16, ey - r * 0.34); g.lineTo(ex + r * 0.18, ey - r * 0.30); g.stroke(); /* 口。食べている時だけ開く */ var mo = st.mouth || 0; var mx = x + dir * r * 0.34, my = yHead + r * 0.38; g.fillStyle = '#7a3b33'; if (mo > 0.04) { g.beginPath(); g.ellipse(mx, my, r * (0.16 + 0.20 * mo), r * (0.10 + 0.26 * mo), 0, 0, 7); g.fill(); } else { g.strokeStyle = '#7a3b33'; g.lineWidth = r * 0.10; g.beginPath(); g.moveTo(mx - r * 0.20, my); g.lineTo(mx + r * 0.20, my); g.stroke(); } g.restore(); } /* 口の位置(食べる判定に使う)。drawChar と同じ式でなければならない */ function mouthAt(x, yHead, r, dir) { return { x: x + dir * r * 0.34, y: yHead + r * 0.38 }; } /* ===== 外から使う口 ===== 使い方(canvasの文脈gへ1人描く): SHURE.draw(g, x, yHead, r, dir, {h:0,hc:0,sk:0,w:0}, {mouth:0, walk:0}, bottom); r=頭の半径。全部この r を基準に組んであるので、r を変えれば丸ごと拡大縮小できる。 dir は +1 で右向き、-1 で左向き。 av を省くと既定(短髪・黒髪・肌0・青服)。st を省くと口を閉じて座っている姿。 */ var SHURE = { draw: drawChar, mouthAt: mouthAt, fix: avFix, def: avDefault, HAIR_N: HAIR_N, HAIR_NM: HAIR_NM, HAIR_COL: HAIR_COL, SKIN_COL: SKIN_COL, WEAR_COL: WEAR_COL, /* 組み合わせの総数 = 8 x 8 x 4 x 8 = 2048通り */ total: function () { return HAIR_N * HAIR_COL.length * SKIN_COL.length * WEAR_COL.length; }, /* 番号から1人作る(同じ番号なら必ず同じ人になる) */ fromSeed: function (n) { n = Math.abs(n | 0); return { h: n % HAIR_N, hc: (n / 8 | 0) % HAIR_COL.length, sk: (n / 64 | 0) % SKIN_COL.length, w: (n / 256 | 0) % WEAR_COL.length }; } }; if (typeof window !== 'undefined') { window.SHURE = SHURE; } if (typeof module !== 'undefined' && module.exports) { module.exports = SHURE; }