SKPhysicsBody de SpriteKit con herramienta de ayuda de polígonos
game-physics cgpath (8)
Me pregunto si hay una herramienta que pueda usarse para la generación fácil de cuerpos de física complejos en SpriteKit. Me gustaría tener un cuerpo físico basado en el volumen con formas de tipo polígono. SpriteKit permite crear tales cuerpos con ese método:
+ (SKPhysicsBody *)bodyWithPolygonFromPath:(CGPathRef)path
Desafortunadamente, la tarea de generar dichas rutas manualmente lleva mucho tiempo, y podría ser problemático al realizar pruebas. Hay una aplicación SpriteHelper que le permite definir la forma del cuerpo dentro de un editor visual fácil de usar, pero esta aplicación no puede exportar rutas que podrían usarse aquí. Fue hecho para cocos2d y hace muchas cosas como el empaque de texturas, etc. que no necesito y no puedo usar con SpriteKit. ¿Alguien sabe una solución que permita definir CGPath fácilmente o tal vez incluso generarlos automáticamente a partir de imágenes png con canal alfa? Aunque la característica de generación automática de mi experiencia necesitaría optimización, porque las formas del cuerpo deberían ser lo más simples posible cuando las texturas pudieran tener formas más complicadas.
Aquí está el guión original (de DazChong) adaptado para 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>
Creé una clase de editor y cargador para crear SKPhysicsBodies complejos e importarlos en su código. Te permite rastrear alrededor de tu sprite, agregar varios cuerpos y exportar todo dentro de una interfaz bastante agradable. Echa un vistazo a la SKImport aquí y el editor .
Esta es una adaptación de la respuesta de Xelt en 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>
Estoy buscando exactamente lo mismo, ya que resultó que he hecho una pequeña aplicación web para este propósito.
SKPhysicsBody Path Generator
como acción en el ejemplo:
Actualización 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>
Impresionante aplicación web pequeña, por DazChong. Y no podemos esperar a la actualización de PhysicsEditor !!
Éste también está en desarrollo.
Puedes descargarlo en fase alfa here
La herramienta del generador Skphysicsbody Path parece faltar. Escribí una aplicación que hace lo mismo aunque en mac: https://itunes.apple.com/us/app/physicsbodymaker/id951249779?ls=1&mt=12
Sé que es un poco tarde, pero acabo de crear una herramienta genial para este propósito que crea automáticamente un camino alrededor de la imagen del sprite (para que no tenga que hacer clic manualmente en los puntos), y luego puede ajustar Varios ajustes para adaptarse mejor a sus necesidades. La herramienta también genera códigos de programa de Objective C y Swift para agregar la ruta a un cuerpo de física de sprites. Espero que sea útil para algunas personas. Gracias:
Solo puedes hacer esto ahora para generar un cuerpo de física desde el PNG de tu sprite:
SKSpriteNode *yourPhysicsSprite = [SKSpriteNode spriteNodeWithImageNamed:@"yourPNG"];
yourPhysicsSprite.physicsBody = [SKPhysicsBody bodyWithTexture:yourPhysicsSprite.texture alphaThreshold:0.0f size:yourPhysicsSprite.texture.size];
Menos preciso y quizás más costoso que hacerlo a mano, pero funciona bien.