Embed icon beside a SKLabelNode - ios

I was wondering if there's any way to set an icon besides a SKLabelNode (since I need to use SKAction to move this label up) like this:
All I found about it was using UILabel (here) or a GitHub project (here), where I can't move or bounce (with SpriteKit-Spring) my label.
I was thinking in create a sprite node with the icon image and set it's position besides the coinsLabel, but since this label is used as a coin counter, it would get larger when increased; and the icon would be overlaid.
I made this example project below to make it easier to visualize (it doesn't have the icon, of course. It's only incrementing and moving coinsLabel by buttons).
If you want, you can download it here.
import SpriteKit
class GameScene: SKScene {
//Declaration
var icon = SKSpriteNode()
var coins = Int()
var coinsLabel = SKLabelNode()
var incrementButton = SKSpriteNode()
//Setup
func setupIcon(){
//icon
icon = SKSpriteNode(imageNamed: "icon")
icon.position = CGPoint(x: self.frame.width / 1.45, y: self.frame.height / 1.075)
icon.setScale(0.1)
}
func setupCoinsLabel(){
//coinsLabel
coinsLabel.position = CGPoint(x: self.frame.width / 150 - 300, y: 0)
coinsLabel.setScale(12.5)
coinsLabel.text = "0"
}
func setupIncrementButton(){
//incrementButton
incrementButton = SKSpriteNode(imageNamed: "incrementButton")
incrementButton.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 3.15)
incrementButton.setScale(2.0)
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
setupIcon()
addChild(icon)
setupCoinsLabel()
icon.addChild(coinsLabel)
setupIncrementButton()
addChild(incrementButton)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
//When touch buttons/screen
for touch in touches{
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
//Increment
if node == incrementButton{
coins += 1
coinsLabel.text = NSString(format: "%i", coins) as String
coinsLabel.position = CGPoint(x: self.frame.width / 150 - coinsLabel.frame.width, y: 0)
}
}
}
}

Just make a SKSpriteNode and add it as a child to the SKLabelNode, you can always set the SKSpriteNode's position to be to the right of the SKLabel regardless of how many digits are in your label, so overlapping would never happen
//Increment
if node == incrementButton{
coins += 1
coinsLabel.text = NSString(format: "%i", coins) as String
icon.position = CGPoint(x: coinsLabel.frame.width / 2, y: 0)
}

Related

SpriteKit scrolling background image showing line between images

I'm learning how to make games in iOS, so I decided to replicate the first level of Mario Bros using my own assets, just to learn how to make them as well.
The issue I'm having right now is that, when creating the scrolling background, using this formula I found on Hacking with Swift, I keep getting a line in between my images.
I've checked that my images have no border in the AI file. (The images are the ones above)
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var player = SKSpriteNode()
private var bg = SKSpriteNode()
override func didMove(to view: SKView) {
let playerTexture = SKTexture(imageNamed: "player")
player = SKSpriteNode(texture: playerTexture)
player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addBackground()
self.addChild(player)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
// MARK: UTILITY FUNCTIONS
func addBackground() {
let bgTexture = SKTexture(imageNamed: "bg")
for i in 0 ... 1 {
bg = SKSpriteNode(texture: bgTexture)
//bg.position = CGPoint(x: (i == 0 ? bgTexture.size().width : bgTexture.size().width - 1) * CGFloat(i), y: self.frame.midY)
bg.position = CGPoint(x: (bgTexture.size().width * CGFloat(i)) - CGFloat(1 * i), y: 0)
bg.size.height = self.frame.height
//bg.anchorPoint = CGPoint.zero
bg.zPosition = -10
self.addChild(bg)
let moveLeft = SKAction.moveBy(x: -bgTexture.size().width, y: 0, duration: 5)
let moveReset = SKAction.moveBy(x: bgTexture.size().width, y: 0, duration: 0)
let moveLoop = SKAction.sequence([moveLeft, moveReset])
let moveForever = SKAction.repeatForever(moveLoop)
bg.run(moveForever)
}
}
}
Just create a new project, copy-paste it and you should see it running
I also changed my GameScene.sks size to: 2688 x 1242
And one last change I made to make the background appear in full screen was to set the LaunchScreen as stated in this answer which seems to be an issue in Xcode 12
I understand that the formula from Hacking with Swift post is making the images overlap by 1px, I've tried removing the 1 * i part from it, yet results are not different.
Other thing I did was to verify the images were "joining" perfectly together, and they do, the lines you can see in the image below are from the AI canvas, both images join in between "cactuses".
I also tried running it into a device in case it might be a bug in the simulator:
Your image has a thin black border on the left handside and along the top, 1 pixel wide. It's black. Remove that and try again.

