3.x怎么实现类似2.x的skewX和skewY

看我上一条回复。把 rootNode.setScale 放在 updateWorldTransform 之前。就 ok 了。

但我是想把skewX和skewY hack到Node里啊,可以直接在一个Tween里设置skew和scale来实现动画

这是另外一个话题了吧?

我提供的 skewX,skewY 与 Scale 一起变化的解决方案还有问题吗?

目前这个 hack 改动,依赖顺序,skew hack 世界矩阵,必须在世界矩阵更新之后。

你可能可以通过 修改 updateWorldTransform 的原型来保证你的 hack 代码在最后执行。

问题是不能通用,我封装好setSkew后,如果先调用setSkew后调用setScale呢

这个我试试

下个版本能加入skew么,很多效果需要用到的,比如skewY和scaleX一起变可以实现整个页面翻转的动画

这个后续看看吧,看是否如此迫切。需要这个功能的可以这里回复。

import { _decorator, Component, Node, TransformBit } from 'cc';
const { ccclass, property } = _decorator;

const oldUpdateWorldTransform = Node.prototype.updateWorldTransform;

(Node.prototype as any)._skewX = 0.0;
(Node.prototype as any)._skewY = 0.0;

Node.prototype.updateWorldTransform = function() {
    if (this._skewX || this._skewY) {
        this.invalidateChildren(TransformBit.SCALE);
        oldUpdateWorldTransform.call(this);
        let m = this['_mat'],a = m.m00,b = m.m01,c = m.m04,d = m.m05;
        m.m00 = a+c*this._skewY;
        m.m01 = b+d*this._skewY;
        m.m04 = c+a*this._skewX;
        m.m05 = d+b*this._skewX;

        for (const child of this.children) { // 一起变要加上更新子节点
            child.invalidateChildren(TransformBit.SCALE | TransformBit.ROTATION);
        }
    } else {
        oldUpdateWorldTransform.call(this);
    }
}

Object.defineProperty(Node.prototype, 'skewX', {
    get() {
        return this._skewX;
    },

    set(v: number) {
        this._skewX = v;
        this.invalidateChildren(TransformBit.SCALE);
    },

    configurable: true,
    enumerable: true,
});

Object.defineProperty(Node.prototype, 'skewY', {
    get() {
        return this._skewY;
    },

    set(v: number) {
        this._skewY = v;
        this.invalidateChildren(TransformBit.SCALE);
    },

    configurable: true,
    enumerable: true,
});

@ccclass('HelloWorld')
export class HelloWorld extends Component {

    start() {
        let rootNode = this.node.getChildByName('RootNode');
        const PI180 = Math.PI / 180;
        let skewX = 0, skewY = 0;
        let scaleX = 1;
        this.schedule(() => {
            skewY++;
            let skX = Math.tan(skewX*PI180);
            let skY = Math.tan(skewY*PI180);
            //如果下面两句注释放开,skew的变更会失效,因为TransformBit.SCALE冲突了
            scaleX-=0.02;

            (rootNode as any).skewX = skX;
            (rootNode as any).skewY = skY;
            rootNode.setScale(scaleX, scaleX);
            
            
        }, 0.1);
    }
}

这样可行了,不依赖顺序。你可以考虑把 skewX, skewY 封装为 skew 对象,这样跟 position, rotation, scale 比较统一。

