Designing physics bodies other than circles - swift

I'm making my first game with SpriteKit and for the enemy aliens (SKSpriteNode subclass) physics bodies I have been using:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width/2)
this has worked so far as most of the aliens are circle-shaped. However, if I were to have ellipse or square aliens what would be the best method of designing their physics bodies? I tried this for square shape:
let squarePath = UIBezierPath(rect: CGRect(x: -64, y: -64, width: 128, height: 128))
self.physicsBody = SKPhysicsBody(polygonFromPath: squarePath.CGPath)
but I don't know if this is the most efficient, and also don't know how to exactly make ellipses. Any suggestions would be great!

Let SpriteKit draw the shape for you!
If you have a sprite and you need to create a PhysicsBody for it just let SpriteKit do it for you. SpriteKit can analyze the texture, find the borders (using the alpha/transparent channel) and finally create a physics body matching the image.
Let's say I add this sprite to my scene
let croc = SKSpriteNode(imageNamed: "croc_walk01")
croc.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(croc)
Automatically creating a PhysicsBody shape
Now I simply add this line
croc.physicsBody = SKPhysicsBody(texture: croc.texture!, size: croc.texture!.size())
And SpriteKit automatically creates the PhysicsBody for me.
Can you see the line line around the sprite? That's it. Let me hide the sprite so you can see it better
croc.hidden = true

Using paths is the only way to create complex physics bodys, however, you can create rectangles with SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 100))
Edit:
Use this to help create efficient paths from images: http://insyncapp.net/SKPhysicsBodyPathGenerator.html
Note: this will only generate the paths for the image, you will need to clean it up your self by deleting paths and connecting the points.

Related

How to add a ripple effect in Swift & SpriteKit

In SpriteKit I have a starting button
startButton = SKSpriteNode(color: UIColor.red, size: CGSize(width: 130, height: 130))
startButton.name = "startButton"
startButton.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(startButton)
and around this node I want to add a ring that will expand and come back in again sort of like a rippling effect, so how would I add this effect?
Because it is in SpriteKit you have a few options, you could create a sprite that is the same shape as your button but only has an outline but no fill. Place this below your button and then when you want to do the ripple effect apply some actions to below image (repeat3times(sequence[scaleUp, fadeOut]))
Or you could take the same stroked image and create an effect in the particle editor, and then apply that particle effect to your button
You can check out my similar solution here How To Create a Pulse Effect On an SKSpriteNode?

SpriteKit: using SKView in UIView instead of initializing a Game project

Completely new to SpriteKit. Currently I have a UIView, and I want to add a sprite node to it (like a small UIImageView, but I want animation for it so using SpriteKit). Therefore I didn't initialize my project to be a game project, as found in almost all of tutorials for SpriteKit. I've found a note here: link and what I have now is sth like:
func initializeImage() {
let imageView = SKView()
imageView.frame = CGRect(x: self.frame.width / 2 - Constants.imageWidth / 2, y: self.frame.height - Constants.imageHeight, width: Constants.imageWidth, height: Constants.imageHeight)
// so place it somewhere in the bottom middle of the whole frame
let sheet = SpriteSheet(texture: ...)
let sprite = SKSpriteNode(texture: sheet.itemFor(column: 0, row: 0))
sprite.position = imageView.center //basically the same position as the imageView.frame's x and y value
let scene = SKScene(size: imageView.frame.size)
scene.backgroundColor = SKColor.clear
scene.addChild(sprite)
imageView.presentScene(scene)
self.frame.addSubview(imageView)
}
The SpriteSheet is similar to this: sprite sheet; it's essentially cutting an image atlas and divide it into smaller images. I tracked the process and this step is indeed giving the smaller image (the var 'sprite'). But if running I only have a black square now (should be the size as defined by Constants). If I set scene.backgroundColor to be white then it's white. May I know how I should proceed from here, as how should I make the sprite showing up?
All of your code looks good except for this:
sprite.position = imageView.center // basically the same position as the imageView.frame's x and y value
That is basically not the position you think it is. The coordinate system in SpriteKit is a) relative to the (SK)scene, not to whatever view the SKView is contained in, and b) flipped vertically relative to the UIKit coordinate system. If you want a sprite centered in the scene, you probably want to set its position based on the scene's size:
sprite.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
By the way, the external SpriteSheet code might not be needed (and you're more likely to benefit from Apple's optimizations) if you slice up your sprite sheet and put it in an Xcode asset catalog.