Swift: SKAction not working on touch

Ive been trying to follow along swift tutorials and I've come across something extremely frustrating with every tutorial I have tried to follow along with, whenever SKAction is used I cannot follow along with any tutorials because my programs never work. This has happened with MULTIPLE programs and I can never figure it out.
Here is the code for the program:
import SpriteKit
import GameplayKit
class GameScene: SKScene{
let player = SKSpriteNode(imageNamed: "playerShip")
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "background")
background.size = self.size
background.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
background.zPosition = -1
self.addChild(background)
player.setScale(1)
player.position = CGPoint(x: self.size.width / 2, y: self.size.height * 0.2)
player.zPosition = 2
self.addChild(player)
}
func fireBullet(){
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.setScale(1)
bullet.zPosition = 1
bullet.position = player.position
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
fireBullet()
}
}
Whenever I build and run the program in the simulator everything is fine and the build succeeds. However whenever I click nothing happens, this happens every single time I attempt to do anything with SKAction. I should mention that the FPS at the bottom right corner of my screen changes every time I click, but nothing else happens, the node count stays the same, and the screen remains the same with the bullet not firing.
Here is the tutorial I am attempting to follow along with: https://www.youtube.com/watch?v=mvlwZs2ehLU
I noticed you aren't actually adding the bullet sprite to your scene after instantiating it. Make sure to add addChild(bullet). I suggest implementing something like this:
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.setScale(1)
bullet.zPosition = 1
bullet.position = player.position
addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1.0)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}

Access Constant from outside Function Swift

