Changing SKPhysicsBody while changing SKSpriteNode position - ios

I've got quite simple game. It's like the classic Labyrinth game which uses accelereometer but user is also able to change the size of the rectangle using 3D Touch.
The player's node is rectangle which moves thanks to the world's physics which gets data from the accelerometer (nothing special here):
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data, error) in
if let motionData = data{
self.physicsWorld.gravity = CGVector(dx: motionData.acceleration.x * 7, dy: motionData.acceleration.y * 7)
}
}
and touchesMoved :
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let maxForce = touch.maximumPossibleForce
let force = touch.force
let forceValue = force/maxForce
player.size = CGSize(width: 26-forceValue*16, height: 26-forceValue*16)
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
resetSize()
}
func resetSize(){
player.size = CGSize(width: 26, height: 26)
}
So when I was changing player's size it worked fine: the node was moving around the screen based on device's orientation and while pressing harder it was getting smaller. The problem was that the physicsBody (I've set view.showsPhysics = true in GameViewController.swift) was always the same so I've added player.physicsBody = SKPhysicsBody(rectangleOf: player.size) but now when I'm pressing the screen the player's node resizes but it also stops moving. How to change the node's size and physicsBody simultaneously and keep it in motion?

When you change the physicsBody you lost all physics parameters that invoved your previous physicsBody: you can copy velocity or angularVelocity or other available parameters but it's not enough, you will never have a continuity because the SpriteKit framework (on the actual version) don't have this option: dynamically change the physicsBody during the game.
You can also change the scaling of you node to modify the physicsBody but you always lost physics variables as for example the accumulated speed due to gravity : this happened because SpriteKit change automatically the physicsBody during the scaling to create others bodies without preserve the accumulated properties.
So my advice is to create a kind of "freezing time" when you touch your ball, where you can change you physicsBody when the ball is stopped.

Related

Create a new sprite every time a touch ends

I am playing around trying to learn swift and I am trying to have a brick sprite placed every time a person's touch ends. I'm new and not quite sure of if or how this is possible with spriteKit. I would like to have the brick interact with physics so if there is another way to do this without creating new sprites that would work as well
Just add the method touchesEnded to your class, create a new SpriteNode and add it to your scene. Something like this:
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let yourNode = SKSpriteNode(imageNamed: "brick.png") //or the name of the brick texture file, if you have one
yourNode.position = CGPoint(x: 100, y: 100)
self.addChild(yourNode)
}
Here we added the node to (100,100), but you could add it to the location of the touch, adding this inside the method:
for touch in touches {
let location = touch.location(in: self)
yourNode.position = location
}
Remember to import SpriteKit to your class.

How to remove gap between two drawing points

Any body can help me how to remove this gap when we draw fast
Here is the image of the issue:
here is the code that we can use to draw on sand background using image pointer.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
lastPoint = touch!.location(in: ImageView)
BindImageView(centerpoint: lastPoint)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let fromPoint = touch!.location(in: ImageView)
//drawLineFrom(fromPoint: lastPoint, toPoint: fromPoint)
BindImageView(centerpoint: fromPoint)
lastPoint = fromPoint
}
func BindImageView(centerpoint:CGPoint)
{
//print(centerpoint)
let imagview = UIImageView()
imagview.image = UIImage(named: "b1")
imagview.center = centerpoint
imagview.frame.size = CGSize(width: 10, height: 15)
self.view.addSubview(imagview)
let angle = atan2(self.lastPoint.y-(centerpoint.y), self.lastPoint.x-(centerpoint.x))
imagview.transform = CGAffineTransform(rotationAngle: angle)
}
Your problem is the following: You create a new image every time a "touch" moved, in order to follow the finger (and you rotate it accordingly). But the image "b1" is just a few pixels in width/height, whereas the distance the finger travelled between two touchesMoved events might be longer.
I would suggest to
create a resizable image (either by code, UIImage.resizableImage(withCapInsets:) or within the assets catalog, button "Slicing")
resize the image to reflect the real distance between two touch points
Nevertheless, if the user is "too quick" and draws "curves", the line you create with the images will still be somewhat angular and not bended. Therefore, it might be better to really draw a bezier path (and the shadow). This is quite more tricky, but also requires less resources than of creating UIImages and UIImageViews over and over.

TouchesMoved() lagging very inconsistently when there is a SkSpriteNode with a physics body

