《狂热运输》开发记录 | 1.画布实现

171 阅读3分钟

  基于PC版《狂热运输》游戏开发的网页版游戏,初期实现城市搭建项目地址

画布功能

  1. canvas绘制坐标点,方便图标定位
  2. 鼠标滚轮控制画布“无损”放大缩小

功能实现

canvas绘制坐标点

  固定宽高,创建白色背景空白画布

// 获取canvas
const canvasScene = document.getElementById('canvasScene');
// canvasWidth:画布宽度,canvasHeight:画布高度,arcInterval:点间隔,arcSize:圆半径
const canvasWidth = 1280, canvasHeight = 640,arcInterval= 10, arcSize = 1;
// 设置画布尺寸 固定在1280*640
canvasScene.width = canvasWidth;
canvasScene.height = canvasHeight;
let ctx = canvasScene.getContext("2d");
// 画布背景色白色
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);

  逐行绘制坐标点,点间隔为10,横向128个点,纵向64个点。
  每个点重新开始一条新路径,先绘制线再填充颜色,每隔3个点增大圆点尺寸。

for (let h = 0; h <= canvasHeight / arcInterval; h++) {
    for (let w = 0; w <= canvasWidth / arcInterval; w++) {
        draw((w * arcInterval), (h * arcInterval), arcSize, arcInterval);
    }
}
// x:x轴坐标 y:y轴坐标 r:圆半径 i:点间隔
const draw = (x, y, r, i) => {
    i = i * 4;
    // 每个点重新开始一条新路径
    ctx.beginPath();
    if (x % i == 0 && y % i == 0) {
        r = r * 2
    }
    ctx.arc(x, y, r, 0, 2 * Math.PI, false);
    ctx.strokeStyle = "#d3d3d3";
    ctx.stroke();
    ctx.fillStyle = "#d3d3d3";
    ctx.fill();
    ctx.closePath();
}

  最终实现效果 image.png

鼠标滚轮控制缩放

  在父元素上加滚轮监听事件

const setWheel = () => {
  let element = document.getElementById("scene");
  let scale = 1;
  element.addEventListener('wheel', function (event) {
    event.preventDefault();
    let delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
    // 判断滚动方向 大于0向上滚动 小于0向下滚动
    if (delta > 0) {
      scale += 0.25;
    } else {
      scale -= 0.25;
    }
    // 数值范围控制
    if (scale > 5) {
      scale = 5
    } else if (scale < 1) {
      scale = 1
    }
    element.style.transform = 'scale(' + scale + ')';
  });
}

  但是放大后的实际效果,圆点模糊。 image.png 于是另辟蹊径,将canvasWidth:画布宽度canvasHeight:画布高度arcInterval:点间隔arcSize:圆半径全部放大10倍。

// canvasWidth:画布宽度,canvasHeight:画布高度,arcInterval:点间隔,arcSize:圆半径
const canvasWidth = 12800, canvasHeight = 6400,arcInterval= 100, arcSize = 10;

Canvas绘制性能问题

  但是这里会有性能问题,随着画布逐渐扩大,绘制的点会更多,为防止阻塞主线程,使用worker在子线程中进行绘制。

// 获取canvas的控制权
const offscreen = document.querySelector('#canvasScene').transferControlToOffscreen();
const canvasWidth = 1280, canvasHeight = 640,arcInterval= 10;
const worker = new Worker('/public/unit/worker.js');
// 传递参数并且移交canvas的所有权给子线程
worker.postMessage({msg: 'init', canvas: offscreen, data: {canvasWidth, canvasHeight, arcInterval, arcSize}}, [offscreen]);
worker.onmessage = (e) => {
  console.log(e, '绘制完成!');
}

  参考这篇文章,使用“Control模式”,将Canvas的所有权交给worker创建的子线程来进行绘制渲染操作。

let offscreen, ctx;
onmessage = (e) => {
    if (e.data.msg == 'init') {
        let canvasInfo = e.data.data;
        let canvas = e.data.canvas;
        setArcList(canvas, canvasInfo);
    }
}

const setArcList = (canvas, {canvasWidth, canvasHeight, arcSize, arcInterval}) => {
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    ctx = canvas.getContext("2d");
    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);
    for (let h = 0; h <= canvasHeight / arcInterval; h++) {
        for (let w = 0; w <= canvasWidth / arcInterval; w++) {
            draw((w * arcInterval), (h * arcInterval), arcSize, arcInterval);
        }
    }
    postMessage({}, []);
}
const draw = (x, y, r, i) => {
    i = i * 4;
    ctx.beginPath();
-    if (x % i == 0 && y % i == 0) {
        r = r * 2
    }
    ctx.arc(x, y, r, 0, 2 * Math.PI, false);
    ctx.strokeStyle = "#d3d3d3";
    ctx.stroke();
    ctx.fillStyle = "#d3d3d3";
    ctx.fill();
    ctx.closePath();
}

至此,一个可放大的矩阵点画布绘制完成。后续进行其他功能开发。