How to exclude positions in a field in SpriteKit? - swift

I have a function that spawns little balls, randomly positioned, on the screen. The problem I face is that I want to distribute the balls randomly, but when I do so, some balls spawn on top of each other. I want to exclude all the positions that are already taken (and maybe a buffer of a few pixels around the balls), but I don't know how to do so. I worked around this by giving the balls a Physicsbody, so they move off from one another if they happen to spawn on top of each other. But I want them to not spawn on top of each other in the first place. My code for now is the following:
spawnedBalls = [Ball]()
level = Int()
func setupLevel() {
let numberOfBallsToGenerate = level * 2
let boundary: CGFloat = 26
let rightBoundary = scene!.size.width - boundary
let topBoundary = scene!.size.height - boundary
while spawnedBalls.count < numberOfBallsToGenerate {
let randomPosition = CGPoint(x: CGFloat.random(in: boundary...rightBoundary), y: CGFloat.random(in: boundary...topBoundary))
let ball = Ball()
ball.position = randomPosition
ball.size = CGSize(width: 32, height: 32)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width)
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = 1
spawnedBalls.append(ball)
self.addChild(ball)
}
}
I don't know if this problem should be solved by having an array that stores all taken positions, or if I should use some kind of FiledNode, where occupied space can be sort of subtracted, but sadly I am unfamiliar with FieldNodes, so I don't know if that's the right way to face the problem.

Step 1) Replace
let randomPosition = ....
with
let randomPosition = randomPositionInOpenSpace()
Step 2) Write the randomPositionInOpenSpace function:
Idea is:
a) generate a random position
b) is it in open space? if so return that
c) repeat until OK
Then Step 3) write the 'is it in open space' function
For that you need to know if the proposed coordinate is near any of the other balls. For circles, you can test the distance between their centers is greater than (radiuses + margins). Distance between centers is pythagoras: sqrt of the x delta squared plus the y delta squared.

Related

How to randomly spawn enemies on my screen

Here is my code for enemy spawn:
let randomNumber = arc4random_uniform(2)
let x: CGFloat = randomNumber == 0 ? 1 : -1
enemy.position = CGPoint(x: (CGFloat(arc4random_uniform(UInt32(UIScreen.main.bounds.width))) * x), y: UIScreen.main.bounds.height)
It works but some of my enemies are spawning outside of my screen. What can I change to make them all spawn inside the border?
honestly haven't the foggiest clue what this is, but it seems like something is being multiplied by x, which would put it outside of its bounds, maybe not all, but if they happen to spawn at a particularly large (or negative) number.

Node automatically move in random direction

I'm trying to create a ball that automatically moves forward in a random direction once it is created. I've tried creating a random angle from 0-360 and having the node rotate and then having an impulse applied to the node, but the node simply stays there once it is created, so I can't tell if it is just the impulse that is not working or if the rotate isn't even working.
enemy.size = CGSize(width: 20, height:20)
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.color = UIColor(red:255.0,green:0.0,blue:0.0,alpha:1.0)
enemy.colorBlendFactor = 1.0
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.linearDamping = 0.0
self.addChild(enemy)
this is just to create the enemy, but i dont know how to apply the random direction and forward movement.
enemy.size = CGSize(width: 20, height:20)
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.color = UIColor(red:255.0,green:0.0,blue:0.0,alpha:1.0)
enemy.colorBlendFactor = 1.0
enemy.physicsBody = SKPhysicsBody(circleOfRadius:10)
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.linearDamping = 0.0
self.addChild(enemy)
let vec = CGVector(dx: CGFloat(arc4random_uniform(100)) / 50.0, dy: CGFloat(arc4random_uniform(100)) / 50.0)
enemy.physicsBody!.applyForce(vec)
this is my edited code.The enemy ball now moves when it is hit by another ball, but I want the enemy ball to just automatically move on its own. Right now it stays in the place in the middle of the screen until hit.
Like Gary already pointed out in the comments you wanna be sure that you have created an added a physicsBody to your SKNode. If you haven't, some neat documentation can be found here.
Now for applying a random force. You basically want to generate a random CGVector and use applyForce on your physics body.
To generate a random vector:
let vec = CGVector(dx: CGFloat(arc4random_uniform(100)) / 50.0, dy: CGFloat(arc4random_uniform(100)) / 50.0)
arc4random_uniform creates a random number between 0 and your upper bound which is passed as a parameter. Then I divide it a bit in order to get a number between 0 and 2. You should tweak that to your needs.
Then just use enemy.physicsBody!.applyForce(vec). Note my use of ! mark. Be sure that you have created and applied a physics body otherwise this will crash.

