Swift (用于Sugar、Sugar等游戏的物理引擎)许多物理精灵的SpriteKit性能优化

Swift (用于Sugar、Sugar等游戏的物理引擎)许多物理精灵的SpriteKit性能优化,swift,performance,sprite-kit,game-physics,physics-engine,Swift,Performance,Sprite Kit,Game Physics,Physics Engine,我是一个iOS游戏开发者,最近我看到了一个有趣的物理和绘图游戏“Sugar,Sugar”。在游戏中,有很多像素粒子(数千个)从屏幕上产生并自由下落到地面。玩家可以画任何形状的线,这可以引导这些粒子到特定的杯子。谷歌图片: 我正在尝试使用SpriteKit和Swift实现类似的效果。以下是我得到的: 然后我遇到了一个性能问题。一旦粒子数>100。CPU和能源成本非常高。(我使用iphone6s)。所以我相信《糖,糖》中的物理引擎比现实的SpriteKit要简单得多。但我不知道那里的物理引擎是什

我是一个iOS游戏开发者,最近我看到了一个有趣的物理和绘图游戏“Sugar,Sugar”。在游戏中,有很多像素粒子(数千个)从屏幕上产生并自由下落到地面。玩家可以画任何形状的线,这可以引导这些粒子到特定的杯子。谷歌图片:

我正在尝试使用SpriteKit和Swift实现类似的效果。以下是我得到的:

然后我遇到了一个性能问题。一旦粒子数>100。CPU和能源成本非常高。(我使用iphone6s)。所以我相信《糖,糖》中的物理引擎比现实的SpriteKit要简单得多。但我不知道那里的物理引擎是什么,我如何在SpriteKit中实现这一点

附言:
我使用一个图像作为所有粒子的纹理,只加载一次以保存性能。我只使用SKSpriteNode,出于性能原因也不使用ShapeNode。

我很久没有做过沙盘模拟了,所以我想我会为您创建一个快速演示

这是在javascript中完成的,鼠标左键添加沙子,鼠标右键绘制线条。根据机器的不同,它将处理数千粒沙子

它的工作原理是创建一个像素阵列,每个像素都有一个x,y位置,一个delta x,y和一个标志来指示它是非活动的(死的)。每一帧我都会清除显示,然后添加墙。然后,我检查每个像素的侧面或下方是否有像素(取决于移动方向),并添加侧向滑动、墙壁反弹或重力。如果一个像素有一段时间没有移动,我就把它设为死像素,只是为了节省计算时间才画出来

sim卡非常简单,第一个像素(颗粒)永远不会碰撞到另一个像素,因为它是用清晰的显示绘制的,像素只能看到在它们之前创建的像素。但这很好地实现了它们的自组织,不会相互重叠

您可以在功能显示中找到逻辑,(从底部开始的第二个功能)有一些自动演示代码,然后是绘制墙、显示墙、获取像素数据以及为每个像素执行sim的代码

它不是完美的(就像你提到的游戏),但它只是一个快速的黑客展示它是如何做到的。此外,我把它的插入窗口大,所以最好的全页浏览

/**simplelCalcanvasmouse.js begin**/
const CANVAS_ELEMENT_ID=“canv”;
常数U=未定义;
变量w,h,cw,ch;//捷径变压器
var画布,ctx,鼠标;
var-globalTime=0;
var createCanvas、resizeCanvas、setGlobals;
var L=日志类型==“函数”?log:function(d){console.log(d);}
createCanvas=函数(){
var c,cs;
cs=(c=document.createElement(“canvas”).style;
c、 id=画布元素id;
cs.position=“绝对”;
cs.top=cs.left=“0px”;
cs.width=cs.height=“100%”;
cs.zIndex=1000;
文件.正文.附件(c);
返回c;
}
resizeCanvas=函数(){
如果(canvas==U){canvas=createCanvas();}
canvas.width=Math.floor(window.innerWidth/4);
canvas.height=数学地板(window.innerHeight/4);
ctx=canvas.getContext(“2d”);
if(typeof setGlobals==“function”){setGlobals();}
}
setGlobals=function(){cw=(w=canvas.width)/2;ch=(h=canvas.height)/2;}
鼠标=(函数(){
函数preventDefault(e){e.preventDefault();}
变量鼠标={
x:0,y:0,w:0,alt:false,shift:false,ctrl:false,buttonRaw:0,
over:false,//鼠标位于元素上方
bm:[1,2,4,6,5,3],//用于设置和清除按钮原始位的掩码;
mouseEvents:“mousemove、mousedown、mouseup、mouseout、mouseover、mouseweel、DOMMouseScroll”。拆分(“,”)
};
var m=小鼠;
函数mouseMove(e){
var t=e.type;
m、 x=e.offsetX;m.y=e.offsetY;
如果(m.x==U){m.x=e.clientX;m.y=e.clientY;}
m、 alt=e.altKey;m.shift=e.shiftKey;m.ctrl=e.ctrlKey;
如果(t==“mousedown”){m.buttonRaw |=m.bm[e.which-1];}
else如果(t==“mouseup”){m.buttonRaw&=m.bm[e.which+2];}
如果(t==“mouseout”){m.buttonRaw=0;m.over=false;}
如果(t==“mouseover”){m.over=true;}
else如果(t==“鼠标轮”){m.w=e.wheeldta;}
else如果(t==“DOMMouseScroll”){m.w=-e.detail;}
if(m.callbacks){m.callbacks.forEach(c=>c(e));}
e、 预防默认值();
}
m、 addCallback=函数(回调){
if(回调类型==“函数”){
如果(m.callbacks==U){m.callbacks=[callback];}
else{m.callbacks.push(callback);}
}else{throw new TypeError(“mouse.addCallback参数必须是函数”);}
}
m、 开始=功能(元素,块上下文菜单){
如果(m.element!==U){m.removeMouse();}
m、 元素=元素===U?单据:元素;
m、 blockContextMenu=blockContextMenu==U?false:blockContextMenu;
m、 forEach(n=>{m.element.addEventListener(n,mouseMove);});
if(m.blockContextMenu===true){m.element.addEventListener(“contextmenu”,PrevenDefault,false);}
}
m、 删除=函数(){
如果(m.元素!==U){
m、 mouseEvents.forEach(n=>{m.element.removeEventListener(n,mouseMove);});
如果(m.ContextMenuBlock==true){m.element.removeEventListener(“contextmenu”,preventDefault);}
m、 元素=m.callbacks=m.ContextMenuBlock=U;
}
}
返回鼠标;
})();
var done=function(){
window.removeEventListener(“调整大小”,调整画布大小)
mouse.remove();
document.body.removeChild(画布);
画布=ctx=鼠标=U;
L(“全部完成!”)
}
调整画布的大小();//创建画布并调整其大小
鼠标。开始(画布,真);//在画布上启动鼠标并阻止上下文菜单
window.addEventListener(“re