SpriteKit的带有多边形辅助工具的SKPhysicsBody

我想知道是否有一个工具可以用于轻松生成SpriteKit中的复杂物理实体。 我想有一个基于体积的物体与多边形types的形状。 SpriteKit允许使用该方法创build这样的实体:

+ (SKPhysicsBody *)bodyWithPolygonFromPath:(CGPathRef)path 

不幸的是,手动生成这样的path是一项耗时的任务,而且在testing时可能会产生问题。 有一个SpriteHelper应用程序,允许你在易于使用的可视化编辑器中定义身体形状,但是这个应用程序不能导出这里可以使用的path。 它是为cocos2d制作的,它做了很多像纹理包装等我不需要的东西,我不能用于SpriteKit。 有谁知道一个解决scheme,将允许定义CGPath的很容易,甚至可以自动生成它们与alpha通道的PNG图像? 虽然从我的经验来看,自动生成function需要优化,因为当纹理可能具有更复杂的形状时,身体的形状应该尽可能简单。

我正在寻找完全相同的东西,因为它结果我做了一个小型的Web应用程序用于这个目的。

SKPhysicsBodypath生成器

作为例子中的行动: 在这里输入图像描述

更新2015-02-13:脚本

 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>SpriteKit Tools - SKPhysicsBody Path Generator</title> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"> <style> /* disable responsive */ .container { max-width: none; width: 970px; } #sprite { background-color: #eee; position: absolute; } #path { cursor: crosshair; opacity: 0.5; } </style> </head> <body> <div class="container"> <h1>SKPhysicsBody Path Generator</h1> <p class="lead">Want to use [SKPhysicsBody bodyWithPolygonFromPath:path] easier way like me? Here with a small helper for easier path drawing, hope it help others too.</p> <div class="row"> <div class="col-md-6"> <h5>Basic Instruction</h5> <ol> <li><small>Drag and drop the sprite image into drop zone.</small></li> <li><small>Start drawing path by clicking on coordinates.</small></li> </ol> </div> <div class="col-md-6"> <h5>Some Rules / Known Issue</h5> <ul> <li><small>Path need to be as a convex polygonal path with counterclockwise winding and no self intersections. The points are specified relative to the owning node's origin. <a href="https://developer.apple.com/documentation/spritekit/skphysicsbody/1520379-bodywithpolygonfrompath" target="_blank">(documentation link)</a></small></li> <li><small>Please use Chrome for best compatibility as I have not tested on other browsers.</small></li> </ul> </div> </div> <hr> <div class="btn-group"> <button class="btn btn-primary" type="button" onclick="resetShape()">Reset Shape</button> <button class="btn btn-primary" type="button" onclick="location.reload()">Reset All</button> </div> <input type="checkbox" onclick="toggleRetinaMode()" id="retinaCheckbox" checked> Retina? (please check before declaring path) <br><br> <canvas id="sprite" width="940" height="100"></canvas> <canvas id="path" width="0" height="100"></canvas> <p class="text-muted"><small>X:<span id="tooltipX">0</span> Y:<span id="tooltipY">0</span></small></p> <br> <h5>Output</h5> <pre> SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"<span id="codeImgName">img</span>"]; CGFloat offsetX = sprite.frame.size.width * sprite.anchorPoint.x; CGFloat offsetY = sprite.frame.size.height * sprite.anchorPoint.y; CGMutablePathRef path = CGPathCreateMutable(); <span id="codeCGPath"></span> CGPathCloseSubpath(path); sprite.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:path]; </pre> </div> <script> // reference from http://davidwalsh.name/resize-image-canvas var spriteCanvas = document.getElementById('sprite'); var spriteContext = spriteCanvas.getContext('2d'); spriteContext.fillText('Drop Sprite Image Here', 400, 50); var pathCanvas = document.getElementById('path'); var pathContext = pathCanvas.getContext('2d'); function render(src){ var image = new Image(); image.onload = function(){ spriteContext.clearRect(0, 0, spriteCanvas.width, spriteCanvas.height); spriteCanvas.width = image.width; spriteCanvas.height = image.height; spriteContext.drawImage(image, 0, 0, image.width, image.height); pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); pathCanvas.width = image.width; pathCanvas.height = image.height; }; image.src = src; } function loadImage(src){ if(!src.type.match(/image.*/)){ console.log('Dropped file is not image format'); return; } var reader = new FileReader(); reader.onload = function(e){ render(e.target.result); }; reader.readAsDataURL(src); var fileName = src.name; var codeImgName = document.getElementById('codeImgName'); codeImgName.innerHTML = fileName; } spriteCanvas.addEventListener('dragover', function(e){ e.preventDefault(); }, true); spriteCanvas.addEventListener('drop', function(e){ e.preventDefault(); loadImage(e.dataTransfer.files[0]); }, true); var retinaMode = true; function toggleRetinaMode(){ var status = document.getElementById('retinaCheckbox'); retinaMode = status.checked ? true : false; } var actualX = 0; var actualY = 0; var displayX = document.getElementById('tooltipX'); var displayY = document.getElementById('tooltipY'); pathCanvas.onmousemove = function(e){ actualX = e.pageX - this.offsetLeft; actualY = e.pageY - this.offsetTop; displayX.innerHTML = retinaMode ? Math.floor(actualX / 2) : actualX; displayY.innerHTML = retinaMode ? Math.floor((spriteCanvas.height - actualY - 1) / 2) : spriteCanvas.height - actualY - 1; } var pathArray = new Array(); pathCanvas.onclick = function(e){ var coor = { actualX: actualX, actualY: actualY, displayX: displayX.innerHTML, displayY: displayY.innerHTML, }; pathArray.push(coor); refreshShape(pathArray); } var codeCGPath = document.getElementById('codeCGPath'); function refreshShape(pathArray){ pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); pathContext.beginPath(); for(var i in pathArray){ if(i == 0) { pathContext.moveTo(pathArray[i].actualX, pathArray[i].actualY); codeCGPath.innerHTML = 'CGPathMoveToPoint(path, NULL, '+pathArray[i].displayX+' - offsetX, '+pathArray[i].displayY+' - offsetY);<br>'; continue; } pathContext.lineTo(pathArray[i].actualX, pathArray[i].actualY); codeCGPath.innerHTML += 'CGPathAddLineToPoint(path, NULL, '+pathArray[i].displayX+' - offsetX, '+pathArray[i].displayY+' - offsetY);<br>'; } pathContext.closePath(); pathContext.lineWidth = 1; pathContext.strokeStyle = 'blue'; pathContext.stroke(); pathContext.fillStyle = 'blue'; pathContext.fill(); } function resetShape(){ pathArray = new Array(); codeCGPath.innerHTML = null; pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); } </script> </body> </html> 

