SpriteNode disappears when returning to GameScene - swift

I have a background sprite that I display in my GameScene. When I go to another scene (BuildViewController) and come back by background is gone. I'm sure it is a simple fix and it isn't anymore complicated than what I have said above. Heres some code :)
while length <= 6 {
while wide <= 6 {
let imageViewBackground = UIImageView(frame: CGRect(x:0 + (wide*100), y:0 + (length*200), width: 100, height: 200))
imageViewBackground.image = UIImage(named: "grass.png")
self.view?.addSubview(imageViewBackground)
self.view?.sendSubview(toBack: imageViewBackground)
wide = wide + 1
}
wide = 0
length = length + 1
}

You may be finding that by adding your imageView to the view controller along with the SKScene that things are drawing all oddly (it could also be that your imageView is getting dealloc'd but I can't say based on your code).
I'm assuming you're using the basic Xcode template to set up your views and stuff; so to make the grass, try something like this in your GameScene class instead of the code above (which I assume is in your GameViewController class). You can put the code in didMove(to view: SKView) or you can add a sceneDidLoad() func. Make a SKSpriteNode for the grass and tile it like you are doing already. The two methods I mention here are the ones most closely related to the viewDidLoad and viewWillAppear methods in UIViewController land.
while length <= 6 {
while wide <= 6 {
let grassSprite = SKSpriteNode(imageNamed: "grass.png")
grassSprite.position = CGPoint(x: (wide * 100), y: (length * 200))
self.addChild(grassSprite
wide += 1
}
wide = 0
length += 1
}
All of this being said, it appears you are trying to make a grass background for something, in which case, you should probably look into SKTileGroup which has some nice conveniences.

Related

Swift - Sprite Kit floating bubbles stuck to corners

I'm trying to create a floating bubble view on my Watch App. The bubbles can collide & bounce off each other & the sides of the screen. But for some reason the bubbles are appearing out of the view bounds & getting stuck on the sides of the frame instead of bouncing off. This code works as expect on my iOS application but when using the same code in my Watch app, it doesn't.
It doesn't make much sense to me that this exact code works perfectly on my iOS app but not on the Watch App.
I'm passing the below code into a SpriteView in my SwiftUI View
let ballCategory: UInt32 = 0xb0001
let edgeCategory: UInt32 = 0xb0010
var nodeCount = 0
override func sceneDidLoad() {
//set physicsWorld properties
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
//set edges as PhysicsBody
let edge = SKPhysicsBody(edgeLoopFrom: self.frame)
edge.friction = 0
edge.categoryBitMask = edgeCategory
self.physicsBody = edge
makebubble()
makebubble()
makebubble()
makebubble()
}
func makebubble() {
let bubbleTexture = SKTexture(imageNamed: "bubble")
let bubble = SKSpriteNode(texture: bubbleTexture)
let bphysicsBody = SKPhysicsBody(circleOfRadius: bubbleTexture.size().height/2)
bphysicsBody.isDynamic = true
bphysicsBody.usesPreciseCollisionDetection = true
bphysicsBody.restitution = 0.5
bphysicsBody.friction = 0
bphysicsBody.angularDamping = 0
bphysicsBody.linearDamping = 0
bphysicsBody.categoryBitMask = ballCategory
bphysicsBody.collisionBitMask = ballCategory | edgeCategory
bphysicsBody.contactTestBitMask = ballCategory | edgeCategory
bubble.physicsBody = bphysicsBody
bubble.name = "bubble"
// Get a random possition within the width of the scene
let x = CGFloat(randomize(number: Int(size.width - 40)))
let y = CGFloat(randomize(number: Int(size.height - 40)))
// position the bubble
bubble.position.x = x
bubble.position.y = y
// Add the bubble
addMyChild(node: bubble)
}
func addMyChild(node:SKSpriteNode){
self.addChild(node)
node.physicsBody!.applyImpulse(CGVector(dx: 10.0, dy: -2.0))
nodeCount += 1
}
// function that returns a random int from 0 to n-1
func randomize(number: Int) -> Int{
return Int(arc4random()) % number
}
This has nothing to to with watchOS and everything to do with the small screen size of Apple Watches. Try running your code on iOS with a frame modifier of width 150 and height 150 and you'll see what I mean; the bubbles will likely stick to the side.
Your bubbles look like they stick to the edges because they slow down over time (due to restitution being 0.5 instead of 1) and it's statistically more probable for a bubble to have its final movement close to the screen edge (since they will eventually move to the edge, bounce off from it, thus slowing down and eventually halting).
Here are 3 things you can do about this:
as mentioned, increase restitution to 1 (this is optional, as it won't solve the "sticking to the edge" problem on its own, but it helps making the slowing down issue better)
detect when the bubbles stop (you can do this by checking the x and y velocity in the update(_:) function of your SKScene) and make a force that moves them slightly in a random direction. If you are in a fancy mood, you can even make a timer to make random, barely noticable forces that act like small air movements (chances are, it will even make the animation a bit more realistic)
create an outside bounding box with slightly non-linear/circular borders and corners to make the bubbles bounce off the walls in a different way

Convert between UIKit and SpriteKit coordinate systems

I'm new to iOS programming, and have almost no experience with SpriteKit, so please forgive me if this is a ridiculous question.
I've been trying to make a basic grid with a 2D array, and I would prefer to work with it from top-left being 0, 0.
After researching the differences in coordinate systems between UIKit and SpriteKit, I came across this answer about Converting Between View and Scene Coordinates but it doesn't seem to change the y value the way I thought it would. I am guessing that I'm not using it right, or maybe this is not what it's meant to do, I don't know.
When I try this:
let convertedCoordinates = convert(cellCoordinates, to: grid)
print(cellCoordinates.y, convertedCoordinates.y)
it doesn't seem to have any effect on the y value.
I have found that when I change to "y: -cy" in the line let cellCoordinates = CGPoint(x: cx, y: cy)
Then it does seem to work the way I am hoping for, but I don't know if that's the only solution or if doing this will work as expected under more complicated situations.
Here is the code I am working with:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
var background: SKShapeNode!
background = SKShapeNode(rectOf: CGSize(width: frame.size.width, height: frame.size.height))
background.fillColor = SKColor.lightGray
self.addChild(background)
let margin = CGFloat(50)
let width = frame.size.width - margin
let height = frame.size.height - margin
let centerX = frame.midX - width / 2
let centerY = frame.midY - height / 2
var grid: SKShapeNode!
grid = SKShapeNode(rectOf: CGSize(width: width, height: height))
grid.strokeColor = SKColor.clear
self.addChild(grid)
let numRows = 2
let numCols = 3
let cellWidth = width / CGFloat(numCols)
for r in 0..<numRows {
for c in 0..<numCols {
let cx = centerX + (cellWidth / 2) + (CGFloat(c) * cellWidth)
let cy = centerY + (cellWidth / 2) + (CGFloat(r) * cellWidth)
//***
let cellCoordinates = CGPoint(x: cx, y: cy)
//***
let cellNode = SKShapeNode(rectOf: CGSize(width: cellWidth, height: cellWidth))
let convertedCoordinates = convert(cellCoordinates, to: grid)
print(cellCoordinates.y, convertedCoordinates.y)
cellNode.strokeColor = SKColor.black
cellNode.lineWidth = 5
cellNode.fillColor = SKColor.darkGray
cellNode.position = convertedCoordinates
let textNode = SKLabelNode(text: String("\(r),\(c)"))
textNode.fontName = "Menlo"
textNode.fontSize = 60
textNode.verticalAlignmentMode = .center
textNode.position = convertedCoordinates
grid.addChild(cellNode)
grid.addChild(textNode)
}
}
}
}
This is more a philosophical answer than an implementation one. As far as somehow flipping SpriteKit's coordinate system, well, you're going to be fighting it constantly. Better to just embrace the system as it is.
The essence of your question though is more one of separation of model and view. When you say
I would prefer to work with it from top-left being 0, 0
what you mean is that mentally you're thinking of the game as a grid of cells with 0,0 at the top left. That's perfectly fine and natural. That's your model of the game. But what are you writing in the code?
let cx = centerX + (cellWidth / 2) + (CGFloat(c) * cellWidth)
let cy = centerY + (cellWidth / 2) + (CGFloat(r) * cellWidth)
let cellCoordinates = CGPoint(x: cx, y: cy)
let convertedCoordinates = convert(cellCoordinates, to: grid)
That's your view struggling to get out. You have the abstract model grid that you're indexing with r,c with 0,0 at the upper left and whose coordinates increase in unit steps down and to the right. Then there's the view of the model, which might depend on screen resolution, aspect ratio, device orientation, whatever. If you keep the two mentally separate, you'll usually find that you can isolate the translation between the two systems to a small interface. In those places you may have to do things like scale the axes or flip one of them, or stretch things in one direction to match aspect ratios.
In a case like this, if you start with your mental model with your preferred 0,0 in the upper left and think about how the game operates, it'll often be in terms of the cells. OK, that suggests that maybe a 2D array or an array of arrays is natural. Maybe the cells will eventually become a class in your game. They'll probably have a node property that stores the SpriteKit node. You might wind up with something like this:
struct boardPosition {
let row: Int
let col: Int
}
class Cell {
let pos: boardPosition
let node: SKNode
init(pos: boardPosition, in board: Board) {
self.pos = pos
node = SKShapeNode(...)
board.node.addChild(node)
}
}
class Board {
let cells: [[Cell]]
let node: SKNode
init(numRows: Int, numColumns: Int) {
...
}
func movePiece(from: boardPosition, to: boardPosition) {
let piece = cell[from.row][from.col].removePiece()
cell[to.row][to.col].addPiece(piece)
}
}
The operation of the game will be in terms of your mental model. The fact that the y-coordinates of the cells' SKNode nodes happen to decrease as the row index increases will be completely buried.
Set all nodes applicable and scene’s anchor point to 0,1 to get it to mount to the top left corner and set your world node’s (if you do not have one, I recommend adding it, it is a basic SKNode that you use to place all of your game nodes in, allowing you to use a separate node for things not applicable to the game world, like hud and overlays) yScale to -1 to have y increment downward instead of upward.
Edit:
When dealing with SKShapeNodes, you do not have to worry about the images being inversed unless you have an obscure shape. When designing the CGPath for the obscure shape, just flip it.
shape.path = shape.path!.copy(using:CGAffineTransform(scaleX:1,y:-1))
The bigger problem is SKShapeNode does not have anchor points. You instead need to move the entire CGPath
To do this, add the following line:
shape.path = shape.path!.copy(using:CGAffineTransform(translationX:shape.frame.width/2,y:shape.frame.height/2))
If dealing with SKSprite nodes in the future....
This will cause your assets to be upside down, so all you would need to do is have your assets flipped before import, use a secondary node to flip the y axis, or assign all nodes with a yScale of -1. Flipping all of your assets prior to import vertically would be the cheapest method, I believe you can flip it inside xcassets as well, but I need to verify that when I get back on a MacOS again.