I have a game that runs like this: The class file is run and uses the function "addObject()" to an add an object to the screen. The object then falls from the top of the screen to the bottom and the player has to try to catch the object by tapping on it. However, the object is declared (as an SKSpriteNode)in the addObject function. So when I go to add the "touch controls" function to the game, I can't reference the object. What can I do to fix this? Here is some of my code:
class GameSceneTest: SKScene, SKPhysicsContactDelegate {
var ObjectDestroyed = 0
let label = SKLabelNode(fontNamed: "Title 1")
var money: Int = 0
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.clearColor()
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
//Change duration
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addObject), SKAction.waitForDuration(1)])
))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addObject() {
let Object = SKSpriteNode(imageNamed: "Object\(arc4random_uniform(6) + 1).png")
Object.size = CGSize(width: 50, height: 50)
Object.physicsBody = SKPhysicsBody(rectangleOfSize: Object.size)
Object.physicsBody?.dynamic = true
//Determine where to spawn Object across y axis
let actually = random(min: Object.size.width/2, max: size.width - Object.size.width/2)
//Position Object slightly off screen along right edge
//and along random y axis point
//Object.position = CGPoint(x: size.width + Object.size.width/2 , y: actually)
Object.position = CGPoint(x: actually, y: size.height + Object.size.height/2)
//Add the Object to the scene
addChild(Object)
//Determines speed of Object (edit later to where speed depends on type of Object)
let actualDuration = random(min: CGFloat(4), max: CGFloat(5))
//Create the Actions
let actionMove = SKAction.moveTo(CGPoint(x: actually, y: gem.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let loseAction = SKAction.runBlock() {
let reveal = SKTransition.flipHorizontalWithDuration(0.5) //Change to flipe vertical
let gameOverScene = GameOverScene(size: self.size, won: false)
self.view?.presentScene(gameOverScene, transition: reveal)
}
Object.runAction(SKAction.sequence([actionMove, loseAction, actionMoveDone]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
if Object.containsPoint(touch.locationInNode(self)) {
Object.removeFromParent()
print("touched")
}
}
}
There are several different ways that you could correctly implement this. One option would be to make an class level variable that is an array of type SKSpriteNode and append the objects to it when you create them in that function.
// Declare an array variable at the class level
var objectArray = [SKSpriteNode]()
// Then in the function after you create the object and configure it
objectArray.append(object)
Another alternative would be to use a dictionary that you add the new objects to. It just depends on exactly what you need to do with the objects when you reference them again.
Note: This answer assumes that you will have multiple objects that you need reference to at any given time. If you only need reference to one at a time, you can just make a class level variable of type SKSpriteNode and set it in your addObject function
For Example:
// Declaring class level variable
var myNode: SKSpriteNode?
When your addObject method is called, set myNode equal to the object that you create and configure
// When you go to use myNode, just unwrap it to make sure that it has a value
if let unwrappedNode = self.myNode {
// Do something with unwrappedNode
}

How to move sprite just left or right using CoreMotion?

For this game I am creating, I want the Sprite "Character" to either move left or right by me tilting the device either left or right.
I have looked into it and changed the code, but it seems like that it does not work.
When I run it, the "Character" moves in only one direction and still goes either up or down.
I would just like the "Character" to move only left or right, not up or down.
Any help on this?
Here is a look at the GameScene.swift file:
import SpriteKit
import CoreMotion
class GameScene: SKScene {
let motionManager = CMMotionManager()
var Ground = SKSpriteNode()
var Character = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
Character = SKSpriteNode(imageNamed: "NinjaGhost")
Character.physicsBody = SKPhysicsBody(circleOfRadius: Character.size.height)
Character.size = CGSize(width: 200, height: 200)
Character.physicsBody?.allowsRotation = false
Character.zPosition = 2
Character.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.addChild(Character)
Ground = SKSpriteNode(imageNamed: "Dirt Background")
Ground.size = CGSize(width: 1920, height: 1080)
Ground.zPosition = 1
Ground.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.addChild(Ground)
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue() ) {
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * 1, 0)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You should follow the swift guidelines, your properties should not start with capital letters, only classes, structs, enums and protocols should. It makes code harder to read on stack overflow (code is marked blue but shouldn't) and in general its not good practice.
Change your code to this because there was a few errors.
character = SKSpriteNode(imageNamed: "NinjaGhost")
character.size = CGSize(width: 200, height: 200) // Better to call this before positioning
character.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2) // Give pos before physics body
character.physicsBody = SKPhysicsBody(circleOfRadius: character.size.width / 2) // You have to divide by 2 when using circleOfRadius to get proper size in relation to the sprite
character.physicsBody?.allowsRotation = false
character.physicsBody?.affectedByGravity = false // Stop falling
character.zPosition = 2
self.addChild(character)
In your motion queue code you are manipulating the scene itself, so that will not work.
Try this code instead which uses the physics body to move the sprite. This is my preferred way of doing it because it gives the sprite more natural movement, especially when changing direction, compared to directly manipulating the sprites position property (If you want to manipulate the sprite position property directly than read this article. http://www.ioscreator.com/tutorials/move-sprites-accelerometer-spritekit-swift)
if let error = error { // Might as well handle the optional error as well
print(error.localizedDescription)
return
}
guard let data = motionManager.accelerometerData else { return }
if UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeLeft {
character.physicsBody?.applyForce(CGVectorMake(-100 * CGFloat(data.acceleration.y), 0))
} else {
character.physicsBody?.applyForce(CGVectorMake(100 * CGFloat(data.acceleration.y), 0))
}
This will make the sprite move on the x axis. I am using data.acceleration.y because in this example the game is in landscape so you have to use the Y data to move it on the x axis.
I am also checking in which landscape orientation the device is so that the motion works in both orientations.
Adjust the 100 here to get the desired force depending on the size of the sprite (higher is more force). Big sprites properly need a few 1000s to move.

(SpriteKit + Swift 2) - Remove sprite when user tapped on it

I am trying to make a simple bubble tapped game. I have a created a SKSpriteKid node in my code. The problem is I want to sprite to disappear when user tap on it. I can't seem to removeparent() the node. How do I tackle this?
I have created a sprite with bubble1 as the name.
func bubblePressed(bubble1: SKSpriteNode) {
print("Tapped")
bubble1.removeFromParent()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
if (touchedNode.name == "bubble1") {
touchedNode.removeFromParent()
print("hit")
}
}
}
//the node's creation.
func bubblesinView() {
//create the sprite
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
//spawn in the X axis min is size, max is the total height minus size bubble
let width_bubble_1 = random(min: bubble1.size.width/2, max: size.width - bubble1.size.height/2)
//y: size.height * X, X is 0 at bottom page to max
//spawn position, let x be random
bubble1.position = CGPoint(x: width_bubble_1, y: size.height)
// Add the monster to the scene
addChild(bubble1)
// Determine speed of the monster from start to end
let bubblespeed = random(min: CGFloat(1.1), max: CGFloat(4.0))
//this tell the bubble to move to the given position, changeble x must be a random value
let moveAction = SKAction.moveTo(CGPoint(x: width_bubble_1, y: size.height * 0.1), duration: NSTimeInterval(bubblespeed))
let actionMoveDone = SKAction.removeFromParent()
bubble1.runAction(SKAction.sequence([moveAction, actionMoveDone]))
}
I am really trying to make this work. Thanks in advance!
If you want to use the name property of the node, you should set it to something. The runtime cannot figure out the variable name as the name. So in bubblesInView function after
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
you should do
bubble1.name = "bubble1"
You just forgot to name the node :
//create the sprite
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
bubble1.name = "bubble1"

Resources