I am adding few nodes with a repeated time interval but they all are not falling naturally .I have added also
item!.physicsBody?.isDynamic = true
item!.physicsBody?.affectedByGravity = true
and i am calling
self.scene?.addChild(itemController.spawnItem()) from Gameplayscene
func spawnItem()-> SKSpriteNode{
let item : SKSpriteNode?;
if Int(randomBetweenNumbers(firstnum: 0, secondnum: 10)) >= 6{
item = SKSpriteNode(imageNamed: "Bomb");
item!.name = "Bomb";
item!.setScale(0.6);
item!.physicsBody = SKPhysicsBody(circleOfRadius: item!.size.height / 2);
}
else{
let num = Int(randomBetweenNumbers(firstnum: 1, secondnum: 6));
item = SKSpriteNode(imageNamed: "Fruit\(num)");
item!.name = "Fruit";
item!.setScale(0.7);
item!.physicsBody = SKPhysicsBody(circleOfRadius: item!.size.height / 2);
}
item!.physicsBody?.categoryBitMask = ColliderType.FRUIT_AND_BOMB
item!.zPosition = 3;
item!.physicsBody?.isDynamic = true
item!.physicsBody?.affectedByGravity = true
item!.physicsBody?.isResting = false
item!.anchorPoint = CGPoint(x: 0.5, y: 0.5)
item!.position.x = randomBetweenNumbers(firstnum: minX, secondnum: maxX)
item!.position.y = 400
return item!;
}
My scene's gravity property y parameter was set to 0 .so all nodes which are being added are not falling to bottom layer so changed to -0.8 it worked for me
Related
So I'm creating a game in Swift with Spritekit and have ran into an issue with getting my game to work. I'm still a beginner with programming so I've likely missed out on a solution to this myself.
Anyway, so the game concept is a simple arcade vertical scroller that involves a player trying to dodge platforms as it descends downward. The mechanics (so far) are a stationary player on the y axis that can move left and right along the x axis while the platforms scroll upward along with the background moving with the platforms to give a visual effect of descent. I've gotten a build working to be fully playable, but there's an issue with spawning the platforms perfectly spaced out. Here's a sketch:
Concept Image
The picture on the left what I'm trying to achieve, while the one on the right is my current and flawed method. The main issue with the one on the right, is that it uses a collision to trigger spawning which means the spawn trigger node (red line) has to be 1 pixel tall to allow for perfect spacing. If the spawn trigger node is more than 1 pixel tall, then the collision may not trigger on that the first pixel of contact and trigger the node a few pixels deep which throws off the entire spacing. Also if the spawn trigger is only 1 pixel tall, it often won't trigger unless the everything is scrolling at slow speeds.
I've tried to think of other methods to approach this but I'm at a loss. I cannot use a simple timer to spawn nodes at intervals because the speed at which the game scrolls is variable and is constantly changing by player controls. The only two other options I can think of (which I don't know how to do either) is either spawn node sets at fixed y-positions and set that on a loop, or change it so the player is actually descending downward while everything is generating around it (seems tougher and maybe unnecessary). I'm considering just rewriting my createPlatforms() method if I need to, but here's the code for that and the background anyway:
var platformGroup = Set<SKSpriteNode>()
var platformSpeed: CGFloat = 0.6 { didSet { for platforms in platformGroup { platforms.speed = platformSpeed } } }
var platformTexture: SKTexture!
var platformPhysics: SKPhysicsBody!
var platformCount = 0
var backgroundPieces: [SKSpriteNode] = [SKSpriteNode(), SKSpriteNode()]
var backgroundSpeed: CGFloat = 1.0 { didSet { for background in backgroundPieces { background.speed = backgroundSpeed } } }
var backgroundTexture: SKTexture! { didSet { for background in backgroundPieces { background.texture = backgroundTexture } } }
func createPlatforms() {
let min = CGFloat(frame.width / 12)
let max = CGFloat(frame.width / 3)
var xPosition = CGFloat.random(in: -min ... max)
if platformCount >= 20 && platformCount < 30 {
stage = 0
setTextures()
xPosition = frame.size.width * 0.125
} else if platformCount == 30 {
stage = 2
setTextures()
} else if platformCount >= 50 && platformCount < 60 {
stage = 0
setTextures()
xPosition = 184
} else if platformCount == 60 {
stage = 3
setTextures()
}
platformPhysics = SKPhysicsBody(rectangleOf: CGSize(width: platformTexture.size().width, height: platformTexture.size().height))
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.physicsBody?.affectedByGravity = false
platformLeft.physicsBody?.collisionBitMask = 0
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 3, height: platformLeft.size.height * 3))
platformLeft.zPosition = 20
platformLeft.name = "platform"
platformLeft.speed = platformSpeed
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = true
platformRight.physicsBody?.collisionBitMask = 0
platformRight.scale(to: CGSize(width: platformRight.size.width * 3, height: platformRight.size.height * 3))
platformRight.zPosition = 20
platformRight.name = "platform"
platformRight.speed = platformSpeed
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.zPosition = 100
scoreNode.name = "scoreDetect"
scoreNode.speed = platformSpeed
let platformTrigger = SKSpriteNode(color: UIColor.orange, size: CGSize(width: frame.width, height: 4))
platformTrigger.physicsBody = SKPhysicsBody(rectangleOf: platformTrigger.size)
platformTrigger.physicsBody?.isDynamic = true
platformTrigger.physicsBody?.affectedByGravity = false
platformTrigger.physicsBody?.categoryBitMask = Collisions.detect
platformTrigger.physicsBody?.contactTestBitMask = Collisions.spawn
platformTrigger.physicsBody?.collisionBitMask = 0
platformTrigger.physicsBody?.usesPreciseCollisionDetection = true
platformTrigger.zPosition = 100
platformTrigger.name = "platformTrigger"
platformTrigger.speed = platformSpeed
let newNodes: Set<SKSpriteNode> = [platformLeft, platformRight, scoreNode, platformTrigger]
for node in newNodes {
platformGroup.insert(node)
}
let yPosition = spawnNode.position.y - transitionPlatform.size().height
let gapSize: CGFloat = -frame.size.width / 6
print(gapSize)
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: platformLeft.position.y - platformLeft.size.height / 2)
platformTrigger.position = CGPoint(x: frame.midX, y: platformLeft.position.y)
print(platformLeft.position.y)
print(platformLeft.frame.midY)
let endPosition = frame.maxY + frame.midY
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
nodeArray.append(node)
node.run(moveSequence)
}
platformCount += 1
}
func startPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
}
run(create)
}
func createBackground() {
for i in 0 ... 1 {
let background = backgroundPieces[i]
background.texture = backgroundTexture
background.anchorPoint = CGPoint(x: 0, y: 0)
background.zPosition = -5
background.size = CGSize(width: frame.size.width, height: frame.size.width * 2.5)
background.position = CGPoint(x: 0, y: background.size.height + (-background.size.height) + (-background.size.height * CGFloat(i)))
self.addChild(background)
nodeArray.append(background)
let scrollUp = SKAction.moveBy(x: 0, y: background.size.height, duration: 5)
let scrollReset = SKAction.moveBy(x: 0, y: -background.size.height, duration: 0)
let scrollLoop = SKAction.sequence([scrollUp, scrollReset])
let scrollForever = SKAction.repeatForever(scrollLoop)
background.run(scrollForever)
}
}
Does anybody have any suggestions on how I approach this or change it so it would work perfectly everytime?
I'm creating multiple nodes automatically and I want to arrange them around me, because at the moment I'm just increasing of 0.1 the current X location.
capsuleNode.geometry?.firstMaterial?.diffuse.contents = imageView
capsuleNode.position = SCNVector3(self.counterX, self.counterY, self.counterZ)
capsuleNode.name = topic.name
self.sceneLocationView.scene.rootNode.addChildNode(capsuleNode)
self.counterX += 0.1
So the question is, how can I have all of them around me instead of just in one line?
Did someone of you have some math function for this? Thank you!
Use this code (macOS version) to test it:
import SceneKit
class GameViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.backgroundColor = NSColor.black
for i in 1...12 { // HERE ARE 12 SPHERES
let sphereNode = SCNNode(geometry: SCNSphere(radius: 1))
sphereNode.position = SCNVector3(0, 0, 0)
// ROTATE ABOUT THIS OFFSET PIVOT POINT
sphereNode.simdPivot.columns.3.x = 5
sphereNode.geometry?.firstMaterial?.diffuse.contents = NSColor(calibratedHue: CGFloat(i)/12,
saturation: 1,
brightness: 1,
alpha: 1)
// ROTATE ABOUT Y AXIS (STEP is 30 DEGREES EXPRESSED IN RADIANS)
sphereNode.rotation = SCNVector4(0, 1, 0, (-CGFloat.pi * CGFloat(i))/6)
scene.rootNode.addChildNode(sphereNode)
}
}
}
P.S. Here's a code for creating 90 spheres:
for i in 1...90 {
let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.1))
sphereNode.position = SCNVector3(0, 0, 0)
sphereNode.simdPivot.columns.3.x = 5
sphereNode.geometry?.firstMaterial?.diffuse.contents = NSColor(calibratedHue: CGFloat(i)/90, saturation: 1, brightness: 1, alpha: 1)
sphereNode.rotation = SCNVector4(0, 1, 0, (-CGFloat.pi * (CGFloat(i))/6)/7.5)
scene.rootNode.addChildNode(sphereNode)
}
You need some math here
How I prepare the circle nodes
var nodes = [SCNNode]()
for i in 1...20 {
let node = createSphereNode(withRadius: 0.05, color: .yellow)
nodes.append(node)
node.position = SCNVector3(0,0,-1 * 1 / i)
scene.rootNode.addChildNode(node)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.arrangeNode(nodes: nodes)
}
func createSphereNode(withRadius radius: CGFloat, color: UIColor) -> SCNNode {
let geometry = SCNSphere(radius: radius)
geometry.firstMaterial?.diffuse.contents = color
let sphereNode = SCNNode(geometry: geometry)
return sphereNode
}
Math behind arrange the view into circle
func arrangeNode(nodes:[SCNNode]) {
let radius:CGFloat = 1;
let angleStep = 2.0 * CGFloat.pi / CGFloat(nodes.count)
var count:Int = 0
for node in nodes {
let xPos:CGFloat = CGFloat(self.sceneView.pointOfView?.position.x ?? 0) + CGFloat(cosf(Float(angleStep) * Float(count))) * (radius - 0)
let zPos:CGFloat = CGFloat(self.sceneView.pointOfView?.position.z ?? 0) + CGFloat(sinf(Float(angleStep) * Float(count))) * (radius - 0)
node.position = SCNVector3(xPos, 0, zPos)
count = count + 1
}
}
Note: In third image I have set.
let xPos:CGFloat = -1 + CGFloat(cosf(Float(angleStep) * Float(count))) * (radius - 0)
let zPos:CGFloat = -1 + CGFloat(sinf(Float(angleStep) * Float(count))) * (radius - 0)
That means if you need view around the camera then
use CGFloat(self.sceneView.pointOfView?.position.x ?? 0) or at the random place then provide the value
Output
I have two SKSpriteNodes that I would like to keep the same position. newLeftObject, and newLeftObjectBackground. What is the best way to make it so that the two objects always keep the same position even when force is applied to them. I tried setting their mass and density equal to each other but they still end up having slightly different positions while falling. Thank you, any help is appreciated!
func createNewObjectLeftAndMoveToCenter() {
colorBackground.removeFromParent()
blackBackground.removeFromParent()
var newLeftObject = SKSpriteNode()
var newRightObject = SKSpriteNode()
var newLeftObjectBackground = SKSpriteNode()
var newRightObjectBackground = SKSpriteNode()
var distance = sqrt(((colorBackground.size.width / -2) - (verticalTarget.position.x)) * ((colorBackground.size.width / -2) - (verticalTarget.position.x)))
var distanceToCenter = sqrt(((0 - (verticalTarget.position.x))) * (0 - (verticalTarget.position.x)))
newLeftObject.size = CGSize(width: distance, height: colorBackground.size.height)
newLeftObject.position = CGPoint(x: verticalTarget.position.x - (newLeftObject.size.width / 2), y: colorBackground.position.y)
newLeftObject.color = colorBackground.color
newLeftObject.zPosition = 30
addChild(newLeftObject)
newLeftObjectBackground.size = CGSize(width: distance + 14, height: blackBackground.size.height)
newLeftObjectBackground.position = CGPoint(x: verticalTarget.position.x - (newLeftObject.size.width / 2), y: colorBackground.position.y)
newLeftObjectBackground.color = UIColor.black
newLeftObjectBackground.zPosition = 20
addChild(newLeftObjectBackground)
newRightObject.size = CGSize(width: distanceToCenter + colorBackground.size.width / 2, height: colorBackground.size.height)
newRightObject.position = CGPoint(x: verticalTarget.position.x + (newRightObject.size.width / 2), y: colorBackground.position.y)
newRightObject.color = colorBackground.color
newRightObject.zPosition = 40
addChild(newRightObject)
newRightObjectBackground.size = CGSize(width: (distanceToCenter + colorBackground.size.width / 2) + 14, height: blackBackground.size.height)
newRightObjectBackground.position = CGPoint(x: verticalTarget.position.x + (newRightObject.size.width / 2), y: colorBackground.position.y)
newRightObjectBackground.color = blackBackground.color
newRightObjectBackground.zPosition = 35
addChild(newRightObjectBackground)
newLeftObject.physicsBody = SKPhysicsBody(rectangleOf: newLeftObject.size)
newLeftObject.physicsBody?.isDynamic = true
newLeftObject.physicsBody?.affectedByGravity = true
newLeftObject.physicsBody?.allowsRotation = true
newLeftObject.physicsBody?.collisionBitMask = 0
newLeftObject.physicsBody?.density = 1
newLeftObject.physicsBody?.mass = 1
newLeftObject.physicsBody?.applyAngularImpulse(1)
newLeftObject.physicsBody?.applyForce(CGVector(dx: -10000, dy: 10000))
newLeftObjectBackground.physicsBody = SKPhysicsBody(rectangleOf: newLeftObjectBackground.size)
newLeftObjectBackground.physicsBody?.isDynamic = true
newLeftObjectBackground.physicsBody?.affectedByGravity = true
newLeftObjectBackground.physicsBody?.collisionBitMask = 0
newLeftObjectBackground.physicsBody?.density = 1
newLeftObjectBackground.physicsBody?.mass = 1
newLeftObjectBackground.physicsBody?.allowsRotation = true
newLeftObjectBackground.physicsBody?.applyAngularImpulse(1)
newLeftObjectBackground.physicsBody?.applyForce(CGVector(dx: -10000, dy: 10000))
newLeftObject.position = newLeftObjectBackground.position
var moveNewObjectToCenter = SKAction.moveTo(x: 0, duration: 0.25)
newRightObject.run(moveNewObjectToCenter)
newRightObjectBackground.run(moveNewObjectToCenter)
colorBackground = newRightObject
blackBackground = newRightObjectBackground
}
Make one a child of the other using addChild()
In my game, when user touches screen, a bullet is created with func fire() but nothing happen.
The following is my code:
private func fire(position: CGPoint) {
let bullet = SKShapeNode(circleOfRadius: 2)
bullet.strokeColor = SKColor.darkGray
bullet.fillColor = SKColor.darkGray
let posX = (position.x) * -1
let posY = (position.y) * -1
let pos = CGPoint(x: posX, y: posY)
let theta = atan2((pos.y - player.position.y), (pos.x - player.position.x))
let finalPos = CGPoint(x: (player.position.x) + (CGFloat(playerRadius) * cos(theta)), y: (player.position.y) + (CGFloat(playerRadius) * sin(theta)))
bullet.physicsBody = SKPhysicsBody(circleOfRadius: 2)
bullet.position = finalPos
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.isDynamic = false
bullet.physicsBody!.pinned = false
numberOfBullets += 1
bullet.name = String(numberOfBullets)
bullet.physicsBody!.collisionBitMask = bodyType.enemy.rawValue
bullet.physicsBody!.categoryBitMask = bodyType.bullet.rawValue
bullet.physicsBody!.contactTestBitMask = bodyType.enemy.rawValue
bullet.physicsBody!.usesPreciseCollisionDetection = true
world.addChild(bullet)
bulletForce(bullet: bullet)
}
private func bulletForce(bullet: SKShapeNode) {
let dx = Double((bullet.position.x - player.position.x))
let dy = Double((bullet.position.y - player.position.y))
let vector = CGVector(dx: dx, dy: dy)
bullet.physicsBody!.applyImpulse(vector)
}
Thanks in advance.
The reason it is failing is because you have isDynamic = false.
isDynamic means that forces can be applied to the body.
For more reading, please see:
read developer.apple.com/reference/spritekit/skphysicsbody
I have a 40x40 rectangle node, and I want to detect when the bottom touches a platform.
I tried this
let feet = SKPhysicsBody(rectangleOfSize: CGSize(width: hero.frame.size.width, height: 1), center: CGPoint(x: 0, y: -(hero.frame.size.height/2 - 0.5)))
then set the categoryBitMask, collisionBitMask, contactTestBitMaskand added it to the hero
hero.physicsBody = SKPhysicsBody(bodies: [feet])
But in didBeginContact the println() doesn't print.
I need a body for the bottom of the rectangle and one for the top, because if hero hits a platform from below the collision should push him down.
Update
Here is how I set the bit masks
let heroFeetCategory: UInt32 = 1 << 0
let edgeCategory: UInt32 = 1 << 1
let groundCategory: UInt32 = 1 << 2
let feet = SKPhysicsBody(rectangleOfSize: CGSize(width: hero.frame.size.width, height: 10), center: CGPoint(x: 0, y: -(hero.frame.size.height/2 - 5)))
feet.collisionBitMask = edgeCategory | groundCategory
feet.contactTestBitMask = groundCategory
feet.categoryBitMask = heroFeetCategory
hero.physicsBody = SKPhysicsBody(bodies: [feet])
hero.physicsBody?.usesPreciseCollisionDetection = true
hero.physicsBody?.velocity = CGVectorMake(0, 0)
hero.physicsBody?.restitution = 0.0
hero.physicsBody?.friction = 0.0
hero.physicsBody?.angularDamping = 0.0
hero.physicsBody?.linearDamping = 1.0
hero.physicsBody?.allowsRotation = false
hero.physicsBody?.mass = 0.0641777738928795
world.addChild(hero)
and for the ground
let ground = SKSpriteNode(color: UIColor.whiteColor(), size: CGSizeMake(38, 38))
ground.name = "groundName"
ground.position = CGPoint(x: 0, y: -(self.frame.size.height/2 - ground.frame.size.height/2))
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.size)
ground.physicsBody?.collisionBitMask = edgeCategory | heroFeetCategory
ground.physicsBody?.contactTestBitMask = heroFeetCategory
ground.physicsBody?.categoryBitMask = groundCategory
world.addChild(ground)
And how I detect if they touch
func didBeginContact(contact: SKPhysicsContact) {
var notTheHero: SKPhysicsBody!;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
notTheHero = contact.bodyB;
} else {
notTheHero = contact.bodyA;
}
println(notTheHero.node) // print "heroName"
if (notTheHero.categoryBitMask == groundCategory) {
println("touch began?"); // is never called
}
}
collision is fine, but when you print the node's name through the physics body with notTheHero.node you only access the SKNode, and not the NSString property of its name. Instead, try something like println(notTheHero.node.name);