You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
5.2 KiB
176 lines
5.2 KiB
export class StickFigureRenderer { |
|
draw(player, ctx, time) { |
|
const headRadius = 10; |
|
const x = player.x; |
|
let y = player.y; |
|
|
|
const crouch = !!player.crouching; |
|
const bodyLength = crouch ? 20 : 30; |
|
const legLength = crouch ? 20 : 25; |
|
if (crouch) y += 15; // shift down a touch so crouch looks grounded |
|
|
|
// Set drawing styles |
|
ctx.strokeStyle = "black"; |
|
ctx.fillStyle = "black"; |
|
ctx.lineWidth = 2; |
|
|
|
// head |
|
ctx.beginPath(); |
|
ctx.arc(x, y, headRadius, 0, Math.PI * 2); |
|
ctx.stroke(); |
|
|
|
// eye |
|
ctx.beginPath(); |
|
ctx.arc(x + player.facing * 5, y - 3, 2, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
// body |
|
const chestY = y + headRadius; |
|
const hipY = y + headRadius + bodyLength; |
|
ctx.beginPath(); |
|
ctx.moveTo(x, chestY); |
|
ctx.lineTo(x, hipY); |
|
ctx.stroke(); |
|
|
|
// arms |
|
ctx.beginPath(); |
|
ctx.moveTo(x, y + 20); |
|
if (player.action === "punch" && player.hitFrame < 10) { |
|
ctx.lineTo(x + 30 * player.facing, y + 10); |
|
} else { |
|
ctx.lineTo(x + 20 * player.facing, y + 20); |
|
} |
|
ctx.moveTo(x, y + 20); |
|
ctx.lineTo(x - 20 * player.facing, y + 20); |
|
ctx.stroke(); |
|
|
|
// legs |
|
ctx.beginPath(); |
|
if (player.state === 'ground' && player.vx !== 0) { |
|
const step = Math.sin(time * 0.75); |
|
const stride = 14; |
|
const lift = 0; |
|
|
|
// right leg |
|
ctx.moveTo(x, hipY); |
|
ctx.lineTo(x + stride * step, hipY + legLength + (-lift * Math.abs(step))); |
|
|
|
// left leg |
|
ctx.moveTo(x, hipY); |
|
ctx.lineTo(x - stride * step, hipY + legLength + (-lift * Math.abs(step))); |
|
} else { |
|
const idleSpread = 10; |
|
ctx.moveTo(x, hipY); |
|
ctx.lineTo(x + idleSpread, hipY + legLength); |
|
ctx.moveTo(x, hipY); |
|
ctx.lineTo(x - idleSpread, hipY + legLength); |
|
} |
|
ctx.stroke(); |
|
ctx.strokeStyle = "black"; |
|
|
|
// speech bubble |
|
if (player.talking) { |
|
const padding = 0; |
|
const maxMessageWidth = 100; |
|
|
|
// set font BEFORE measuring |
|
ctx.font = "12px sans-serif"; |
|
|
|
const lines = this._wrapText(ctx, player.message, maxMessageWidth); |
|
const textWidth = ctx.measureText(player.message).width; |
|
const widths = lines.map(line => ctx.measureText(line).width); |
|
const contentWidth = Math.max(...widths, textWidth); |
|
const bubbleWidth = Math.min(contentWidth + padding + 10, maxMessageWidth + padding + 10); |
|
const lineHeight = 14; |
|
const bubbleHeight = lines.length * lineHeight + padding; |
|
const bubbleX = x - bubbleWidth - 15; |
|
const bubbleY = y - 20; |
|
|
|
ctx.beginPath(); |
|
ctx.fillStyle = "white"; |
|
ctx.strokeStyle = "black"; |
|
if (typeof ctx.roundRect === "function") { |
|
ctx.roundRect(bubbleX, bubbleY, bubbleWidth, bubbleHeight, 4); |
|
} else { |
|
ctx.rect(bubbleX, bubbleY, bubbleWidth, bubbleHeight); |
|
} |
|
ctx.fill(); |
|
ctx.stroke(); |
|
|
|
ctx.fillStyle = "black"; |
|
for (let i = 0; i < lines.length; i++) { |
|
ctx.fillText(lines[i], bubbleX + padding, bubbleY + padding + i * lineHeight); |
|
} |
|
} |
|
|
|
let nameY = y - 80; |
|
if (player.isAlive) { |
|
nameY = this._drawHealthBar(ctx, player) |
|
} |
|
|
|
// name tag |
|
ctx.save(); |
|
ctx.font = "10px sans-serif"; |
|
ctx.textAlign = "center"; |
|
ctx.textBaseline = "bottom"; |
|
ctx.fillStyle = "black"; |
|
ctx.fillText(player.id, x, nameY); |
|
ctx.restore(); |
|
} |
|
|
|
_drawHealthBar(ctx, player) { |
|
const x = player.x; |
|
const headCenterY = player.y; |
|
const topOfHead = headCenterY - 10; |
|
const barWidth = 40; |
|
const barHeight = 5; |
|
|
|
const barX = x - barWidth / 2; |
|
const barY = topOfHead - 10; |
|
|
|
ctx.fillStyle = "#ddd"; |
|
ctx.fillRect(barX, barY, barWidth, barHeight); |
|
|
|
const pct = Math.max(0, Math.min(1, player.hp / player.maxHp)); |
|
let color = '#27ae60'; |
|
|
|
if (pct <= 0.33) { |
|
color = '#e74c3c'; |
|
} else if (pct <= 0.66) { |
|
color = '#f1c40f'; |
|
} |
|
|
|
ctx.fillStyle = color; |
|
ctx.fillRect(barX, barY, barWidth * pct, barHeight); |
|
|
|
|
|
ctx.strokeStyle = '#000'; |
|
ctx.lineWidth = 1; |
|
ctx.strokeRect(barX, barY, barWidth, barHeight); |
|
|
|
return barY; |
|
} |
|
|
|
_wrapText(ctx, text, maxWidth) { |
|
const words = text.split(" "); |
|
const lines = []; |
|
let line = ""; |
|
|
|
for (let i = 0; i < words.length; i++) { |
|
const testLine = line + words[i] + " "; |
|
const testWidth = ctx.measureText(testLine).width; |
|
|
|
if (testWidth > maxWidth && i > 0) { |
|
lines.push(line.trimEnd()); |
|
line = words[i] + " "; |
|
} else { |
|
line = testLine; |
|
} |
|
} |
|
|
|
lines.push(line.trim()); |
|
return lines; |
|
} |
|
|
|
} |
|
|
|
|