Touch contact with circle sprite oversize - ios

I've got a simple spritekit project in which I'm noticing that the point at which contact is registered with my sprite is occurring well outside the circle physics body.
Demonstrated here:
https://gfycat.com/DentalBriskAfricangroundhornbill
With showPhysics on: http://i.imgur.com/F4BcXhb.png
Code for the object spawning the circles:
func spawnObject() {
object = SKSpriteNode(imageNamed: "Oval")
//object.fillColor = SKColor.white
object.name = "ball"
object.position = CGPoint(x: 280, y: 520)
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 2)
object.physicsBody!.isDynamic = true
object.physicsBody!.contactTestBitMask = object.physicsBody!.collisionBitMask
object.physicsBody?.friction = 0.2
object.physicsBody?.restitution = 0.2
object.physicsBody?.mass = 5
addChild(object)
}
The circles themselves interact perfectly with one another - it appears touch is the outlier.
Would appreciate any assistance :)
Edit:
Example touch code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let tappedNodes = nodes(at: location)
for node in tappedNodes {
if node.name == "ball" {
node.alpha = 0.5
}
}
}
}

Related

make SKPhysicsBody unidirectional

I have a SKSpriteNode as a ball, it's been given all the SKPhysicsBody properties move around in all direction. What I want now is to make it unidirectional (only move in that direction it hasn't move to before and not go back in to a path it had move upon). Currently I have following thoughts on this the problem,
make a fieldBitMask, to the path that is iterated by it and repel
the ball to not go back
apply some kind of force/ impulses on the ball from touchesBegan/ touchesMoved method to keep it from going back
something that can be handled in update method
a lifesaver from stackflowoverflow, who is coding even on the weekend :)
Supporting Code snippets for better understanding,
//getting current touch position by using UIEvent methods
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchPoint = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchPoint = nil
}
//ball created
func createPlayer(){
player = SKSpriteNode(imageNamed: "player")
player.position = CGPoint(x: 220, y: 420)
player.zPosition = 1
//physics for ball
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
player.physicsBody?.allowsRotation = false
player.physicsBody?.linearDamping = 0.5
player.physicsBody?.categoryBitMask = collisionTypes.player.rawValue
player.physicsBody?.contactTestBitMask = collisionTypes.finish.rawValue
player.physicsBody?.collisionBitMask = collisionTypes.wall.rawValue
addChild(player)
}
//unwarp the optional property, calclulate the postion between player touch and current ball position
override func update(_ currentTime: TimeInterval) {
guard isGameOver == false else { return }
if let lastTouchPosition = lastTouchPoint {
//this usually gives a large value (related to screen size of the device) so /100 to normalize it
let diff = CGPoint(x: lastTouchPosition.x - player.position.x, y: lastTouchPosition.y - player.position.y)
physicsWorld.gravity = CGVector(dx: diff.x/100, dy: diff.y/100)
}
}
Well it was a combination little hacks in touchesBegan/ touchesMoved and update func,
First you need to catch on which touch occurred, get it's name (in my
case I made nodes which had alpha of 0, but become visible upon
moving over them i.e alpha 1). In touchesBegan, touchesMoved as follow
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name
{
if name == "vortex"
{
touching = false
self.view!.isUserInteractionEnabled = false
print("Touched on the interacted node")
}else{
self.view!.isUserInteractionEnabled = true
touching = true
}
}
}
Second use a BOOL touching to track user interactions, on the screen by using getting a tap recogniser setup, as follow
func setupTapDetection() {
let t = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
view?.addGestureRecognizer(t)
}
#objc func tapped(_ tap: UITapGestureRecognizer) {
touching = true
}
Finally in update put checks as follow,
guard isGameOver == false else { return }
self.view!.isUserInteractionEnabled = true
if(touching ?? true){
if let lastTouchPosition = lastTouchPoint {
//this usually gives a large value (related to screen size of the device) so /100 to normalize it
let diff = CGPoint(x: lastTouchPosition.x - player.position.x, y: lastTouchPosition.y - player.position.y)
physicsWorld.gravity = CGVector(dx: diff.x/100, dy: diff.y/100)
}
}
}

Spritkit : Detect collision while drawing line using finger

