Reputation: 255
I am creating a mobile application in Swift 3's SpriteKit. I am trying to create two rocks that a character has to dodge. I have the random generation working with ->
func createTopRock(){
var topRockChoice = [SKTexture(image: #imageLiteral(resourceName: "rockDown")), SKTexture(image: #imageLiteral(resourceName: "rockGrassDown")), SKTexture(image: #imageLiteral(resourceName: "rockSnowDown")), SKTexture(image: #imageLiteral(resourceName: "rockIceDown"))]
let topRock = SKSpriteNode.init(texture: topRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
topRock.zPosition = -9
topRock.name = "TopRock"
topRock.position = CGPoint(x: self.frame.width + topRock.size.width * 2, y: frame.maxY - topRock.frame.height / 2)
topRock.physicsBody = SKPhysicsBody(texture: topRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
topRock.physicsBody?.categoryBitMask = physicsCatagory.topRock
topRock.physicsBody?.collisionBitMask = physicsCatagory.plane
topRock.physicsBody?.contactTestBitMask = physicsCatagory.plane
topRock.physicsBody?.affectedByGravity = false
topRock.physicsBody?.isDynamic = false
self.addChild(topRock)
let randomNumTop = arc4random_uniform(3) + 3
spawnDelayForeverTop = Timer.scheduledTimer(timeInterval: TimeInterval(randomNumTop), target: self, selector: #selector(self.createTopRock), userInfo: nil, repeats: false)
}
func createBtmRock(){
var btmRockChoice = [SKTexture(image: #imageLiteral(resourceName: "rock")), SKTexture(image: #imageLiteral(resourceName: "rockGrass")), SKTexture(image: #imageLiteral(resourceName: "rockSnow")), SKTexture(image: #imageLiteral(resourceName: "rockIce"))]
let btmRock = SKSpriteNode.init(texture: btmRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
btmRock.zPosition = -9
btmRock.position = CGPoint(x: self.frame.width, y: frame.minY + btmRock.frame.height / 2)
btmRock.name = "BtmRock"
btmRock.physicsBody = SKPhysicsBody(texture: btmRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
btmRock.physicsBody?.categoryBitMask = physicsCatagory.topRock
btmRock.physicsBody?.collisionBitMask = physicsCatagory.plane
btmRock.physicsBody?.contactTestBitMask = physicsCatagory.plane
btmRock.physicsBody?.affectedByGravity = false
btmRock.physicsBody?.isDynamic = false
self.addChild(btmRock)
let randomNumBtm = arc4random_uniform(2) + 1
spawnDelayForeverBtm = Timer.scheduledTimer(timeInterval: TimeInterval(randomNumBtm), target: self, selector: #selector(createBtmRock), userInfo: nil, repeats: false)
}
As you can see from the code everything works. The one thing I do not want to do is have it like Flappy Bird and have the btmRock
and topRock
to have the same position, so to prevent that I have added the timeIntervals
to be different. This works to an extent; but the player still runs into some rocks that are nearly impossible to go through.
I feel like I am going about this wrong and I don't know how to fix this. I want them to be close enough sometimes to be hard but not nearly impossible. When I tried to implement a bool
system to when a top rock is created and is within a certain distance don't create a bottom rock. What I ran into trying to do this is that I would never have a bottom rock after that occurred. Thanks in advance.
Upvotes: 2
Views: 51
Reputation: 6061
Personally I wouldn't use Timer in my SpriteKit game (for reasons that you can find all over SO). A very simple solution could be to call the generation of the rocks from your Update func.
I would also create a base bottom rock and base top rock and make copies of it as needed, because creating physics objects on the fly can get expensive (or I would create an array of the objects at start and pull from the array as needed)
P.S. I wrote this off the cuff, so I cannot guarantee typo's don't exist ;)
var genInterval = 4 //How many seconds between rocks on bottom or top (not between top & bottom. that will be half of this value)
var genOffset = genInterval / 2 //offset the bottom rocks half of the interval so that they do't line up
var bottomRock: SKSpriteNode! //your base bottom rock for copying
var topRock: SKSpriteNode! //your base top rock for copying
var updateTopTime: Double = 0
var updateBottomTime: Double = 0
func setupRocks() {
//create the base bottom rock
var btmRockChoice = [SKTexture(image: #imageLiteral(resourceName: "rock")), SKTexture(image: #imageLiteral(resourceName: "rockGrass")), SKTexture(image: #imageLiteral(resourceName: "rockSnow")), SKTexture(image: #imageLiteral(resourceName: "rockIce"))]
bottomRock = SKSpriteNode(texture: btmRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
btmRock.zPosition = -9
btmRock.position = CGPoint(x: self.frame.width, y: frame.minY + btmRock.frame.height / 2)
btmRock.name = "BtmRock"
btmRock.physicsBody = SKPhysicsBody(texture: btmRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
btmRock.physicsBody?.categoryBitMask = physicsCatagory.topRock
btmRock.physicsBody?.collisionBitMask = physicsCatagory.plane
btmRock.physicsBody?.contactTestBitMask = physicsCatagory.plane
btmRock.physicsBody?.affectedByGravity = false
btmRock.physicsBody?.isDynamic = false
//create the base top rock
var topRockChoice = [SKTexture(image: #imageLiteral(resourceName: "rockDown")), SKTexture(image: #imageLiteral(resourceName: "rockGrassDown")), SKTexture(image: #imageLiteral(resourceName: "rockSnowDown")), SKTexture(image: #imageLiteral(resourceName: "rockIceDown"))]
topRock = SKSpriteNode(texture: topRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
topRock.zPosition = -9
topRock.name = "TopRock"
topRock.position = CGPoint(x: self.frame.width + topRock.size.width * 2, y: frame.maxY - topRock.frame.height / 2)
topRock.physicsBody = SKPhysicsBody(texture: topRockChoice[mapChoice], size: CGSize(width: (self.scene?.size.width)! / 10, height: (self.scene?.size.height)! / 2.2))
topRock.physicsBody?.categoryBitMask = physicsCatagory.topRock
topRock.physicsBody?.collisionBitMask = physicsCatagory.plane
topRock.physicsBody?.contactTestBitMask = physicsCatagory.plane
topRock.physicsBody?.affectedByGravity = false
topRock.physicsBody?.isDynamic = false
}
override func update(_ currentTime: CFTimeInterval) {
//optional prevents generation if game is not playing
guard gameState == .playing else { return }
if updateTopTime == 0 {
updateTopTime = currentTime
}
if updateBottomTime == 0 {
updateBottomTime = currentTime
}
if currentTime - updateBottomTime > genOffset {
createBtmRock()
genOffset = genInterval
updateBottomTime = currentTime
}
else if currentTime - updateTopTime > genInterval {
createTopRock()
updateTopTime = currentTime
}
}
func createTopRock() {
//You can make this number a class variable to increase the rate as the game progresses
let randomNum = arc4random_uniform(3)
//there is a 1 in 3 chance that this rock will get created
if randomNum == 1 {
let rock = topRock.copy as! SKSpriteNode()
self.addChild(rock)
}
}
func createBtmRock() {
//You can make this number a class variable to increase the rate as the game progresses
let randomNum = arc4random_uniform(2)
//there is a 1 in 2 chance that this rock will get created
if randomNum == 0 {
let rock = bottomRock.copy as! SKSpriteNode()
self.addChild(rock)
}
}
Upvotes: 1
Reputation: 9777
The logic in your final paragraph seems like a plausible solution. You will need to implement some kind of custom logic to prevent a top and bottom rock from spawning within some set distance.
Inside your createBtmRock
function, when you set the new rock's position, check to see if it is close to any existing top rocks. You can determine closeness by comparing the position
property of the new rock to all existing rocks.
If you find the new rock would be too close to existing rocks, don't add the new rock to the scene, and continue the timer to make sure you generate bottom rocks in the future.
This is a simple solution to your immediate problem. You may want to look into procedural generation strategies for generating your rocks. If you want to refine the behavior later (by making rocks harder to pass as the game progresses, for example), you will need a more permanent solution.
Consider creating a separate function that will generate positions for your rocks. You can handle all of the position generation logic separately and use the result to position your rocks.
func generateRockPositions() -> [CGPoint]
You could do this in advance when the user starts the game, or generate positions as the game progresses. This will allow you to make changes to your procedural generation algorithm in the future more easily.
Hopefully that helps, best of luck with your game!
Upvotes: 1