I am creating a game with SpriteKit. The background is a png image, endlessly moving (parallax scroll):
func parallaxScroll(image: String, y: CGFloat, z: CGFloat, duration: Double, needsPhysics: Bool) {
for i in 0 ... 1 {
// position the first node on the left, and position second on the right
let node = SKSpriteNode(imageNamed: image)
node.position = CGPoint(x: 1023 * CGFloat(i), y: y)
node.zPosition = z
addChild(node)
if needsPhysics {
node.physicsBody = SKPhysicsBody(texture: node.texture!, size: node.texture!.size())
node.physicsBody?.isDynamic = false
node.physicsBody?.contactTestBitMask = 1
node.name = "ground"
}
// make this node move the width of the screen by whatever duration was passed in
let move = SKAction.moveBy(x: -1024, y: 0, duration: duration)
// make it jump back to the right edge
let wrap = SKAction.moveBy(x: 1024, y: 0, duration: 0)
// make these two as a sequence that loops forever
let sequence = SKAction.sequence([move, wrap])
let forever = SKAction.repeatForever(sequence)
// run the animations
node.run(forever)
}
}
The example function below places a box at random y position:
#objc func createObstacle() {
let obstacle = SKSpriteNode(imageNamed: "rectangle")
obstacle.zPosition = -2
obstacle.position.x = 768
addChild(obstacle)
obstacle.physicsBody = SKPhysicsBody(texture: obstacle.texture!, size: obstacle.texture!.size())
obstacle.physicsBody?.isDynamic = false
obstacle.physicsBody?.contactTestBitMask = 1
obstacle.name = "obstacle"
let rand = GKRandomDistribution(lowestValue: -200, highestValue: 350)
obstacle.position.y = CGFloat(rand.nextInt())
// make it move across the screen
let action = SKAction.moveTo(x: -768, duration: 9)
obstacle.run(action)
}
func playerHit(_ node: SKNode) {
if node.name == "obstacle" {
player.removeFromParent()
}
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA == player {
playerHit(nodeB)
} else if nodeB == player {
playerHit(nodeA)
}
}
Instead of placing it at random, I would like to place it on the "ground". The following image illustrates the current placement and the desired placement:
Does anyone know how to place an obstacle node (object) on the ground which is not flat instead of random y position?
You could cast a ray, at the box's intended x position, from the top of the screen to the bottom, and when the ray hits the ground, place the box directly above the hitpoint. See enumerateBodies(alongRayStart:end:using:). In your case you might want to insert into createObstacle something like:
let topOfScreen = size.height / 2
let bottomOfScreen = -size.height / 2
obstacle.position.x = -768
physicsWorld.enumerateBodies(alongRayStart: CGPoint(x: obstacle.position.x, y: topOfScreen), end: CGPoint(x: obstacle.position.x, y: bottomOfScreen)) { body, point, normal, stop in
if body.node?.name == "ground" {
// body is the ground's physics body. point is the point that an object
// falling from the sky would hit the ground.
// Assuming the obstacle's anchor point is at its center, its actual position
// would be slightly above this point
let positionAboveBody = point + (obstacle.size.height / 2)
obstacle.position.y = positionAboveBody
}
}
Related
I’m working on a matching game. In my app, I would like a functionality that tracks the touches of two nodes at time. When the user touches the nodes and if the textures displayed after the user touches the nodes match; I don’t want the user to be able to interact with the two nodes anymore.
However, if the textures displayed after the user touches the two nodes do not match. I want the two nodes the user touched to reset back to their original position before the user touched the nodes.
For example, let’s say the user touches the node named “fruit match card1” and it displays the texture of an “apple” then they touch the node named “fruit match card 2” and it also displays the texture of an “apple”. Since, these two nodes display the same texture that match. I don’t want the user to be able to interact with those nodes anymore since they clicked nodes that display the same texture.
However, let’s say the user touched the node named “Fruit match card 1” and it displays the “Apple” texture. Then they touched the node named “Fruit match Card 3” and it displays the “Grapes” texture. Since, these two nodes do not display the same texture I want those nodes to reset back to their original position before the user touched them.
Any advice or help on how I can have those kind of functionalities in my app? Basically having a functionality that tracks the touches of two nodes at a time and also a functionality that will reset the nodes back to their original position of the textures of the two nodes touched do not match? Thanks!
Here is my current code:
import Foundation
import SpriteKit
class EasyScreen: SKScene {
override func didMove(to view: SKView) {
var background = SKSpriteNode(imageNamed: "Easy Screen Background")
let timerText = SKLabelNode(fontNamed: "Arial")
timerText.fontSize = 40
timerText.fontColor = SKColor.white
timerText.position = CGPoint(x: 20, y: 400)
timerText.zPosition = 1
var counter:Int = 90
timerText.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
counter-=1
timerText.text = " Time: \(counter)"
print("\(counter)")
if counter <= 0{
let newScene = TryAgainScreen(fileNamed: "Try Again Screen")
newScene?.scaleMode = .aspectFill
self.view?.presentScene(newScene)
}
},SKAction.wait(forDuration: 1)])))
background.position = CGPoint(x: 0, y: 0)
background.size.width = self.size.width
background.size.height = self.size.height
background.anchorPoint = CGPoint(x: 0.5,y: 0.5)
let matchCardOne = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardTwo = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardThree = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardFour = SKSpriteNode(imageNamed: "Fruit Match Card")
let soundButton = SKSpriteNode(imageNamed: "Sound On")
matchCardOne.name = "FruitMatchCard1"
matchCardTwo.name = "FruitMatchCard2"
matchCardThree.name = "FruitMatchCard3"
matchCardFour.name = "FruitMatchCard4"
soundButton.name = "Sound"
matchCardOne.size = CGSize(width: 150, height: 300)
matchCardTwo.size = CGSize(width: 150, height: 300)
matchCardThree.size = CGSize(width: 150, height: 300)
matchCardFour.size = CGSize(width: 150, height: 300)
soundButton.size = CGSize(width: 75, height: 75)
matchCardOne.zPosition = 1
matchCardTwo.zPosition = 1
matchCardThree.zPosition = 1
matchCardFour.zPosition = 1
soundButton.zPosition = 3
matchCardOne.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardTwo.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardThree.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardFour.anchorPoint = CGPoint(x: 0.5, y: 0.5)
soundButton.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardOne.position = CGPoint(x: -125, y: 60)
matchCardTwo.position = CGPoint(x: -125, y: -260)
matchCardThree.position = CGPoint(x: 70, y: 60)
matchCardFour.position = CGPoint(x: 70 , y: -260)
soundButton.position = CGPoint(x: -180, y: -600)
addChild(background)
addChild(matchCardOne)
addChild(matchCardTwo)
addChild(matchCardThree)
addChild(matchCardFour)
addChild(timerText)
addChild(soundButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view?.isMultipleTouchEnabled = false
let touch = touches.first
let positionInSceneOne = touch!.location(in: self)
let tappedNodes = nodes(at: positionInSceneOne)
for node in tappedNodes{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard1" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
}
let touchTwo = touches.first
let positionInSceneTwo = touch!.location(in: self)
let tappedNodesTwo = nodes(at: positionInSceneTwo)
for node in tappedNodesTwo{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard2" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
}
let touchThree = touches.first
let positionInSceneThree = touch!.location(in: self)
let tappedNodesThree = nodes(at: positionInSceneThree)
for node in tappedNodesThree{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard3" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
}
let touchFour = touches.first
let positionInSceneFour = touch!.location(in: self)
let tappedNodesFour = nodes(at: positionInSceneFour)
for node in tappedNodesFour{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard4" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
}
}
You don't want to track the textures.
The texture i.e. the image of the node is just a representation of some state that the node is in, so you need to track the node's state instead.
You could do this by either having an array of nodes and their state but a better way is to subclass SKSpriteNode to create your card node and then add a custom bool property for isFaceUp, which will make it much easier to decide if cards can be touched or matched etc.
I'm trying to create a House Stack-like game (https://apps.apple.com/gb/app/house-stack/id1458713825?l) but having problem to assign physics mechanism.
My main problem is to create a single object when Node drop and contact with others and move them down in a solid form, once they reach to specific height.
I've tried to create fixed-joints with contact between nodes and move them when last Node position y = 0 but fixed-joints are not working as i thought. Nodes are shaking like a snake and collapsing when they moved.
Also tried to create a new single object with contact and calculate its height but couldn't figure out how should i use init(bodies: [SKPhysicsBody]). Even if i assign physicsbody properties to new object, i can't measure or call its height and update body with another Node contact.
Any idea or example would be nice. Here is my codes, thanks in advance.
SKNode(inside touchesBegan to create same node multiple times):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let Node1 = SKSpriteNode(imageNamed: "Object1")
Node1.position = MovingObject.position
Node1.zPosition = 2
Node1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: Node1.size.width, height: Node1.size.height))
Node1.physicsBody?.friction = 0.9
Node1.physicsBody?.restitution = 0.1
Node1.physicsBody?.allowsRotation = true
Node1.physicsBody!.contactTestBitMask = Node1.physicsBody!.collisionBitMask
Node1.physicsBody!.categoryBitMask = Node1Category
addChild(Node1)
}
Moving Object:
Inside GameScene;
var MovingObject = SKSpriteNode(imageNamed: "Object1")
let ObjectMove = SKAction.sequence([(SKAction.moveBy(x: 200, y: 0, duration: 0.5)),(SKAction.moveBy(x: -400, y: 0, duration: 1)),(SKAction.moveBy(x: 200, y: 0, duration: 0.5))])
Inside override func didMove(to view: SKView);
MovingObject.position = CGPoint(x: 0, y: frame.maxY * 0.55)
MovingObject.zPosition = 1
MovingObject.run(SKAction.repeatForever(ObjectMove))
addChild(MovingObject)
Joints and moves:
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.node?.name == "Node1" && contact.bodyB.node?.name == "Node1"
{
let joint2 = SKPhysicsJointFixed.joint(withBodyA: contact.bodyA , bodyB: contact.bodyB, anchor: CGPoint(x: contact.contactPoint.x, y:contact.contactPoint.y))
let pos2 = (contact.bodyA.node?.position.x)!-(contact.bodyB.node?.position.x)!
if pos2 <= Node1.size.width * 0.5 && pos2 >= -Node1.size.width * 0.5{
physicsWorld.add(joint2)
}
if (contact.bodyB.node?.position.y)! >= 0 {
let movenodedown = SKAction.moveBy(x: 0, y: -Node1.size.height, duration: 0.5)
contact.bodyB.node?.run(movenodedown)
ground.run(movenodedown)
}
}
For those who need to be enlightened about similar problems;
I found my solution by using enumerateChildNodes(withName:using:) and physicsBody.isDynamic = false together instead of fixed joints. Code is below.
func didBegin(_ contact: SKPhysicsContact)
{
if contact.bodyA.node?.name == "Node1" && contact.bodyB.node?.name == "Node1"
{
let pos2 = (contact.bodyA.node?.position.x)!-(contact.bodyB.node?.position.x)!
if pos2 <= Node1.size.width * 0.5 && pos2 >= -Node1.size.width * 0.5.
{
contact.bodyB.node?.physicsBody?.isDynamic = false
contact.bodyA.node?.physicsBody?.collisionBitMask = 0
contact.bodyA.node?.physicsBody?.contactTestBitMask = 0
if (contact.bodyB.node?.position.y)! >= 0
{
let movenodesdown = SKAction.moveBy(x: 0, y: Node1.size.height , duration: 0.5)
enumerateChildNodes(withName: "Node1") { (nodes, stop) in
let Node1 = nodes as! SKSpriteNode
Node1.run(movenodesdown)
}
}
}
}
}
When I run loop on node everything works fine, node moving correct. But when i run loop on next action interrupts when next reaches left border of the screen (halfway). I assume something wrong with copy. Thanks!
func moveBackground (name: String, speed: CGFloat = 0.01) {
guard let node = childNode(withName: name) as? SKTileMapNode else {
fatalError("\(name) node not loaded")
}
let width = node.frame.size.width
let startPositionX = width
node.position = CGPoint(x: startPositionX , y: 0)
let next = node.copy() as! SKTileMapNode
next.tileSet = node.tileSet
next.position = CGPoint(x: startPositionX , y: 0)
self.addChild(next)
let distance = width * 2
let duration = TimeInterval(speed * distance)
let moveAction = SKAction.moveBy(x: -distance, y: 0, duration: duration)
let resetAction = SKAction.moveTo(x: startPositionX, duration: 0)
let sequence = SKAction.sequence([moveAction, resetAction])
let loop = SKAction.repeatForever(sequence)
//node.run(loop)
next.run(loop)
}
I'm making a game in Sprite Kit and Swift and I have a Sprite at the bottom of the screen and falling Sprites from the top which I want to catch and stick to the Sprite at the bottom, so I'm trying to use SKPhysicsJointFixed but when the objects collide instead of the falling object sticking to the one at the bottom which is supposed to catch and have it attached, it seems as if the bottom Sprite adapts the physics of the falling sprite and then falls off the screen with it. Here's the code I have in my didBeginContact method. and skewer is the name of the Sprite at the bottom which should always be at the bottom and not disappear.
if contact.bodyA.node!.name == "Skewer"
{
let boundX = skewer.physicsBody?.node?.position.x
let fixedJoint = SKPhysicsJointFixed.jointWithBodyA(contact.bodyA.node!.physicsBody, bodyB: contact.bodyB.node!.physicsBody, anchor: CGPoint(x: boundX!, y: boundY))
physicsWorld.addJoint(fixedJoint)
// contact.bodyB.node!.removeFromParent()
}
else
{
contact.bodyA!.node!.removeFromParent()
}
and the physics for the bottom screen Sprite are here
func makeSkewer()
{
skewer.name = "Skewer"
skewer.position = CGPoint(x: size.width * 0.5, y: size.height * 0.244)
skewer.physicsBody = SKPhysicsBody(rectangleOfSize: skewer.size)
skewer.physicsBody?.affectedByGravity = false
skewer.physicsBody?.categoryBitMask = kSkewerCategory
skewer.physicsBody?.contactTestBitMask = kFoodCategory
skewer.physicsBody?.collisionBitMask = kSceneEdgeCategory
addChild(skewer)
}
and physics for the falling Sprites are here
func random() ->CGFloat
{
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat,max: CGFloat) -> CGFloat
{
return random() * (max - min) + min
}
func addFood()
{
let randomCatchIndex = Int(arc4random_uniform(UInt32(foods.count)))
let food = SKSpriteNode(imageNamed: foods[randomCatchIndex])
let actualX = random(min: food.size.width/2, max: size.width - food.size.width/2)
let actualDuration = random(min: CGFloat(1.5), max: CGFloat(8.0))
let actionMove = SKAction.moveTo(CGPoint(x: actualX, y: -food.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
food.physicsBody = SKPhysicsBody(rectangleOfSize: food.size)
food.position = CGPoint(x: actualX, y: size.height + food.size.height/2)
food.physicsBody?.categoryBitMask = kFoodCategory
food.physicsBody?.contactTestBitMask = kSkewerCategory
food.physicsBody?.collisionBitMask = 0x0
food.physicsBody?.dynamic = true
food.runAction(SKAction.sequence([actionMove, actionMoveDone]))
addChild(food)
}
Set the skewer to not have dynamic physics. What you have currently is it not being affected by gravity, and as soon as it locks onto the food (which is traveling down and has momentum), the skewer moves with it.
In the creation of the skewer, run the following line:
skewer.physicsBody?.dynamic = false
You can also now ignore the affectedByGravity as that is something that only affects dynamic objects.
Is there a way to give an SKNode its own physics? I have an SKShapeNode call "backGround" which I use for the parent node of most of my other nodes. I am constantly moving "background" to the left, to give the illusion that the player is moving forward. However, one of the objects that has "backGround" as a parent node is a pin with a rope hanging from it. When background accelerates to the left, is there a way to make it so the rope doesn't swing back and forth, as ropes tend to do when accelerating or decelerating?
EDIT: Here is my code:
func createRopeNode(pos: CGPoint) -> SKSpriteNode{
let ropeNode = SKSpriteNode(imageNamed: "Ball")
ropeNode.size = CGSize(width: 5, height: 5)
ropeNode.physicsBody = SKPhysicsBody(rectangleOfSize: ropeNode.size)
ropeNode.physicsBody?.affectedByGravity = true
ropeNode.physicsBody?.collisionBitMask = 0
ropeNode.alpha = 1
ropeNode.position = CGPoint(x: pos.x + 0, y: pos.y)
ropeNode.name = "RopePiece"
let text = SKSpriteNode(imageNamed: "RopeTexture")
ropeNode.zPosition = -5
text.runAction(SKAction.rotateByAngle(atan2(-dx!, dy!), duration: 0))
ropeNode.addChild(text)
return ropeNode
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
if (!playerIsConnected){
playerIsConnected = true
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
dx = pin.position.x - playerPoint!.x
dy = pin.position.y - playerPoint!.y
let length = sqrt(pow(dx!, 2) + pow(dy!, 2))
let distanceBetweenRopeNodes = 5
let numberOfPieces = Int(length)/distanceBetweenRopeNodes
var ropeNodes = [SKSpriteNode]()
//adds the pieces to the array at respective locations
for var index = 0; index < numberOfPieces; ++index{
let point = CGPoint(x: pin.position.x + CGFloat((index) * distanceBetweenRopeNodes) * sin(atan2(dy!, -dx!) + 1.5707), y: pin.position.y + CGFloat((index) * distanceBetweenRopeNodes) * cos(atan2(dy!, -dx!) + 1.5707))
let piece = createRopeNode(point)
ropeNodes.append(piece)
world.addChild(ropeNodes[index])
}
let firstJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[0].physicsBody, bodyB: pin.physicsBody, anchor:
CGPoint(x: (ropeNodes[0].position.x + pin.position.x)/2, y: (ropeNodes[0].position.y + pin.position.y)/2))
firstJoint.frictionTorque = 1
self.physicsWorld.addJoint(firstJoint)
for var i = 1; i < ropeNodes.count; ++i{
let nodeA = ropeNodes[i - 1]
let nodeB = ropeNodes[i]
let middlePoint = CGPoint(x: (nodeA.position.x + nodeB.position.x)/2, y: (nodeA.position.y + nodeB.position.y)/2)
let joint = SKPhysicsJointPin.jointWithBodyA(nodeA.physicsBody, bodyB: nodeB.physicsBody, anchor: middlePoint)
joint.frictionTorque = 0.1
self.physicsWorld.addJoint(joint)
}
finalJoint?.frictionTorque = 1
finalJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[ropeNodes.count - 1].physicsBody, bodyB: player.physicsBody, anchor:
CGPoint(x: (ropeNodes[ropeNodes.count - 1].position.x + playerPoint!.x)/2, y: (ropeNodes[ropeNodes.count - 1].position.y + playerPoint!.y)/2))
self.physicsWorld.addJoint(finalJoint!)
}
}
else{
physicsWorld.removeJoint(finalJoint!)
playerIsConnected = false
}
}
Anchor points are what you are looking for. Move the anchor point of the scene to only move the "camera" of the scene (what is displayed onscreen). This will not jostle the pin and rope. Keep in mind that the anchor point is on a slightly different scale from the scene.
Where the width of the scene could be 1024, the "width" of the of the anchor point for one scene length is 1 (basically counting as one width of the node). Same for the height, where it could be 768, the "height" would still be 1 in the anchor point coordinate space. So to move half a screen width, move the anchor point 0.5
The anchor point is a CGPoint, so you can go vertically as well. Here's a quick example:
var xValue : Float = 0.75
var yValue : Float = 0.0
self.scene?.anchorPoint = CGPointMake(xValue, yValue);
And for further reading, here's a link to the documentation on anchor points for sprites.