I am trying to detect collision contactPoint between existing line and line which user currently drawing using finger.
Here is my code :
let padding: CGFloat = 100
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
let startPoint1 = CGPoint(x: self.frame.minX + padding , y: self.frame.minY + padding)
let leftHorizontalPoint = CGPoint(x: self.frame.minX + padding, y: self.frame.maxY - padding)
let line1 = SKShapeNode()
let line_path:CGMutablePath = CGMutablePath()
line_path.move(to: startPoint1)
line_path.addLine(to: leftHorizontalPoint)
line1.path = line_path
line1.lineWidth = 3
line1.strokeColor = UIColor.white
addChild(line1)
line1.physicsBody = SKPhysicsBody(edgeLoopFrom: line1.frame)
line1.physicsBody?.isDynamic = true
line1.physicsBody?.categoryBitMask = PhysicsCategory.solidLine
line1.physicsBody?.collisionBitMask = PhysicsCategory.currentLine
line1.physicsBody?.contactTestBitMask = PhysicsCategory.currentLine
}
Then on touchBegin, touchMove & touchEnd I am having following code :
var currentLineNode: SKShapeNode!
var startPoint: CGPoint = CGPoint.zero
func touchDown(atPoint pos : CGPoint) {
startPoint = pos
}
func touchMoved(toPoint pos : CGPoint) {
if currentLineNode != nil {
currentLineNode.removeFromParent()
}
currentLineNode = SKShapeNode()
currentLineNode.zPosition = 1
let line_path:CGMutablePath = CGMutablePath()
line_path.move(to: startPoint)
line_path.addLine(to: pos)
currentLineNode.path = line_path
currentLineNode.lineWidth = 3
currentLineNode.strokeColor = UIColor.red
addChild(currentLineNode)
currentLineNode.physicsBody = SKPhysicsBody(edgeLoopFrom: currentLineNode.frame)
currentLineNode.physicsBody?.isDynamic = true
currentLineNode.physicsBody?.categoryBitMask = PhysicsCategory.currentLine
currentLineNode.physicsBody?.collisionBitMask = PhysicsCategory.solidLine
currentLineNode.physicsBody?.contactTestBitMask = PhysicsCategory.solidLine
}
func touchUp(atPoint pos : CGPoint) {
if currentLineNode != nil {
currentLineNode.removeFromParent()
}
currentLineNode = SKShapeNode()
let line_path:CGMutablePath = CGMutablePath()
line_path.move(to: startPoint)
line_path.addLine(to: pos)
currentLineNode.path = line_path
currentLineNode.lineWidth = 3
currentLineNode.strokeColor = UIColor.red
addChild(currentLineNode)
currentLineNode.physicsBody = SKPhysicsBody(edgeLoopFrom: currentLineNode.frame)
currentLineNode.physicsBody?.isDynamic = true
currentLineNode.physicsBody?.categoryBitMask = PhysicsCategory.currentLine
currentLineNode.physicsBody?.collisionBitMask = PhysicsCategory.solidLine
currentLineNode.physicsBody?.contactTestBitMask = PhysicsCategory.solidLine
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
physicsWorld's contactDelegate as follow : (Delegate not even executed)
extension GameScene : SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
// This never get detected :(
print(contact.contactPoint)
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
}
}
Here is my output where even though passing from white colored line its not detecting collision.
What could be wrong? Any suggestion on this will be helpful.
You are using 2 edge based bodies. Edge based bodies will always be isDynamic = false no matter if you set it or not. You need at least 1 volume based body to be able to perform a contact.
Plus on top of that you are constantly removing and adding a node, which is a terrible idea.
I would recommend adding your node on the touchesBegan only, then update only the path on touchesMoved
you are creating your physics body with an edge loop. Apple defines edge loops as...
An edge has no volume or mass and is always treated as if the isDynamic property is equal to false. Edges may only collide with volume-based physics bodies.
changing your physics body to this works
currentLineNode.physicsBody = SKPhysicsBody(rectangleOf: currentLineNode.frame.size, center: CGPoint(x: startPoint.x + currentLineNode.frame.size.width / 2, y: startPoint.y + currentLineNode.frame.size.height / 2))
also should be noted that changing your physics body in touchesEnded is redundant and adds nothing. I removed it from touchesEnded and it works fine

Need Help giving sprite node button an animation while it is being pressed

I'm sure I sound like a completed noob but I'm working on my first iOS game and it includes a shoot button which is an arcade style button that I would like to animate by swapping to the "shootPressed" image while the user holds it down then swaps back when released.
Here I have my touches began, moved, and ended. Keep in my mind I left out any nonconflicting code. I've been stuck on this problem for the last two days.
UPDATE: I've included the function I used for creating my button node
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in:self)
let node = self.atPoint(location)
if(node.name == "Shoot") {
shootButton.texture = SKTexture(imageNamed: "shootPushed")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in:self)
let node = self.atPoint(location)
if (node.name == "Shoot") {
shootBeams()
shootButton.texture = SKTexture(imageNamed: "shootUnpushed" )
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in:self)
let node = self.atPoint(location)
if node.name == ("shoot") {
shootButton.texture = SKTexture(imageNamed: "shootPushed")
}
}
}
func addButtons() {
var buttonT1 = SKTexture(imageNamed: "shootUnpushed")
var buttonT2 = SKTexture(imageNamed: "shootPushed")
var shootButton = SKSpriteNode(texture: buttonT1)
shootButton.name = "Shoot"
shootButton.position = CGPoint(x: self.frame.maxX - 130, y: leftButton.position.y)
shootButton.zPosition = 7
shootButton.size = CGSize(width: shootButton.size.width*0.2, height: shootButton.size.height*0.2)
self.addChild(shootButton)
}
The shootButton you create in addButtons is a local variable.
So, when you do shootButton.texture = SKTexture(imageNamed: "shootPushed") inside the touchesMoved this is certainly not referring to the same node.
As this apparently compiles this would mean that you already have a property shootButton in the class. The problem would most likely be fixed if you change the var shootButton = SKSpriteNode(texture: buttonT1) in you addButtons function.
It should simply initialize the shootButton-property rather than creating a new local variable:
func addButtons() {
shootButton = SKSpriteNode(texture: buttonT1)
shootButton.name = "Shoot"
shootButton.position = CGPoint(x: self.frame.maxX - 130, y: leftButton.position.y)
shootButton.zPosition = 7
shootButton.size = CGSize(width: shootButton.size.width*0.2, height: shootButton.size.height*0.2)
self.addChild(shootButton)
}