Black Line in Vertical Moving Swift Background

i have a Problem and need your help.
I have modified a code from this old thread
here
That i get a vertical moving background. The code works nice. But i have between every moving background image a tiny black line.
looks like something overlaps. But why?
It would be nice if anyone can help me to fix this error.
Thanks alot
Here is my code:
var bg = SKSpriteNode()
var bg2 = SKSpriteNode()
var bg3 = SKSpriteNode()
var parallax = SKAction()
override func didMove(to view: SKView) {
bg = SKSpriteNode(imageNamed: "back1")
bg.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bg.zPosition = 1
bg.size = self.size
bg2 = SKSpriteNode(imageNamed: "back2")
bg2.position = CGPoint(x: self.size.width/2, y: self.size.height/2+self.size.height)
bg2.zPosition = 1
bg2.size = self.size
bg3 = SKSpriteNode(imageNamed: "back3")
bg3.position = CGPoint(x: self.size.width/2, y:self.size.height/2+self.size.height+self.size.height)
bg3.zPosition = 1
bg3.size = self.size
self.addChild(bg)
self.addChild(bg2)
self.addChild(bg3)
parallax = SKAction.repeatForever(SKAction.move(by: CGVector(dx: 0, dy: -self.frame.size.height), duration: 4))
bg.run(parallax)
bg2.run(parallax)
bg3.run(parallax)}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bg.position.y <= -self.frame.size.height {
bg.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg2.position.y <= -self.frame.size.height {
bg2.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg3.position.y <= -self.frame.size.height {
bg3.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
}
You are having floating point rounding issues. The concept of 1/2 a pixel does not exist, so when you get to a position like 12.5, the system needs to either make it 12, or 13.
Now, since decimal does not convert well to fractal binary you are going to end up with numbers like 12.499929932092434234234324 and 12.50000000342423423424, but as far as you know, it is still 12.5.
To fix this, you need to force your position to always round in the same direction, either up or down.
You are probably going to want to round down since most grids work in a 0 based indexing system.
To fix your code, we need to do:
override func update(_ currentTime: TimeInterval) {
bg.position.x.round(.down)
bg.position.y.round(.down)
bg2.position.x.round(.down)
bg2.position.y.round(.down)
bg3.position.x.round(.down)
bg3.position.y.round(.down)
// Called before each frame is rendered
if bg.position.y <= -self.frame.size.height {
bg.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg2.position.y <= -self.frame.size.height {
bg2.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg3.position.y <= -self.frame.size.height {
bg3.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
}
Now of course, as you get better with development, you are going to want to move these types of things outside of your update function. Eventually, you will want to perform these checks when position changes, this way if nothing is moving, you are not needlessly executing these lines of code to fix a position that does not need fixing.
Edit:
Noticed another problem, you need to do += the height, not = height, because if you are at -1, that will also cause a gap, since you are not accounting for the line of height - 1 (Basically, your first image ends at height - 2, and your new image starts at height, making a gap at height - 1)

Fast-paced SpriteKit game has irregular CPU activity and is jittery/lags despite frame rate staying high - Swift

I'm having the issue on a simple but fast-paced SpriteKit game, but I've reduced my code just to a bouncing ball and still get the issue to a lesser extent:
override func didMove(to view: SKView) {
super.didMove(to: view)
physicsWorld.contactDelegate = self
physicsWorld.speed = 1
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
borderBody.contactTestBitMask = BallCategory
addBall()
}
func addBall() {
let size = CGSize(width: 20, height: 20)
let position = CGPoint(x: frame.width / 2, y: 50)
let texture = SKTexture(image: #imageLiteral(resourceName: "whiteCircle"))
let ball = SKSpriteNode(texture: texture, size: size)
ball.position = position
ball.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
ball.fillColor = .white
ball.lineWidth = 0
addStandardProperties(node: ball, name: "ball", z: 5, contactTest: 0, category: BallCategory)
ball.physicsBody?.isDynamic = true
addChild(ball)
launchBall()
}
func addStandardProperties(node: SKNode, name: String, z: CGFloat, contactTest: UInt32, category: UInt32) {
node.name = name
node.zPosition = z
node.physicsBody?.isDynamic = false
node.physicsBody?.affectedByGravity = false
node.physicsBody?.mass = 0
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0
node.physicsBody?.linearDamping = 0
node.physicsBody?.angularDamping = 0
node.physicsBody?.angularVelocity = 0
node.physicsBody?.contactTestBitMask = contactTest
node.physicsBody?.categoryBitMask = category
}
func launchBall() {
let ball = childNode(withName: "ball")!
ball.physicsBody?.velocity = CGVector(dx: 0, dy: 500)
}
This code results in a ball (SKSpriteNode) bouncing up and down. When I run this, CPU usage starts at around 10% on my iPhone 6s and then after increases to around 25-30% after maybe 30-60 seconds (no idea why it's increasing). Throughout all of this, the frame rate stays very close to 60 FPS, usually going no lower than 58 FPS (it's the same way when I run the full game).
Almost any time an alert pops up (e.g., text messages, logging into Game Center, etc.), the lag shows up and shows up at random times when I'm running the full game.
I've also tried deleting and re-running the app, cleaning the project, deleting derived data and running in Release mode. None of these worked permanently.
Should I give up on SpriteKit and try another framework? If so, which? Cocos2D?
Any help is appreciated.
This is the result of Apple prioritising system calls over just about everything else.
When the system wants to know something, check something or otherwise do its thing it does so at the mercy of everything else.
No other engine will be able to help with this, there's no way to silence the system's constant activities through code.
You can get a slight improvement by putting on Flight Mode and turning off WIFI and Bluetooth. The system seems to be somewhat aware that it's in a quieter mode and does less because it's got no 4G or other connectivity it can go communicating with.
Further, there's been some pretty big changes to palm rejection in iOS 11 that's played havoc with the first round of iPad Pro models and creative software, creating multi-second rejection of all touch input. When this kind of thing can make it through to a GM you can be pretty sure they're slipping other messiness through.
Here's some complaints about iOS 11 performance: https://www.macrumors.com/2017/09/25/ios-11-app-slowdowns-performance-issues/
Turns out I had 2 SKViews in my view controller. By default, when you start a project as a SpriteKit game, Xcode sets the view controller root/superview of the GameViewController as an SKView. At some point, I had added a second SKView because I didn't intend for the scene to take up the entire screen and I apparently didn't realize that the VC root view was still set as an SKView. So every time GameViewController loaded, it was loading two SKViews, which is why I saw 120 FPS in Xcode.
I fixed the issue by simply removing the SKView class designation from the VC root view.

How to generate a new node when another one hits a certain y value

I have a circle moving up a line, and when that circle reaches a certain y point, how can I make it so that another node would generate from below?
Here is the code I currently have for populating the circles, but I am not able to use it with a physics body, as it generates too many nodes and slows down my app:
func createCirclesOnLine(line: CGFloat) {
var currentY : CGFloat = -110
let maxY = self.size.width * 15
let spacing : CGFloat = 120
while currentY < maxY {
let circle = SKSpriteNode(imageNamed: "first#2x")
circle.physicsBody?.dynamic = false
circle.position = CGPointMake(line, currentY)
//circle.physicsBody?.restitution = -900
circle.size = CGSizeMake(75, 75)
// circle.physicsBody = SKPhysicsBody(rectangleOfSize: circle.size)
let up = SKAction.moveByX(0, y: 9000, duration: 90)
circle.runAction(up)
foregroundNode.addChild(circle)
currentY += CGFloat((random() % 400) + 70)
}
Will post more code if necessary.
There are two ways you can go about this. One is to simply check every circle's y position to see if it's above the screen. You'll need a reference to the circles so...
class GameScene: SKScene {
var circles = Array<SKSpriteNode>()
...
In your createCirlcesOnLine function, add each circle to the array as you create it.
...
self.addChild(circle)
circles.append(circle)
Then, in your update method, enumerate through the circles to see if any of them are above the top of the screen.
override func update(currentTime: NSTimeInterval) {
for circle in circles {
if circle.position.y > self.size.height + circle.size.height/2 {
//Send circle back to the bottom using the circle's position property
}
}
}
This solution will work but causes a lot of unnecessary checks on every update cycle.
A second more efficient (and slightly more complicated) recommendation is to add an invisible node above the top of the screen that stretches the screen width. When the circle collides with it, just move it to the bottom of the screen. Look into implementing the SKPhysicsContactDelegate protocol and what needs to happen for that to work. If you run into problems with this solution, post a separate question with those issues.