SpriteKit's SKPhysicsBody with polygon helper tool

好久不见. 提交于 2019-11-26 23:53:47
DazChong

I am looking for the exact same thing, as it turn out I have done a small web app for this purpose.

SKPhysicsBody Path Generator

as action in example:

Update 2015-02-13: script

<!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>

I created an editor and loader class to create complex SKPhysicsBodies and import them into your code. It allows you to trace around your sprite, add multiple bodies and export all within a pretty nice interface. Check out the SKImport here and the editor.

I know this is a bit late, but I've just created a cool tool for this purpose which automatically creates a path around the sprite image (so you don't have to manually click on the points yourself), and then you can adjust various settings to better suit your requirements. The tool also outputs both Objective C and Swift program code for adding the path to a sprite physics body. Hope it's helpful to some people. Thanks:

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

Here is the original script (from DazChong) adapted for Swift

SKPhysicsBody Path Generator Swift Version

<!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>

The Skphysicsbody Path generator tool seems to be missing. I wrote an app that does the same thing though on mac: https://itunes.apple.com/us/app/physicsbodymaker/id951249779?ls=1&mt=12

Awesome little web app, by DazChong. And canot wait for the update of PhysicsEditor!!

This one is also in development

You can download it in alpha stage here

This is an adaptation of Xelt's answer in Swift 3.

<!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>

You can just do this now to generate physics body from your sprite's PNG:

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

Less precise and perhaps more costly than doing by hand, but works fine.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!