I'm using Swift 3.0, SpriteKit, and Xcode 8.2.1, testing on an iPhone 6s running iOS 10.2.
The problem is simple... on the surface. Basically my TouchesMoved() updates at a very inconsistent rate and is destroying a fundamental part of the UI in my game. Sometimes it works perfectly, a minute later it's updating at half of the rate that it should.
I've isolated the problem. Simply having an SKSpriteNode in the scene that has a physics body causes the problem... Here's my GameScene code:
import SpriteKit
import Darwin
import Foundation
var spaceShip = SKTexture(imageNamed: "Spaceship")
class GameScene: SKScene{
var square = SKSpriteNode(color: UIColor.black, size: CGSize(width: 100,height: 100))
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
self.addChild(square)
//This is what causes the problem:
var circleNode = SKSpriteNode(texture: spaceShip, color: UIColor.clear, size: CGSize(width: 100, height: 100))
circleNode.physicsBody = SKPhysicsBody(circleOfRadius: circleNode.size.width/2)
self.addChild(circleNode)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
var positionInScreen = touch.location(in: self)
square.position = positionInScreen
}
}
}
The problem doesn't always happen so sometimes you have to restart the app like 5 times, but eventually you will see that dragging the square around is very laggy if you do it slowly. Also I understand it's subtle at times, but when scaled up this is a huge problem.
My main question:
Why does me having an SKSpriteNode with a physics body cause TouchesMoved() to lag and nothing else to lag, and how can I prevent this?
Please for the love of code and my sanity save me from this abyss!!!
It looks like this is caused by the OS being too busy to respond to touch events. I found two ways to reproduce this:
Enable Airplane Mode on the device, then disable it. For the ~5-10 seconds after disabling Airplane Mode, the touch events lag.
Open another project in Xcode and select "Wait for the application to launch" in the scheme editor, then press Build and Run to install the app to the device without running it. While the app is installing, the touch events lag.
It doesn't seem like there is a fix for this, but here's a workaround. Using the position and time at the previous update, predict the position and time at the next update and animate the sprite to that position. It's not perfect, but it works pretty well. Note that it breaks if the user has multiple fingers on the screen.
class GameScene: SKScene{
var lastTouchTime = Date.timeIntervalSinceReferenceDate
var lastTouchPosition = CGPoint.zero
var square = SKSpriteNode(color: UIColor.black, size: CGSize(width: 100,height: 100))
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
self.addChild(square)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchTime = Date().timeIntervalSinceReferenceDate
lastTouchPosition = touches.first?.location(in: self) ?? .zero
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentTime = Date().timeIntervalSinceReferenceDate
let timeDelta = currentTime - lastTouchTime
for touch in touches{
square.removeAction(forKey: "TouchPrediction")
let oldPosition = lastTouchPosition
let positionInScreen = touch.location(in: self)
lastTouchPosition = positionInScreen
square.position = positionInScreen
//Calculate the difference between the sprite's last position and its current position,
//and use it to predict the sprite's position next frame.
let positionDelta = CGPoint(x: positionInScreen.x - oldPosition.x, y: positionInScreen.y - oldPosition.y)
let predictedPosition = CGPoint(x: positionInScreen.x + positionDelta.x, y: positionInScreen.y + positionDelta.y)
//Multiply the timeDelta by 1.5. This helps to smooth out the lag,
//but making this number too high cause the animation to be ineffective.
square.run(SKAction.move(to: predictedPosition, duration: timeDelta * 1.5), withKey: "TouchPrediction")
}
lastTouchTime = Date().timeIntervalSinceReferenceDate
}
}
I had similar issues when dragging around an image using the touchesMoved method. I was previously just updating the node's position based on where the touch was, which was making the movement look laggy. I made it better like this:
//in touchesMoved
let touchedPoint = touches.first!
let pointToMove = touchedPoint.location(in: self)
let moveAction = SKAction.move(to: pointToMove, duration: 0.01)// play with the duration to get a smooth movement
node.run(moveAction)
Hope this helps.

Swift SpriteKit - Gradually increase rotation

I'm experimenting round with #nickfalk's answer on How to rotate sprite in sprite kit with swift on how to rotate a sprite in sprite kit.
How would I adjust this to gradually increase rotation speed up to a maximum, then when the screen is clicked, it gradually slows down and goes in the reverse direction for x amount of time?
Thanks!
Toby.
Ok, the following (slightly messy proof of concept) spins a sprite at a constant speed. Upon tap+hold it gradually slows the rotation to a halt. Ending the touch immediately returns the rotation to full speed.
I've set up a scene with the following properties: var sprite : SKSpriteNode? and var shouldDecelerate = false:
The sprite is set up with the preferred details and have a repeactActionForever-action running a 360 degrees rotation. From here its fairly straightforward:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
shouldDecelerate = true
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
shouldDecelerate = false
sprite?.speed = 1
sprite!.runAction(SKAction.speedTo(sprite!.speed, duration: 1/60))
}
override func update(currentTime: CFTimeInterval) {
if let sprite = sprite {
if sprite.speed > 0 && shouldDecelerate {
let newSpeed = max(sprite.speed - 0.1, 0) // we don't want a negative speed as it will reverse the rotation
sprite.runAction(SKAction.speedTo(newSpeed, duration: 1/60))
}
}
}
If you want a gradual increase in speed you basically just need an if with opposite logic of the one I've included in update() above, oh and you should also remove the sprite?.speed = 1 line in touchesEnded().
If you need to have other move-actions where the speed is not effected by the rotation-speed I suggest you hook the sprite up to an SKNode and let this handle the other actions.

How does SKScene.anchorPoint affects physicsBody?

When I changed anchorPoint for the GameScene (from default 0,0 to 0.5,0.5) the physicsBody for the sprite disappeared from the screen debugging with kView.showsPhysics = true.
It still exists, because each ship collides with each other but it looks like position of the physicsBody was shifted (out of screen).
Why this change affects physicsBody of scene's child?
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0.5, 0.5)
physicsWorld.gravity = CGVector(0, 0)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 100)
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
sprite.runAction(SKAction.repeatActionForever(action))
self.addChild(sprite)
}
}
}
I did the same thing. I put the anchor point of the SKScene also in the middle (CGPointMake(0.5, 0.5)) and it totally destroyed the physics. You will notice this especially when adding joints to the physics scene, you will not be able to put the physics joint anchor points in the right place.
My advice, don't change the SKScene anchorpoint if using SKPhysics (costs me a lot of time and rewriting to find out).
Changing the anchor point of the scene does not change coordinate of physics world. The same goes with every node and its physics body. With your own nodes you can compensate using special SKPhysicsBody init with center: (the center in the owning node’s coordinate system).
With physics world as for now you can do nothing.

Resources