I'm creating a Snake Game using swift and the library SpriteKit.
At the top of my program I declare a variable
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode(imageNamed: "head")
[...]
}
In this way I can access to this variable in all of my functions. But I'd like to change the snake's image when the it moves.
For example, if the snake moves Up, I'd like to display the image of the snake looking up.
So I have this code to control the movements of the snake
if movesController[0]==true{
playerY += 56.0000000
}
if movesController[1]==true{
playerY -= 56.0000000
}
if movesController[2]==true{
playerX += 56.0000000
}
if movesController[3]==true{
playerX -= 56.0000000
}
and to change the image of the snake I thought I could just do this
if movesController[0]==true{
playerY += 56.0000000
player = SKSpriteNode(imageNamed: "head_up")
}
if movesController[1]==true{
playerY -= 56.0000000
player = SKSpriteNode(imageNamed: "head_down")
}
if movesController[2]==true{
playerX += 56.0000000
player = SKSpriteNode(imageNamed: "head_right")
}
if movesController[3]==true{
playerX -= 56.0000000
player = SKSpriteNode(imageNamed: "head_left")
}
But adding this 4 lines, the Snake is unable to move...
So I tried changing the declaration of the player variable
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var imageName = "head"
var player = SKSpriteNode(imageNamed: "\("imageName)")
[...]
}
and then change the 4 lines I added before with imageName = "move_up" (or down - left - right)
But at the start of the program it gave me an error
Cannot use instance member 'imageName' within property initializer; property initializers run before 'self' is available
What should I do? My last option is to create another playerVariable and put it above the real snake image, but it would mean adding more nodes for nothing.
You must change player's texture, but not create new player at each step. Try add this function:
func changeHead(imageNamed name: String){
player.texture = SKTexture(imageNamed: name)
}
And the program text will be changed in this way:
if movesController[0]==true{
playerY += 56.0000000
changeHead(imageNamed: "head_up")
}
if movesController[1]==true{
playerY -= 56.0000000
changeHead(imageNamed: "head_down")
}
if movesController[2]==true{
playerX += 56.0000000
changeHead(imageNamed: "head_right")
}
if movesController[3]==true{
playerX -= 56.0000000
changeHead(imageNamed: "head_left")
}
Actually, your way to solve this problem is true.Put self before the imageName variable. It would be work.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var imageName = "head"
var player = SKSpriteNode(imageNamed: "\("self.imageName)")
[...]
}
Related
I am trying to include enumerateChildNode with a For Loop. My code is below.
func movement() {
scene?.enumerateChildNodes(withName: "//enemy") {
(node, stop) in
enemyArray.append(node as! SKSpriteNode)
}
for i in enemyArray {
if enemy.position = player.position + 1 {
player.position = player.position + 1
}
continue
}
What the code above does is check for every enemy SKSpriteNode on the screen (lets say it finds 6), and then checks their positions relative to the player. If the players position is near the enemy position, it moves the players position, and then continues the for loop from where it was and checks the players position from the remaining enemies.
However, if the player moves near an enemy position that the for loop already checked (which was false at the time), it will miss that the player is near that enemy. EG: The for loop checks enemies 1,2,3 (finds 3 is near player and moves the player near 2) then checks 4,5,6. It will miss 2...
What I want the code to do
If enemy.position = player.position + 1 {
//move player
*stop the loop, and check the players position from the beginning of the for loop*
}
Call the method again and break out of the loop. But, this causes an infinite loop as you know while posting your question. Not sure how you're planning on changing the value of "i" since it's scope is within the method. Anyways here's how you achieve this:
var array = [1,2,3,4,5,6]
movement()
func movement() {
for i in array {
if i == 3 {
print("hi")
movement()
break
}
}
}
Have that situation: I have bouncing red ball and several randomly generated platforms.
In case of contact with bottom of the platform ball should pass platform through. In other case, it will be top of platform and ball should bounce. I changing .collisionBitMask and .contactTestBitMask params for player to pass through platform or bounce on it. But my code working only for the last one platform, you can see on screenshot. For other platforms not working - ball pass it through from the top side too. I have array of that platforms and don't know how to check current platform.
Here is part of my code:
var platforms = [SKSpriteNode]()
....
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
let floor = BitMaskCategory.floor
let player = BitMaskCategory.player
let platformCategory = BitMaskCategory.platform
let bodyA = contact.bodyA.categoryBitMask
let bodyB = contact.bodyB.categoryBitMask
if bodyA == player && bodyB == platformCategory || bodyA == platformCategory && bodyB == player {
for platform in platforms {
if self.player.position.y > platform.position.y {
self.player.physicsBody?.collisionBitMask = floor | platformCategory
self.player.physicsBody?.contactTestBitMask = floor | platformCategory
} else if self.player.position.y < platform.position.y {
self.player.physicsBody?.collisionBitMask = floor
self.player.physicsBody?.contactTestBitMask = floor
}
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
let player = BitMaskCategory.player
let bar = BitMaskCategory.platform
let floor = BitMaskCategory.floor
let bodyA = contact.bodyA.categoryBitMask
let bodyB = contact.bodyB.categoryBitMask
if bodyA == bar && bodyB == player || bodyA == player && bodyB == bar {
for platform in platforms {
if self.player.position.y > platform.position.y {
self.player.physicsBody?.collisionBitMask = floor
self.player.physicsBody?.contactTestBitMask = floor
}
}
}
}
}
So, I don't know how to check current platform. If I placing 3 constant platforms by hand all is working fine :D
Please help, don't know how to realize this task.
I have this approach for solutions (Red platforms)
You can get code example here: https://github.com/Maetschl/SpriteKitExamples/tree/master/PlatformTest
This use object velocity for getting the direction and add collision mask
override func update(_ currentTime: TimeInterval) {
player.physicsBody?.collisionBitMask = 0b0000 // Reset the mask
// For UP only Platform
if (player.physicsBody?.velocity.dy)! < CGFloat(0.0) {
player.physicsBody?.collisionBitMask |= 0b0001 // The pipe | operator adds the mask by binary operations
}
// For Down only platforms
if (player.physicsBody?.velocity.dy)! > CGFloat(0.0) {
player.physicsBody?.collisionBitMask |= 0b0010 // The pipe | operator adds the mask by binary operations
}
}
Ok, so Im going straight off Apple's tutorial using a joystick moving an SCNNode for SceneKithere.
I've copied the code and gotten the joystick to both move and rotate the character - but not simultaneously, and not in the right direction relative to the node.
All the correct code is in that download, but what I've done is here is where I get the angle offset of the joystick handle and the float2 from the joystick UI-
characterDirection = float2(Float(padNode.stickPosition.x), -Float(padNode.stickPosition.y))
let direction = theDude.characterDirection(withPointOfView: renderer.pointOfView)
directionAngle = CGFloat(atan2f(direction.x, direction.z))
public func characterDirection(withPointOfView pointOfView: SCNNode?) -> float3 {
let controllerDir = theDude.direction //THIS ISNT BEING UPDATED
if controllerDir.allZero() {
return float3.zero
}
var directionWorld = float3.zero
if let pov = pointOfView {
let p1 = pov.presentation.simdConvertPosition(float3(controllerDir.x, 0.0, controllerDir.y), to: nil)
let p0 = pov.presentation.simdConvertPosition(float3.zero, to: nil)
directionWorld = p1 - p0
directionWorld.y = 0
if simd_any(directionWorld != float3.zero) {
let minControllerSpeedFactor = Float(0.2)
let maxControllerSpeedFactor = Float(1.0)
let speed = simd_length(controllerDir) * (maxControllerSpeedFactor - minControllerSpeedFactor) + minControllerSpeedFactor
directionWorld = speed * simd_normalize(directionWorld)
}
}
return directionWorld
}
I didn't write the last part and still trying to understand it. But what is relevant is I have a float3 and an angle, and they are conflicting when I try to run them both as SCNActions in my renderer update func:
Here is what Apple basically had in update:
// move
if !direction.allZero() {
theDude.characterVelocity = direction * Float(characterSpeed)
var runModifier = Float(1.0)
theDude.walkSpeed = CGFloat(runModifier * simd_length(direction))
// move character - IMPORTANT
theDude.directionAngle = CGFloat(atan2f(direction.x, direction.z))
theDude.node.runAction(SCNAction.move(by: SCNVector3(theDude.characterDirection(withPointOfView: theDude.node)), duration: TimeInterval(40))) //HERE - random time
theDude.isWalking = true
} else {
theDude.isWalking = false
theDude.node.removeAllActions()
}
}
Where on the commented line I applied the move and here Apple had the rotation applied:
var directionAngle: CGFloat = 0.0 {
didSet {
theDude.node.runAction(
SCNAction.rotateTo(x: 0.0, y: directionAngle, z: 0.0, duration: 0.1, usesShortestUnitArc:true))
}
}
They are both happening, problem is I don't know really what to put as my time and my node moves say, left when I have the joystick pointed right, etc because I am not doing the move correctly.
I tried to copy the demo but they have a moving floor, so it is different. What am I doing wrong here?
I have two images: ball and obstacle. If the ball hits the obstacle it's game over.
But since my obstacle and ball are not shaped as a rectangle, it looks like it's gameover before they hit each other. I understand that their frame actually does hit each other. But in the game, my gameover function is called before it looks like my ball and obstacle intersect.
Hope to find some help so I can have the result that my two images really looks like they hit each other before my gameover function is called.
if CGRectIntersectsRect(ball.frame, obstacle.frame) {
gameOver()
}
extension CGPoint {
func distance(to p: CGPoint) -> CGFloat {
return sqrt((p.x - self.x) * (p.x - self.x) + (p.y - self.y) * (p.y - self.y))
}
}
extension UIView {
func collided(with otherView: UIView) -> Bool {
let myRadius = bounds.width/2
let hisRadius = otherView.bounds.width/2
return self.center.distance(to: otherView.center) <= myRadius + hisRadius
}
}
Now it reads nicely too:
if ball.collided(with: obstacle) {
gameOver()
}
I'm using SpriteKit and Swift to build an iPhone game, and my collision detection system is not functioning as I want it to. When my "bullet" physics body and my "player" physics body collide, the collision detection function is being called multiple times, usually 4-12 times. I have tried setting "usesPreciseCollisionDetection" to true, but it is still problematic. Also, it seems the method is called more times when the bullet hits the player at an angle rather than straight on. Any thoughts how to fix this?
Collision Types:
enum ColliderType:UInt32 {
case Player = 0b1
case Bullet = 0b10
}
Player Physics Body Settings:
playerBody!.categoryBitMask = ColliderType.Player.rawValue
playerBody!.contactTestBitMask = ColliderType.Bullet.rawValue
playerBody!.collisionBitMask = ColliderType.Bullet.rawValue
Bullet Physics Body Settings:
bulletBody!.categoryBitMask = ColliderType.Bullet.rawValue
bulletBody!.contactTestBitMask = ColliderType.Player.rawValue
bulletBody!.collisionBitMask = ColliderType.Player.rawValue
Collision Detection Method:
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == 0b1) && (contact.bodyB.categoryBitMask == 0b10) {
playerVbullet(contact.bodyA, bullet: contact.bodyB)
}
if (contact.bodyA.categoryBitMask == 0b10) && (contact.bodyB.categoryBitMask == 0b11) {
playerVbullet(contact.bodyB, bullet: contact.bodyA)
}
}
Function Called on Collision:
func playerVbullet(player:SKPhysicsBody, bullet:SKPhysicsBody) {
bullet.node?.removeFromParent()
collisions++
println(collisions)
}
Give your bullet node a name:
let bullet = SKSPriteNode()
bullet.name = "bullet"
Then check your physics contact
if (contact.bodyA.node?.name == "bullet") {
contact.bodyA.node?.removeFromParent()
} else if contact.bodyB.node?.name == "bullet" {
contact.bodyB.node?.removeFromParent()
}
Good luck!