Download js/fighter.js from HelloSun/sfdemo: direct link, hf CLI and curl.
- Browser
- Download file 17 kB
-
https://huggingface.co/spaces/HelloSun/sfdemo/resolve/main/js/fighter.js
- Command line
-
hf download hf://spaces/HelloSun/sfdemo/js/fighter.js
-
curl -L -o fighter.js https://huggingface.co/spaces/HelloSun/sfdemo/resolve/main/js/fighter.js
17 kB
| // fighter.js - Procedural skeletal fighter renderer. | |
| // Sprites are drawn from a pose description (like Ikemen's AnimElements) so the | |
| // engine needs no external binary assets and works fully offline in the browser. | |
| const SKIN = { | |
| palette: [ | |
| { key: 'A', main: '#f2c48c', dark: '#c08a52', line: '#5a3a1e' }, // skin | |
| { key: 'B', main: '#3b5bdb', dark: '#24389e', line: '#131c4a' }, // cloth | |
| { key: 'C', main: '#e8590c', dark: '#a63c05', line: '#4a1a02' }, // accent | |
| { key: 'D', main: '#ffffff', dark: '#c8c8c8', line: '#5a5a5a' }, // light | |
| ], | |
| }; | |
| /** | |
| * Pose = set of keyframed joints. Each pose is {t, joints:{...}} normalized. | |
| * Joint coords are in character space, origin at feet center, y negative up, | |
| * matching MUGEN's coordinate convention. Units roughly pixels at 1.0 scale. | |
| */ | |
| export const SKELETON = { | |
| hip: [0, -95], | |
| chest: [0, -135], | |
| neck: [0, -152], | |
| head: [0, -168], | |
| // arms: shoulder -> elbow -> hand | |
| shF: [4, -146], elF: [14, -126], haF: [22, -108], | |
| shB: [-4, -146], elB: [-12, -128], haB: [-18, -112], | |
| // legs: hip -> knee -> foot | |
| hipF: [4, -95], knF: [8, -50], ftF: [10, -2], | |
| hipB: [-4, -95], knB: [-8, -50], ftB: [-10, -2], | |
| }; | |
| /** Default pose: neutral idle stance */ | |
| function idlePose(t = 0) { | |
| const bob = Math.sin(t * 0.12) * 2; | |
| return { | |
| hip: [0, -95 + bob], | |
| chest: [0, -135 + bob], | |
| neck: [0, -152 + bob], | |
| head: [0, -168 + bob], | |
| shF: [5, -146 + bob], elF: [16, -124 + bob], haF: [26, -108 + bob], | |
| shB: [-5, -146 + bob], elB: [-15, -126 + bob], haB: [-24, -112 + bob], | |
| hipF: [5, -95 + bob], knF: [9, -50], ftF: [11, -2], | |
| hipB: [-5, -95 + bob], knB: [-9, -50], ftB: [-11, -2], | |
| }; | |
| } | |
| /** | |
| * Build the full animation table for a character from pose generators. | |
| * Each anim entry: { frames:[pose...], time: ticks per frame, loop:bool } | |
| */ | |
| export function buildAnims(def) { | |
| const A = {}; | |
| const mk = (name, frames, time, loop = true) => { A[name] = { frames, time, loop }; }; | |
| // ---- idle: subtle breathing ---- | |
| mk('stand', [idlePose(0), idlePose(30), idlePose(0)], 6, true); | |
| // ---- crouch ---- | |
| const crouch = { | |
| hip: [0, -58], chest: [0, -96], neck: [0, -112], head: [0, -126], | |
| shF: [6, -106], elF: [16, -88], haF: [22, -70], | |
| shB: [-6, -106], elB: [-16, -90], haB: [-22, -74], | |
| hipF: [6, -58], knF: [12, -32], ftF: [12, -2], | |
| hipB: [-6, -58], knB: [-10, -34], ftB: [-12, -2], | |
| }; | |
| mk('crouch', [crouch], 8, true); | |
| mk('crouch_turn', [crouch], 8, true); | |
| // ---- walk forward ---- | |
| const walkFrames = []; | |
| for (let i = 0; i < 6; i++) { | |
| const ph = i / 6 * Math.PI * 2; | |
| const swing = Math.sin(ph) * 18; | |
| const lift = Math.max(0, Math.sin(ph)) * 8; | |
| const bounce = Math.abs(Math.cos(ph)) * 2; | |
| walkFrames.push({ | |
| hip: [0, -95 + bounce], chest: [0, -135 + bounce], neck: [0, -152 + bounce], head: [0, -168 + bounce], | |
| shF: [5, -146 + bounce], elF: [14 - swing * 0.4, -126], haF: [20 - swing * 0.7, -110], | |
| shB: [-5, -146 + bounce], elB: [-12 + swing * 0.4, -128], haB: [-18 + swing * 0.6, -114], | |
| hipF: [5, -95 + bounce], knF: [8 + swing * 0.3, -50 + lift * 0.3], ftF: [10 + swing * 0.5, -2 - lift], | |
| hipB: [-5, -95 + bounce], knB: [-8 - swing * 0.3, -50 - lift * 0.3], ftB: [-10 - swing * 0.5, -2], | |
| }); | |
| } | |
| mk('walk_fwd', walkFrames, 5, true); | |
| // ---- walk back ---- | |
| const backFrames = walkFrames.map(f => ({ | |
| ...f, | |
| shF: [-f.shF[0], f.shF[1]], elF: [-f.elF[0], f.elF[1]], haF: [-f.haF[0], f.haF[1]], | |
| shB: [-f.shB[0], f.shB[1]], elB: [-f.elB[0], f.elB[1]], haB: [-f.haB[0], f.haB[1]], | |
| })); | |
| mk('walk_back', backFrames, 7, true); | |
| // ---- jump (rise / fall) ---- | |
| mk('jump_neutral', [{ | |
| hip: [0, -92], chest: [0, -130], neck: [0, -146], head: [0, -161], | |
| shF: [2, -140], elF: [2, -124], haF: [0, -146], | |
| shB: [-4, -140], elB: [-14, -128], haB: [-20, -116], | |
| hipF: [5, -92], knF: [12, -56], ftF: [14, -22], | |
| hipB: [-4, -92], knB: [-6, -54], ftB: [-8, -20], | |
| }], 6, false); | |
| mk('jump_fwd', [{ | |
| hip: [0, -92], chest: [0, -130], neck: [0, -146], head: [0, -161], | |
| shF: [8, -138], elF: [20, -126], haF: [30, -116], | |
| shB: [-8, -140], elB: [-20, -130], haB: [-30, -120], | |
| hipF: [8, -92], knF: [20, -58], ftF: [26, -24], | |
| hipB: [-6, -92], knB: [-2, -52], ftB: [-2, -18], | |
| }], 6, false); | |
| mk('jump_back', [{ | |
| hip: [0, -92], chest: [0, -130], neck: [0, -146], head: [0, -161], | |
| shF: [4, -142], elF: [10, -132], haF: [16, -122], | |
| shB: [-4, -140], elB: [-10, -128], haB: [-14, -116], | |
| hipF: [4, -92], knF: [10, -56], ftF: [8, -22], | |
| hipB: [-4, -92], knB: [-10, -54], ftB: [-14, -20], | |
| }], 6, false); | |
| // ---- crouching attack poses ---- | |
| mk('crouch_light', [ | |
| { ...crouch, shF: [10, -100], elF: [26, -92], haF: [38, -86] }, | |
| { ...crouch, shF: [12, -100], elF: [30, -90], haF: [44, -84] }, | |
| ], 4, false); | |
| mk('crouch_heavy', [ | |
| { ...crouch, shF: [12, -104], elF: [28, -96], haF: [40, -92], chest: [4, -96] }, | |
| { ...crouch, shF: [14, -106], elF: [32, -98], haF: [46, -94], chest: [6, -96] }, | |
| ], 6, false); | |
| // ---- standing punches ---- | |
| mk('stand_light', [ | |
| { ...idlePose(0), shF: [8, -146], elF: [22, -138], haF: [30, -134] }, | |
| { ...idlePose(0), shF: [10, -146], elF: [26, -138], haF: [36, -134], chest: [2, -135] }, | |
| ], 3, false); | |
| mk('stand_medium', [ | |
| { ...idlePose(0), shF: [10, -146], elF: [28, -142], haF: [40, -140], chest: [3, -135] }, | |
| { ...idlePose(0), shF: [12, -148], elF: [34, -144], haF: [48, -142], chest: [5, -135] }, | |
| ], 4, false); | |
| mk('stand_heavy', [ | |
| { ...idlePose(0), shF: [8, -150], elF: [26, -150], haF: [36, -150], chest: [2, -135] }, | |
| { ...idlePose(0), shF: [10, -150], elF: [32, -152], haF: [44, -152], chest: [5, -135] }, | |
| ], 6, false); | |
| // ---- kick ---- | |
| const kickBase = { | |
| hip: [0, -95], chest: [0, -135], neck: [0, -152], head: [0, -168], | |
| shF: [4, -146], elF: [12, -124], haF: [20, -110], | |
| shB: [-6, -146], elB: [-16, -128], haB: [-24, -114], | |
| hipF: [5, -95], knF: [9, -50], ftF: [11, -2], | |
| hipB: [-5, -95], knB: [-9, -50], ftB: [-11, -2], | |
| }; | |
| mk('stand_kick', [ | |
| { ...kickBase, hip: [0, -98], knF: [22, -84], ftF: [30, -56], chest: [-2, -137] }, | |
| { ...kickBase, hip: [0, -100], knF: [30, -90], ftF: [44, -66], chest: [-4, -138] }, | |
| ], 5, false); | |
| // ---- air attacks ---- | |
| const airBase = A['jump_neutral'].frames[0]; | |
| mk('air_light', [{ ...airBase, shF: [8, -134], elF: [22, -126], haF: [32, -120] }], 5, false); | |
| mk('air_heavy', [{ ...airBase, shF: [10, -138], elF: [28, -132], haF: [42, -128], hip: [0, -90] }], 6, false); | |
| mk('air_kick', [{ ...airBase, knF: [18, -76], ftF: [26, -52], hip: [0, -88] }], 5, false); | |
| // ---- special / projectile ---- | |
| mk('special', [ | |
| { ...idlePose(0), shF: [6, -140], elF: [4, -122], haF: [0, -104], chest: [-3, -136] }, | |
| { ...idlePose(0), shF: [8, -144], elF: [8, -128], haF: [4, -110], chest: [-4, -136] }, | |
| { ...idlePose(0), shF: [10, -148], elF: [18, -134], haF: [22, -122], chest: [-2, -135] }, | |
| ], 5, false); | |
| mk('special_air', [{ ...airBase, shF: [8, -136], elF: [2, -120], haF: [-2, -104] }], 6, false); | |
| // ---- uppercut / anti-air ---- | |
| mk('anti_air', [ | |
| { ...kickBase, hip: [0, -92], knF: [8, -60], ftF: [10, -30], shF: [4, -142], elF: [10, -124], haF: [16, -106], chest: [0, -132] }, | |
| { ...kickBase, hip: [0, -86], knF: [8, -56], ftF: [10, -26], shF: [6, -146], elF: [14, -128], haF: [20, -110], chest: [2, -136] }, | |
| { ...kickBase, hip: [0, -80], knF: [10, -52], ftF: [12, -22], shF: [8, -150], elF: [18, -136], haF: [26, -122], chest: [4, -140] }, | |
| ], 4, false); | |
| // ---- hurt / knockdown ---- | |
| mk('hurt_stand', [{ | |
| hip: [0, -93], chest: [-4, -133], neck: [-6, -150], head: [-10, -165], | |
| shF: [2, -144], elF: [8, -128], haF: [4, -118], | |
| shB: [-8, -144], elB: [-20, -130], haB: [-28, -118], | |
| hipF: [5, -93], knF: [11, -50], ftF: [14, -2], | |
| hipB: [-8, -93], knB: [-12, -48], ftB: [-16, -2], | |
| }], 5, false); | |
| mk('hurt_air', [{ | |
| hip: [0, -88], chest: [-6, -126], neck: [-8, -142], head: [-12, -156], | |
| shF: [-2, -138], elF: [-14, -126], haF: [-24, -114], | |
| shB: [-10, -136], elB: [-24, -126], haB: [-34, -116], | |
| hipF: [10, -88], knF: [22, -60], ftF: [26, -34], | |
| hipB: [-10, -88], knB: [-22, -58], ftB: [-26, -32], | |
| }], 6, false); | |
| mk('hurt_crouch', [{ | |
| hip: [0, -56], chest: [-5, -92], neck: [-7, -108], head: [-10, -122], | |
| shF: [4, -102], elF: [10, -88], haF: [6, -78], | |
| shB: [-8, -102], elB: [-18, -90], haB: [-24, -80], | |
| hipF: [7, -56], knF: [13, -32], ftF: [14, -2], | |
| hipB: [-8, -56], knB: [-12, -30], ftB: [-16, -2], | |
| }], 5, false); | |
| mk('knockdown', [{ | |
| hip: [0, -20], chest: [-6, -40], neck: [-10, -50], head: [-20, -60], | |
| shF: [-10, -46], elF: [-30, -40], haF: [-46, -34], | |
| shB: [-20, -44], elB: [-44, -38], haB: [-60, -32], | |
| hipF: [10, -20], knF: [26, -16], ftF: [34, -8], | |
| hipB: [-6, -20], knB: [-24, -16], ftB: [-36, -8], | |
| }], 8, false); | |
| mk('lie_down', [{ | |
| hip: [0, -14], chest: [-8, -22], neck: [-16, -26], head: [-30, -28], | |
| shF: [-12, -28], elF: [-34, -24], haF: [-52, -20], | |
| shB: [-24, -26], elB: [-48, -22], haB: [-66, -18], | |
| hipF: [16, -12], knF: [38, -10], ftF: [56, -6], | |
| hipB: [-10, -12], knB: [-30, -10], ftB: [-46, -6], | |
| }], 10, false); | |
| // ---- dizzy ---- | |
| mk('dizzy', [ | |
| { hip: [0, -90], chest: [0, -128], neck: [-4, -144], head: [-10, -158], shF: [0, -138], elF: [10, -126], haF: [14, -136], shB: [-8, -138], elB: [-18, -126], haB: [-22, -136], hipF: [4, -90], knF: [8, -48], ftF: [10, -2], hipB: [-4, -90], knB: [-8, -48], ftB: [-10, -2] }, | |
| { hip: [0, -89], chest: [2, -127], neck: [0, -143], head: [-6, -157], shF: [2, -137], elF: [12, -125], haF: [16, -125], shB: [-6, -137], elB: [-16, -125], haB: [-20, -125], hipF: [4, -89], knF: [8, -47], ftF: [10, -2], hipB: [-4, -89], knB: [-8, -47], ftB: [-10, -2] }, | |
| ], 10, true); | |
| // ---- guard ---- | |
| mk('guard', [{ | |
| hip: [0, -92], chest: [-4, -132], neck: [-4, -148], head: [-6, -164], | |
| shF: [4, -144], elF: [-2, -130], haF: [-8, -146], | |
| shB: [-6, -144], elB: [-16, -132], haB: [-10, -142], | |
| hipF: [5, -92], knF: [9, -48], ftF: [11, -2], | |
| hipB: [-5, -92], knB: [-9, -48], ftB: [-11, -2], | |
| }], 4, true); | |
| // ---- victory / taunt ---- | |
| mk('win', [ | |
| { ...idlePose(0), shF: [6, -150], elF: [16, -162], haF: [22, -174], chest: [0, -136] }, | |
| { ...idlePose(0), shF: [6, -152], elF: [14, -166], haF: [18, -178], chest: [0, -136] }, | |
| ], 8, true); | |
| // custom anims from character definition override/extend | |
| if (def.anims) { | |
| for (const k in def.anims) A[k] = def.anims[k]; | |
| } | |
| return A; | |
| } | |
| export { SKIN, idlePose }; | |
| /** | |
| * Draw a character using its current pose. | |
| * ctx must be already translated to the character's feet position, | |
| * and scaled so 1 unit = 1 pixel, with y pointing UP (caller does ctx.scale(1,-1)). | |
| */ | |
| export function drawFighter(ctx, char, pose, opts = {}) { | |
| const skin = opts.skin || 'A'; | |
| const cloth = opts.cloth || 'B'; | |
| const accent = opts.accent || 'C'; | |
| const S = SKIN.palette; | |
| const col = k => S.find(p => p.key === k); | |
| const cS = col(skin), cB = col(cloth), cC = col(accent); | |
| const bodyMain = opts.bodyColor || cB.main; | |
| const bodyDark = opts.bodyDark || cB.dark; | |
| const bodyLine = opts.bodyLine || cB.line; | |
| const skinMain = opts.skinColor || cS.main; | |
| const skinDark = opts.skinDark || cS.dark; | |
| const skinLine = opts.skinLine || cS.line; | |
| const accMain = opts.accentColor || cC.main; | |
| const accDark = opts.accentDark || cC.dark; | |
| const p = pose; | |
| if (!p) return; | |
| ctx.lineCap = 'round'; | |
| ctx.lineJoin = 'round'; | |
| // --- helper to draw a limb segment as a tapered capsule --- | |
| const limb = (a, b, r1, r2, fill, stroke) => { | |
| const dx = b[0] - a[0], dy = b[1] - a[1]; | |
| const len = Math.hypot(dx, dy) || 1; | |
| const nx = -dy / len, ny = dx / len; | |
| const ang = Math.atan2(dy, dx); | |
| ctx.beginPath(); | |
| ctx.moveTo(a[0] + nx * r1, a[1] + ny * r1); | |
| ctx.lineTo(b[0] + nx * r2, b[1] + ny * r2); | |
| ctx.arc(b[0], b[1], r2, Math.atan2(ny, nx), Math.atan2(-ny, -nx), false); | |
| ctx.lineTo(a[0] - nx * r1, a[1] - ny * r1); | |
| ctx.arc(a[0], a[1], r1, Math.atan2(-ny, -nx), Math.atan2(ny, nx), false); | |
| ctx.closePath(); | |
| ctx.fillStyle = fill; | |
| ctx.fill(); | |
| if (stroke) { ctx.lineWidth = 2; ctx.strokeStyle = stroke; ctx.stroke(); } | |
| }; | |
| const mirror = opts.facing < 0 ? -1 : 1; | |
| // ---- BACK LEG ---- | |
| limb(p.hipB, p.knB, 7.5, 6, bodyDark, bodyLine); | |
| limb(p.knB, p.ftB, 6, 4.5, bodyDark, bodyLine); | |
| drawFoot(ctx, p.ftB, 7, 4, bodyDark, bodyLine); | |
| // ---- BACK ARM ---- | |
| limb(p.shB, p.elB, 6.5, 5, opts.backArm || bodyDark, bodyLine); | |
| limb(p.elB, p.haB, 5, 4, opts.backArm || bodyDark, bodyLine); | |
| drawFist(ctx, p.haB, 5.5, opts.backArm || skinDark, skinLine); | |
| // ---- TORSO ---- | |
| drawTorso(ctx, p, bodyMain, bodyDark, bodyLine, accMain, accDark, mirror); | |
| // ---- FRONT LEG ---- | |
| limb(p.hipF, p.knF, 8, 6.5, bodyMain, bodyLine); | |
| limb(p.knF, p.ftF, 6.5, 5, bodyMain, bodyLine); | |
| drawFoot(ctx, p.ftF, 8, 4.5, bodyMain, bodyLine); | |
| // ---- HEAD ---- | |
| drawHead(ctx, p, skinMain, skinDark, skinLine, bodyLine, opts, mirror); | |
| // ---- FRONT ARM ---- | |
| limb(p.shF, p.elF, 7, 5.5, bodyMain, bodyLine); | |
| limb(p.elF, p.haF, 5.5, 4.5, bodyMain, bodyLine); | |
| drawFist(ctx, p.haF, 6, opts.frontArm ? opts.frontArm : skinMain, skinLine); | |
| } | |
| function drawTorso(ctx, p, main, dark, line, acc, accDark, mirror) { | |
| const hip = p.hip, chest = p.chest, neck = p.neck; | |
| const wHip = 11, wChest = 14, wNeck = 7; | |
| ctx.beginPath(); | |
| ctx.moveTo(hip[0] - wHip, hip[1] + 4); | |
| ctx.quadraticCurveTo(hip[0] - wHip - 2, (hip[1] + chest[1]) / 2, chest[0] - wChest, chest[1] + 2); | |
| ctx.lineTo(chest[0] - wNeck, neck[1]); | |
| ctx.lineTo(chest[0] + wNeck, neck[1]); | |
| ctx.lineTo(chest[0] + wChest, chest[1] + 2); | |
| ctx.quadraticCurveTo(hip[0] + wHip + 2, (hip[1] + chest[1]) / 2, hip[0] + wHip, hip[1] + 4); | |
| ctx.closePath(); | |
| ctx.fillStyle = main; | |
| ctx.fill(); | |
| ctx.lineWidth = 2; | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| // accent sash across chest | |
| ctx.beginPath(); | |
| ctx.moveTo(chest[0] - wChest + 1, chest[1] + 4); | |
| ctx.lineTo(chest[0] + wChest - 1, chest[1] - 6); | |
| ctx.lineTo(chest[0] + wChest - 3, chest[1] - 10); | |
| ctx.lineTo(chest[0] - wChest + 3, chest[1] + 1); | |
| ctx.closePath(); | |
| ctx.fillStyle = acc; | |
| ctx.fill(); | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| // belt | |
| ctx.beginPath(); | |
| ctx.ellipse(hip[0], hip[1] + 3, wHip + 1.5, 4.5, 0, 0, Math.PI * 2); | |
| ctx.fillStyle = accDark; | |
| ctx.fill(); | |
| ctx.lineWidth = 1.5; | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| } | |
| function drawHead(ctx, p, main, dark, line, bodyLine, opts, mirror) { | |
| const n = p.neck, h = p.head; | |
| // neck | |
| ctx.beginPath(); | |
| ctx.moveTo(n[0] - 4.5, n[1] - 2); | |
| ctx.lineTo(n[0] + 4.5, n[1] - 2); | |
| ctx.lineTo(h[0] + 5, h[1] + 6); | |
| ctx.lineTo(h[0] - 5, h[1] + 6); | |
| ctx.closePath(); | |
| ctx.fillStyle = dark; | |
| ctx.fill(); | |
| ctx.lineWidth = 1.5; | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| // skull | |
| const r = 13; | |
| ctx.beginPath(); | |
| ctx.ellipse(h[0], h[1], r, r * 1.12, 0, 0, Math.PI * 2); | |
| ctx.fillStyle = main; | |
| ctx.fill(); | |
| ctx.lineWidth = 2; | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| // hair / headband | |
| if (opts.hair !== false) { | |
| ctx.beginPath(); | |
| ctx.ellipse(h[0] - mirror * 1, h[1] + 4, r * 0.98, r * 0.95, 0, Math.PI * 0.95, Math.PI * 2.05); | |
| ctx.closePath(); | |
| ctx.fillStyle = opts.hairColor || '#2b2118'; | |
| ctx.fill(); | |
| // headband tail | |
| ctx.beginPath(); | |
| ctx.moveTo(h[0] - mirror * 6, h[1] + 3); | |
| ctx.lineTo(h[0] - mirror * 22, h[1] - 4); | |
| ctx.lineTo(h[0] - mirror * 20, h[1] - 10); | |
| ctx.closePath(); | |
| ctx.fillStyle = opts.accentColor || '#e8590c'; | |
| ctx.fill(); | |
| ctx.strokeStyle = bodyLine; | |
| ctx.lineWidth = 1.5; | |
| ctx.stroke(); | |
| } | |
| // face | |
| ctx.fillStyle = '#1a1a1a'; | |
| const eyeY = h[1] + 1; | |
| ctx.beginPath(); | |
| ctx.ellipse(h[0] + mirror * 4.5, eyeY, 1.9, 2.6, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // brow | |
| ctx.strokeStyle = '#2b2118'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(h[0] + mirror * 1.5, eyeY - 5); | |
| ctx.lineTo(h[0] + mirror * 8.5, eyeY - 3.5); | |
| ctx.stroke(); | |
| // mouth | |
| ctx.lineWidth = 1.6; | |
| ctx.strokeStyle = '#5a3a1e'; | |
| ctx.beginPath(); | |
| if (opts.mouthOpen) { | |
| ctx.ellipse(h[0] + mirror * 5, eyeY + 7, 3.2, 2.6, 0, 0, Math.PI * 2); | |
| ctx.fillStyle = '#5a2010'; | |
| ctx.fill(); | |
| } else { | |
| ctx.moveTo(h[0] + mirror * 2.5, eyeY + 7.5); | |
| ctx.lineTo(h[0] + mirror * 8, eyeY + 7.5); | |
| ctx.stroke(); | |
| } | |
| } | |
| function drawFist(ctx, p, r, fill, line) { | |
| ctx.beginPath(); | |
| ctx.arc(p[0], p[1], r, 0, Math.PI * 2); | |
| ctx.fillStyle = fill; | |
| ctx.fill(); | |
| ctx.lineWidth = 1.8; | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| } | |
| function drawFoot(ctx, p, w, h, fill, line) { | |
| ctx.beginPath(); | |
| ctx.ellipse(p[0], p[1] - h * 0.3, w, h, 0, 0, Math.PI * 2); | |
| ctx.fillStyle = fill; | |
| ctx.fill(); | |
| ctx.lineWidth = 1.8; | |
| ctx.strokeStyle = line; | |
| ctx.stroke(); | |
| } | |