Move the object in a specific area

I have a circle called circle and a line called ground in the View.
I can drag & move the circle in the whole view, but when I try to limit it (in the code) to move only under the ground area - it works (when I try to drag it above the ground) and the circle moves only by my x location dragging, BUT when I try to drag it back under the ground - the circle only moves by the x location dragging and the y location dragging doesn't change (the y is the ground y).
How can I make it move back again, in a free way, under the ground and not only by the x location of the dragging?
Here is what I tried so far:
let ground = SKSpriteNode()
var circle = SKShapeNode(circleOfRadius: 40)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// ground.physicsBody?.dynamic = false
ground.color = SKColor.redColor()
ground.size = CGSizeMake(self.frame.size.width, 5)
ground.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.size.height / 5)
self.addChild(ground)
circle.position = CGPointMake(CGRectGetMidX(self.frame) ,CGRectGetMinY(ground.frame) - (circle.frame.size.height / 2))
circle.strokeColor = SKColor.blackColor()
circle.glowWidth = 1.0
circle.fillColor = SKColor.orangeColor()
self.addChild(circle)
}
var lastTouch: CGPoint? = nil
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
if circle.position.y < ground.position.y - (circle.frame.size.height / 2){
circle.position = touchLocation
} else {
circle.position.x = CGFloat(touchLocation.x)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.locationInNode(self)
if circle.position.y < ground.position.y - (circle.frame.size.height / 2) {
circle.position = touchLocation
} else {
circle.position.x = CGFloat(touchLocation.x)
}
}
I have solved it:
In touchesMoved func I wrote this code:
for touch in touches {
if touchLocation.y < ground.position.y {
circle.position = touchLocation
} else {
circle.position.x = touchLocation.x
}
}

SpriteKit friction not seeming to working properly

On a project in Xcode 7 I have a few SKSpriteNodes that move back and forth on the screen and another one, called user, that is meant to jump from sprite to sprite, progressively up the screen. However, when user lands on one of the moving sprites the moving sprite just slides right out from under it and user falls back down. I thought that this meant that I needed to increase the friction property on the nodes so that user would "stick" to the nodes, but this just makes it bounce on the other nodes. My problem is that the nodes moving back and forth seem to "slippery," and user just doesn't stay on them.
Here's my code:
My class for user:
class UserNode: SKSpriteNode
{
class func newNode(position position: CGPoint) -> UserNode
{
let position = position
let sprite = UserNode(imageNamed: "userImage")
sprite.position = position
sprite.size = CGSize(width: sprite.size.width * 2, height: sprite.size.height * 2)
sprite.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "userImage"), size: sprite.size)
sprite.physicsBody?.affectedByGravity = true
sprite.physicsBody?.dynamic = true
sprite.physicsBody?.allowsRotation = false
sprite.physicsBody?.friction = 0.2
return sprite
}
}
and for moving user (the methods in my gamescene)
let scale: CGFloat = 2.0
let damping: CGFloat = 0.98
var point = CGPoint?()
func moveNodeToPoint(sprite: SKSpriteNode, point: CGPoint)
{
let dx = (point.x - sprite.position.x) * scale
let dy = (point.y - sprite.position.y) * scale
sprite.physicsBody?.velocity = CGVectorMake(dx, dy)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
/* Called when a touch begins */
for _: AnyObject in touches
{
if !welcomeNode.hidden
{
let fadeAway = SKAction.fadeOutWithDuration(0.3)
welcomeNode.runAction(fadeAway)
directionsNode.runAction(fadeAway)
touchStartNode.runAction(fadeAway)
welcomeNode.hidden = true
directionsNode.hidden = true
touchStartNode.hidden = true
}
//////////
point = CGPointMake(self.frame.midX, user.position.y + 300)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
{
point = nil
}
override func update(currentTime: CFTimeInterval)
{
/* Called before each frame is rendered */
if (point != nil)
{
moveNodeToPoint(user, point: point!)
}
else
{
let dx = user.physicsBody!.velocity.dx * damping
let dy = user.physicsBody!.velocity.dy * damping
user.physicsBody?.velocity = CGVectorMake(dx, dy)
}
}
and for moving the platforms:
let screenSize = UIScreen.mainScreen().bounds
let width = screenSize.size.width * 2
let firstAction = SKAction.moveBy(CGVector(dx: width, dy: 0), duration: 2)
let secondAction = SKAction.moveBy(CGVector(dx: -width, dy: 0), duration: 2)
let actions = [firstAction, secondAction]
let barAction = SKAction.sequence(actions)
let mainBarAction = SKAction.repeatActionForever(barAction)
platform.runAction(mainBarAction)

Resources