From 5215b29a0de4c2e36dc2ac3333e0da629fa18e39 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 9 Apr 2022 19:06:53 +0300 Subject: [PATCH] better name tags and rounded bubble --- src/client/object/canvas-utils.ts | 123 ++++++++++++++++++++++++++++- src/client/object/player-entity.ts | 5 +- src/client/object/pony.ts | 8 +- src/server/object/game.ts | 2 +- 4 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/client/object/canvas-utils.ts b/src/client/object/canvas-utils.ts index 5520b92..5c0184f 100644 --- a/src/client/object/canvas-utils.ts +++ b/src/client/object/canvas-utils.ts @@ -1,6 +1,88 @@ import { CanvasTexture, LinearFilter, ClampToEdgeWrapping } from 'three'; +/** + * Draws a rounded rectangle using the current state of the canvas. + * If you omit the last three params, it will draw a rectangle + * outline with a 5 pixel border radius + * + * https://stackoverflow.com/a/3368118 + * + * @param {CanvasRenderingContext2D} ctx + * @param {Number} x The top left x coordinate + * @param {Number} y The top left y coordinate + * @param {Number} width The width of the rectangle + * @param {Number} height The height of the rectangle + * @param {Number} [radius = 5] The corner radius; It can also be an object + * to specify different radii for corners + * @param {Number} [radius.tl = 0] Top left + * @param {Number} [radius.tr = 0] Top right + * @param {Number} [radius.br = 0] Bottom right + * @param {Number} [radius.bl = 0] Bottom left + * @param {Boolean} [fill = false] Whether to fill the rectangle. + * @param {Boolean} [stroke = true] Whether to stroke the rectangle. + */ +export function roundRect( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number | { tl: number; tr: number; br: number; bl: number }, + fill: boolean, + stroke: boolean, +) { + if (typeof stroke === 'undefined') { + stroke = true; + } + if (typeof radius === 'undefined') { + radius = 5; + } + if (typeof radius === 'number') { + radius = { tl: radius, tr: radius, br: radius, bl: radius }; + } else { + var defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }; + for (var side in defaultRadius) { + radius[side] = radius[side] || defaultRadius[side]; + } + } + ctx.beginPath(); + ctx.moveTo(x + radius.tl, y); + ctx.lineTo(x + width - radius.tr, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); + ctx.lineTo(x + width, y + height - radius.br); + ctx.quadraticCurveTo( + x + width, + y + height, + x + width - radius.br, + y + height, + ); + ctx.lineTo(x + radius.bl, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); + ctx.lineTo(x, y + radius.tl); + ctx.quadraticCurveTo(x, y, x + radius.tl, y); + ctx.closePath(); + if (fill) { + ctx.fill(); + } + if (stroke) { + ctx.stroke(); + } +} + +export interface CanvasUtilsOptions { + fill?: boolean; + backgroundColor?: string; + foregroundColor?: string; + rounded?: boolean; + roundedRadius?: number; + textBorderColor?: string; + textBorderSize?: number; + textShadowBlur?: number; +} + export class CanvasUtils { + constructor(private options?: CanvasUtilsOptions) {} + public createTextCanvas( text: string | string[], bold = true, @@ -37,14 +119,47 @@ export class CanvasUtils { ctx.textAlign = 'center'; // Draw background - ctx.fillStyle = '#fff'; - ctx.fillRect(0, 0, width, height); + if (this.options?.fill ?? true) { + ctx.fillStyle = this.options?.backgroundColor || '#fff'; + if (this.options?.rounded) { + roundRect( + ctx, + 0, + 0, + width, + height, + this.options?.roundedRadius || 4, + true, + false, + ); + } else { + ctx.fillRect(0, 0, width, height); + } + } // Scale the text to fit within the canvas const scaleFactor = Math.min(1, width / longestLine); - ctx.translate(width / 2 - padding, padding + fontSize / 2); + ctx.translate( + Math.floor(width / 2 - padding) + 0.5, + Math.floor(padding + fontSize / 2) + 0.5, + ); ctx.scale(scaleFactor, 1); - ctx.fillStyle = '#000'; + + // Draw the text + + if (this.options?.textShadowBlur !== undefined) { + ctx.shadowColor = this.options?.textBorderColor || '#000'; + ctx.shadowBlur = this.options?.textShadowBlur; + if (this.options?.textBorderSize !== undefined) { + ctx.lineWidth = this.options?.textBorderSize; + lines.forEach((line, i) => { + ctx.strokeText(line, padding, i * fontSize + padding); + }); + } + ctx.shadowBlur = 0; + } + + ctx.fillStyle = this.options?.foregroundColor || '#000'; lines.forEach((line, i) => { ctx.fillText(line, padding, i * fontSize + padding); }); diff --git a/src/client/object/player-entity.ts b/src/client/object/player-entity.ts index 7aa7e6f..e02ddc6 100644 --- a/src/client/object/player-entity.ts +++ b/src/client/object/player-entity.ts @@ -8,7 +8,10 @@ import { import { CanvasUtils } from './canvas-utils'; import { ChatBubble } from './chat-bubble'; -const chatBuilder = new CanvasUtils(); +const chatBuilder = new CanvasUtils({ + rounded: true, + roundedRadius: 8, +}); export class PlayerEntity extends PonyEntity { private _updateQueue: PositionUpdatePacket[] = []; diff --git a/src/client/object/pony.ts b/src/client/object/pony.ts index 9a09a60..3d54538 100644 --- a/src/client/object/pony.ts +++ b/src/client/object/pony.ts @@ -5,7 +5,13 @@ import { FullStatePacket } from '../../common/types/packet'; import { NameTag } from './nametag'; import { CanvasUtils } from './canvas-utils'; -const nameTagBuilder = new CanvasUtils(); +const nameTagBuilder = new CanvasUtils({ + fill: false, + textBorderColor: '#000', + foregroundColor: '#fff', + textShadowBlur: 2, + textBorderSize: 1, +}); export class PonyEntity { public velocity = new THREE.Vector3(0, 0, 0); diff --git a/src/server/object/game.ts b/src/server/object/game.ts index 2760bde..3a44478 100644 --- a/src/server/object/game.ts +++ b/src/server/object/game.ts @@ -110,6 +110,6 @@ export class Game { ); this.io.emit('playerupdate', playerInfo); this._changedPlayers.length = 0; - }, 10); + }, 1000 / 60); } }