im creating a class to add my Meteors to the scene:
import SpriteKit
class Meteor: SKShapeNode {
var meteorNode : SKShapeNode?
var size : CGSize!
//Acoes
var acaoApagar = SKAction.removeFromParent()
var acaoAndar : SKAction!
//Numero Randomico
var numRandom : Int = 0
var textoStringMeteor : String!
//Colisoes
let CollisionShoot : UInt32 = 0x1 << 0
let CollisionHouse : UInt32 = 0x1 << 1
let CollisionMeteor : UInt32 = 0x1 << 2
init(size : CGSize, pontoMeteor: CGPoint, textoMeteor: String)
{
super.init()
//super.init(ellipseOfSize: <#CGSize#>)
self.size = size
self.textoStringMeteor = textoMeteor
self.acaoAndar = SKAction.moveTo(pontoMeteor, duration: 10)
criarMeteoro()
setarTexto(self.textoStringMeteor)
}
func setarTexto(numeroLabel : String) {
let numNode = SKLabelNode(fontNamed:"Chalkduster")
numNode.text = numeroLabel;
numNode.fontSize = 20;
numNode.position = CGPoint(x: 0, y: -10);
numNode.name = "textoNode"
meteorNode?.addChild(numNode)
}
func criarMeteoro() {
var randomX = CGFloat(Int(arc4random()) % Int(size.width))
//Meteor Node
meteorNode = SKShapeNode(circleOfRadius: CGFloat(20))
meteorNode!.physicsBody = SKPhysicsBody(circleOfRadius: 20)
meteorNode!.physicsBody!.dynamic = true
meteorNode!.fillColor = UIColor.blueColor()
meteorNode!.physicsBody!.categoryBitMask = CollisionMeteor
meteorNode!.physicsBody!.contactTestBitMask = CollisionShoot | CollisionHouse
meteorNode!.position = CGPoint(x: randomX, y: size.height + 900)
meteorNode!.name = "meteorNode"
self.addChild(meteorNode!)
meteorNode!.runAction(SKAction.sequence([acaoAndar, acaoApagar]))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here im calling the class Meteor and passing all parameters:
var meteor = Meteor(size: CGSize(width: size.width, height: size.height), pontoMeteor: ponto, textoMeteor: "30")
foreground.addChild(meteor)
And for the last i have my contact to see if there was contact between the nodes:
func didBeginContact(contact: SKPhysicsContact) {
var bodyA = contact.bodyA!.node!
var bodyB = contact.bodyB!.node!
if bodyA.name == "tiroCanhao" && bodyB.name == "meteorNode"
{
bodyB.removeAllActions()
bodyB.removeFromParent()
bodyA.removeFromParent()
self.pointsPlayer++
self.qtdtiros += 2
}
else if bodyA.name == "meteorNode" && bodyB.name == "tiroCanhao"
{
bodyA.removeAllActions()
bodyA.removeFromParent()
bodyB.removeFromParent()
self.pointsPlayer++
self.qtdtiros += 2
}
}
Now my problem is the follow:
After the contact i want to call the setarTexto method on the Meteor class of the object and change the text but that isnt working. I get this error:
Could not cast value of type 'SKShapeNode' (0x10bb5ea88) to
'MathDefense.Meteor' (0x10af6b7b0).
What im doing is this (on the if statement on contact):
let text = bodyB as! Meteor
text.setarTexto("20")
I even tried
text.textoStringMeteor = "20"
But none of those worked. I already done some research but didn't find any person with my problem or trying to do something similar.
Any solution?
Thanks.
I found the solution, to fix my error i made the following:
Remove the SKShapenode since the class is already a SKShapenode (that was the error).
Then when creating my node i made everthing with self instead of putting in the Meteornode. That fixed all.
Example:
Right: self.position = CGPoint(x: randomX, y: size.height + 900)
Wrong: meteorNode!.position = CGPoint(x: randomX, y: size.height + 900)
To pass texture if you are using a SKSpritenode just put it on your superinit.
Related
Im trying to create a dodgeball feature in my game and I need to repeatedly spawn a dodgeball and the current code I used (shown below) results in Thread 1: Exception: "Attemped to add a SKNode which already has a parent: name:'(null)' texture:[ 'dodgeball5' (500 x 500)] position:{-140.00019836425781, -55.124687194824219} scale:{1.00, 1.00} size:{30, 30} anchor:{0.5, 0.5} rotation:0.00". What am I doing incorrect?
class ClassicLevelScene: SKScene {
// Right Pointing Cannons
var rightPointingCannon: [SKReferenceNode] = []
// Dodgeball 5 Constants
var dodgeball5 = SKSpriteNode(imageNamed: "dodgeball5")
// Dodgeball SKActions
var dodgeballRepeat = SKAction()
let dodgeballMoveLeft = SKAction.moveBy(x: -400, y: 0, duration: 0.5)
let dodgeballMoveRight = SKAction.moveBy(x: 400, y: 0, duration: 0.5)
let dodgeballMoveDone = SKAction.removeFromParent()
let dodgeballWait = SKAction.wait(forDuration: 1)
var dodgeballLeftSequence = SKAction()
var dodgeballRightSequence = SKAction()
override func didMove(to view: SKView) {
// Cannon Setup
for child in self.children {
if child.name == "rightPointingCannon" {
if let child = child as? SKReferenceNode {
rightPointingCannon.append(child)
dodgeball5.position = child.position
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(spawnDodgeball),
SKAction.wait(forDuration: 1.0)
])
))
}
}
}
// Dodgeball Right Sequence
dodgeballRightSequence = SKAction.sequence([dodgeballMoveRight, dodgeballMoveDone, dodgeballWait])
}
func spawnDodgeball() {
dodgeball5.zPosition = -1
dodgeball5.size = CGSize(width: 30, height: 30)
addChild(dodgeball5)
dodgeball5.run(dodgeballRightSequence)
}
}
Assuming single ball and ball can go off screen:
I would actually recommend against cloning the sprite.
Simply reuse it.
Once it's off the screen, you can reset it's position and speed and display it again. No need to create lots of objects, if you only need one.
One SKNode only can have 1 parent at time.
You need to create a new SKSpriteNode or clone the dodgeball5 before to add again on spawnDodgeball()
You can use
addChild(dodgeball5.copy())
instead of
addChild(dodgeball5)
But be make sure dodgeball5 have no parent before.
Your code seems to be unnecessarily complex.
all you need to do is copy() you ball and add it to your scene
Also all of your SKActions don't need to be declared at a class level
And it's not clear why you are creating an array of cannons when only one is instantiated
class ClassicLevelScene: SKScene {
// Dodgeball 5 Constants
var dodgeball5 = SKSpriteNode(imageNamed: "dodgeball5")
override func didMove(to view: SKView) {
setupCannonAndBall()
let dodgeballMoveRight = SKAction.moveBy(x: 400, y: 0, duration: 0.5)
let dodgeballMoveDone = SKAction.removeFromParent()
let dodgeballWait = SKAction.wait(forDuration: 1)
dodgeballRightSequence = SKAction.sequence([dodgeballMoveRight, dodgeballMoveDone, dodgeballWait])
//setup sequences ^ before spawning ball
let spawn = SKAction.run(spawnDodgeball)
let wait = SKAction.wait(forDuration: 1.0)
run(SKAction.repeatForever(SKAction.sequence([spawn, wait])))
}
func setupCannonAndBall() {
if let cannonRef = SKReferenceNode(fileNamed: "rightPointingCannon") {
dodgeball5.position = cannonRef.position
dodgeball5.isHidden = true
dodgeball5.size = CGSize(width: 30, height: 30)
dodgeball5.zPosition = -1
}
}
func spawnDodgeball() {
let dodgeball = dodgeball.copy()
dodgeball.isHidden = false
addChild(dodgeball)
dodgeball.run(dodgeballRightSequence)
}
}
My aim is to create a fire effect using a CAEmitterLayer, whose strength can be altered via changing the value of the alphaSpeed property of its given CAEmitterCells. A smaller value of the alphaSpeed would result in a "roaring" fire, whilst a larger value would suppress the fire.
So far, I have a subclass of CAEmitterLayer called FireEmitterLayer with an initialiser given by:
convenience init(view: UIView) {
self.init()
emitterPosition = CGPoint(x: 0.5 * view.bounds.width, y: view.bounds.height)
emitterSize = CGSize(width: view.bounds.width, height: 0.05 * view.bounds.height)
renderMode = .additive
emitterShape = .line
emitterCells = fireEmitterCells
}
The emitter cells are generated for an array of UIImages representing 30x30 images of flames:
private var fireEmitterCells: FireEmitterCells {
var emitterCells = FireEmitterCells()
for assetIdentifier in assetIdentifiers {
let emitterCell = fireEmitterCell(for: assetIdentifier)
emitterCells.append(emitterCell)
}
return emitterCells
}
Each cell is created using this method:
private func fireEmitterCell(for assetIdentifier: UIImage.AssetIdentifier) -> CAEmitterCell {
let fireEmitterCell = CAEmitterCell()
fireEmitterCell.contents = UIImage(assetIdentifier: assetIdentifier).resized(to: CGSize(width: 20.0, height: 20.0)).cgImage
fireEmitterCell.alphaSpeed = -0.3
fireEmitterCell.birthRate = fireBirthRate
fireEmitterCell.lifetime = fireLifetime
fireEmitterCell.lifetimeRange = 0.5
fireEmitterCell.color = UIColor.init(red: 0.8, green: 0.4, blue: 0.2, alpha: 0.6).cgColor
fireEmitterCell.emissionLongitude = .pi
fireEmitterCell.velocity = 80.0
fireEmitterCell.velocityRange = 5.0
fireEmitterCell.emissionRange = 0.5
fireEmitterCell.yAcceleration = -200.0
fireEmitterCell.scaleSpeed = 0.3
return fireEmitterCell
}
Is there a way to alter the alphaSpeed value of these cells from an instance of this subclass, called say, fireEmitterLayer within some UIViewController:
var fireEmitterLayer = FireEmitterLayer(view: view)
I've tried adding this method within the FireEmitterLayer class
private func setAlphaSpeed(_ alphaSpeed: Float) {
guard let emitterCells = emitterCells else { return }
for emitterCell in emitterCells {
emitterCell.alphaSpeed = alphaSpeed
}
}
but this doesn't work ...
Any help is appreciated :-)
I set my cells dynamically for my emitters whenever I want using a func as follows :
func makeEmitterCell(thisEmitter : CAEmitterLayer, newColor: UIColor, priorColor: UIColor, contentImage : UIImage) -> CAEmitterCell {
let cell = CAEmitterCell()
cell.birthRate = lastChosenBirthRate
cell.lifetime = lastChosenLifetime
cell.lifetimeRange = lastChosenLifetimeRange
//cell.color = newColor.cgColor
cell.emissionLongitude = lastChosenEmissionLongitude
cell.emissionLatitude = lastChosenEmissionLatitude
cell.emissionRange = lastChosenEmissionRange
cell.spin = lastChosenSpin
cell.spinRange = lastChosenSpinRange
cell.scale = lastChosenScale
cell.scaleRange = lastChosenScaleRange
cell.scaleSpeed = lastChosenScaleSpeed
cell.alphaSpeed = Float(lastChosenAlphaSpeed)
cell.alphaRange = Float(lastChosenAlphaRange)
cell.autoreverses = lastChosenAutoReverses
cell.isEnabled = lastChosenIsEnabled
cell.velocity = lastChosenVelocity
cell.velocityRange = lastChosenVelocityRange
cell.xAcceleration = lastChosen_X_Acceleration
cell.yAcceleration = lastChosen_Y_Acceleration
cell.zAcceleration = 0
cell.contents = contentImage.cgImage
//Access the property with this key path format: #"emitterCells.<name>.<property>"
cell.name = "myCellName"
let animation = CABasicAnimation(keyPath: "emitterCells.myCellName.color")
animation.fromValue = priorColor.cgColor
animation.toValue = newColor.cgColor
animation.duration = CFTimeInterval(cellAnimationTimeValue)
if thisEmitter == particleEmitter_1
{
particleEmitter_1.add(animation, forKey: "emitterCells.myCellName.color")
}
if thisEmitter == particleEmitter_2
{
particleEmitter_2.add(animation, forKey: "emitterCells.myCellName.color")
}
return cell
} // ends makeEmitterCell
There's a color animation for my design at the end of that func that you get as bonus code in a way.
I keep these variables around to allow me to control the cell features on the fly ;
var lastChosenBirthRate : Float = 300
var lastChosenLifetime : Float = 3
var lastChosenLifetimeRange : Float = 0
var lastChosenEmissionLongitude : CGFloat = 2 * CGFloat.pi
var lastChosenEmissionLatitude : CGFloat = 2 * CGFloat.pi
var lastChosenEmissionRange : CGFloat = 2 * CGFloat.pi
var lastChosenSpin : CGFloat = 1
var lastChosenSpinRange : CGFloat = 0
var lastChosenScale : CGFloat = 1
var lastChosenScaleRange : CGFloat = 0
var lastChosenScaleSpeed : CGFloat = -0.3
var lastChosenAlphaSpeed : CGFloat = -0.1
var lastChosenAlphaRange : CGFloat = 0
var lastChosenAutoReverses : Bool = false
var lastChosenIsEnabled : Bool = true
var lastChosenVelocity : CGFloat = 20
var lastChosenVelocityRange : CGFloat = 0
var lastChosen_X_Acceleration : CGFloat = 0
var lastChosen_Y_Acceleration : CGFloat = 0
I make emitter cells in several places in my code. Here is one. Part of this func makes the emitter cells and sets them for the desired emitter :
func changeParticlesCellImageRightNow(thisEmitter : CAEmitterLayer, thisContentImage : UIImage, theColor : UIColor)
{
var priorColorToPassDown = UIColor()
if thisEmitter == particleEmitter_1
{
priorColorToPassDown = lastColorUsedEmitter1
}
if thisEmitter == particleEmitter_2
{
priorColorToPassDown = lastColorUsedEmitter2
}
let color1 = makeEmitterCell(thisEmitter: thisEmitter, newColor: theColor, priorColor: priorColorToPassDown, contentImage: thisContentImage)
thisEmitter.emitterCells = [color1]
} // ends changeParticlesCellImageRightNow
Here is a call to that func :
// Force the change to occur right now, using the existing colors
changeParticlesCellImageRightNow(thisEmitter : particleEmitter_1, thisContentImage : emitter_1_Image, theColor : lastColorUsedInGeneral)
changeParticlesCellImageRightNow(thisEmitter : particleEmitter_2, thisContentImage : emitter_2_Image, theColor : lastColorUsedInGeneral)
And here is the emitter layer objects
let particleEmitter_1 = CAEmitterLayer()
let particleEmitter_2 = CAEmitterLayer()
You might be able to cobble together an approach in a similar vein, setting the emitter cells birthrate, velocity, velocity range, alpha speed, scale, scale range, scale speed, etc. as best fits roaring or suppressed. A lot depends on your flexibility at the top level.
The goal is to round the corners of an unconventional grid similar to the following:
https://s-media-cache-ak0.pinimg.com/564x/50/bc/e0/50bce0cb908913ebc2cf630d635331ef.jpg
https://s-media-cache-ak0.pinimg.com/564x/7e/29/ee/7e29ee80e957ec22bbba630ccefbfaa2.jpg
Instead of a grid with four corners like a conventional grid, these grids have multiple corners in need of rounding.
The brute force approach would be to identify tiles with corners exposed then round those corners either with a different background image or by clipping the corners in code.
Is there a cleaner approach?
The grid is rendered for an iOS app in a SpriteKit SKScene.
This is a really interesting question.You can build your matrix with different approaches but surely you must resolve everytime the changes about the 4 corners in background for each tiles.
Suppose you start with a GameViewController like this (without load SKS files and with anchorPoint equal to zero):
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .resizeFill
scene.anchorPoint = CGPoint.zero
view.presentScene(scene)
}
}
My idea is to build a matrix like this:
import SpriteKit
class GameScene: SKScene {
private var sideTile:CGFloat = 40
private var gridWidthTiles:Int = 5
private var gridHeightTiles:Int = 6
override func didMove(to view: SKView) {
self.drawMatrix()
}
func drawMatrix(){
var index = 1
let matrixPos = CGPoint(x:50,y:150)
for i in 0..<gridHeightTiles {
for j in 0..<gridWidthTiles {
let tile = getTile()
tile.name = "tile\(index)"
addChild(tile)
tile.position = CGPoint(x:matrixPos.x+(sideTile*CGFloat(j)),y:matrixPos.y+(sideTile*CGFloat(i)))
let label = SKLabelNode.init(text: "\(index)")
label.fontSize = 12
label.fontColor = .white
tile.addChild(label)
label.position = CGPoint(x:tile.frame.size.width/2,y:tile.frame.size.height/2)
index += 1
}
}
}
func getTile()->SKShapeNode {
let tile = SKShapeNode(rect: CGRect(x: 0, y: 0, width: sideTile, height: sideTile), cornerRadius: 10)
tile.fillColor = .gray
tile.strokeColor = .gray
return tile
}
}
Output:
Now we can construct a background for each tile of our matrix.
We can made the same tile node but with a different color (maybe more clear than the tile color) and without corner radius. If we split this background in 4 parts we have:
left - bottom background tile
left - top background tile
right - bottom background tile
right - top background tile
Code for a typical background tile:
func getBgTileCorner()->SKShapeNode {
let bgTileCorner = SKShapeNode(rect: CGRect(x: 0, y: 0, width: sideTile/2, height: sideTile/2))
bgTileCorner.fillColor = .lightGray
bgTileCorner.strokeColor = .lightGray
bgTileCorner.lineJoin = .round
bgTileCorner.isAntialiased = false
return bgTileCorner
}
Now with the SKSCropNode we can obtain only the corner using the background tile and the tile:
func getCorner(at angle:String)->SKCropNode {
let cropNode = SKCropNode()
let tile = getTile()
let bgTile = getBgTileCorner()
cropNode.addChild(bgTile)
tile.position = CGPoint.zero
let tileFrame = CGRect(x: 0, y: 0, width: sideTile, height: sideTile)
switch angle {
case "leftBottom": bgTile.position = CGPoint(x:tile.position.x,y:tile.position.y)
case "rightBottom": bgTile.position = CGPoint(x:tile.position.x+tileFrame.size.width/2,y:tile.position.y)
case "leftTop": bgTile.position = CGPoint(x:tile.position.x,y:tile.position.y+tileFrame.size.height/2)
case "rightTop": bgTile.position = CGPoint(x:tile.position.x+tileFrame.size.width/2,y:tile.position.y+tileFrame.size.height/2)
default:break
}
tile.fillColor = self.backgroundColor
tile.strokeColor = self.backgroundColor
tile.lineWidth = 0.0
bgTile.lineWidth = 0.0
tile.blendMode = .replace
cropNode.position = CGPoint.zero
cropNode.addChild(tile)
cropNode.maskNode = bgTile
return cropNode
}
Output for a typical corner:
let corner = getCorner(at: "leftBottom")
addChild(corner)
corner.position = CGPoint(x:50,y:50)
Now we can rebuild the drawMatrix function with the corners for each tile:
func drawMatrix(){
var index = 1
let matrixPos = CGPoint(x:50,y:150)
for i in 0..<gridHeightTiles {
for j in 0..<gridWidthTiles {
let tile = getTile()
tile.name = "tile\(index)"
let bgTileLB = getCorner(at:"leftBottom")
let bgTileRB = getCorner(at:"rightBottom")
let bgTileLT = getCorner(at:"leftTop")
let bgTileRT = getCorner(at:"rightTop")
bgTileLB.name = "bgTileLB\(index)"
bgTileRB.name = "bgTileRB\(index)"
bgTileLT.name = "bgTileLT\(index)"
bgTileRT.name = "bgTileRT\(index)"
addChild(bgTileLB)
addChild(bgTileRB)
addChild(bgTileLT)
addChild(bgTileRT)
addChild(tile)
tile.position = CGPoint(x:matrixPos.x+(sideTile*CGFloat(j)),y:matrixPos.y+(sideTile*CGFloat(i)))
let label = SKLabelNode.init(text: "\(index)")
label.fontSize = 12
label.fontColor = .white
tile.addChild(label)
label.position = CGPoint(x:tile.frame.size.width/2,y:tile.frame.size.height/2)
bgTileLB.position = CGPoint(x:tile.position.x,y:tile.position.y)
bgTileRB.position = CGPoint(x:tile.position.x,y:tile.position.y)
bgTileLT.position = CGPoint(x:tile.position.x,y:tile.position.y)
bgTileRT.position = CGPoint(x:tile.position.x,y:tile.position.y)
index += 1
}
}
}
Output:
Very similar to your screenshots (these are two tile example:)
Now when you want to remove a tile, you can decide what corner you want to remove or leave because for each tile you have also the relative 4 corners :
Output:
Okay, the grid creation process isn't really relative to this. You just need some way of differentiating between a blank spot in the grid and a filled spot. In my example I have a Tile object with a type of .blank or .regular. You need to have all 15 images (you can change the style to whatever you like, although they have to be in the same order and they have to be prefixed with 1..15). It uses bit calculation to figure out which image to use as a background and offsets the background image by 1/2 tile size for x and y. Other than that it is pretty self explanitory. Those background images were my tester images I created when developing this, so feel free to use them.
struct GridPosition {
var col: Int = 0
var row: Int = 0
}
class GameScene: SKScene {
private var backgroundLayer = SKNode()
private var tileLayer = SKNode()
private var gridSize: CGSize = CGSize.zero
private var gridRows: Int = 0
private var gridCols: Int = 0
private var gridBlanks = [Int]()
private var tiles = [[Tile]]()
var tileSize: CGFloat = 150
override func didMove(to view: SKView) {
backgroundLayer.zPosition = 1
addChild(backgroundLayer)
tileLayer.zPosition = 2
addChild(tileLayer)
gridRows = 8
gridCols = 11
gridBlanks = [0,1,3,4,5,6,7,9,10,11,12,13,15,16,17,19,20,21,22,23,31,32,33,36,40,43,56,64,67,69,70,71,72,73,75,77,78,79,82,85,86,87]
createGrid()
createBackgroundTiles()
}
func createGrid() {
for row in 0 ..< gridRows {
var rowContent = [Tile]()
for col in 0 ..< gridCols {
let currentTileLocation: Int = row * gridCols + col
var tile: Tile
if gridBlanks.contains(currentTileLocation) {
tile = Tile(row: row, col: col, type: .blank, tileSize: tileSize)
}
else {
tile = Tile(row: row, col: col, type: .regular, tileSize: tileSize)
}
tile.position = positionInGrid(column: col, row: row)
tile.zPosition = CGFloat(100 + gridRows - row)
tileLayer.addChild(tile)
rowContent.append(tile)
}
tiles.append(rowContent)
}
}
func tileByGridPosition(_ gridPos: GridPosition) -> Tile {
return (tiles[Int(gridPos.row)][Int(gridPos.col)])
}
func positionInGrid(column: Int, row: Int) -> CGPoint {
let startX = 0 - CGFloat(gridCols / 2) * tileSize
let startY = 0 - CGFloat(gridRows / 2) * tileSize + tileSize / 2
return CGPoint(
x: startX + CGFloat(column) * tileSize,
y: startY + CGFloat(row) * tileSize)
}
func createBackgroundTiles() {
for row in 0...gridRows {
for col in 0...gridCols {
let topLeft = (col > 0) && (row < gridRows) && tileByGridPosition(GridPosition(col: col - 1, row: row)).type == .regular
let bottomLeft = (col > 0) && (row > 0) && tileByGridPosition(GridPosition(col: col - 1, row: row - 1)).type == .regular
let topRight = (col < gridCols) && (row < gridRows) && tileByGridPosition(GridPosition(col: col, row: row)).type == .regular
let bottomRight = (col < gridCols) && (row > 0) && tileByGridPosition(GridPosition(col: col, row: row - 1)).type == .regular
// The tiles are named from 0 to 15, according to the bitmask that is made by combining these four values.
let value = Int(NSNumber(value: topLeft)) | Int(NSNumber(value: topRight)) << 1 | Int(NSNumber(value: bottomLeft)) << 2 | Int(NSNumber(value: bottomRight)) << 3
// Values 0 (no tiles)
if value != 0 {
var gridPosition = positionInGrid(column: col, row: row)
gridPosition.x -= tileSize / 2
gridPosition.y -= tileSize / 2
let backgroundNode = SKSpriteNode(imageNamed: ("background_tile_\(value)"))
backgroundNode.size = CGSize(width: tileSize, height: tileSize)
backgroundNode.alpha = 0.8
backgroundNode.position = gridPosition
backgroundNode.zPosition = 1
backgroundLayer.addChild(backgroundNode)
}
}
}
}
}
class Tile: SKSpriteNode {
private var row = 0
private var col = 0
var type: TileType = .blank
init(row: Int, col: Int, type: TileType, tileSize: CGFloat) {
super.init(texture: nil ,color: .clear, size:CGSize(width: tileSize, height: tileSize))
self.type = type
size = self.size
let square = SKSpriteNode(color: type.color, size: size)
square.zPosition = 1
addChild(square)
}
}
Only thing that comes to mind is when one node touches another node, at that moment in time evaluate the display of said node, as well as change the neighbors that are affected by it.
What we did was lay out the tiles then call this function to round the nodes of exposed tiles.
// Rounds corners of exposed tiles. UIKit inverts coordinates so top is bottom and vice-versa.
fileprivate func roundTileCorners() {
// Get all tiles
var tiles = [TileClass]()
tileLayer.enumerateChildNodes(withName: ".//*") { node, stop in
if node is TileClass {
tiles.append(node as! TileClass)
}
}
// Round corners for each exposed tile
for t in tiles {
// Convert tile's position to root coordinates
let convertedPos = convert(t.position, from: t.parent!)
// Set neighbor positions
var leftNeighborPos = convertedPos
leftNeighborPos.x -= tileWidth
var rightNeighborPos = convertedPos
rightNeighborPos.x += tileWidth
var topNeighborPos = convertedPos
topNeighborPos.y += tileHeight
var bottomNeighborPos = convertedPos
bottomNeighborPos.y -= tileHeight
// Set default value for rounding
var cornersToRound : UIRectCorner?
// No neighbor below & to left? Round bottom left.
if !isTileAtPoint(point: bottomNeighborPos) && !isTileAtPoint(point: leftNeighborPos) {
cornersToRound = cornersToRound?.union(.topLeft) ?? .topLeft
}
// No neighbor below & to right? Round bottom right.
if !isTileAtPoint(point: bottomNeighborPos) && !isTileAtPoint(point: rightNeighborPos) {
cornersToRound = cornersToRound?.union(.topRight) ?? .topRight
}
// No neightbor above & to left? Round top left.
if !isTileAtPoint(point: topNeighborPos) && !isTileAtPoint(point: leftNeighborPos) {
cornersToRound = cornersToRound?.union(.bottomLeft) ?? .bottomLeft
}
// No neighbor above & to right? Round top right.
if !isTileAtPoint(point: topNeighborPos) && !isTileAtPoint(point: rightNeighborPos) {
cornersToRound = cornersToRound?.union(.bottomRight) ?? .bottomRight
}
// Any corners to round?
if cornersToRound != nil {
t.roundCorners(cornersToRound: cornersToRound!)
}
}
}
// Returns true if a tile exists at <point>. Assumes <point> is in root node's coordinates.
fileprivate func isTileAtPoint(point: CGPoint) -> Bool {
return nodes(at: point).contains(where: {$0 is BoardTileNode })
}
In my application, I have items randomly popping up from the bottom of the screen. The way I set my code forced me to use hard coded values for x-position from where the items are popping up. Instead of those x-positions being hard coded, I want to change them to for instance GameScene.size.width / 4, so that the application's game play stays the same for all devices. Here is how I set up my code:
class Items{
var node = SKNode()
var item1 = SKSpriteNode(imageNamed: "CY")
var item2 = SKSpriteNode(imageNamed: "SY")
var item3 = SKSpriteNode(imageNamed: "PY")
var velocity = CGPoint.zero
var positionOffset = CGFloat(0)
var minVelocity = CGFloat(200)
init(pOffset: CGFloat) {
positionOffset = pOffset
node.zPosition = 1
node.addChild(item1)
node.addChild(item2)
node.addChild(item3)
node.hidden = true
}
....
class GameWorld{
var size = CGSize()
var node = SKNode()
var can1 = Items(pOffset: 208) //this is what is deciding the position
var can2 = Items(pOffset: 620)
init() {
node.addChild(can1.node)
node.addChild(can2.node)
}
....
The offset line in GameWorld class is what is deciding the position. What can I change in order to be able to say "GameScene.size.width / 4" for can 1 and similarly the same for can2 and not have an issue? I did try different ways to get it to work, but nothing seems to go my way. When I'm able to say GameScene.size.width / 4, for some reason, the app launches, but none of the gameplay loads up. Any help will be greatly appreciated.
Here is my GameScene Class:
class GameScene: SKScene {
... // added sprites but erased the code for now for space purposes
var touchLocation = CGPoint(x: 0, y: 0)
var nrTouches = 0
var rightTap: Bool = false
var leftTap: Bool = false
var delta: NSTimeInterval = 1/60
static var world = GameWorld()
override init(size: CGSize) {
super.init(size: size)
GameScene.world.size = size
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()
let pos1 = CGPoint(x: size.width / 1.333, y: size.height / 1.1)
addChild(GameScene.world.node)
delta = NSTimeInterval(view.frameInterval) / 60
sprite1.position = pos1
sprite2.position = pos1
sprite3.position = pos1
sprite1.zPosition = 2
sprite2.zPosition = 2
sprite3.zPosition = 2
sprite2.hidden = true
sprite3.hidden = true
addChild(sprite1)
addChild(sprite2)
addChild(sprite3)
}
.....
Edit: added GameScene Class
For this scenario, you cannot declare:
var can1 = Items(pOffset: 208)
var can2 = Items(pOffset: 620)
The way you are doing it now. This is because at compile time, the game has no idea what the screen size is. Instead do not initialize the variable, just declare it like this:
var can1 : Items!
var can2 : Items!
Then this next part is tricky, if you are doing auto layouts, you need to make sure that the view is already created and sized before you present your scene. This does't happen till somewhere around viewDidAppear in the view controller
In your scenes didMoveToView, initialize your variables:
override func didMoveToView(view : SKView)
{
... //super and other code
can1 = Items(pOffset: view.frame.size.width/4)
can2 = Items(pOffset: view.frame.size.width/2)
}
I'm new to Swift development and I'm trying to create a simple racing game. The road in the game has lane markers that animate from the top of the screen to the bottom then loop back up to the top. I am using SKShapeNodes for the lane line markers.
I have been able to get one lane marker to work and now I just need to create an array(I think?) of lane markers so that I can have 2 columns of lanes and enough to give the game the illusion of being a real road.
I'm able to create the array but I get a run-time error when I try to add a SKShapeNode to it. The error I get is:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode=0x0)
fatal error: Cannot index empty buffer
Here is the relevant source code:
class PlayScene: SKScene {
let road = SKSpriteNode(imageNamed: "road8")
let car = SKSpriteNode(imageNamed: "car")
let laneLineNode = SKShapeNode()
var laneLineNodeArray = [[SKShapeNode]]()
var groundSpeed = 25
}
override func didMoveToView(view: SKView) {
//draw a lane line
var path = CGPathCreateMutable();
var laneLineWidth = self.size.width / 85
var laneLineHeight = self.size.height / 11
var laneLine = CGRect(x: ((self.frame.size.width / 5) * 2), y:
(self.frame.size.height + laneLineHeight),
width: laneLineWidth, height: laneLineHeight)
laneLineNode.lineWidth = 0
laneLineNode.path = CGPathCreateWithRect(laneLine, nil)
laneLineNode.fillColor = SKColor.yellowColor()
laneLineNode.zPosition = 2
//THIS IS THE LINE I GET THE ERROR ON
laneLineNodeArray[0][0] = laneLineNode
self.addChild(self.road)
self.addChild(self.laneLineNode)
self.addChild(self.car)
}
override func update(currentTime: CFTimeInterval) {
//move the lane lines
scrollLaneLines(self.scene!)
}
}
Thanks for any help you can give me.
First of all... You are closing your class too soon with this code:
var groundSpeed = 25
} // <--
Second... Your are not initializing your array before using it. In my test, I got the error because of that.
You should use this syntax to initialize your Array or use append as #Arnab suggest.
let laneLineNode = SKShapeNode()
var laneLineNodeArray = [[laneLineNode]]
You have created an empty array so far. Use "append" to add data to the array before you access the elements by index.
Thank you! I was able to get it to work with this in Class PlayScene:
var laneLineNodeArray:[[SKShapeNode]] = []
and this in func didMoveToView:
//draw a lane line
var path = CGPathCreateMutable();
var laneLineWidth = self.size.width / 85
var laneLineHeight = self.size.height / 11
var laneLine = CGRect(x: 0, y: (self.frame.size.height + laneLineHeight),
width: laneLineWidth, height: laneLineHeight)
var laneLineRowNumber: Int
var laneLineY: CGFloat
for r in 0...6 {
var laneLineRow:[LaneLine] = []
laneLineRowNumber = r
for c in 0...1 {
let laneLineNode = LaneLine()
laneLineNode.lineWidth = 0
laneLineNode.path = CGPathCreateWithRect(laneLine, nil)
laneLineNode.fillColor = SKColor.yellowColor()
laneLineNode.zPosition = 2
//lanelineY determines the Y placement of the lane.
laneLineY = self.frame.size.height - (CGFloat((laneLineHeight) * 2)
* CGFloat(r))
if c == 0 {
laneLineNode.position = CGPointMake(laneWidth * 2, laneLineY)
}
else if c == 1 {
laneLineNode.position = CGPointMake(laneWidth * 3, laneLineY)
}
self.addChild(laneLineNode)
laneLineRow.append(laneLineNode)
}
laneLineNodeArray.append(laneLineRow)
}