Spritekit - Create at Random Position Without Overlapping

SpriteKit - Create at random position without overlapping

I am sure there are a few different ways to attack the problem and this would be the closest to what you already wrote.

let sprites = [SKSpriteNode]() //loaded with your sprites to spawn
let maxX = size.width //whatever your max is
let maxY = size.height //whatever your max is

var spritesAdded = [SKSpriteNode]()

for currentSprite in sprites{

addChild(currentSprite)

var intersects = true

while (intersects){

let xPos = CGFloat( Float(arc4random()) / Float(UINT32_MAX)) * maxX
let yPos = CGFloat( Float(arc4random()) / Float(UINT32_MAX)) * maxY

currentSprite.position = CGPoint(x: xPos, y: yPos )

intersects = false

for sprite in spritesAdded{
if (currentSprite.intersectsNode(sprite)){
intersects = true
break
}
}
}

spritesAdded.append(currentSprite)
}

Things to consider is if you don't already know where it is safe to add a sprite you run the risk of performance. For instance adding your last sprite may take 1,000,000 attempts before it randomly picks a point that doesn't intersect with others.

If you don't have to have them all added at once I would attack the problem on the update loop and do something like this...

var sprites = [SKSpriteNode]() //loaded with your sprites to spawn
var maxX : CGFloat = 0.0 //whatever your max is
var maxY : CGFloat = 0.0 //whatever your max is

var spritesAdded = [SKSpriteNode]()

override func didMoveToView(view: SKView) {
maxX = size.width //whatever your max is
maxY = size.height //whatever your max is
}

func addSprite(){

if let currentSprite = sprites.first {

let xPos = CGFloat( Float(arc4random()) / Float(UINT32_MAX)) * maxX
let yPos = CGFloat( Float(arc4random()) / Float(UINT32_MAX)) * maxY

currentSprite.position = CGPoint(x: xPos, y: yPos )

for sprite in spritesAdded{
if (currentSprite.intersectsNode(sprite)){
return
}
}

addChild(currentSprite)
spritesAdded.append(currentSprite)
sprites.removeFirst()
}
}

override func update(currentTime: NSTimeInterval) {
addSprite()
}

Kind of rough but the idea is every update it will try to add a sprite. This way if you run out of room it won't lock up. Hopefully that helps.

Set node to random Y position without overlapping

Here is the code for how to search an array:

for(int i=0;i<arrayNode.count;i++)
{
if(arrayNode[0].yPosition == randomNumber) //Random number
//Pick another number
}

Deleting from an array can be a bit more tricky. However, if you make the array an NSMutableArray, you get access to the removeObject: method. When you need to remove the node, you should have access to it, probably through some contact event. You can pass this node into:

[arrayNode removeObject: yourNode];

Random Node Location without Overlap

I suggest creating a function that assigns the node to a random spot.

func randomSpot(node: SKSpriteNode) {
let index2 = arc4random() % 3;
if index2 == 0 {
node.position = CGPoint(x: CGRectGetMidX(self.frame) - 100 , y: CGRectGetMidY(self.frame))
} else if index2 == 1 {
node.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
} else if index2 == 2{
node.position = CGPoint(x: CGRectGetMidX(self.frame) + 100, y: CGRectGetMidY(self.frame))
}
}

Then actually randomize it:

randomSpot(sprite1)

Make sure it isn't in the same spot:

randomSpot(sprite2)
while sprite2.position == sprite1.position {
randomSpot(sprite2)
}

And for sprite3:

randomSpot(sprit3)
while sprite3.position == sprite1.position || sprite3.position == sprite2.position {
randomSpot(sprite3)
}

Note: you can do it with an extension:

extension SKSpriteNode {
func randomSpot() {
let index2 = arc4random() % 3;
if index2 == 0 {
self.position = CGPoint(x: CGRectGetMidX(self.parent!.frame) - 100 , y: CGRectGetMidY(self.parent!.frame))
} else if index2 == 1 {
self.position = CGPoint(x: CGRectGetMidX(self.parent!.frame), y: CGRectGetMidY(self.parent!.frame))
} else if index2 == 2{
self.position = CGPoint(x: CGRectGetMidX(self.parent!.frame) + 100, y: CGRectGetMidY(self.parent!.frame))
}
}

}

And then you would call it:

sprite1.randomSpot()

How do I spawn multiple nodes without them overlapping?

Here's how to check if a node exists at a particular position.

You might want to throw the check into a loop so that if the position is taken, it will retry with a newly generated point. Otherwise you'll get some dots that just won't show. Just depends on what you're doing with them.

override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()

runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(blackDots),
SKAction.waitForDuration(1.0)])))
}

func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}

func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}