Adding multiple nodes on different positions on Y axis

I am trying to add many nodes on different positions on Y axis. The problem is that for some reason the positions are always (0:0).
I've went through pretty much every SO question related to this but couldn't find answer.
I am generating random number between the maximum and minimum value:
func randomBetweenTwoNumbers(firstNumber: CGFloat, secondNumber: CGFloat) -> CGFloat{
return CGFloat(arc4random())/CGFloat(UINT32_MAX) * abs(firstNumber - secondNumber) + min(firstNumber, secondNumber)
}
Now I am trying to add them like this:
func addLeftSparks(){
let randomNumber = Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
print(randomNumber)
let positions = [CGPoint(x: -275, y: randomNumber), CGPoint(x: -275, y: randomNumber)]
print(positions) // this is (0:0)
positions.enumerated().forEach { (index, point) in
let spriteNode = SKNode()
spriteNode.position = point
let sprite = SKSpriteNode(imageNamed: "Spark")
sprite.name = "Spark"
spriteNode.addChild(sprite)
}
}
The thing I am trying to achieve in picture:
Any help is appreciated, really.
You generated a random number, but you used it twice
let randomNumber = Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
print(randomNumber)
let positions = [CGPoint(x: -275, y: randomNumber), CGPoint(x: -275, y: randomNumber)]
positions is 2 points with the same y, so you will only see one node. You need to call the random number function for each new random number you want.
(I know you said it was 0,0, but I don't think that's right, so I am ignoring that part -- if you really believe that, put more information in the print statement so that you know you aren't being fooled by some other output)

Aligning second sprite to physics body of first sprite in Spritekit/Swift

I have a sprite with a physics body.
To move this sprite, in the update() function I continually set the velocity of the sprite to 150 units to go right, and -150 to go left. (See attached code.)
I have a second physics body that I'd like to have follow the first sprite. The coordinates of this second physics body are at the bottom of the first one, and 20 points to the right.
I'd like the second physics body to always follow the first, with the offset.
My code mostly works but I've noticed that the distance between the first and second bodies varies slightly, and I want there to be no variation. When moving right, the distance between them is compressed a little. When moving left, the distance between them increases a little. See a video HERE: https://www.youtube.com/watch?v=K9FhIdMwp7k
Here is the code I'm using:
override func update(_ currentTime: TimeInterval) {
switch playerDirection {
case "stopped":
playerSpeed = 0.0
case "right":
playerSpeed = 150.0
case "left":
playerSpeed = -150.0
default:
print("default")
}
// ball is the first sprite and footBall is the second sprite:
ball.physicsBody?.velocity = CGVector(dx: playerSpeed, dy: ball.physicsBody!.velocity.dy)
footBall.position = CGPoint(x: (ball.position.x + 20), y: ball.position.y - (ball.size.height / 2) + 4)
myCam.position = CGPoint(x: round(ball.position.x), y: self.size.height / 2)
...
I've been playing around with using override func didSimulatePhysics() without success, also.
I did a different test that just does away with using velocity to move the player, and instead directly increment the ball.position.x (ball = player) and footBall.position.x and when I do this everything is perfectly aligned. But if I take this route I'll have to change how my game physics work elsewhere in the game, which I'd prefer to ignore.
Thanks for having a look.
Would it meet your requirements if you add the second node as a child of the first? That is, instead of having both at the same level:
scene.addChild(ball)
scene.addChild(footBall)
replace with:
scene.addChild(ball)
ball.addChild(footBall)
and just set the footBall position as the offset you need:
footBall.position = CGPoint(x: 20, y: - (ball.size.height / 2) + 4)
then any time the ball moves the footBall will also move as it is a child node, so you can remove the manual position update code for the footBall.
I used SKPhysicsJoint to get around the problem. Here's my working code (testing at this point, so still kinda sloppy, but it works in principle) :
//add a nice player
ball = SKSpriteNode(texture:spriteArray2[0])
ball.size = CGSize(width: 150, height: 48)
//ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height / 2)
ball.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 16, height: 48))
ball.zPosition = 1
ball.physicsBody?.affectedByGravity = true
ball.position = CGPoint(x: 80.0, y: 200.0)
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = UInt32(2 | 32 | 256) //2 = floors, 32 and 256= doors.
ball.physicsBody?.contactTestBitMask = 0
ball.physicsBody?.isDynamic = true
ball.physicsBody?.restitution = 0.0
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.mass = 60.0
ball.physicsBody?.friction = 0.00
addChild(ball)
//add a nice ball for when jumping
footBall = SKSpriteNode(color: SKColor.blue, size: CGSize(width: 24, height: 16))
footBall.position = ball.position
footBall.name = "footBall"
footBall.zPosition = 1
footBall.physicsBody = SKPhysicsBody(circleOfRadius: 4)
footBall.physicsBody?.affectedByGravity = false
footBall.physicsBody?.categoryBitMask = 16
footBall.physicsBody?.collisionBitMask = 2 // floors
footBall.physicsBody?.contactTestBitMask = UInt32(32 | 64 | 128 | 256) // the doors bear snake
footBall.physicsBody?.isDynamic = true
footBall.physicsBody?.restitution = 0.0
//footBall.physicsBody?.allowsRotation = false
//footBall.physicsBody?.mass = 60.0
//footBall.physicsBody?.friction = 0.00
addChild(footBall)
let myCGPoint = ball.position // sets joint position
let myJoint = SKPhysicsJointFixed.joint(withBodyA: ball.physicsBody!, bodyB: footBall.physicsBody!, anchor: myCGPoint)
scene?.physicsWorld.add(myJoint)
...
then down in the update loop I set velocity for the player as so:
ball.physicsBody?.velocity = CGVector(dx: playerSpeed, dy: ball.physicsBody!.velocity.dy)
In my original code, in my update loop I also adjusted the position of the footBall to match the ball (player) position as needed, but in the revised code, because of the joint being used, by moving the ball the footBall moves along with it without any need to otherwise apply any force or velocity or change the x position of the footBall. The footBall just tags along for the ride, which works.
At first, this new method didn't work correctly, and I discovered through trial and error that the reason why is that "footBall.physicsBody?.allowsRotation = false" cannot be specified as a property on footBall. By uncommenting this line it works fine, however.
The point of this exercise is to use the footBall as a jumping test point for when the player's feet are stretched out during a jump. So the footBall is thus slightly forward of the main player's body. Then I'll dynamically turn on or off this footBall as needed for jumps.
Somewhat related are the following pages on "foot sensor":
http://gamedevwithoutacause.com/?p=1076
http://www.iforce2d.net/b2dtut/jumpability