SpriteKit. Why .strokeTexture does not work for SKShapeNode?

I can't understand why the "strokeTexture" option isn't working on a SKShapeNode. Or, how can I do border with texture? Thanks in advance!
let shape = SKShapeNode(circleOfRadius: 100)
shape.position = CGPoint(x: 200, y: 400)
shape.fillColor = SKColor.white
shape.fillTexture = SKTexture(imageNamed: "test")
shape.lineWidth = 50
shape.strokeColor = SKColor.white
shape.strokeTexture = SKTexture(imageNamed: "test(5)")
Output:
Test image:
Test(5) image:
It doesn't work in Simulator!
Try it on your device.
I get the feeling this stroke might be being done with metal, whereas the fill is somehow not being done with metal, so is visible in the simulator.
The fill also doesn't rotate with the object on the device, but the stroke/outline and its texture do.
This would tend to indicate that an SKShapeNode without a fill might have reasonable performance.
Set the width and height of the texture, that is used to render the stroke, multiple of 8 pixels. In your case Test(5) has dimensions 100x100. If you change this texture for another one with dimensions, e.g. 96x96 pixels, the stroke will be rendered correctly and displayed. I don't know, why there are no reference to this fact in the official documentation.

Using edgeLoopFromRect in Swift

I am trying to create a impenetrable border at the edge of the game scene frame.
In Swift I have attempted to re-write this as:
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 0,y: 100,width: self.frame.size.width,height: self.frame.size.height-200))
}
Instead of giving me a clear boundary at the edge of frame it is providing a weak border, which my player (with no specified mass) can pass over.
I'm also not sure why I am having to subtract and add to the y axis to get the correct border but this is less of a concern. Any help would be much appreciated.
The key is to move your sprites by applying a force/impulse or by setting the velocity property so that they respond correctly to
collisions with other physics bodies (such as an edge loop)
forces such as linear/angular damping, friction, and force fields.
You should first define a bitmask category to represent the physics body of your game scene. I typically use the 32-bit binary value 00000000000000000000000000000001, which I define as a constant:
let SCENE_EDGE_CATEGORY: UInt32 = 0x1
With that constant defined, you can now define the scene's physics body in a very readable way:
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody!.categoryBitMask = SCENE_EDGE_CATEGORY
For any sprite that you want to be constrained by the edge of the scene, set its physics body's collision bitmask to the scene's category bitmask. You're telling Sprite Kit that you want the physics engine to handle collisions between that sprite and the scene's edge:
someSprite.physicsBody!.collisionBitMask = SCENE_EDGE_CATEGORY
Hope this helps!
Try this:
override init(size: CGSize)
{
super.init(size: size)
physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRectMake(0, 0, size.width, size.height))
anchorPoint = CGPointMake(0.0, 0.0)
}
works fine for me.
This will definitely help you to define the edge of the screen in Swift.
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)

Get a list of nodes in an specific area?

