I'm using Xcode 8.3.3.
I made a custom "Platform" class for some platforms. This is the first time I'm trying to use a custom class on a sprite made in the scene editor and for some reason the sprite just isn't using it.
It seems pretty cut and dry, I setup a sprite with default settings in the scene editor, then set the custom class designator like this (a screen cap of my actual project):
Here is the actual class:
import UIKit
import SpriteKit
class Platform: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "blue_button01")
super.init(texture: texture, color: SKColor.clear, size: texture.size())
physicsBody = SKPhysicsBody(rectangleOf: texture.size())
physicsBody?.categoryBitMask = CollisionTypes.platform.rawValue
physicsBody?.contactTestBitMask = CollisionTypes.player.rawValue
physicsBody?.collisionBitMask = CollisionTypes.player.rawValue
physicsBody?.affectedByGravity = false
physicsBody?.restitution = 0
physicsBody?.isDynamic = false
physicsBody?.allowsRotation = false
setScale(2)
zPosition = 2
physicsBody?.linearDamping = 0.0
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
There are no errors it runs and complies fine, but when the game loads it loads a red sprite with default settings. I am expecting it to load the sprite with the settings of the "Platform.swift: class but it doesn't.
Any advice would be greatly appreciated.
(I'm aware of other methods to lay out levels, but I really like laying out the basics in the scene editor and then customizing everything in code.)
EDIT:
My end goal for my project's work flow is to open the .sks and place sprites where i want them, how large i want them, and what texture I want them to use, for example with selected blue platform here in the screenshot in the .sks I would place it like so:
Then with my custom class I want to set up all the other requirements for the platform like collision bit masks, linear dampening, rotation, etc.
then after the custom class is applied I want to reference and use the sprite with it's custom class applied in my gamescene.swift file to setup things like movement actions.
When your sprite node is initialised from the .sks file, the init that you defined is not called. Instead, it calls the init(coder:) initializer. You should set up your sprite in this initializer:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
texture = SKTexture(image: #imageLiteral(resourceName: "Spaceship"))
let texture = SKTexture(imageNamed: "blue_button01")
self.texture = texture
color = .clear
size = texture.size()
physicsBody = SKPhysicsBody(rectangleOf: texture.size())
physicsBody?.categoryBitMask = CollisionTypes.platform.rawValue
physicsBody?.contactTestBitMask = CollisionTypes.player.rawValue
physicsBody?.collisionBitMask = CollisionTypes.player.rawValue
physicsBody?.affectedByGravity = false
physicsBody?.restitution = 0
physicsBody?.isDynamic = false
physicsBody?.allowsRotation = false
setScale(2)
zPosition = 2
physicsBody?.linearDamping = 0.0
}
Related
My question is
When i touch black arrow areas i still touch whole picture, but i want only touch and have action when i touch red area only.
I tried to give a name to spriteNode plane = childNode(withName: "play") but it take/touch all image frame not only alphaBody.
red and black areas
I did some searches on google but no positive results.
Many thanks.
a really simple solution could be to put hit test areas on your plane object (I've left them slightly red for the example but you would make them transparent). I created my plane class in the editor and dragged it as a reference into my scene, but you could easily just program the hit zones from scratch as well.
Then in your plane class add them to the plane and detect touches within the plane class itself. It's not exact but not that much farther off then your alpha tracing was in your picture.
A nice bonus of this method is that you can isolate this touches to areas of the plane. Some uses for that could be...
touch left/right wing to turn that direction
touch body to refuel
touch wings to change guns
here is the code I used in my Plane class
class Plane: SKSpriteNode {
private var background: SKSpriteNode!
private var wingsHitTest: SKSpriteNode!
private var bodyHitTest: SKSpriteNode!
private var smallWingsHitTest: SKSpriteNode!
init() {
super.init(texture: nil, color: .clear, size: CGSize.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isUserInteractionEnabled = true
self.color = .clear
if let background = self.childNode(withName: "//background") as? SKSpriteNode {
self.background = background
}
if let wingsHitTest = self.childNode(withName: "//wingsHitTest") as? SKSpriteNode {
self.wingsHitTest = wingsHitTest
}
if let bodyHitTest = self.childNode(withName: "//bodyHitTest") as? SKSpriteNode {
self.bodyHitTest = bodyHitTest
}
if let smallWingsHitTest = self.childNode(withName: "//smallWingsHitTest") as? SKSpriteNode {
self.smallWingsHitTest = smallWingsHitTest
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch? {
let touchLocation = touch.location(in: self)
if wingsHitTest.contains(touchLocation) {
print("hit in the wings")
}
else if smallWingsHitTest.contains(touchLocation) {
print("hit in the back wings")
}
else if bodyHitTest.contains(touchLocation) {
print("hit in the body")
}
}
}
}
It seems like the behavior you want is not included in SpriteKit so you will have to implement it yourself.
Quoting from the link above:
You will have to find out the locationInNode(spriteNode) of the UITouch object object to get the coordinates within the sprite and then read the alpha value from the sprite Image (not trivial) or precompute bitmasks (ones and zeros) from your images and read the value at the corresponding point in the bitmask for the sprite
So you will need to load your image as UIImage or CGImage and check whether the color at the pixels where the touch occurred was transparent or not. You can find more information on how to do that in this question: How to get pixel color from SKSpriteNode or from SKTexture?
I am coding a Bullet Hell type game for the IOS, which features multiple enemies coming down toward the player. For the enemies, I created a custom class called Enemy which is a subclass of the SKSpritenode class.
class Enemy : SKSpriteNode {
var type : String;
var health : Int;
var armor : Int;
var healthBar : HealthBar; //The HealthBar Class
init(type: String, position: CGPoint, texture : SKTexture, color: UIColor, size: CGSize){
self.type = type;
self.health = enemyHealths[self.type]!
self.armor = enemyArmors[self.type]!
self.healthBar = HealthBar(healthBarWidth: 40, healthBarHeight: 4, hostile: true, health: self.health, maxHealth: self.health, position: position) //Initialize HealthBar
super.init(texture: texture, color: color, size: size)
self.position = position;
self.physicsBody = SKPhysicsBody(rectangleOf: self.size);
self.physicsBody?.affectedByGravity = false;
self.physicsBody?.collisionBitMask = 0;
self.physicsBody?.isDynamic = true;
self.addChild(self.healthBar) //Add HealthBar Instance
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(currentTime: TimeInterval){
}
}
In this class I initialize another class, a HealthBar class, which is also a subclass of the SKSPritenode class, but when I try to add it as a child to the Enemy instance it shows up on the far right side of the screen. I am wondering if there are specific child position mechanics that are making this happen or of its just an error in the code. One thing I can think of might be causing this error is that when I update the HealthBar position I set its position to the same of the Enemy's, which is added as a child to the GameScene. This might cause an error with that coordinate not being available when the Healthbar is added as a child to the smaller Enemy.
As you can see the Enemy spawns just fine, but the HealthBar is on the far right of the screen instead of being right above the Enemy where its supposed to be.
Well, it turns out my suspicions were correct. Since you were adding a child to the smaller Enemy Instance, coordinate CGPoint(x: 0, y: 0) corresponded to the center of the Enemy Instance. Thus to get the HealthBar above the Enemy Instance, all you had to was set its position to the aforementioned coordinate with a small positive offset in the Y value.
Making a class in SceneKit is important. However, I cannot get it to work.
Here is my class code
import UIKit
import SceneKit
class Ship: SCNNode {
override init(){
super.init()
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
let node = SCNNode(geometry: box)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And here is my code in the ViewController (I am using ARKit)
let tempShip = Ship()
tempShip.position = SCNVector3(0.1,0.1,0.1)
sceneView.scene.rootNode.addChildNode(tempShip)
I think I am missing something basic.
You probably have not created an SCNScene and added it to your view. At least there's no sign of that in the code you posted. You need to have something like
sceneView.scene = SCNScene()
or create it using one of SCNScene's init methods.
Then you'll have a scene you can hang your node on. Don't forget to add lighting and camera.
Also: don't subclass SCNNode. Use an extension instead. If you subclass SCNNode or SCNScene you can't use the Xcode Scene Editor. See SceneKit editor set custom class for node.
You should be seeing a warning that your node variable is not being used, you need to set the geometry on the node. Change your init method to this:
override init(){
super.init()
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
self.geometry = box
}
An SCNView's scene property is an optional. Change the last line to:
guard let scene = sceneView.scene else { return }
scene.rootNode.addChildNode(tempShip)
I am building a game in SpriteKit. The game will include 5 players and instead of coding SKActions five times for every player, I want to consolidate all characters into one and code one time for each SKAction, but the code below doesn't seem to work. Am I missing something?
import SpriteKit
var player1 = SKSpriteNode()
var player2 = SKSpriteNode()
var player3 = SKSpriteNode()
var player4 = SKSpriteNode()
var player5 = SKSpriteNode()
var mainPlayer = SKSpriteNode()
// in view did load
player1 = mainPlayer
player2 = mainPlayer
player3 = mainPlayer
player4 = mainPlayer
player5 = mainPlayer
//in touches began
let rightMoveAction = SKAction.moveByX(50, y: 0, duration: 0.1)
mainPlayer.runAction(SKAction.repeatActionForever(rightMoveAction))
I'm assuming your motivation is to make the code shorter and because mainPlayer is probably a complex node that you don't want to replicate the code 5 times. You don't show where you add the nodes to the scene, but the way you currently have it will crash when you try to add player2 because it is already added. It looks like you are trying to simply make 5 of the same character that can perform the same action.
Here is a general strategy I would use to try to accomplish what you're after. Since your mainPlayer node is probably some complex character that you will be reusing (possibly even in other scenes), then you might consider making it into its own class (and put the code in a separate .swift file). You can then add the functions it will use to move and perform other SKActions
For example:
import SpriteKit
class Character : SKSpriteNode {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
//setup your node here
self.userInteractionEnabled = true//you need this to detect touches
//log so you know it was created - take this out at some point
print("character created")
}
//create an example action that will move the node to the right by 50
func moveRight() {
print("moving right")
self.runAction(SKAction.moveByX(50, y: 0, duration: 0.1))
}
//you can also override the touch functions so you'll know when the node was touched
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("charater touched")
}
}
Then in your scene, when you want to create and use a new character, you can do this:
//create the character
let newPlayer = Character.init(texture: nil, color: UIColor.blueColor(), size: CGSizeMake(50, 50))
//position the character where you want
newPlayer.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
//add the character to the scene
self.addChild(newPlayer)
/move the character to the right
newPlayer.moveRight()
Making things more "modular" like this is great for re-usability of code. Now you can reuse the Character class whenever and wherever you need. Even in an entirely different game.
In terms of making all characters run the same action with one function call, I don't know of anything that would allow this. Although you could write your own function for it by perhaps placing all of the character nodes into an array and passing the array as an argument to a custom function, but personally I would just call the moveRight function on each of the nodes after you instantiated them.
I am trying to develop a game which need to have a common background across all the scenes using SpriteKit and Swift. Since the background is common and its actions need to be continuous, I created a custom singleton subclass of SKSpriteNode like this:
class BackgroundNode: SKSpriteNode {
static let sharedBackground = BackgroundNode()
private init()
{
let texture = SKTexture(imageNamed: "Background")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
addActors()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func addActors() {
addClouds()
}
private func addClouds() {
addCloud1()
}
private func addCloud1() {
let cloud1 = SKSpriteNode(imageNamed: "Cloud1")
cloud1.size = CGSizeMake(0.41953125*self.size.width, 0.225*self.size.height)
cloud1.position = CGPointMake(-self.size.width/2, 0.7*self.size.height)
self.addChild(cloud1)
let moveAction = SKAction.moveTo(CGPointMake(self.size.width/2, 0.7*self.size.height), duration: 20)
cloud1.runAction(SKAction.repeatActionForever(moveAction))
}
}
And from the GameScene class I am adding this node to the current view like this:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
BackgroundNode.sharedBackground.size = CGSizeMake(self.size.width, self.size.height)
BackgroundNode.sharedBackground.position = CGPointMake(self.size.width/2, self.size.height/2)
addChild(BackgroundNode.sharedBackground)
}
}
The background image is showing up correctly, but the cloud is not getting added. As of the code above The cloud should appear out of the screen and animate into and then again out of the screen through the other edge, but to verify if it is getting added, I even tried adding the cloud to the center of the screen without any animations. Still the cloud didn't show up. What can be the issue here? And how to fix it?
EDIT
I figured out that the child is actually getting added but is getting added and moving through some points far above the screen. I also figured out that it MIGHT have something to do with anchor point of the cloud, but whatever value I set as anchor point, the cloud always remains on the top right corner of the screen. What can I do about the anchor points to make the clouds appear as it should(considering the lower left corner as (0, 0) is what I want)
Solved the issue. The problem was that I had to manually set the anchor points of the Scene and the node. Setting the anchor point of both the Scene and the node to (0, 0) solved the issue. The new code looks as follows:
GameScene
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0, 0) //Added this
BackgroundNode.sharedBackground.size = CGSizeMake(self.size.width, self.size.height)
BackgroundNode.sharedBackground.position = CGPointMake(0, 0)
addChild(BackgroundNode.sharedBackground)
}
BackgroundNode
private init()
{
let texture = SKTexture(imageNamed: "Background")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
anchorPoint = CGPointMake(0, 0) //Added this
addActors()
}