如何在JS图形编程中围绕轴心旋转对象组
实现Container子对象围绕轴心旋转的解决方案
我正在用JavaScript开发一款基于HTML Canvas的轻量简易2D引擎,目前在实现继承机制。参考PixiJS设计了entity类和container类,二者都包含draw函数,接收graphics类引用、旋转偏移量、x和y坐标作为参数。container类用于让多组entity对象协同运动,现在需要实现子对象围绕container的轴心旋转的效果(即容器旋转时,子对象保持相对容器的位置,绕容器中心转动)。
现有代码
Container类
//Container Class class container { //List of objects (children) objects = []; //Position and rotation x = 0; y = 0; rot = 0; //Layer layer = 0; //Add a child to the container addChild = function(child) { //Add reference to array this.objects.push(child); //Sort by layer this.objects = this.objects.sort((a, b) => { if (a.layer > b.layer) { return 1 } return -1}) } //Draw draw = function(gfx, rot, x, y) { //Loop through all children for(let i=0; i<this.objects.length; i++) { let ent = this.objects[i] //Call draw function from child and offset rotation, x, and y, by current x and y. //Basically, the x and y from the input of the function offsets the container, and then we //offset the children by the container's x and y. //Allows for endless inheritance and offsets ent.draw(gfx, this.rot + rot, this.x + x, this.y + y); } } }
Entity类
//Entity class class entity { //Sprite sprite = null; //Position and rotation x = 0; y = 0; rot = 0; //Layer layer = 0; //Create new sprite on creation (don't worry about this) constructor() { this.sprite = new sprite(); } //Draw function draw = function(gfx, rot, x, y) { //Draws the sprite at this entities position offsetted by the input position and rotation gfx.drawspr(this.sprite, this.x + x, this.y + y, this.rot + rot); } }
Sprite类
//Sprite class class sprite { //Image img = null; width = 0; height = 0; //Loads image load = async function(src) { //New image object this.img = new Image(); //Sets source to src this.img.src = src //Waits for image to load await new Promise((resolve, reject) => { setTimeout(() => { resolve("timeout"); }, 10000); this.img.onload = function() { resolve(); } }); //Sets the width to the image this.img.onload = null; this.width = this.img.width; this.height = this.img.height; } }
drawspr函数
//Draws sprite. (spr - sprite object, x - x position on screen to render, y - y position on screen to render //rot - rotation to render the sprite drawspr = function(spr, x, y, rot) { //Save current 2d context transform this.ctx.save(); if(rot!=0) { //Rotate the screen to draw the image if its rotation is bigger than 0 this.ctx.rotate(rot*Math.PI/180); } //Draw the image at the x and y, and reset 2d context transform this.ctx.drawImage(spr.img, x, y, spr.width, spr.height) this.ctx.restore(); }
问题分析
现有代码的核心问题是仅通过数值叠加坐标和旋转角度,没有利用Canvas上下文的变换机制。这种方式无法实现子对象围绕容器轴心旋转的效果——容器旋转时,子对象只会跟着平移并自身旋转,不会绕容器中心做圆周运动。
修改方案
1. 重构Container的draw方法
利用Canvas上下文的translate和rotate实现整体变换,让子对象自动继承容器的旋转和位置:
// 修改后的Container Class class container { objects = []; x = 0; y = 0; rot = 0; layer = 0; addChild = function(child) { this.objects.push(child); // 简化排序逻辑 this.objects = this.objects.sort((a, b) => a.layer - b.layer); } draw = function(gfx, parentRot = 0, parentX = 0, parentY = 0) { // 计算容器的世界坐标与总旋转角度 const worldX = parentX + this.x; const worldY = parentY + this.y; const totalRot = parentRot + this.rot; // 保存当前上下文状态,避免影响其他元素 gfx.save(); // 平移到容器的世界位置 gfx.translate(worldX, worldY); // 应用容器的总旋转角度(转为弧度) gfx.rotate(totalRot * Math.PI / 180); // 绘制子对象:此时上下文已完成容器的变换,子对象只需使用本地坐标 for(let i = 0; i < this.objects.length; i++) { const ent = this.objects[i]; // 父级变换已由上下文处理,传递0即可 ent.draw(gfx, 0, 0, 0); } // 恢复上下文状态 gfx.restore(); } }
2. 调整Entity的draw方法
实体的坐标是相对于父容器的本地坐标,只需处理自身的相对变换:
// 修改后的Entity class class entity { sprite = null; x = 0; y = 0; rot = 0; layer = 0; constructor() { this.sprite = new sprite(); } draw = function(gfx, parentRot = 0, parentX = 0, parentY = 0) { // 计算实体的相对旋转与位置 const localRot = parentRot + this.rot; const localX = parentX + this.x; const localY = parentY + this.y; gfx.drawspr(this.sprite, localX, localY, localRot); } }
3. 修正drawspr函数的旋转逻辑
原来的旋转基于Canvas原点,需改为围绕精灵自身中心旋转(若需要):
// 修改后的drawspr函数 drawspr = function(spr, x, y, rot) { this.ctx.save(); // 平移到精灵的目标位置 this.ctx.translate(x, y); if(rot !== 0) { // 应用旋转角度(转为弧度) this.ctx.rotate(rot * Math.PI / 180); } // 平移到精灵中心(若不需要围绕中心旋转,可删除此行) this.ctx.translate(-spr.width / 2, -spr.height / 2); // 绘制精灵,此时原点在精灵的旋转中心(或左上角) this.ctx.drawImage(spr.img, 0, 0, spr.width, spr.height); this.ctx.restore(); }
效果说明
修改后,当你调整Container的rot属性时,所有子Entity会自动围绕Container的轴心(即Container的x/y位置)旋转,同时保持自身相对容器的位置不变。例如:
- 创建一个Container,设置
x = canvas.width/2、y = canvas.height/2(画布中心) - 添加几个Entity,分别设置
x为-50、0、50,y为0 - 不断增加Container的
rot值,就能看到子Entity绕画布中心旋转的效果
内容的提问来源于stack exchange,提问作者Vinny
相关产品推荐
相关产品推荐