I'm working in a side-scolling game and I need to know what nodes are in an area to implement something like "line of sight". Right now I'm trying using enumerateBodyiesInRect() however it's detecting bodies that are 20px or more from the evaluated rect and I cannot figure out why it's so imprecise.
This is what I'm trying now:
import SpriteKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate
{
var player = SKShapeNode()
var world = SKShapeNode()
var rShape = SKShapeNode()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.scaleMode = SKSceneScaleMode.AspectFit
self.size = view.bounds.size
// Add world
world = SKShapeNode(rectOfSize: view.bounds.size)
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
world.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2) // Move camera
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 25, height: 25))
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.frame.size)
player.physicsBody.dynamic = false
player.strokeColor = SKColor.blueColor()
player.fillColor = SKColor.blueColor()
player.position = CGPointMake(90, -50)
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
// Define rect position and size (area that will be evaluated for bodies)
var r : CGRect = CGRect(x: 200, y: 200, width: 25, height: 25)
// Show rect for debug
rShape.removeFromParent()
rShape = SKShapeNode(rect: r)
rShape.strokeColor = SKColor.redColor()
self.addChild(rShape)
// Evaluate rect
rShape.fillColor = SKColor.clearColor()
self.physicsWorld.enumerateBodiesInRect(r) {
(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
self.rShape.fillColor = SKColor.redColor() // Paint the area blue if it detects a node
}
}
}
This code should show the evaluated rect and ray on the screen (for debugging purposes) and paint them red if they contact the player node. However you can see in the screenshot how it turns red when the player is 25px or more away from it, it's like if the drawing is a little bit off, or smaller than the actual area being evaluated. You can copy paste it to a project to duplicate the problem.
Could this be because this is just beta or am I doing something wrong?
You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.
Think of it like a 'tractor beam' or a 'warp rectangle'.
I believe you want SKPhysicsWorld's enumerateBodyiesInRect() instance method, which will iterate over all nodes in a given rectangle. If you're looking to get at the physics world through your scene, usage could look like this:
self.physicsWorld.enumerateBodiesInRect(CGRect(x: 0, y: 0, width: 50, height: 50)) {(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
// enumerates all nodes in given frame
}
I've experimented quite a bit with enumerateBodiesInRect now, and I've found it to be incredibly inaccurate. It seems to not have any of the claimed functionality, and instead produces random results. I honestly cannot even determine any pattern from its products.
enumerateBodiesAlongRay seems better, but still very buggy. The problem with that function seems to be the conversion between Screen and PhysicsWorld coordinates. I would avoid that one, as well.
I think your solution should simply be to use the existing contact detection system. All of your desired functionality can be written in the didBeginContact() and didEndContact() functions. This has the added benefit of allowing you to specify distinct functionality for both entering and leaving the area. You can also add particle effects, animations, and similar, as well as intentionally ignoring specific types of nodes.
The only thing to ensure success with this method is to clarify that the contact area has a unique category, that the contactTestBitMask contains all desired nodes and the collisionBitMask is set to 0.
The enumerateBodiesInRect method of SKPhysicsWorld expects the rect parameter to be in scene coordinates. This is important. If you have a scene hierarchy of nodes, you need to convert the rect you calculate from a reference node to the scene coordinates.
I faced a lot of issues with this method returning bodies that were off by values like 30px to the left etc. and finally realized the issue was because of the rect parameter not defined in scene coordinate space.
In my case, I had a worldNode inside my scene, and all objects were created in the worldNode. My camera was moving the worldNode about, and applying scaling to it for zooming out and in.
In order to use enumerateBodiesInRect correctly, I had to do something as follows:
// get your world rect based on game logic
let worldRect = getWorldRect()
// calculate the scene rect
let sceneRectOrigin = scene.convertPoint(worldRect.origin, fromNode:scene.worldNode)
let worldScale = scene.worldNode.xScale // assert this is not 0
// now to get the scene rect relative to the world rect, in scene coordinates
let sceneRect = CGRectMake( sceneRectOrigin.x, sceneRectOrigin.y, worldRect.width / worldScale, worldRect.height / worldScale)
world.physicsWorld.enumerateBodiesInRect(sceneRect) {
// your code here
}
Hope this helps.
I am not sure if this is a good practice. Correct me if not. But I am using
let shapeNode = SKShapeNode()
shapeNode.intersects(playerNode)
I checked selected nodes with simple loop if they intersect the player. Additionally I created SKShapeNodes which are drawn in front of nodes representing view sight of other actors in the game. They are moved along those actors.
There is only nodesAtPoint: method.
To achieve what you want you'd better to store all enemies in an array and have an int variable, something like nextEnemyIndex. This approach lets you to easily return the next enemy node, it's much more efficient than trying to find a node on the scene.
yes problem may occur because of your player's image, for example try to use 10px smaller body size:
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGRectMake(self.frame.origin.x, self.frame.origin.y, self.size.width-10, self.size.height-10)));