Displaying 4 textures using for loop

I'm creating a game using Swift 2.0 and Sprite-Kit with Xcode7. I want to implement 4 purple balls that are suppose to resemble lives. So every time the player gets hit he loses one purple ball. They are supposed to appear side by side. I was wondering if instead of hardcoding 4 balls on to the scene I could instead use a for loop to display 4 balls.
let purpleBall = SKSpriteNode(texture: purpleTexture)
purpleBall.position = CGPointMake(self.frame.size.width * 0.65, self.frame.size.height * 0.92)
self.addChild(purpleBall)
I haven't been successful on getting 4 balls to appear on the scene. This was one of my attempts.
for(var i = 0.50; i <= 0.90; i = i + 0.10) {
let purpleBall = SKSpriteNode(texture: purpleTexture)
purpleBall.position = CGPointMake(self.frame.size.width * i, self.frame.size.height * 0.92)
self.addChild(purpleBall)
}
Here I get an error: Binary operator cannot be applied to operands of type 'CGFloat' and 'Double'
Do I have to convert i to CGFloat? and will this code actually place 4 different balls side by side or only move the single one to each new position.
Your code has an off by one error in the for loop. It should be a less than sign rather than less than or equal too. Your current code is going to create five purple balls.
You shouldn't really let i equal anything but an integer it's confusing. You could rewrite the code like this and it would do the same thing, without the worry of the float/double errors you're currently getting. Note that I will also use a constant called maxBalls, and distanceBetweenBalls so that you can easily change the number of balls and the distance without any complicated rewriting:
let maxBalls = 4
for(var i = 0; i < maxBalls; i++) {
let purpleBall = SKSpriteNode(texture: purpleTexture)
let ballXPosition = .50 + (i * .1)
purpleBall.position = CGPointMake(self.frame.size.width * ballXPosition, self.frame.size.height * 0.92)
self.addChild(purpleBall)
This should avoid the issues you were facing before, hope that helps.