You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为穿过固定矩形区域的SKSpriteNode添加扭曲效果?

解决方案:固定区域的Sprite扭曲效果实现

问题核心

需要让移动的SKSpriteNode穿过屏幕固定矩形区域时产生扭曲效果,但当前使用SKEffectNode时,它会自适应子节点尺寸导致扭曲效果不一致,且希望避免通过添加/移除子节点实现。


修正SKEffectNode的使用方式

SKEffectNode默认贴合子节点大小,要让它固定尺寸,可添加透明占位节点强制其保持固定宽高,稳定扭曲坐标系:

// 创建固定尺寸的扭曲EffectNode
func createFixedSizeWarpNode(frame: CGRect) -> SKEffectNode {
    let effectNode = SKEffectNode()
    effectNode.position = frame.origin
    effectNode.zPosition = priorityPos.upEffectNodeZ
    
    // 添加透明占位节点,强制EffectNode维持固定尺寸
    let placeholder = SKSpriteNode(color: .clear, size: frame.size)
    placeholder.position = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
    effectNode.addChild(placeholder)
    
    // 设置扭曲网格参数
    let destinationPositions: [vector_float2] = [
        vector_float2(-0.1, 1), vector_float2(0.5, 1.3), vector_float2(1.1, 1),
        vector_float2(0.1, 0.5), vector_float2(0.5, 0.5), vector_float2(0.9, 0.5),
        vector_float2(-0.1, 0), vector_float2(0.5, -0.3), vector_float2(1.1, 0)
    ]
    let warpGeometryGrid = SKWarpGeometryGrid(columns: 10, rows: 1)
    effectNode.warpGeometry = warpGeometryGrid.replacingByDestinationPositions(positions: destinationPositions)
    
    return effectNode
}

之后在场景更新时,检测Sprite与扭曲区域的交集,动态调整节点层级(这是当前最直接的实现方式,无需频繁创建销毁节点):

override func update(_ currentTime: TimeInterval) {
    for sprite in movingSprites {
        let isInWarpArea = sprite.frame.intersects(warpNode.frame)
        // 进入区域时移至EffectNode子节点
        if isInWarpArea && sprite.parent != warpNode {
            sprite.removeFromParent()
            warpNode.addChild(sprite)
        }
        // 离开区域时移回原场景
        else if !isInWarpArea && sprite.parent == warpNode {
            sprite.removeFromParent()
            self.addChild(sprite)
        }
    }
}

无节点层级操作方案:自定义Shader

如果完全不想移动节点,可使用SKShader实现基于位置的区域扭曲,让Sprite自动在指定区域内变形:

  1. 创建Shader文件(warpEffect.fsh):
void main() {
    // 定义屏幕上的扭曲区域(可根据需求修改尺寸和位置)
    vec2 warpAreaMin = vec2(u_screenSize.x/2 - 150.0, u_screenSize.y/2 - 200.0);
    vec2 warpAreaMax = vec2(u_screenSize.x/2 + 150.0, u_screenSize.y/2 + 200.0);
    
    vec2 texCoord = v_tex_coord;
    vec2 spritePos = gl_FragCoord.xy;
    
    // 检测是否处于扭曲区域内
    if (spritePos.x > warpAreaMin.x && spritePos.x < warpAreaMax.x &&
        spritePos.y > warpAreaMin.y && spritePos.y < warpAreaMax.y) {
        // 计算区域内相对位置(0-1范围)
        vec2 relativePos = (spritePos - warpAreaMin) / (warpAreaMax - warpAreaMin);
        
        // 应用扭曲逻辑:中间向上凸起,上下边缘向外拉伸
        float warpAmount = sin(relativePos.y * 3.14159);
        texCoord.x += (relativePos.x - 0.5) * warpAmount * 0.2;
        texCoord.y += (0.5 - relativePos.y) * warpAmount * 0.3;
    }
    
    gl_FragColor = texture2D(u_texture, texCoord);
}
  1. 在Swift中加载Shader并传递屏幕参数:
func bindWarpShader(to sprite: SKSpriteNode, view: SKView) {
    let shader = SKShader(fileNamed: "warpEffect.fsh")
    shader.uniforms = [
        SKUniform(name: "u_screenSize", vectorFloat2: vector_float2(Float(view.bounds.width), Float(view.bounds.height)))
    ]
    sprite.shader = shader
}

这种方式只需给目标Sprite绑定Shader,无需任何节点层级操作,Sprite穿过指定区域时会自动触发扭曲效果。


方案对比

  • SKEffectNode方案:适合复杂的网格扭曲,效果精度和可控性更强;
  • 自定义Shader方案:轻量化实现,无节点层级变动,适合简单的区域扭曲需求。

内容的提问来源于stack exchange,提问作者F.Lachlan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 16:01:48