I am searching for the most efficient way to Nodes with random SKSpriteNodes on given positions.
I have 10 Platforms, on each of them I want to have an Enemy (SKNode) that contains for 3 different SKSpriteNodes which will randomly spawn. These SKSpriteNodes all do other behaviors, thats why I can't simply change the Texture of the SKNode.
As with my Code, I wanted to spawn the same Node on different Platforms.
However, the code does not work properly, the nodes do spawn but only at 1 position(the position from the last SpawnEnemycode in didMoveToView), meaning 10 nodes at 1 position.
I also tried it with adding 10 different Enemy nodes, each given a Platform. But this just looks like a copy & paste code thats inefficient.
How can I fix my problem, or is there either another way to spawn them more efficiently?
My Code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var Enemy: SKNode!
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.physicsWorld.contactDelegate = self
self.physicsBody?.velocity = CGVectorMake(0, 0)
self.anchorPoint = CGPoint(x: 0, y: 0.20)
backgroundColor = SKColor(red: 81.0/255.0, green: 192.0/255.0, blue: 201.0/255.0, alpha: 1.0)
self.Enemy1 = SKNode()
addChild(Enemy1)
SpawnPlayer()
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform1.position.y + 15))
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform2.position.y + 15))
//Same code for all the other Platforms.
}
func SpawnEnemy(position: CGPoint!){
switch (arc4random_uniform(3)){
case 0:
Picture1.filteringMode = .Nearest
Picture2.filteringMode = .Nearest
Animation1 = SKAction.animateWithTextures([Picture1,Picture2], timePerFrame: 0.5)
AnimationRepeat1 = SKAction.repeatActionForever(Animation1)
Sprite1 = SKSpriteNode (imageNamed: "Sprite1.png")
Sprite1.size = CGSize(width: 64, height: 64)
Sprite1.zPosition = 2
Sprite1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 45, height: 50))
Sprite1.physicsBody?.dynamic = true
Sprite1.physicsBody?.allowsRotation = false
Sprite1.physicsBody?.restitution = 0.0
Sprite1.physicsBody?.usesPreciseCollisionDetection = true
Sprite1.physicsBody?.categoryBitMask = EnemyCategory
Sprite1.physicsBody?.collisionBitMask = PlayerCategory | Platform1Category
Sprite1.physicsBody?.contactTestBitMask = Wall1Category | Wall2Category | PlayerCategory
Sprite1.runAction(AnimationRepeat1)
Enemy.position = position
Enemy.addChild(Sprite1)
case 1:
Picture3.filteringMode = .Nearest
Picture4.filteringMode = .Nearest
Animation1 = SKAction.animateWithTextures([Picture3,Picture4], timePerFrame: 0.5)
AnimationRepeat1 = SKAction.repeatActionForever(Animation1)
Sprite2 = SKSpriteNode (imageNamed: "Sprite2.png")
Sprite2.size = CGSize(width: 64, height: 64)
Sprite2.zPosition = 2
Sprite2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 45, height: 50))
Sprite2.physicsBody?.dynamic = true
Sprite2.physicsBody?.allowsRotation = false
Sprite2.physicsBody?.restitution = 0.0
Sprite2.physicsBody?.usesPreciseCollisionDetection = true
Sprite2.physicsBody?.categoryBitMask = EnemyCategory
Sprite2.physicsBody?.collisionBitMask = PlayerCategory | Platform1Category
Sprite2.physicsBody?.contactTestBitMask = Wall1Category | Wall2Category | PlayerCategory
Sprite2.runAction(AnimationRepeat1)
Enemy.position = position
Enemy.addChild(Sprite2)
case 2:
Picture5.filteringMode = .Nearest
Picture6.filteringMode = .Nearest
Animation1 = SKAction.animateWithTextures([Picture5,Picture6], timePerFrame: 0.5)
AnimationRepeat1 = SKAction.repeatActionForever(Animation1)
Sprite3 = SKSpriteNode (imageNamed: "Sprite3.png")
Sprite3.size = CGSize(width: 64, height: 64)
Sprite3.zPosition = 2
Sprite3.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 45, height: 50))
Sprite3.physicsBody?.dynamic = true
Sprite3.physicsBody?.allowsRotation = false
Sprite3.physicsBody?.restitution = 0.0
Sprite3.physicsBody?.usesPreciseCollisionDetection = true
Sprite3.physicsBody?.categoryBitMask = EnemyCategory
Sprite3.physicsBody?.collisionBitMask = PlayerCategory | Platform1Category
Sprite3.physicsBody?.contactTestBitMask = Wall1Category | Wall2Category | PlayerCategory
Sprite3.runAction(AnimationRepeat1)
Enemy.position = position
Enemy.addChild(Sprite3)
default:
return
}
}
The reason you're only seeing one position is that you're only creating one "enemy" and moving it around with SpawnEnemy. Since your sprites are all added to the same "enemy", they will all show up wherever your enemy was moved to last.
Let's chop down what your code is to the relevant bits:
var Enemy: SKNode!
override func didMoveToView(view: SKView) {
self.Enemy1 = SKNode()
addChild(Enemy1)
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform1.position.y + 15))
SpawnEnemy(CGPoint(x: self.frame.size.width / 2, y: Platform2.position.y + 15))
}
func SpawnEnemy(position: CGPoint!){
switch (arc4random_uniform(3)){
case 0:
Enemy.position = position
Enemy.addChild(Sprite1)
case 1:
Enemy.position = position
Enemy.addChild(Sprite2)
case 2:
Enemy.position = position
Enemy.addChild(Sprite3)
default:
return
}
}
So, in didMoveToView(), you create one Enemy node. Then, as you call SpawnEnemy, you do a bunch of stuff to add other sprites, but as far as Enemy is concerned, you're just changing its position. Since you're adding the sprites to the same node, when you move that Enemy node, the child sprites move with it, too.
You have two options to fix this. Either add your sprites to some higher-level node (like the scene), or create new "enemy" nodes for each of the sprites you intend to add. Either should work, but the best choice will depend on what you're trying to accomplish with them - the scope of that discussion is beyond what you're talking about here.
Related
I have this code where I am connecting two nodes, everything working as expected. Question is that I want to remove the connecting blue line between nodes (attached image for ref). How should I do that?
//This function creates a player sprite node and place it on screen
func addPlayer () {
player = SKShapeNode(circleOfRadius: 30)
player.position = CGPoint(x: playableArea.midX, y: playableArea.midY - 600)
player.fillColor = UIColor.black
player.strokeColor = UIColor.white
player.lineWidth = 4
player.physicsBody = SKPhysicsBody(circleOfRadius: 30)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.name = "characterColor"
addChild(player)
}
//This function creates second node and attach it as tail of player node and also joint player and tail node
func addTail () {
tail = SKShapeNode(circleOfRadius: 25)
tail.position = CGPoint(x: player.position.x, y: player.position.y - 60)
tail.physicsBody = SKPhysicsBody(circleOfRadius: 25)
tail.fillColor = UIColor.black
addChild(tail)
let joint = SKPhysicsJointPin.joint(withBodyA: player!.physicsBody!, bodyB: tail.physicsBody!, anchor: player.position)
joint.shouldEnableLimits = true
joint.lowerAngleLimit = 0
joint.upperAngleLimit = 0
joint.frictionTorque = 0.1
physicsWorld.add(joint)
}
in your GameViewController you have view.showPysics = true this is used for debugging your physics object but you need to turn it off when you don't wan to see it anymore
you also have framerat eand node count set to true as well
view.showsFPS = true
view.showsNodeCount = true
just turn them to false to not display them anymore
Can some body help me, I created a SpriteKit node called(player) and once I set its physics it fall off the screen:
let player = SKSpriteNode(imageNamed: "car")
player.position.x = -300
player.zPosition = 1
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody.categoryBitMask = 1
addChild(player)
I already tried adding
affectedByGravity
but the same result for the player and the enemy.
If I got it, you want your car not to be effected by gravity.
If it is so you might want to setup the physics of your "world", like:
func setWorld() {
self.backgroundColor = UIColor.white
//This should do the trick!
physicsWorld.gravity = CGVector(dx:0, dy:0)
//This is useful if you want to create wall along the borders
let frame = CGRect(x: self.frame.minX, y: self.frame.minY, width: self.frame.width, height: self.frame.height)
//here you set the borders as walls
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
physicsBody?.friction = 0
physicsBody?.restitution = 1
physicsBody?.linearDamping = 0
}
and call this function in didMove
Log: Here
Please excuse me if this is a stupid, horrid question, but I have been stuck on it and can't find answers anywhere. I have a few nodes: leftMaze, rightMaze, and player. I am trying to detect when the player collides with any of the other two nodes.
Keep in mind that the only physics I want to actually apply to the two maze nodes is gravity. Other than that, I just want them to pass through the player. Also, feel free to give me all the constructive criticism on my code as I am very new and want to get better! Thank you all so much!
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
// PLAYER CONFIG //
player.xScale = 0.25
player.yScale = 0.25
player.position = CGPoint(x:0,y:0 - (self.frame.height/4))
player.physicsBody = SKPhysicsBody(circleOfRadius: max(player.size.width / 2,
player.size.height / 2))
player.physicsBody!.affectedByGravity = false
player.physicsBody!.collisionBitMask = 0
// SPAWNING GAME OBJECTS //
addChild(player)
// DEBUG //
print(size.width, " / " ,size.height)
print(CGPoint(x: size.width * 0.5 ,y: size.height * 0.1))
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(createMaze),userInfo:nil, repeats: true)
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.node == player && contact.bodyB.node == leftMaze{
print("Left Collision!")
}
}
func createMaze(){
/*-------- Right Maze Init --------*/
// HOW BIG THE RIGHT PART OF THE MAZE IS
let randomNumber = arc4random_uniform(UInt32(self.frame.width * 0.66))
// HOW FAR TO PLACE THE RIGHT PART OF THE MAZE FROM THE RIGHT PART OF THE SCREEN
let distanceRight = self.frame.maxX-CGFloat(randomNumber)
// DEFINITION OF RIGHT MAZE
rightMaze.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: Int(randomNumber), height: mazeHeight), cornerRadius: 0).cgPath
rightMaze.position = CGPoint(x: CGFloat(distanceRight), y: frame.maxY)
rightMaze.fillColor = UIColor.black
//ADDING A PHYSICS BODY AND GRAVITY
rightMaze.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: Int(randomNumber), height: mazeHeight))
rightMaze.physicsBody!.affectedByGravity = true
rightMaze.physicsBody!.collisionBitMask = 0
/*-------- End of Right Maze Init --------*/
/*-------- Left Maze Init --------*/
// WHERE TO PLACE THE LEFT PART OF THE MAZE
let distanceLeft = self.frame.minX
// DEFINITION OF THE LEFT MAZE
leftMaze.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: Int(self.frame.width-CGFloat(randomNumber)-(player.size.width+20)), height: mazeHeight), cornerRadius: 0).cgPath
leftMaze.position = CGPoint(x: CGFloat(distanceLeft), y: frame.maxY)
leftMaze.fillColor = UIColor.black
//ADDING A PHYSICS BODY AND GRAVITY
leftMaze.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: Int(self.frame.width-CGFloat(randomNumber)-(player.size.width+20)), height: mazeHeight))
leftMaze.physicsBody!.affectedByGravity = true
leftMaze.physicsBody!.collisionBitMask = 0
/*-------- End of Left Maze Init --------*/
addChild(rightMaze)
addChild(leftMaze)
You have no contactTestBitMask on your sprites, so your didBegin is never called.
I currently have two SKSpriteNodes that I have added SKPhysicsBodies to. When they have no SKJoint attached, they collide as expected. As soon as I add the SKPhysicsJoint, they just pass right through each other. Any joint I add functions properly, but the SKPhysicsJointLimit only limits the extent to which the nodes can travel apart from each other, not how close they can get. How can I fix this?
Here is code I am using for the joint:
let joint = SKPhysicsJointLimit.joint(withBodyA: object1.physicsBody!, bodyB: object2.physicsBody!, anchorA: CGPoint(x: object1.position.x + iconController.position.x, y: object1.position.y + iconController.position.y), anchorB: CGPoint(x: object2.position.x + iconController.position.x, y: object2.position.y + iconController.position.y))
joint.maxLength = screen.height * 0.4
physicsWorld.add(joint)
PhysicsBody of both nodes:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.allowsRotation = false
self.physicsBody?.friction = 0
self.physicsBody?.mass = 0.1
I have tested it with different values for the above modifications of the SKPhysicsBody and it performs the same.
An SKPhysicsJoint object connects two physics bodies so that they are simulated together by the physics world.
You can use also SKPhysicJointPin:
A pin joint allows the two bodies to independently rotate around the
anchor point as if pinned together.
If your objects work well before the SKPhysicsJoint addition with the physic engine so they fired the didBeginContact as you wish and as you have setted, I think your problem is simply a wrong anchor. Try to add:
let skView = self.view as! SKView
skView.showsPhysics = true
to your scene initialization code: you will see an outline of the physic bodies and maybe you'll see the issue immediatly.
To help you I'll try to make an example of elements configured to collide each other:
enum CollisionTypes: UInt32 {
case Boundaries = 1
case Element = 2
}
class GameScene: SKScene,SKPhysicsContactDelegate {
private var elements = [SKNode]()
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
let boundariesFrame = CGRectMake(20, 20, 200, 400)
let boundaries = SKShapeNode.init(rect: boundariesFrame)
boundaries.position = CGPointMake(350,150)
let boundariesBody = SKPhysicsBody.init(edgeLoopFromRect: boundariesFrame)
boundariesBody.dynamic = false
boundariesBody.categoryBitMask = CollisionTypes.Boundaries.rawValue
boundariesBody.contactTestBitMask = CollisionTypes.Element.rawValue
boundaries.physicsBody = boundariesBody
addChild(boundaries)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
body.dynamic = true
body.categoryBitMask = CollisionTypes.Element.rawValue
body.contactTestBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
body.collisionBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
}
}
}
Hope it can help you to find your issue.
I've recently put the finishing touches to my first app however I've spotted a bug that is driving me crazy!
The object of the game is the character (a cat), jumps over obstacles but also has to avoid birds that fly through the air, the score increases every time a bird fly past without contact.
The game is over if contact is made when the cat makes contact with a bird or if the cat fails to jump over an obstacle and is pushed back to the left edge of the screen where a dummy contact node is placed.
Now the bug... everything works as expected however if the cat is in mid air when a bird makes contact the collision propels the cat off the visible screen, at this stage the player is then unable to reset the scene by tapping the screen, only if the cat is visible on the screen would you be able to reset the scene by tapping once and play again.
Here is all the code I could think of that may be useful, if anyone knows how this bug can be crushed please let me know.
override func didMoveToView(view: SKView) {
//details of the cat
cat = SKSpriteNode(texture: catTexture1)
cat.position = CGPoint(x: self.frame.size.width / 2.2, y: self.frame.size.height / 5.1 )
cat.runAction(run, withKey: "runningAction")
cat.physicsBody = SKPhysicsBody(circleOfRadius: cat.size.height / 2.0)
cat.physicsBody!.dynamic = true
cat.physicsBody!.allowsRotation = false
cat.physicsBody?.categoryBitMask = catCategory
cat.physicsBody?.collisionBitMask = crowCategory | worldCategory
cat.physicsBody?.contactTestBitMask = crowCategory | contact2Category
cat.physicsBody!.restitution = -10
moving.addChild(cat)
//contact node increases score when bird makes contact
var contact = SKSpriteNode(color: UIColor.clearColor(), size: CGSizeMake(1, 450))
contact.position = CGPoint(x: self.frame.size.width / 3.3, y: self.frame.size.height / 2.0 )
contact.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(1, 450))
contact.physicsBody!.dynamic = false
contact.physicsBody!.allowsRotation = false
contact.physicsBody?.categoryBitMask = scoreCategory
contact.physicsBody?.contactTestBitMask = crowCategory
self.addChild(contact)
//2nd contact node runs game over code
var contact2 = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(1, 450))
contact2.position = CGPoint(x: self.frame.size.width / 4.0, y: self.frame.size.height / 2.0 )
contact2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(1, 450))
contact2.physicsBody!.dynamic = false
contact2.physicsBody!.allowsRotation = false
contact2.physicsBody?.categoryBitMask = contact2Category
contact2.physicsBody?.contactTestBitMask = catCategory
self.addChild(contact2)
//touch that initiates reset
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if ableToJump == true {
if (moving.speed > 0){
cat.physicsBody!.velocity = CGVectorMake(0, 0)
cat.physicsBody!.applyImpulse(CGVectorMake(0, 30))
} else if (canRestart) {
resetLabelNode.hidden = true
self.resetScene()
}
}
}
So I found the answer to this on a similar post, what they recommended was to set a frame round the visible screen. I just added the below code to didMoveToView
let frame = CGRectMake(255, 0, 515, self.frame.size.height)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
I hope this helps someone in the future!