如何为穿过固定矩形区域的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自动在指定区域内变形:
- 创建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); }
- 在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
相关产品推荐
相关产品推荐