我创build了一个编辑器和加载器类来创build复杂的SKPhysicsBody并将其导入到您的代码中。 它允许你跟踪你的精灵,添加多个主体,并在一个漂亮的界面中导出所有内容。 在这里查看SKImport和编辑器 。

截屏

我知道这有点晚了,但是我刚刚创build了一个很酷的工具来自动创build一个围绕精灵图像的path(所以你不必手动点自己的点),然后你可以调整各种设置,以更好地满足您的要求。 该工具还输出Objective C和Swift程序代码,用于将path添加到精灵物理主体。 希望对一些人有帮助。 谢谢:

http://www.radicalphase.com/pathgen/

这里是适合Swift的原始脚本(来自DazChong)

SKPhysicsBodypath生成器Swift版本

 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>SpriteKit Tools - SKPhysicsBody Path Generator (Swift version </title> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"> <style> /* disable responsive */ .container { max-width: none; width: 970px; } #sprite { background-color: #eee; position: absolute; } #path { cursor: crosshair; opacity: 0.5; } </style> </head> <body> <div class="container"> <h1>SKPhysicsBody Path Generator</h1> <p class="lead">Want to use SKPhysicsBody(polygonFromPath: path) easier way like me? Here with a small helper for easier path drawing, hope it help others too.</p> <div class="row"> <div class="col-md-6"> <h5>Basic Instruction</h5> <ol> <li><small>Drag and drop the sprite image into drop zone.</small></li> <li><small>Start drawing path by clicking on coordinates.</small></li> </ol> </div> <div class="col-md-6"> <h5>Some Rules / Known Issue</h5> <ul> <li><small>Path need to be as a convex polygonal path with counterclockwise winding and no self intersections. The points are specified relative to the owning node's origin. <a href="https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKPhysicsBody_Ref/Reference/Reference.html#//apple_ref/occ/clm/SKPhysicsBody/bodyWithPolygonFromPath:" target="_blank">(documentation link)</a></small></li> <li><small>Please use Chrome for best compatibility as I have not tested on other browsers.</small></li> </ul> </div> </div> <hr> <div class="btn-group"> <button class="btn btn-primary" type="button" onclick="resetShape()">Reset Shape</button> <button class="btn btn-primary" type="button" onclick="location.reload()">Reset All</button> </div> <input type="checkbox" onclick="toggleRetinaMode()" id="retinaCheckbox" checked> Retina? (please check before declaring path) <br><br> <canvas id="sprite" width="940" height="100"></canvas> <canvas id="path" width="0" height="100"></canvas> <p class="text-muted"><small>X:<span id="tooltipX">0</span> Y:<span id="tooltipY">0</span></small></p> <br> <h5>Output</h5> <pre> let sprite = SKSpriteNode(imageNamed: "codeImgName") let offsetX = sprite.size.width * sprite.anchorPoint.x let offsetY = sprite.size.height * sprite.anchorPoint.y let path = CGPathCreateMutable() <span id="codeCGPath"></span> CGPathCloseSubpath(path) sprite.physicsBody = SKPhysicsBody(polygonFromPath: path) </pre> </div> <script> // reference from http://davidwalsh.name/resize-image-canvas var spriteCanvas = document.getElementById('sprite'); var spriteContext = spriteCanvas.getContext('2d'); spriteContext.fillText('Drop Sprite Image Here', 400, 50); var pathCanvas = document.getElementById('path'); var pathContext = pathCanvas.getContext('2d'); function render(src){ var image = new Image(); image.onload = function(){ spriteContext.clearRect(0, 0, spriteCanvas.width, spriteCanvas.height); spriteCanvas.width = image.width; spriteCanvas.height = image.height; spriteContext.drawImage(image, 0, 0, image.width, image.height); pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); pathCanvas.width = image.width; pathCanvas.height = image.height; }; image.src = src; } function loadImage(src){ if(!src.type.match(/image.*/)){ console.log('Dropped file is not image format'); return; } var reader = new FileReader(); reader.onload = function(e){ render(e.target.result); }; reader.readAsDataURL(src); var fileName = src.name; var codeImgName = document.getElementById('codeImgName'); codeImgName.innerHTML = fileName; } spriteCanvas.addEventListener('dragover', function(e){ e.preventDefault(); }, true); spriteCanvas.addEventListener('drop', function(e){ e.preventDefault(); loadImage(e.dataTransfer.files[0]); }, true); var retinaMode = true; function toggleRetinaMode(){ var status = document.getElementById('retinaCheckbox'); retinaMode = status.checked ? true : false; } var actualX = 0; var actualY = 0; var displayX = document.getElementById('tooltipX'); var displayY = document.getElementById('tooltipY'); pathCanvas.onmousemove = function(e){ actualX = e.pageX - this.offsetLeft; actualY = e.pageY - this.offsetTop; displayX.innerHTML = retinaMode ? Math.floor(actualX / 2) : actualX; displayY.innerHTML = retinaMode ? Math.floor((spriteCanvas.height - actualY - 1) / 2) : spriteCanvas.height - actualY - 1; } var pathArray = new Array(); pathCanvas.onclick = function(e){ var coor = { actualX: actualX, actualY: actualY, displayX: displayX.innerHTML, displayY: displayY.innerHTML, }; pathArray.push(coor); refreshShape(pathArray); } var codeCGPath = document.getElementById('codeCGPath'); function refreshShape(pathArray){ pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); pathContext.beginPath(); for(var i in pathArray){ if(i == 0) { pathContext.moveTo(pathArray[i].actualX, pathArray[i].actualY); codeCGPath.innerHTML = 'CGPathMoveToPoint(path, nil, '+pathArray[i].displayX+' - offsetX, '+pathArray[i].displayY+' - offsetY)<br>'; continue; } pathContext.lineTo(pathArray[i].actualX, pathArray[i].actualY); codeCGPath.innerHTML += 'CGPathAddLineToPoint(path, nil, '+pathArray[i].displayX+' - offsetX, '+pathArray[i].displayY+' - offsetY)<br>'; } pathContext.closePath(); pathContext.lineWidth = 1; pathContext.strokeStyle = 'blue'; pathContext.stroke(); pathContext.fillStyle = 'blue'; pathContext.fill(); } function resetShape(){ pathArray = new Array(); codeCGPath.innerHTML = null; pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); } </script> </body> </html> 

Skphysicsbodypath生成器工具似乎丢失。 我写了一个应用程序,虽然在mac上做同样的事情: https : //itunes.apple.com/us/app/physicsbodymaker/id951249779?ls=1&mt=12

由DazChong制作的令人敬畏的小型networking应用程序。 和canot等待PhysicsEditor的更新!

这个也在开发中

你可以在这里的 alpha阶段下载

在这里输入图像描述

这是在Swift 3中对Xelt的回答进行了修改。

 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>SpriteKit Tools - SKPhysicsBody Path Generator (Swift version </title> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"> <style> /* disable responsive */ .container { max-width: none; width: 970px; } #sprite { background-color: #eee; position: absolute; } #path { cursor: crosshair; opacity: 0.5; } </style> </head> <body> <div class="container"> <h1>SKPhysicsBody Path Generator</h1> <p class="lead">Want to use SKPhysicsBody(polygonFromPath: path) easier way like me? Here with a small helper for easier path drawing, hope it help others too.</p> <div class="row"> <div class="col-md-6"> <h5>Basic Instruction</h5> <ol> <li><small>Drag and drop the sprite image into drop zone.</small></li> <li><small>Start drawing path by clicking on coordinates.</small></li> </ol> </div> <div class="col-md-6"> <h5>Some Rules / Known Issue</h5> <ul> <li><small>Path need to be as a convex polygonal path with counterclockwise winding and no self intersections. The points are specified relative to the owning node's origin. <a href="https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKPhysicsBody_Ref/Reference/Reference.html#//apple_ref/occ/clm/SKPhysicsBody/bodyWithPolygonFromPath:" target="_blank">(documentation link)</a></small></li> <li><small>Please use Chrome for best compatibility as I have not tested on other browsers.</small></li> </ul> </div> </div> <hr> <div class="btn-group"> <button class="btn btn-primary" type="button" onclick="resetShape()">Reset Shape</button> <button class="btn btn-primary" type="button" onclick="location.reload()">Reset All</button> </div> <input type="checkbox" onclick="toggleRetinaMode()" id="retinaCheckbox" checked> Retina? (please check before declaring path) <br><br> <canvas id="sprite" width="940" height="100"></canvas> <canvas id="path" width="0" height="100"></canvas> <p class="text-muted"><small>X:<span id="tooltipX">0</span> Y:<span id="tooltipY">0</span></small></p> <br> <h5>Output</h5> <pre> let sprite = SKSpriteNode(imageNamed: "codeImgName") let offsetX = sprite.size.width * sprite.anchorPoint.x let offsetY = sprite.size.height * sprite.anchorPoint.y let path = CGMutablePath() <span id="codeCGPath"></span> path.closeSubpath() sprite.physicsBody = SKPhysicsBody(polygonFromPath: path) </pre> </div> <script> // reference from http://davidwalsh.name/resize-image-canvas var spriteCanvas = document.getElementById('sprite'); var spriteContext = spriteCanvas.getContext('2d'); spriteContext.fillText('Drop Sprite Image Here', 400, 50); var pathCanvas = document.getElementById('path'); var pathContext = pathCanvas.getContext('2d'); function render(src){ var image = new Image(); image.onload = function(){ spriteContext.clearRect(0, 0, spriteCanvas.width, spriteCanvas.height); spriteCanvas.width = image.width; spriteCanvas.height = image.height; spriteContext.drawImage(image, 0, 0, image.width, image.height); pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); pathCanvas.width = image.width; pathCanvas.height = image.height; }; image.src = src; } function loadImage(src){ if(!src.type.match(/image.*/)){ console.log('Dropped file is not image format'); return; } var reader = new FileReader(); reader.onload = function(e){ render(e.target.result); }; reader.readAsDataURL(src); var fileName = src.name; var codeImgName = document.getElementById('codeImgName'); codeImgName.innerHTML = fileName; } spriteCanvas.addEventListener('dragover', function(e){ e.preventDefault(); }, true); spriteCanvas.addEventListener('drop', function(e){ e.preventDefault(); loadImage(e.dataTransfer.files[0]); }, true); var retinaMode = true; function toggleRetinaMode(){ var status = document.getElementById('retinaCheckbox'); retinaMode = status.checked ? true : false; } var actualX = 0; var actualY = 0; var displayX = document.getElementById('tooltipX'); var displayY = document.getElementById('tooltipY'); pathCanvas.onmousemove = function(e){ actualX = e.pageX - this.offsetLeft; actualY = e.pageY - this.offsetTop; displayX.innerHTML = retinaMode ? Math.floor(actualX / 2) : actualX; displayY.innerHTML = retinaMode ? Math.floor((spriteCanvas.height - actualY - 1) / 2) : spriteCanvas.height - actualY - 1; } var pathArray = new Array(); pathCanvas.onclick = function(e){ var coor = { actualX: actualX, actualY: actualY, displayX: displayX.innerHTML, displayY: displayY.innerHTML, }; pathArray.push(coor); refreshShape(pathArray); } var codeCGPath = document.getElementById('codeCGPath'); function refreshShape(pathArray){ pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); pathContext.beginPath(); for(var i in pathArray){ if(i == 0) { pathContext.moveTo(pathArray[i].actualX, pathArray[i].actualY); codeCGPath.innerHTML = 'path.move(to: CGPoint(x: '+pathArray[i].displayX+' - offsetX, y: '+pathArray[i].displayY+' - offsetY))<br>'; continue; } pathContext.lineTo(pathArray[i].actualX, pathArray[i].actualY); codeCGPath.innerHTML += 'path.addLine(to: CGPoint(x: '+pathArray[i].displayX+' - offsetX, y: '+pathArray[i].displayY+' - offsetY))<br>'; } pathContext.closePath(); pathContext.lineWidth = 1; pathContext.strokeStyle = 'blue'; pathContext.stroke(); pathContext.fillStyle = 'blue'; pathContext.fill(); } function resetShape(){ pathArray = new Array(); codeCGPath.innerHTML = null; pathContext.clearRect(0, 0, pathCanvas.width, pathCanvas.height); } </script> </body> </html> 

你现在可以这样做,从你的精灵的PNG生成物理体:

 SKSpriteNode *yourPhysicsSprite = [SKSpriteNode spriteNodeWithImageNamed:@"yourPNG"]; yourPhysicsSprite.physicsBody = [SKPhysicsBody bodyWithTexture:yourPhysicsSprite.texture alphaThreshold:0.0f size:yourPhysicsSprite.texture.size]; 

不太精确,也许比手工更昂贵,但工作正常。