func blackDots() {
let dot = SKSpriteNode(imageNamed: "first@2x")
dot.size = CGSizeMake(75, 75)
dot.name = "dotted"

let position = CGPointMake(500 * random(min: 0, max: 1), 500 * random(min: 0, max: 1))

if positionIsEmpty(position) {
dot.position = position
addChild(dot)
}
}

func positionIsEmpty(point: CGPoint) -> Bool {
self.enumerateChildNodesWithName("dotted", usingBlock: {
node, stop in

let dot = node as SKSpriteNode
if (CGRectContainsPoint(dot.frame, point)) {
return false
}
})
return true
}

How to prevent spawning overlap in swift sprite kit?

Not spawning a node on top of another is simply enough by using intersectsNode(_ node: SKNode) -> Bool.

As for not spawning too close, that's another story. The only way you can do that is too have all your current nodes in an array, enumerate the array and check each node's position to that of the spawning node. Dependent on your parameters, you either spawn or not spawn.


I am not versed in Swift so you will have to translate the code yourself.

-(void)testMethod {

// an array with all your current nodes
NSMutableArray *myArray = [NSMutableArray new];

// the potential new spawn node with the proposed spawn position
SKSpriteNode *mySpawnNode = [SKSpriteNode new];

BOOL tooClose = NO;

// enumerate your node array
for(SKSpriteNode *object in myArray) {

// get the absoulte x and y position distance differences of the spawn node
// and the current node in the array
// using absolute so you can check both positive and negative values later
// in the IF statement
float xPos = fabs(object.position.x - mySpawnNode.position.x);
float yPos = fabs(object.position.y - mySpawnNode.position.y);

// check if the spawn position is less than 10 for the x or y in relation
// to the current node in the array
if((xPos < 10) || (yPos < 10))
tooClose = YES;
}

if(tooClose == NO) {
// spawn node
}
}

Note that the array should be a property and not declared in the scope I have it in the example.

How to re-spawn in a different place once unhidden

To spawn a node at random point within given rectangle you can use this method(s):

 func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}

func randomPointWithinRectangle(rectangle: CGRect) -> CGPoint {

var point : CGPoint = CGPoint()

point.x = randomBetweenNumbers(rectangle.origin.x + sprite.size.width / 2, secondNum: rectangle.origin.x + rectangle.size.width - sprite.size.width / 2)

point.y = randomBetweenNumbers(rectangle.origin.y + sprite.size.width / 2, secondNum: rectangle.origin.y + rectangle.size.height - sprite.size.width / 2)


return point

}

Next thing is hiding/unhiding part... Note that I am currently on outdated version of Swift :D, and what works for me, might not work for you. But logic is the same.

To achieve that, you can use SKAction sequence, like this:

func startSpawningSprites(){

let wait = SKAction.waitForDuration(0.5)

let fadeOut = SKAction.fadeOutWithDuration(0.5)

let fadeIn = SKAction.fadeInWithDuration(0.3)

//Use weak reference to self to prevent retain cycle. in Swift 2.0 you should use appropriate syntax.
weak var weakSelf = self

let changePosition = SKAction.runBlock({

weakSelf!.sprite.position = weakSelf!.randomPointWithinRectangle(weakSelf!.rectangle.calculateAccumulatedFrame())
})

let sequence = SKAction.sequence([wait, fadeOut, changePosition, wait, fadeIn])

sprite.runAction(SKAction.repeatAction(sequence, count:10))

}

Here, the code is pretty much straight forward and it does what I stated above(hide,move and unhide the node). A rectangle variable is defined as a property just for easier debugging.

About other part of your question..I am not sure if I understand it...But I can give it a try. If you care about overlapping between nodes and you want to avoid it, then you have to check if there is a node at given point (nodeAtPoint method might be useful). Theoretically this can be resource consuming. If interested why, read more here.

EDIT:

To answer the question from your comment :

Variables sprite and rectangle are defined as properties of a GameScene:

class GameScene:SKScene{

let sprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 30, height: 30))

//just for debugging purposes
let rectangle = SKSpriteNode(color: SKColor.orangeColor(), size: CGSize(width: 150, height: 150))

override func didMoveToView(view: SKView) {

setupScene()


startSpawningSprites()
}


func setupScene(){

sprite.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
sprite.zPosition = 4
sprite.name = "sprite" //later on, you can access or enumerate node(s) by name

addChild(sprite)

rectangle.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
rectangle.alpha = 0.5
rectangle.zPosition = 3
rectangle.name = "rectangle"
addChild(rectangle)

}

func startSpawningSprites(){
//...
}

}

The end result is intended to look like this :
random spawning



Related Topics



Leave a reply



Submit