1赞
import { _decorator, Component, Node, TransformBit, tween, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

const oldUpdateWorldTransform = Node.prototype.updateWorldTransform;

(Node.prototype as any)._skewX = 0.0;
(Node.prototype as any)._skewY = 0.0;

Node.prototype.updateWorldTransform = function() {
    if (this._skewX || this._skewY) {
        this.invalidateChildren(TransformBit.SCALE);
        oldUpdateWorldTransform.call(this);
        let m = this['_mat'],a = m.m00,b = m.m01,c = m.m04,d = m.m05;
        m.m00 = a+c*this._skewY;
        m.m01 = b+d*this._skewY;
        m.m04 = c+a*this._skewX;
        m.m05 = d+b*this._skewX;

        for (const child of this.children) { // 一起变要加上更新子节点
            child.invalidateChildren(TransformBit.SCALE | TransformBit.ROTATION);
        }
    } else {
        oldUpdateWorldTransform.call(this);
    }
}

Object.defineProperty(Node.prototype, 'skewX', {
    get() {
        return this._skewX;
    },

    set(v: number) {
        this._skewX = v;
        this.invalidateChildren(TransformBit.SCALE);
    },

    configurable: true,
    enumerable: true,
});

Object.defineProperty(Node.prototype, 'skewY', {
    get() {
        return this._skewY;
    },

    set(v: number) {
        this._skewY = v;
        this.invalidateChildren(TransformBit.SCALE);
    },

    configurable: true,
    enumerable: true,
});

@ccclass('HelloWorld')
export class HelloWorld extends Component {

    start() {
        let rootNode = this.node.getChildByName('RootNode');
        const PI180 = Math.PI / 180;

        let skew = { x: 0, y: 0};

        tween(rootNode)
            .parallel(
                tween(rootNode).by(2, { scale: new Vec3(-0.7, -0.7, 0) }),
                tween(skew).by(2, { y: 30}, {
                    onUpdate(target, ratio) {
                        (rootNode as any).skewY = Math.tan(target.y*PI180);
                    },
                } )
            ).id(1)
            .reverse(1)
            .union()
            .repeatForever()
            .start();
            
    }
}

这是使用 tween 的代码

大佬牛批~~~
hack只是补救办法,还是希望新版本能把skew加上,这个本来就是常规功能

我整理了一下代码,你现在可以这样用了:

        tween(rootNode)
            .by(2, { 
                scale: new Vec3(-0.7, -0.7, 0),
                // @ts-expect-error
                skew: new Vec2(0, 30),
            })

完整代码:

import { _decorator, Component, Node, TransformBit, tween, Vec3, Vec2 } from 'cc';
const { ccclass, property } = _decorator;

const oldUpdateWorldTransform = Node.prototype.updateWorldTransform;

(Node.prototype as any)._skew = new Vec2(0, 0);

const PI180 = Math.PI / 180;

Node.prototype.updateWorldTransform = function() {
    if (this._skew.x || this._skew.y) {
        const skewX = Math.tan(this._skew.x * PI180);
        const skewY = Math.tan(this._skew.y * PI180);

        this.invalidateChildren(TransformBit.SCALE);
        oldUpdateWorldTransform.call(this);

        let m = this['_mat'],a = m.m00,b = m.m01,c = m.m04,d = m.m05;
        m.m00 = a+c*skewY;
        m.m01 = b+d*skewY;
        m.m04 = c+a*skewX;
        m.m05 = d+b*skewX;

        for (const child of this.children) { // 一起变要加上更新子节点
            child.invalidateChildren(TransformBit.SCALE | TransformBit.ROTATION);
        }
    } else {
        oldUpdateWorldTransform.call(this);
    }
}

Object.defineProperty(Node.prototype, 'skew', {
    get() {
        return this._skew;
    },

    set(v: Vec2) {
        this._skew = v;
        this.invalidateChildren(TransformBit.SCALE);
    },

    configurable: true,
    enumerable: true,
});

@ccclass('HelloWorld')
export class HelloWorld extends Component {

    start() {
        let rootNode = this.node.getChildByName('RootNode');
        tween(rootNode)
            .by(2, { 
                scale: new Vec3(-0.7, -0.7, 0),
                // @ts-expect-error
                skew: new Vec2(0, 30),
            }).id(1)
            .reverse(1)
            .union()
            .repeatForever()
            .start();
    }
}
1赞

现在简洁多了,我尝试整合到我的节点扩展里

1赞

可惜 native 上无效, 应该是因为都是直接调用的 C++ 方法, 没有再从 js 走一遍了, 没有这样 hack 的余地

2赞

对啊,skew属于常规功能,在2D项目中很有用的,希望官方下一版本能加上。

请问一下,移除skew是出于性能考虑吗?2D里面实现skew真的很常规

这个我也不确定早期移除的原因。但应该跟性能关系不大。
有可能是 skew 在 3D 里面比较少用,当时就没考虑在 3.x 中加。

可以请求在3.x的2D里面加上吗?2D真的很需要这个属性,我相信现在用3.x开发2D游戏的还是非常多的

去 github 上建个 issue 吧。