基本上我有一个带有孩子的容器对象'相对于其父级修改的,我想通过更改父级的旋转值来旋转所有对象,同时保持各个子项的方向稳定。 (如旋转整个物体)我觉得我没有很好地解释这一点,所以这里有两个例子。 PhysicsJS:http://wellcaffeinated.net/PhysicsJS/(参见第一个例子,0.7和球 - 请注意在碰撞后零或七旋转时如何保持对象的整体形状。在PhaserJS中这个例子也是如此( http://phaser.io/examples/v2/groups/group-transform-rotate)与机器人。现在,只是为了看看我是否可以,我试图用我自己的库复制前面提到的PhysicsJS示例 - https://jsfiddle.net/khanfused/r4LgL5y9/(简化为简洁)
Art.prototype.modules.display.rectangle.prototype.draw = function() {
// Initialize variables.
var g = Art.prototype.modules.display.rectangle.core.graphics,
t = this;
// Execute the drawing commands.
g.save();
g.translate(t.parent.x ? t.parent.x + t.x : t.x, t.parent.y ? t.parent.y + t.y : t.y);
/* Point of interest. */
g.rotate(t.parent.rotation ? t.rotation : t.rotation);
g.scale(t.scale.x, t.scale.y);
g.globalAlpha = t.opacity === 'super' ? t.parent.opacity : t.opacity;
g.lineWidth = t.lineWidth === 'super' ? t.parent.lineWidth : t.lineWidth;
g.fillStyle = t.fill === 'super' ? t.parent.fill : t.fill;
g.strokeStyle = t.stroke === 'super' ? t.parent.stroke : t.stroke;
g.beginPath();
g.rect(t.width / -2, t.height / -2, t.width, t.height);
g.closePath();
if (t.fill) {
g.fill();
}
if (t.stroke) {
g.stroke();
}
g.restore();
return this;
};
请参阅标记的兴趣点 - 旋转画布的位置。如果对象具有父对象,则它将按父对象的值加上对象的值旋转 - 否则,只是对象的值。我尝试了一些不同的组合,比如......
•父 - 对象
•object - parent
......我查看了PhysicsJS和Phaser的消息来源,寻找正确方向的某种线索,但无济于事。
如何旋转组但不更改其布局?
答案 0 :(得分:2)
要使用要应用于组的所有成员的变换转换组周围的对象,然后使用自己的变换渲染每个成员。在通过局部变换转换每个成员之前,您需要保存当前变换,以便它可以用于下一个组成员。在渲染每个组成员的最后,您必须将转换恢复回其上方组的状态。
数据结构
group = {
origin : { x : 100, y : 100},
rotate : 2,
scale : { x : 1, y : 1},
render : function(){ // the function that draws to the canvas
ctx.strokeRect(-50,-50,100,100);
},
groups : [ // array of groups
{
origin : { x : 100, y : 100},
rotate : 2,
scale : { x : 1, y : 1},
render : function(){... }// draw something
groups : [] // could have more members
}], // the objects to be rendered
}
递归渲染
渲染嵌套转换最好通过递归来完成,其中renderGroup函数检查任何子组并调用自身来呈现该组。这使得使用最少的代码很容易拥有复杂的嵌套对象。树是递归的简单示例,其中终止条件到达最后一个节点。但是,如果允许嵌套组成员引用树中的其他成员,则很容易出错。这将导致Javascript阻止页面崩溃。
function renderGroup(group){
ctx.save();
// it is important that the order of transforms us correct
ctx.translate(group.origin.x, group.origin.y);
ctx.scale(group.scale.x, group.scale.y);
ctx.rotate(group.rotate);
// draw what is needed
if(group.render !== undefined){
group.render();
}
// now draw each member of this group.groups
for ( var i = 0 ; i < group.groups.length; i ++){
// WARNING this is recursive having any member of a group reference
// another member within the nested group object will result in an
// infinite recursion and computers just don't have the memory or
// speed to complete the impossible
renderGroup(group.groups[i]); // recursive call
};
// and finally restore the original transform
ctx.restore();
}
这就是如何嵌套转换以及W3C如何使用渲染。但我永远不会这样做。由于需要使用保存和恢复,这是帧速率的杀手,这是因为ctx.getTransform支持非常有限(仅限Chrome)。由于无法获得转换,因此必须使用镜像进行转换,因为如果要维护矩阵,可以应用许多优化。你可以使用setTransform和一些小数学实时获得1000个精灵,在画布上使用这种方式或者更差的帧速率。
<强>演示强>
使用安全递归运行示例。
以鼠标所在的位置绘制嵌套对象。
该演示只是一个递归渲染,取自我所拥有的其他代码并剪切以适应此演示。它扩展了递归渲染以允许动画和渲染顺序。请注意,比例是不均匀的,因此迭代的深度会有一些偏差。
// adapted from QuickRunJS environment.
//===========================================================================
// simple mouse
//===========================================================================
var mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, buttonRaw : 0,
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];}
e.preventDefault();
}
mouse.start = function(element, blockContextMenu){
if(mouse.element !== undefined){ mouse.removeMouse();}
mouse.element = element;
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
if(blockContextMenu === true){
element.addEventListener("contextmenu", preventDefault, false);
mouse.contextMenuBlocked = true;
}
}
mouse.remove = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(n => { mouse.element.removeEventListener(n, mouseMove); } );
if(mouse.contextMenuBlocked === true){ mouse.element.removeEventListener("contextmenu", preventDefault);}
mouse.contextMenuBlocked = undefined;
mouse.element = undefined;
}
}
return mouse;
})();
//===========================================================================
// fullscreen canvas
//===========================================================================
// delete needed for my QuickRunJS environment
function removeCanvas(){
if(canvas !== undefined){
document.body.removeChild(canvas);
}
canvas = undefined;
}
// create onscreen, background, and pixelate canvas
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){ createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.ctx = canvas.getContext("2d");
}
//===========================================================================
// general set up
//===========================================================================
var canvas,ctx;
canvas = undefined;
// create and size canvas
resizeCanvas();
// start mouse listening to canvas
mouse.start(canvas,true); // flag that context needs to be blocked
// listen to resize
window.addEventListener("resize",resizeCanvas);
var holdExit = 0; // To stop in QuickRunJS environment
var font = "18px arial";
//===========================================================================
// The following function are for creating render nodes.
//===========================================================================
// render functions
// adds a box render to a node;
function addBoxToNode(node,when,stroke,fill,lwidth,w,h){
function drawBox(){
ctx.strokeStyle = this.sStyle;
ctx.fillStyle = this.fStyle;
ctx.lineWidth = this.lWidth;
ctx.fillRect(-this.w/2,-this.h/2,this.w,this.h);
ctx.strokeRect(-this.w/2,-this.h/2,this.w,this.h);
}
var renderNode = {
render : drawBox,
sStyle : stroke,
fStyle : fill,
lWidth : lwidth,
w : w,
h : h,
}
node[when].push(renderNode);
return node;
}
// adds a text render to a node
function addTextToNode(node,when,text,x,y,fill){
function drawText(){
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = this.fStyle
ctx.fillText(this.text,this.x,this.y);
}
var renderNode = {
render : drawText,
text : text,
fStyle : fill,
x : x,
y : y,
}
node[when].push(renderNode); // binds to this node
return node;
}
// renders a node
function renderNode(renderList){
var i,len = renderList.length;
for(i = 0; i < len; i += 1){
renderList[i].render();
}
}
//---------------------------------------------------------------------------
// animation functions
// add a rotator to a node. Rotates the node
function addRotatorToNode(node,speed){
function rotator(){
this.transform.rot += this.rotSpeed;
}
node.animations.push(rotator.bind(node))
node.rotSpeed = speed;
}
// addd a wobbla to a nod. Wobbles the node
function addWobblaToNode(node,amount){
function wobbla(){
this.transform.sx = 1 - ((Math.cos(this.transform.rot) + 1) / 2) * this.scaleAmount ;
this.transform.sy = 1 - ((Math.sin(this.transform.rot) + 1) / 2) * this.scaleAmount ;
}
node.animations.push(wobbla.bind(node))
node.scaleAmount = amount;
}
// add a groover to a node. Move that funcky thang.
function addGrooverToNode(node,amount){
function wobbla(){
this.transform.x += Math.cos(this.transform.rot) * this.translateDist ;
this.transform.y += Math.sin(this.transform.rot*3) * this.translateDist ;
}
node.animations.push(wobbla.bind(node))
node.translateDist = amount;
}
// function to animate and set a transform
function setTransform(){
var i, len = this.animations.length;
for(i = 0; i < len; i ++){ // do any animtions that are on this node
this.animations[i]();
}
// set the transfomr
ctx.scale(this.transform.sx, this.transform.sy);
ctx.translate(this.transform.x, this.transform.y);
ctx.rotate(this.transform.rot);
}
//---------------------------------------------------------------------------
// node creation
// creats a node and returns it
function createNode(){
return {
transform : undefined,
setTransform : setTransform, // function to apply the current transform
animations : [], // animation functions
render : renderNode, // render main function
preRenders : [], // render to be done befor child nodes are rendered
postRenders : [], // render to be done after child nodes are rendered
nodes : [],
itterationCounter : 0, // important counts iteration depth
};
}
function addNodeToNode(node,child){
node.nodes.push(child);
}
// adds a transform to a node and returns the transform
function createNodeTransform(node,x,y,sx,sy,rot){
return node.transform = {
x : x, // translate
y : y,
sx : sx, //scale
sy : sy,
rot : rot, //rotate
};
}
// only one top node
var nodeTree = createNode(); // no details as yet
// add a transform to the top node and keep a ref for moving
var topTransform = createNodeTransform(nodeTree,0,0,1,1,0);
// top node has no render
var boxNode = createNode();
createNodeTransform(boxNode,0,0,0.9,0.9,0.1)
addRotatorToNode(boxNode,-0.02)
addWobblaToNode(boxNode,0.2)
addBoxToNode(boxNode,"preRenders","Blue","rgba(0,255,0,0.2)",3,100,100)
addTextToNode(boxNode,"postRenders","FIRST",0,0,"red")
addTextToNode(boxNode,"postRenders","text on top",0,20,"red")
addNodeToNode(nodeTree,boxNode)
function Addnode(node,x,y,scale,rot,text,anRot,anSc,anTr){
var boxNode1 = createNode();
createNodeTransform(boxNode1,x,y,scale,scale,rot)
addRotatorToNode(boxNode1,anRot)
addWobblaToNode(boxNode1,anSc)
addGrooverToNode(boxNode1,anTr)
addBoxToNode(boxNode1,"preRenders","black","rgba(0,255,255,0.2)",3,100,100)
addTextToNode(boxNode1,"postRenders",text,0,0,"black")
addNodeToNode(node,boxNode1)
// add boxes to coners
var boxNode2 = createNode();
createNodeTransform(boxNode2,50,-50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
var boxNode2 = createNode();
createNodeTransform(boxNode2,-50,-50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
var boxNode2 = createNode();
createNodeTransform(boxNode2,-50,50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
var boxNode2 = createNode();
createNodeTransform(boxNode2,50,50,0.8,0.8,0.1)
addRotatorToNode(boxNode2,0.2)
addBoxToNode(boxNode2,"postRenders","black","rgba(0,255,255,0.2)",3,20,20)
addNodeToNode(boxNode1,boxNode2)
}
Addnode(boxNode,50,50,0.9,2,"bot right",-0.01,0.1,0);
Addnode(boxNode,50,-50,0.9,2,"top right",-0.02,0.2,0);
Addnode(boxNode,-50,-50,0.9,2,"top left",0.01,0.1,0);
Addnode(boxNode,-50,50,0.9,2,"bot left",-0.02,0.2,0);
//===========================================================================
// RECURSIVE NODE RENDER
//===========================================================================
// safety var MUST HAVE for those not used to recursion
var recursionCount = 0; // number of nodes
const MAX_RECUSION = 30; // max number of nodes to itterate
// safe recursive as global recursion count will limit nodes reandered
function renderNodeTree(node){
var i,len;
// safty net
if((recursionCount ++) > MAX_RECUSION){
return;
}
ctx.save(); // save context state
node.setTransform(); // animate and set transform
// do pre render
node.render(node.preRenders);
// render each child node
len = node.nodes.length;
for(i = 0; i < len; i += 1){
renderNodeTree(node.nodes[i]);
}
// do post renders
node.render(node.postRenders);
ctx.restore(); // restore context state
}
//===========================================================================
// RECURSIVE NODE RENDER
//===========================================================================
ctx.font = font;
function update(time){
ctx.setTransform(1,0,0,1,0,0); // reset top transform
ctx.clearRect(0,0,canvas.width,canvas.height);
// set the top transform to the mouse position
topTransform.x = mouse.x;
topTransform.y = mouse.y;
recursionCount = 0;
renderNodeTree(nodeTree);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
&#13;