I'm using Sprite Kit's built-in physics engine in a game I'm writing. In order to check whether or not a character is on the ground, I'm using the bodyAlongRayStart() function of the SKScene's physicsWorld to check for bodies underneath the bottom-left and bottom-right corners of the character (this function belong's to the character's class):
func isInAir(sceneRef:GameScene) -> Bool {
var isInAir:Bool = false
let distance:CGFloat = 32 // how far from the origin we should check
// define where the rays start and end
let bottomLeftRayStart:CGPoint = CGPoint(x: self.position.x - (self.hitbox.size.width/2), y: self.position.y - (self.hitbox.size.height/2))
let bottomLeftRayEnd:CGPoint = CGPoint(x: self.position.x - (self.hitbox.size.width/2), y: (self.position.y - (self.hitbox.size.height/2) - distance))
let bottomRightRayStart:CGPoint = CGPoint(x: self.position.x + (self.hitbox.size.width/2), y: self.position.y - (self.hitbox.size.height/2))
let bottomRightRayEnd:CGPoint = CGPoint(x: self.position.x + (self.hitbox.size.width/2), y: (self.position.y - (self.hitbox.size.height/2) - distance))
// Are there any bodies along the lines?
var bottomLeftBody:SKPhysicsBody! = sceneRef.physicsWorld.bodyAlongRayStart(bottomLeftRayStart, end:bottomLeftRayEnd)
var bottomRightBody:SKPhysicsBody! = sceneRef.physicsWorld.bodyAlongRayStart(bottomRightRayStart, end:bottomRightRayEnd)
// no bodies found
if bottomLeftBody == nil && bottomRightBody == nil {
isInAir = true
} else {
// if one of the rays finds a wall (floor) or slope, we're not in the air
if (bottomLeftBody != nil && (bottomLeftBody!.categoryBitMask == _entityType.wall.rawValue || bottomLeftBody!.categoryBitMask == _entityType.slope.rawValue)) || (bottomRightBody != nil && (bottomRightBody.categoryBitMask == _entityType.wall.rawValue || bottomRightBody!.categoryBitMask == _entityType.slope.rawValue)) {
isInAir = false
}
}
return isInAir
}
This code is called in the scene's update() function (though I should probably move it to didSimulatePhysics()...) and it works fine! What's annoying, however, is that Swift in its infinite wisdom decides to print Chance to the console every time it detects a body along the ray.
With only one character in the scene it's not too much of a bother, but I'll be adding enemies soon (who work in much the same way, but will have different functions for handling their movement) and all those println() calls will slow the simulator right down. I can't see a way to suppress this; the call is coming from the class library which I can't alter.
Is there a reliable way to override the standard println() function, gloabally? Something like this:
func println(object: Any) {
#if DEBUG
Swift.println(object)
#endif
}
And if so, where in my project should I put it? It's currently in AppDelegate.swift but other libraries (and the bodyAlongRayStart() function) are still printing to the console even when I change the flag...
I submitted this bug to Apple. They told me it's fixed in the next version of Xcode. If it's really bugging you, you can download Xcode 6.3 beta.
Related
I am currently making an iPhone game using SpriteKit in which two archers fire arrows at each other in an attempt to take away all of their opponent's health. I've got the mechanism for firing the arrow when the user drags back on the screen, but am wondering if there is a way to make the arrow sprite rotate in mid air as it follows the arc (facing upwards immediately after release and facing downward immediately prior to landing) so that it actually looks as if it were fired from a bow in real life.
This is the piece of code that is ran when the user's touch ends. You can see where I apply the impulse but I am not sure how to go about implementing the rotation. I understand how I could get the initial rotation to match the angle of the trajectory line using simple trigonometry, but don't know how to make it rotate with its arc.
func touchUp(atPoint pos : CGPoint) {
if arrowFired == false && firstTouchMade == true && pointOne != nil {
//Set pointTwo
pointTwo = pos
//Define the vert/hor of the pull-back
let verticalLeg = ((pointOne?.y)! - (pointTwo?.y)!)
let horizontalLeg = ((pointOne?.x)! - (pointTwo?.x)!)
//Apply the impulse
arrow?.run(SKAction.applyImpulse(CGVector(dx: horizontalLeg / 9, dy: verticalLeg / 9), duration: 0.001))
arrow?.physicsBody?.affectedByGravity = true
let array = [trajectoryLine, trajectoryLabel] as [Any]
removeChildren(in: array as! [SKNode])
arrowFired = true
} else if firstTouchMade == false && zoomingIn == false {
zoomingIn = true
cameraNode.run(SKAction.move(to: (arrow?.position)!, duration: 1), completion: {
self.firstTouchMade = true
self.zoomingIn = false
})
cameraNode.run(SKAction.scale(by: 0.5, duration: 1))
}
}
Sorry if this is an obvious question, I am relatively new to programming, Swift, and SpriteKit especially.
I have created some particles animations with specific sprites which works fine if I use them in the function:
override init(size: CGSize)
I use the following lines:
let sheet_particles = Particles()
let particles_node = SKSpriteNode(texture: sheet_particles.particle000())
particles_node.name = kparticles
particles_node.position = CGPoint(x: 500, y: 500)
particles_node.zPosition = 5
background.addChild(particles_node)
particles_node.runAction(particlesAction)
To make them appear in my scene.
The problem I have is if I try to use them in other functions in my scene, I can not see them.
func panForTranslation(translation : CGPoint) {
let position = selectedNode.position
if selectedNode.name! == kpuzzleNodeName {
selectedNode.position = CGPoint(x: position.x + translation.x * 2, y: position.y + translation.y * 2)
switch selectedNode.name2 {
case "0":
if selectedNode.frame.intersects(NPuzzle13.frame) {
particles_node.position = selectedNode.position
particles_node.runAction(particlesAction)
NPuzzle13.hidden = false
selectedNode.removeFromParent()
}
I see no particles sprite when the condition "0" happens but I see correctly the NPuzzle13. When I check the position of the particles_node node, its position is equal with the node selectedNode. All that is OK, except for the visibility of the particles... What am I missing? Thanks.
About zPosition seems all correct. I dont see any anchorPoint in your code.
I think your switch-case is jumped (not fired, not executed) because you check switch selectedNode.name2 instead of switch selectedNode.name
So in my game i want to really make the life index more dynamic and not just an label that counts down. I've add three sprites on top of the screen like this:
They are inserted by this code in a for loop:
// Add Life to player
var lifeIndexCount = 0
var positionAdd:CGFloat = 10.0
for lifeIndexCount in 0..<3 {
let lifeIndex = SKSpriteNode(imageNamed: "bullet.png")
lifeIndex.position = CGPoint(x: self.frame.size.width * -1.5, y: self.frame.size.height * 0.93)
let lifeIndexMove = SKAction.moveTo(CGPoint(x: (size.width * 0.05) + positionAdd, y: size.height * 0.93), duration: NSTimeInterval(0.7))
let lifeIndexRotation = SKAction.rotateByAngle(CGFloat(-2 * M_PI), duration: 0.3)
lifeIndex.runAction(SKAction.sequence([lifeIndexMove, lifeIndexRotation]))
addChild(lifeIndex)
positionAdd = positionAdd + 25.0
I want to remove each one as soon as the player ship misses a shot. Like a little animation of the life sprite rotating and get of the screen. I've set my contact delegate but i don't know how to grab them and animate so they can go off when a miss shot occurred.
func didBeginContact(contact: SKPhysicsContact) {
var contactBody1: SKPhysicsBody
var contactBody2: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
contactBody1 = contact.bodyA
contactBody2 = contact.bodyB
} else {
contactBody1 = contact.bodyB
contactBody2 = contact.bodyA
}
if ((contactBody1.categoryBitMask == 1) && (contactBody2.categoryBitMask == 2)) {
//Run when the bullet contacts the line
contactBody2.node!.removeFromParent()
// I WANT TO INSERT CODE HERE TO REMOVE A LIFE AT THE TOP
} else if ((contactBody1.categoryBitMask == 2) && (contactBody2.categoryBitMask == 4)) {
//Run when the bullet contacts the limbo
}
}
Thanks!
There are several ways to do it, but they all pretty much boil down to giving the node you want animated and removed a name, and matching that name in didBeginContact:. I'm fond of enumerateChildNodesWithName for readability and ease of use. The baked in pattern matching is pretty handy if your use case has something like arrowSprite1, arrowSprite2, etc...
It's also a good idea to clean up anything else that node may be doing, like cancelling SKActions or requests sent out to selectors.
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKNode_Ref/index.html#//apple_ref/occ/instm/SKNode/enumerateChildNodesWithName:usingBlock:
I am new to swift a Sprite kit. In the app I am trying to make I have a submarine moving through the ocean. Every time the user clicks the screen the gravity starts pulling the sub in the opposite direction. My problem is that i can't find a way to keep the sub from leaving the screen. I have tried to solve it by making a physicsBody around the screen, but the sub still leaves the screen. I have also tried the following code in the updateCurrentTime fund.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
self.physicsWorld.gravity = CGVectorMake(0,gravity)
if (sub.position.y >= self.size.height - sub.size.height / 2){
sub.position.y = self.size.height - self.sub.size.height / 2
}
if (sub.position.y <= sub.size.height / 2) {
sub.position.y = self.sub.size.height / 2
}
}
But this doesn't do anything either.
Any help would be greatly appreciated!!!!! thanks in advance!
P.S. I can't believe that it is that hard to keep things on the screen!!!
frustrating!
Try SKConstraint - it doesn't require a physicsBody. The code would look something like this, and would constrain the sub sprite to the scene:
let width2 = sub.size.width/2
let height2 = sub.size.height/2
let xRange = SKRange(lowerLimit:0+width2,upperLimit:size.width-width2)
let yRange = SKRange(lowerLimit:0+height2,upperLimit:size.height-height2)
sub.constraints = [SKConstraint.positionX(xRange,Y:yRange)]
Try this in the update:
if sub.frame.maxY >= view!.frame.height {
sub.position.y = view!.frame.height - sub.size.height / 2
sub.physicsBody!.affectedByGravity = false
}
if sub.frame.minY <= 0 {
sub.position.y = sub.size.height / 2
sub.physicsBody!.affectedByGravity = false
}
And then inside of the event where you want to reverse gravity don't forget to do this:
sub.physicsBody!.affectedByGravity = true
Alternatively, instead of using gravity you could use this which is a better option in my opinion:
// This moves the object to the top of the screen
let action = SKAction.moveToY(view!.frame.height - character.size.height / 2, duration: 5.0) // Or however much time you want to the action to run.
action.timingMode = .EaseInEaseOut // Or something else
character.runAction(action)
// Change view!.frame.height - character.size.height / 2 to just character.size.height / 2 to move to the bottom.
I'm new to iOS programing and I'm experimenting to learn trying to create a game in swift using Sprite Kit.
What I'm trying to achieve is having a constant flow of blocks being created and moving rightwards on the screen.
I start by creating a set which contains all the initial blocks, then an action "constant movement" is added to each one, which makes them move slowly to the right. What I'm having trouble is adding new blocks to the screen.
The last column of blocks has an "isLast" boolean set to true, when it passes a certain threshold it is supposed to switch to false and add a new column of blocks to the set which now have "isLast" set to true.
Each block in the set has the "constantMovement" action added which makes them move slowly to the right, the new blocks have it added as well, but they don't work as the original ones.
Not all of the move, even tho if I print "hasActions()" it says they do, and the ones that do move stop doing so when they get to the middle of the screen. I have no idea why this happens, can somebody experienced give me a hint please?
This is the update function:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let constantMovement = SKAction.moveByX(-1, y: 0, duration: 10);
background.runAction(SKAction.repeatActionForever(constantMovement));
let removeBlock = SKAction.removeFromParent();
let frame = self.frame;
var currentBlockSprite:SKSpriteNode;
var newBlock: Block;
for block in blocks {
currentBlockSprite = block.sprite!;
currentBlockSprite.runAction(constantMovement);
if(block.column == NumColumns - 1) {
block.isLast = true;
}
if(block.isNew) {
println("position \(currentBlockSprite.position.x) has actions \(currentBlockSprite.hasActions())");
}
if(block.isLast && currentBlockSprite.position.x < frame.maxX - 50) {
println("the block that hits is " + block.description);
println("HITS AT \(currentBlockSprite.position.x)");
block.isLast = false;
for row in 0..<NumRows {
newBlock = Block(column: NumColumns - 1, row: row, blockType: BlockType.random(), isLast: true, isNew: true);
blocks.addElement(newBlock);
addBlockSprite(newBlock);
println("new block: " + newBlock.description + "position \(newBlock.sprite?.position.x)");
}
}
if(currentBlockSprite.position.x < frame.minX) {
currentBlockSprite.runAction(removeBlock);
blocks.removeElement(block);
}
}
}
My whole project is in here: https://github.com/thanniaB/JumpingGame/tree/master/Experimenting
but keep in mind that since I'm new to this it might be full of cringeworthy bad practices.
I would remove any SKAction code from the update function as that's kind of a bad idea. Instead I would just apply the SKAction when you add your block sprite to the scene, like this.
func addBlockSprite(block: Block) {
let blockSprite = SKSpriteNode(imageNamed: "block");
blockSprite.position = pointForColumn(block.column, row:block.row);
if(block.blockType != BlockType.Empty) {
addChild(blockSprite);
let constantMovement = SKAction.moveByX(-10, y: 0, duration: 1)
var currentBlockSprite:SKSpriteNode
let checkPosition = SKAction.runBlock({ () -> Void in
if(blockSprite.position.x < -512){
blockSprite.removeAllActions()
blockSprite.removeFromParent()
}
})
let movementSequence = SKAction.sequence([constantMovement, checkPosition])
let constantlyCheckPosition = SKAction.repeatActionForever(movementSequence)
blockSprite.runAction(constantlyCheckPosition)
}
block.sprite = blockSprite;
}
That would then allow you to simply add a new block whenever you see fit and it will have the appropriate action when it's added.
I've used 512 as thats the size of the iPhone 5 screen but you could swap this out for another screen size or what would be better would be a variable that dynamically reflects the screen size.