My sprite node change position on the screen. And I need to detect when sprite move to up, move to down, move to left, move to right.
Define a variable oldPosition and keep track of the sprite position in the update: method:
var oldPosition: CGPoint?
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if Int((oldPosition?.x)!) > Int((sprite?.position.x)!) {
// sprite moves to the left
} else if Int((oldPosition?.x)!) < Int((sprite?.position.x)!) {
// sprite moves to the right
}
if Int((oldPosition?.y)!) > Int((sprite?.position.y)!) {
// sprite moves down
} else if Int((oldPosition?.y)!) < Int((sprite?.position.y)!) {
// sprite moves up
}
// keep track
oldPosition = sprite?.position
}
I hope it helps.
You’ve got two options here. As you’ve not posted any code I can’t really advise which is the best:
If you’ve sub-classed SKNode, then override the frame property to add a didSet observer clause:
override var frame : CGRect {
didSet {
// compute the change in the frame’s origin
let delta : CGPoint = CGPoint(x: frame.origin.x - oldValue.origin.x,
y: frame.origin.y - oldValue.origin.y)
// etc
}
}
See SKNode docs at https://developer.apple.com/documentation/spritekit/sknode
If you don’t want to subclass, you can use NSNotificationCenter to add an observer to your node via addObserver:selector:name:object: . See the documentation at:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html
I’d probably go for option 1.
Hope that helps.
Related
I'm working on a project where I'll have a virtual joystick on the screen which moves a character around the board. I found a few good examples and the one I'm using currently is located # https://github.com/Ishawn-Gullapalli/SpritekitJoystick/tree/master/SpritekitJoystick
While the player is moved around just like I want, I'm unable to figure out how by using this method I can change the player animation based on the direction he's going. I'm able to determine direction but my usual methods of invoking a SKAction doesn't animate the player, it just moves him to the new location while pausing the idle animation. I'm moving my player in the update statement like below and am looking for tips on how to change animations appropriately.
I'm including my update statement below. I've also tried to pass in a CGPoint value with the same effect. Thanks in advance for looking.
override func update(_ currentTime: TimeInterval) {
if joystick.joyStickTouched == true {
mainHero.position.x += joystick.moveRight(speed: movementSpeed)
// Example of me trying to animate my player
if joystick.moveRight(speed: movementSpeed) > 0 {
walkRight()
}
mainHero.position.x -= joystick.moveLeft(speed: movementSpeed)
mainHero.position.y += joystick.moveUp(speed: movementSpeed)
mainHero.position.y -= joystick.moveDown(speed: movementSpeed)
joystick.update(currentTime)
}
}
func walkRight() {
mainHero.isPaused = false
let idleAnimation = SKAction(named: "MainWalkRight")!
let rightGroup = SKAction.group([idleAnimation])
mainHero.run(rightGroup, withKey: "Right")
}
I would like to know how to move a sprite in spritekit depending on the side of the screen the user is pressing down on. Like if the user taps the right side of the screen the sprite should move to the right and when the user removes his finger the sprites stops moving.
Any suggestions?
thank you so much
use the touchesBegan func and get position of touch with:
for location: AnyObject in touches {
var positionOfTouch = location.locationInNode(self)
//position of touch is of value CGPoint
}
store the value of xposition and yposition
var xPostion = positionOfTouch.position.x
var yPosition = positionOfTouch.position.y
//all in the touchesBegan func
make an if else statement to see if the user touches a side or not
if xPosition < 10 && yPosition > 10 && yPosition < self.size.height-10 {
//left side
let moveToLeftSide = SKAction.moveTo(x: 0, duration: 10)
object.run(moveToLeftSide)
}
continue setting values for the sides, then set the touchesEnded func
inside the touchesEnded func, write:
object.removeAllActions()
hope it helped... if you found this answer helpful remember to set this answer to correct ; )
I'm creating a simple game with Swift and SpriteKit.
I want to add an endless background (vertically), I only found answers for background with images but I need to do it without image, only background color.
I thought about checking if the player is in the frame.maxY, if so, to move it back to the starting point, but I was wondering if there is a better idea.
//Does not matter which ring we chose as all ring's 'y' position is the same.
func moveBackgroundUp(){
if ((mPlayer.position.y >= self.frame.maxY) || (mRingOne.position.y >= self.frame.maxY)) {
mPlayer.position.y = 150 //mPlayers original starting point.
for ring in mRings {
ring.position.y = 350
}
}
}
Thanks in advance!
Don't just move a background up the screen, that' really isn't the way to go about it. What you should do is detect the position of the camera (assuming it moves with the player), and when it's position is about to occupy space outside of the occupied space of your current background sprite, add a new background sprite to the scene where the last one left off. Here is an example of how to do that with just a red sprite:
First add a property to the scene to track level position:
// To track the y-position of the level
var levelPositionY: CGFloat = 0.0
Now create a method to update your background:
func updateBackground() {
let cameraPos = camera!.position
if cameraPos.y > levelPositionY - (size.height * 0.55) {
createBackground()
}
}
func createBackground() {
// Create a new sprite the size of your scene
let backgroundSprite = SKSpriteNode(color: .red, size: size)
backgroundSprite.anchorPoint = CGPoint(x: 0.5, y: 0)
backgroundSprite.position = CGPoint(x: 0, y: levelPositionY)
// Replace backgroundNode with the name of your backgroundNode to add the sprite to
backgroundNode.addChild(backgroundSprite)
levelPositionY += backgroundSprite.size.height
}
Now you want to call updateBackground inside your overridden update(_:) method:
override func update(_ currentTime: TimeInterval) {
// All your other update code
updateBackground()
}
Also, make sure to create an initial background when you first create the scene:
override func didMove(to view: SKView) {
createBackground()
}
NOTE! - It's important to set the custom anchor point for the background sprite for this code to work properly. An anchor of (0.5, 0) allows the background sprite to be anchored in the middle of the scene on the x-axis, but at the bottom of the scene on the y-axis. This allows you to easily stack one on top of the other.
EDIT - I forgot to mention that it's also a good idea to conserve resources and remove any background nodes that are outside the viewable area and won't be coming back in (i.e. a continuous scrolling game where you can't go backwards). You could do that by updating your updateBackground method above:
func updateBackground() {
let cameraPos = camera!.position
if cameraPos.y > levelPositionY - (size.height * 0.55) {
createBackground()
}
// Make sure to change 'backgroundNode' to whatever the name of your backgroundNode is.
for bgChild in backgroundNode.children {
// This will convert the node's coordinates to scene's coordinates. See below for this function
let nodePos = fgNode.convert(fgChild.position, to: self)
if !isNodeVisible(bgChild, positionY: nodePos.y) {
// Remove from it's parent node
bgChild.removeFromParent()
}
}
}
func isNodeVisible(_ node: SKNode, positionY: CGFloat) -> Bool {
if !camera!.contains(node) {
if positionY < camera!.position.y - size.height * 2.0 {
return false
}
}
return true
}
So above you just loop through all the children inside your background node and detect if they are out of view, and if so remove them from the parent. Make sure to change my generic backgroundNode to whatever the name of your background node is.
I am trying to animate multiple SKSpriteNode's of the same type using single SKAction.
What I would like to do, is to display four SKSpriteNode's of the same type on the screen.
Then animate them all when the button is pressed and move them to the left.
As soon as the first SKSpriteNode would leave the screen I want to add another SKSpriteNode off the screen position on the right side and add him to this SKAction loop.
Image below shows in detail what I am after.
So far I was able to display four SKSpriteNode's of the same type on the screen and add them to the array.
var squareBox = SKSpriteNode()
func addSquares(size:CGSize){
for var i = 0; i < 4; i++ {
// Create a new sprite node from an image
squareBox = SKSpriteNode(imageNamed: "noBox")
// Square pysics settings
squareBox.physicsBody = SKPhysicsBody(rectangleOfSize: self.squareBox.frame.size)
squareBox.physicsBody!.dynamic = false
squareBox.physicsBody!.categoryBitMask = squareBoxCategory
squareBox.physicsBody!.contactTestBitMask = leftEdgeCategory | edgeCategory
// SquareBox positioning on X
var xPos = size.width/5 + squareBox.size.height/2
var xPosInt = Int(xPos) * (i + 1)
xPos = CGFloat(xPosInt)
var yPos = size.height/2 + (squareBox.size.height/2)
squareBox.position = CGPointMake(xPos - squareBox.size.height/2, yPos)
self.addChild(squareBox)
squareArray.append(squareBox)
}
}
I am not sure if that is correct way to animate four SKSpriteNode's of the same type, but when the button is pressed they all seem to move to the left as desired.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
self.nodeAtPoint(location)
if self.nodeAtPoint(location) == self.button {
let move = SKAction.moveByX(-size.width/5 - squareBox.size.height/2, y: 0, duration: 1)
print("button clicked!")
for box in squareArray {
box.runAction(move)
}
}
}
}
I created an invisible 1px line on the left edge of the screen to define when the first SKSpriteNode leaves the screen.
func addLeftEdge(size:CGSize){
let leftEdge = SKNode()
leftEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointMake(1, 1), toPoint: CGPointMake(1, size.height))
leftEdge.physicsBody!.categoryBitMask = leftEdgeCategory
self.addChild(leftEdge)
}
The problem with this approach is that SKSpriteNode's in the array when I animate them don't respond on didBeginContact when they touch the 1px line.
func didBeginContact(contact: SKPhysicsContact) {
println("contact!")
}
In the end I want to be able to apply the Force to all SKSpriteNode's on the screen for a few seconds to move them to the left and descend the force speed over the time until they all stop. My idea was to create four objects of the same type and add them to the screen + add them to the array var squareArray = [SKSpriteNode]().
Then when the first SKSpriteNode leaves the screen I would remove it from array and add it again at the end and position it off the screen on the right so it will be moved in seamless animation.
I have a feeling that my entire approach is wrong.
Please advise what is the best approach to animate multiple SKSpriteNode's to achieve my goal as described above. Am I on the right track ?
Please help, thank you.
In order to detect contact, one of the SKSpriteNodes must have physicsBody.Dynamic= YES;
Since you want neither to be dynamic, there is a quick fix I have found:
leftEdge.physicsBody.Dynamic=YES; //This allows it to enter onContact
leftEdge.physicsBody.collisionBitMask=0; //Disables edge from being moved by collision
leftEdge.physicsBody.AffectedByGravity=NO; //Prevents any in game gravity from moving the edge
This allows you have an edge that is dynamic and can trigger onContact yet the edge will still act as if it is not dynamic.
I think this solution isn't very clean and requires a lot more code than what is necessary to get this effect. In the update() function since there are only 4 or 5 boxes why don't you simply loop through the objects and check if any of their frame's minX's are less than 0 and if they are reset them to the starting point which I'm assuming is (view.frame.width + box.size.width) because that's completely offscreen. That way there's no array manipulation in that you don't have to remove and append objects to the array. Also you don't need to attach a physicsBody where none is required. And you don't need the leftEdge
I would like to know how to remove my SKNodes when they are off screen to help my game run more smoothly.
How To Do This On Sprite Kit
Thanks So Much
Here is an easy solution in Swift 4:
class GameScene: SKScene {
let s = SKLabelNode(fontNamed: "Chalkduster")
override func didMove(to view: SKView) {
s.text = "test"
s.fontSize = 50
addChild(s)
let moveRight = SKAction.moveBy(x: 40, y: 0, duration: 0.5)
s.run(SKAction.repeatForever(moveRight))
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if ((s.parent != nil) && !intersects(s)) {
s.removeFromParent()
print("Sprite removed.")
}
}
}
You have a sprite (in this case a SKLabelNode but any sprite node will do) that is moving horizontally and you want to delete this sprite once is out of the frame bounds.
You can use the intersects function to check this and then remove that sprite from its parent. I have also checked that the sprite has a parent before removing it (by checking if s.parent is not nil) as we wish to remove the sprite only once.
https://stackoverflow.com/a/24195006/2494064
Here is a link to an answer that removes nodes that go off the top of the screen. You would just have to replicate this to cover the entire border and set all of the walls to have the same contactBitMask values.
Basically the logic is to remove the SKSpriteNodes when they contact physicsbodies that you have resting just outside the visible screen.