I’m playing around with SpriteKit and GameplayKit. But when I run my code, the sprite I’m expecting to wander around, does nothing.
I used lldb to print out some values, this is the print out:
(lldb) po agentSystem.components
▿ 1 elements
- [0] : <GKAgent2D: 0x7fc1b8e55ff0>
(lldb) po agentSystem.components
0 elements
The first is printed just after addComponentWithEntity: is called.
The second is printed in the update: method.
Of course this must be the reason the sprite does not wander, but what could be causing the problem?
I’ve even looked at and used code from Apple’s sample code for Agents but that still doesn’t seem to fix the issue.
Here is my scene’s code:
import SpriteKit
import GameplayKit
class GameScene: SKScene
{
// MARK: - Properties
let agentSystem = GKComponentSystem(componentClass: GKAgent2D.self)
var lastUpdateTimeInterval: CFTimeInterval = 0.0
// MARK: - Scene Lifecycle
override func didMoveToView(view: SKView)
{
// Wave enemy
let agent = GKAgent2D()
agent.behavior = GKBehavior(goal: GKGoal(toWander: 10), weight: 100)
let waveEnemy = WaveEnemy()
waveEnemy.addComponent(agent)
agentSystem.addComponentWithEntity(waveEnemy)
let waveSprite = waveEnemy.componentForClass(VisualComponent)!.spriteNode
waveSprite.position = CGPoint(x: frame.size.width * 0.5, y: frame.size.height * 0.5)
addChild(waveSprite)
}
override func update(currentTime: CFTimeInterval)
{
if lastUpdateTimeInterval == 0
{
lastUpdateTimeInterval = currentTime
}
let deltaTime: CFTimeInterval = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
// Update component system
agentSystem.updateWithDeltaTime(deltaTime)
}
}
I had a similar issue. Turns out my GKEntity had a weak reference to it which caused it to automatically get removed from the GKComponentSystem.
In your case you're creating waveEnemy which needs to be held on to. You can store it in an array on the scene.
class GameScene: SKScene {
...
var enemies = [GKEntity]()
override func didMoveToView(view: SKView) {
...
let waveEnemy = WaveEnemy()
waveEnemy.addComponent(agent)
enemies.append(waveEnemy) // Creates a strong reference for waveEnemy
...
}
...
}
Related
The SKAction on my SKShapeNode isn't working, the code isn't getting executed, and the node isn't moving, even though the console is logging "Snake is moving". Is is because the node is a property of the SKScene and the actions are part of lower scope functions?
class LevelScene: SKScene, SnakeShower {
var snake: Snake {
let theSnake = Snake(inWorld: self.size)
return theSnake
}
override func didMove(to view: SKView) {
self.backgroundColor = .green
snake.delegate = self
}
var myNode: SKShapeNode {
let node = SKShapeNode(rectOf: snake.componentSize)
node.position = snake.head
node.fillColor = .red
return node
}
func presentSnake() { // function called by the snake in the delegate (self)
self.addChild(myNode)
startMoving()
}
func startMoving() {
print("snake is moving")
myNode.run(SKAction.repeatForever(SKAction.sequence([
SKAction.move(by: self.snake.direction.getVector(), duration: 0.2),
SKAction.run({
if self.myNode.position.y > (self.size.height / 2 - self.snake.componentSize.height / 2) {
self.myNode.removeAllActions()
}
})
])))
}
}
It used to work when they property was declared in the same function as the action
myNode is a computed property. self.addChild(myNode) adds a node, but there is no myNode stored property.
myNode.run First computes a new node without adding it to the scene. Then it calls the run the action on it. but since it's a different node that is not on the scene, it will never run.
Change your myNode defintion to:
var myNode : SKShapeNode!
and in didMove(to view: add:
myNode = SKShapeNode(rectOf: snake.componentSize)
myNode.position = snake.head
myNode.fillColor = .red
I had an issue almost identical to this, where I had unresponsive child nodes in a referenced node. Turns out that referenced nodes default to isPaused being true (answer thread found here)
The solution was to change the isPaused property to false.
referencedNode.isPaused = false
I have created a particle effect using the editor and now I'd like to change particleColor in code. I have set particleColorSequence to nil (otherwise I the colors would come from the color ramp in the editor and not my code) and particleColorBlendFactor is set to 1.0. I assign a random color to particleColor in the update method with the hopes that it will change each time through the loop. It does choose a random color the first time through, but then the color never varies. Can someone please explain why?
Global
let emitter = SKEmitterNode(fileNamed: "squares.sks")
let colors = [SKColor.red, SKColor.green, SKColor.blue]
didMove(to view:)
emitter?.particleColorBlendFactor = 1.0
emitter?.particleColorSequence = nil
addChild(emitter!)
update(_ currentTime:)
let random = Int(arc4random_uniform(UInt32(self.colors.count)))
emitter?.particleColor = colors[random]
This hardly counts as an answer but I couldn't fit it all into a comment so...please bear with me.
The good news is that your code seems to work!
I tried creating a new Sprite Kit project and pasted your code into that so I ended up with a GameScene class of type SKScene looking like this:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let emitter = SKEmitterNode(fileNamed: "squares.sks")
let colors = [SKColor.red, SKColor.green, SKColor.blue]
var lastUpdateTime: TimeInterval?
override func didMove(to view: SKView) {
emitter?.particleColorBlendFactor = 1.0
emitter?.particleColorSequence = nil
addChild(emitter!)
}
override func update(_ currentTime: TimeInterval) {
var delta = TimeInterval()
if let last = lastUpdateTime {
delta = currentTime - last
} else {
delta = currentTime
}
if delta > 1.0 {
lastUpdateTime = currentTime
let random = Int(arc4random_uniform(UInt32(self.colors.count)))
emitter?.particleColor = colors[random]
}
}
}
And then I created a new SKEmitterNode from the template (I used fire...just to chose something) and named it squares.sks.
When I run that, I can see this:
So...where does that leave us?
I'm thinking there must be something different in your setup.
If you try to create a new example project like mine, are you able to get it to work?
Yeah...hardly an answer I know, but think of it as a reassurance that you are on the right path at least :)
I am adding sklabelnodes and skspritenodes to the scene with these functions:
var levelnode = SKSpriteNode()
var labelLevel = SKLabelNode(fontNamed: "Courier-Bold")
func addlevels(){
var level = score + 1
if (level > 0){
sprite()
levelnode.position = CGPointMake(frame.size.width / 2.2, -frame.size.height/15.6 - frame.size.height/17.6)
levelnode.name = "1"
addChild(levelnode)
sklabel()
labelLevel.text = ("1")
labelLevel.position.x = levelnode.position.x - (levelnode.size.width/40)
labelLevel.position.y = levelnode.position.y - (levelnode.size.width/6)
labelLevel.name = "1"
addChild(labelLevel)
}
if (level > 1){
sprite()
levelnode.position = CGPointMake(frame.size.width / 1.42 - frame.size.width/100, -frame.size.height/10 - frame.size.height/17.6)
levelnode.name = "2"
addChild(levelnode)
sklabel()
labelLevel.text = ("2")
labelLevel.position.x = levelnode.position.x - (levelnode.size.width/40)
labelLevel.position.y = levelnode.position.y - (levelnode.size.width/6)
labelLevel.name = "2"
addChild(labelLevel)
}
// this goes on and on till level 25
}
func sprite(){
levelnode = SKSpriteNode(imageNamed: "levelnode")
levelnode.size.width = frame.size.width / 8
levelnode.size.height = levelnode.size.width
levelnode.zPosition = 1
}
func sklabel(){
labelLevel = SKLabelNode(fontNamed: "Courier-Bold")
labelLevel.zPosition = 2
labelLevel.fontColor = SKColor.blackColor()
labelLevel.fontSize = frame.size.height / 35
}
When I am changing the scene, the sprites and labels are removed from the willmovefromview function:
override func willMoveFromView(view: SKView) {
removeAllChildren()
}
but this is taking too slow if I compare it with other scenes where even more skspritenodes are added..
I think It has to do with the functions where I add the sprites and labels, but what is wrong about it?
EDIT:
the function that takes me back too the menu:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
errint = false
for touch: AnyObject in touches {
let location: CGPoint! = touch.locationInNode(self)
let nodeAtPoint = self.nodeAtPoint(location)
if (nodeAtPoint.name != nil) {
if nodeAtPoint.name == "menu" {
removeallAction()
removeAllChildren()
var scene1 = GameMenuScene(size: self.size)
scene1.button = self.button
scene1.button2 = self.button2
scene1.button3 = self.button3
scene1.viewController = self.viewController
let transition = SKTransition.moveInWithDirection(SKTransitionDirection.Left, duration: 0.75)
self.view?.presentScene(scene1, transition: transition)
}
}
}
}
Doesn't transitioning to another scene deallocate the current scene?
And if that scene is deallocated, then there's no need for the nodes and such to be removed...
Could you do me a favor, and transition out without removing the children and actions, then transition back in and see if it matters??
Anyway, speed is trivial if you're testing on the simulator... the simulator really only simulates opengl, the core of SpriteKit.
Please try it on your device.
BTW, I'm not sure of stuff but nobody seemed to have answered you so I tried...
EDIT:
HOLY MOTHER OF GOD WHY DIDN'T I SEE THE TIMESTAMP IT WAS ASKED TWO MONTHS AGO.
Too late now I guess... but to anybody reading this, don't answer questions at 2 am.
I am trying to make a mouse down event, but keep getting the error "Use of undeclared type 'NSEvent'" on the "override func mouseDown( theEvent: NSEvent!) { " line. After researching and trying different things, I still got nothing. Anyone had this problem before?
import UIKit
import SpriteKit
let BallCategoryName = "ball"
let MainBallCategoryName = "mainBall"
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
// 1. Create a physics body that borders the screen
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
// 2. Set the friction of that physicsBody to 0
borderBody.friction = 0
// 3. Set physicsBody of scene to borderBody
self.physicsBody = borderBody
physicsWorld.gravity = CGVectorMake(0, 0)
let ball = childNodeWithName(BallCategoryName) as SKSpriteNode
var mainBall = childNodeWithName(MainBallCategoryName) as SKSpriteNode
ball.physicsBody!.applyImpulse(CGVectorMake(10, -10))
ball.physicsBody!.allowsRotation = false
ball.physicsBody!.friction = 0
ball.physicsBody!.restitution = 1
ball.physicsBody!.linearDamping = 0
ball.physicsBody!.angularDamping = 0
}
override func mouseDown( theEvent: NSEvent!) {
let action = SKAction.moveTo(
CGPoint(x:theEvent.locationInWindow.x,y:theEvent.locationInWindow.y),
duration:2
);
MainBallCategoryName.runAction(action)
}
}
iOS devices don't have mice, and iOS doesn't have an NSEvent class. It has a UIEvent class, but there's nothing special about a function named mouseDown on iOS. iOS events report touches, not mouse buttons.
If you copied this code from a tutorial, the tutorial was for OS X, not iOS. You should find an iOS tutorial instead.
In an OS X app you should not:
import UIKit
but instead use
import AppKit
I have a SKLabelNode added in my SKScene and it's color is black. But somewhere in the code I am doing
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
var keysArr = playerLabels.allKeys
for k in keysArr {
var playerLabel = k as SKLabelNode
if CGRectContainsPoint(playerLabel.frame, location) {
selectedPlayer = playerLabels.objectForKey(playerLabel) as AvailablePlayer
playerLabel.fontColor = UIColor.redColor()
}
}
}
}
and it does not change the label node color. I have to do
playerLabel.removeFromParent()
self.addChild(playerLabel)
so that the color change would take effect. This looks like a hack to me and I am wondering if I am doing something wrong or if there is another way to do this.
I find that if I do something with the scene like a null action the sprite updates, e.g.:
final class MainViewController: UIViewController {
/// The view controller for the main scene (self!)
private(set) static var viewController: MainViewController!
private var mainNode: SCNNode!
private var uiAction: SCNAction!
override func viewDidLoad() {
super.viewDidLoad()
...
// Retrieve and store the main node
mainNode = main.rootNode.childNodeWithName("ship", recursively: true)!
// Animate the UI (needed to allow buttons to respond - probably an iOS bug :( )
uiAction = SCNAction.repeatAction((SCNAction.rotateByX(0, y: 0, z: 0, duration: 1)), count: 1)
// Give other objects access to this view controller
MainViewController.viewController = self
}
...
/// Force the update the UI by doing a nothing animation (probably an iOS 8 bug :( )
func updateUI() {
mainNode.runAction(uiAction)
}
}
I then call updateUI every time I modify a sprite, e.g.:
MainViewController.viewController.updateUI()
Not ideal since you have to manually call updateUI, but it is the best I have come up with :(