So I'd like to move a square from the bottom left of the screen to the top right. However every time this action happens, the square has a little push towards the right side of the screen and also remains with its previous tilt... Can I just reset its physics simulation?
object.removeAllActions()
object.position.y = frame.maxY + 50
object.position.x = frame.miX + 50
Here's how to reset completely a node's physics:
object.physicsBody = SKPhysicsBody()
I do this all the time. I find it easier to make a function to do this for me, so that way you don't have to reconfigure the pb each time you want it to be reset:
class Player: SKSpriteNode {
func resetPB() {
self.physicsBody = SKPhysicsBody(rectangleOf: self.size)
self.physicsBody?.categoryBitMask = UInt(4)
self.physicsBody?.mass = 35
// And so on...
}
}
Or if you aren't using a subclass:
func resetPlayerPB(_ player: SKSpriteNode) {
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
// And so on...
}
Related
init () {
super.init(texture: nil, color: .clear, size: initialSize)
// Create physicsBody based on a frame of the sprite
// basically giving it gravity and physics components
let bodyTexture = textureAtlas.textureNamed("MainCharacterFlying1")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
// Lose momentum quickly with high linear dampening
self.physicsBody?.linearDamping = 0.9
// weighs around 30 kg
self.physicsBody?.mass = 30
// no rotation
self.physicsBody?.allowsRotation = false
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
}
// Animations to make the main character seem like it is flying
func createAnimations() {
let rotateUpAction = SKAction.rotate(byAngle: 0, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(byAngle: -1, duration: 0.8)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying1"), textureAtlas.textureNamed("MainCharacterFlying2"),textureAtlas.textureNamed("MainCharacterFlying3"), textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying5"),textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying3"),textureAtlas.textureNamed("MainCharacterFlying2")]
var flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.1)
flyAction = SKAction.repeatForever(flyAction)
flyAnimation = SKAction.group([flyAction,rotateUpAction])
let soarFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying5")]
var soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAction = SKAction.repeatForever(soarAction)
let soarAnimation = SKAction.group([soarAction,rotateDownAction])
}
When I run this code on the IOS Simulator, I have to click on the screen once in order for my main sprite to show up on the screen, or else it will not. And when I click on the sprite, the sprite will start flapping its wings and go up (I have other code for that) however, the rotateUpAction and rotateDownAction are not showing up at all on the Simulator. So I was wondering if there were any solutions and anyone willing to answer.
Thank you for your time. Also, this code from the class of the main character, the name of the class is "Player"
you declare let soarAnimation inside your createAnimations function, meaning it's not in scope when you call self.run(soarAnimation). Solution: declare soarAnimation as a class property. Alternate solution: have createAnimations() return the SKAction and grab it in init that way
I have a sprite that the user is able to move side to side by pressing on the left or right side of the screen. If you hold down on either side, the player sprite will leave the screen. I want to stop that from happening using physic bodies, but I can't seem to make it work.
To start, here are my categories.
//Categories for physics bodies
let sceneCategory:UInt32 = 0x1 << 0 // Equal to 1
let playerCategory:UInt32 = 0x1 << 1 // Equal to 2
Here is where I set the physics body of the scene itself.
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody?.categoryBitMask = sceneCategory
self.physicsBody?.contactTestBitMask = playerCategory
self.physicsBody?.collisionBitMask = 0
self.physicsBody?.isDynamic = false
This is how I have the physics set for the player itself. I'm trying to set the physics body around the car itself. Is the way I have it setup the same as setting an alpha mask physics body?
let texture = SKTexture(imageNamed: "PorscheBlue")
player.physicsBody = SKPhysicsBody(texture: texture, size: player.size)
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = sceneCategory
player.physicsBody?.collisionBitMask = 0
Then, in the didBegin method, I just wanted it to print showing that the method was called. This isn't happening though for some reason.
func didBegin(_ contact: SKPhysicsContact)
{
print("called")
}
Why isn't the didBegin method being called? Do I have the physics set up properly? How can I make it so the player isn't allowed to leave the screen when moving?
Thank you
EDIT: So when having the physics boundaries visible, it looks like the boundaries for the scene are only being drawn on the top and bottom. I can't see any lines being drawn on the sides. That may be the issue, but I can't get it to show on the sides.
I resolved my issue by changing the dynamic value. I had both set to false, which causes the didBegin function to not be called. At least one of the nodes needs to be dynamic.
I have a separate class called "Floor" with below.
class Floor: SKNode {
override init() {
super.init()
//let edgeFrame = CGRect(origin: CGPoint(x: 1,y: 1), size: CGSize(width: 1078, height: 1950))
//self.physicsBody = SKPhysicsBody(edgeLoopFrom: edgeFrame)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
// Apply a physics body to the node
// self.physicsBody?.isDynamic = false
// Set the bit mask properties
self.physicsBody?.categoryBitMask = floorCategory
self.physicsBody?.contactTestBitMask = nailDropCategory
self.physicsBody?.collisionBitMask = balloonCategory
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemted")
}
}
I basically have falling SKSpriteNode's that start at the top of the screen and goes to the bottom where it touches the "Floor" and removes itself then restarts from top again. Issue I'm having is all my SKSpriteNode keeps getting stuck on top and not falling through the border around the frame of the screen. How can I tell my application to ignore those specific nodes and let them in? Appreciate any help!
Here is the object that is moving left and right on the screen but it just falls off the side of the screen without the edgeLoop
if let accelerometerData = motionManager.accelerometerData {
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
balloon.physicsBody?.velocity = CGVector(dx: accelerometerData.acceleration.x * -500.0, dy: 0)
} else {
balloon.physicsBody?.velocity = CGVector(dx: accelerometerData.acceleration.x * 500.0, dy: 0)
}
}
}
What is happening is that your objects are hitting the top boundary of the edge loop and not being able to make it into the scene.
there is several ways you can do you this, if you absolutely needed the edge loop on the sides I would suggest elongating the loop to be higher than the scene and creating the objects inside the loop but above the visible area. However since you haven't given any indication that you actually need the loop on the sides all I would do is get rid of the edge loop detection.
create a box that is the width of the scene and say a 100px high then put a physics body on it of type floorCategory. then put this box 50 px below the bottom of the screen. Assuming that your floor sprite box has an anchorPoint of 0.5, 0.5 this will hide the box below the screen and the top of the box will sit flush with the bottom of the screen.
Now you will be able to detect when your objects hit the bottom of the screen and you will no longer have to worry about them passing through the edge loop at the top.
OR
an example of elongating the loop would be...
You create a rectangle taller than the screen (green border in the image) to apply the edge loop to, not to the screen size itself
I'm having the issue on a simple but fast-paced SpriteKit game, but I've reduced my code just to a bouncing ball and still get the issue to a lesser extent:
override func didMove(to view: SKView) {
super.didMove(to: view)
physicsWorld.contactDelegate = self
physicsWorld.speed = 1
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
borderBody.contactTestBitMask = BallCategory
addBall()
}
func addBall() {
let size = CGSize(width: 20, height: 20)
let position = CGPoint(x: frame.width / 2, y: 50)
let texture = SKTexture(image: #imageLiteral(resourceName: "whiteCircle"))
let ball = SKSpriteNode(texture: texture, size: size)
ball.position = position
ball.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
ball.fillColor = .white
ball.lineWidth = 0
addStandardProperties(node: ball, name: "ball", z: 5, contactTest: 0, category: BallCategory)
ball.physicsBody?.isDynamic = true
addChild(ball)
launchBall()
}
func addStandardProperties(node: SKNode, name: String, z: CGFloat, contactTest: UInt32, category: UInt32) {
node.name = name
node.zPosition = z
node.physicsBody?.isDynamic = false
node.physicsBody?.affectedByGravity = false
node.physicsBody?.mass = 0
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0
node.physicsBody?.linearDamping = 0
node.physicsBody?.angularDamping = 0
node.physicsBody?.angularVelocity = 0
node.physicsBody?.contactTestBitMask = contactTest
node.physicsBody?.categoryBitMask = category
}
func launchBall() {
let ball = childNode(withName: "ball")!
ball.physicsBody?.velocity = CGVector(dx: 0, dy: 500)
}
This code results in a ball (SKSpriteNode) bouncing up and down. When I run this, CPU usage starts at around 10% on my iPhone 6s and then after increases to around 25-30% after maybe 30-60 seconds (no idea why it's increasing). Throughout all of this, the frame rate stays very close to 60 FPS, usually going no lower than 58 FPS (it's the same way when I run the full game).
Almost any time an alert pops up (e.g., text messages, logging into Game Center, etc.), the lag shows up and shows up at random times when I'm running the full game.
I've also tried deleting and re-running the app, cleaning the project, deleting derived data and running in Release mode. None of these worked permanently.
Should I give up on SpriteKit and try another framework? If so, which? Cocos2D?
Any help is appreciated.
This is the result of Apple prioritising system calls over just about everything else.
When the system wants to know something, check something or otherwise do its thing it does so at the mercy of everything else.
No other engine will be able to help with this, there's no way to silence the system's constant activities through code.
You can get a slight improvement by putting on Flight Mode and turning off WIFI and Bluetooth. The system seems to be somewhat aware that it's in a quieter mode and does less because it's got no 4G or other connectivity it can go communicating with.
Further, there's been some pretty big changes to palm rejection in iOS 11 that's played havoc with the first round of iPad Pro models and creative software, creating multi-second rejection of all touch input. When this kind of thing can make it through to a GM you can be pretty sure they're slipping other messiness through.
Here's some complaints about iOS 11 performance: https://www.macrumors.com/2017/09/25/ios-11-app-slowdowns-performance-issues/
Turns out I had 2 SKViews in my view controller. By default, when you start a project as a SpriteKit game, Xcode sets the view controller root/superview of the GameViewController as an SKView. At some point, I had added a second SKView because I didn't intend for the scene to take up the entire screen and I apparently didn't realize that the VC root view was still set as an SKView. So every time GameViewController loaded, it was loading two SKViews, which is why I saw 120 FPS in Xcode.
I fixed the issue by simply removing the SKView class designation from the VC root view.
I'm working with collision detection with SpriteKit and Swift. I have a SKScene that responds to a collision with the didBeginContact function:
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == ColliderType.Food && contact.bodyB.categoryBitMask == ColliderType.Head) {
placeFoodInRandomGridLocation()
}
}
func placeFoodInRandomGridLocation() {
let randomX = arc4random_uniform(UInt32(myGrid.columnCount))
let randomY = arc4random_uniform(UInt32(myGrid.rowCount))
foodSpriteHolder.position = CGPoint(x: colLines[Int(randomX)], y: rowLines[Int(randomY)])
}
The problem is that I can easily adjust the position of the foodSpriteHolder before this didBeginContact function fires. It simply will not move the foodSpriteHolder when the placeFoodInRandomGridLocation is called.
It seems like a scope issue. I'm just not sure how to isolate why the position won't update. I can even make the foodSpriteHolder visibility hidden in this flow...So, I know i can access it.
For reference here is how the physics body is setup for the Food class item that is within the foodSpriteHolder:
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size, center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Food
Lastly the placeFoodInRandomGridLocation function definitely gets called...The position just won't update.
Thanks for any ideas,
Josh
The answer in this case was to remove the node when the collision is detected:
contact.bodyA.node?.removeFromParent()
Now I can create a new node in a new position. Since affectedByGravity is set to false the node wouldn't update its position.