I am currently making a game in sprite kit. In the game, there are random shapes and objects on the ground that you can pick up and rotate and stack on to one another to build up to a target height. Once the target height is reached you move on to the next level. Each object and shape is created in a swift file separate from the GameScene under the SKNode class. I am able to pick the object up and move it around on the screen using the touchesBegan, moved, and ended methods in the node. While I'm holding on to the object I would like to be able to tap and hold the screen with a second finger anywhere on the screen that rotates the object until the finger is let go, so, the first finger grabs and hold the object while the other finger rotates it. The problem is that I have found it almost impossible for the SKNode to detect a touch outside of the actual node. Below is the code I have used for the SKNode. Any advice or suggestions would be greatly appreciated, thank you.
import UIKit
import SpriteKit
class GameObjects: SKNode {
var object: SKSpriteNode
var action: () -> Void
init(objectImage: String, buttonAction: () -> Void ){
object = SKSpriteNode(imageNamed: objectImage)
action = buttonAction
let object1Texture = SKTexture(imageNamed: "object1")
object1Texture.filteringMode = SKTextureFilteringMode.Nearest
let size = CGSize(width: 100, height: 100)
object.physicsBody = SKPhysicsBody(texture: object1Texture, size: size)
object.physicsBody?.dynamic = true
object.physicsBody?.allowsRotation = true
object.physicsBody?.affectedByGravity = true
super.init()
userInteractionEnabled = true
addChild(object)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in (touches ) {
let location: CGPoint = touch.locationInNode(self)
let moveUp = SKAction.moveToY(location.y + 200, duration: 0.05)
object.runAction( moveUp)
}
object.physicsBody?.dynamic = false
object.physicsBody?.affectedByGravity = false
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in (touches ) {
let location: CGPoint = touch.locationInNode(self)
object.physicsBody?.allowsRotation = true
object.physicsBody?.dynamic = true
object.position.x = location.x
object.position.y = location.y + 200
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
object.physicsBody?.dynamic = true
object.physicsBody?.affectedByGravity = true
}
}
Related
I'm trying to have the "player" sprite move slowly toward touches on the screen. The code that I have now moves the sprite immediately to where the touch happens on the screen. Here is my current code. Thanks!
//
// GameScene.swift
// testing
//
// Created by Matthew Jahnes on 7/3/18.
// Copyright © 2018 Matthew Jahnes. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var player = SKSpriteNode()
override func didMove(to view: SKView) {
self.anchorPoint = CGPoint(x:0.5, y:0.5)
player = SKSpriteNode(color: UIColor.red, size: CGSize(width: 90, height:90))
player.position = CGPoint(x:0, y:0)
self.addChild(player)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
player.position.x = location.x
player.position.y = location.y
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
You need to
Create an action for moving the player to the destination point.
Stop the previous move action if exists
Run the new move action
Here's the code
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let destination = touch.location(in: self)
let move = SKAction.move(to: destination, duration: 2)
player.removeAction(forKey: "move")
player.run(move, withKey: "move")
}
I am using Xcode 8.1 Swift 3 to make small bouncing game.
The player is supposed to create walls around a bouncing ball and this ball is supposed to bounce on each wall.
On touch down, I move to point and on touch ended, I create a line share node between the two touches began and end.
I added the physics that were needed to my node, then I added child to this node (see my node below).
What happens is that for each touches began and touches ends, 'Swift' draws the line node and attaches it to self but only the first node bounces the ball.
All lines (walls) after the first one is not affecting the ball.
Here is my code the GameScene Swift file:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// vars lets and nodes
let startingBall = SKShapeNode(circleOfRadius: 10)
let myPath = CGMutablePath()
let ballCategory : UInt32 = 1
let wallCategory : UInt32 = 2
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
myPath.move(to: t.location(in: self))
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
myPath.addLine(to: t.location(in: self))
let wallNode = SKShapeNode(path: myPath)
wallNode.lineWidth = 5.0
wallNode.fillColor = SKColor.green
wallNode.strokeColor = SKColor.green
wallNode.physicsBody = SKPhysicsBody(edgeLoopFrom: myPath)
wallNode.physicsBody?.categoryBitMask = wallCategory
self.addChild(wallNode)
}
}
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody?.categoryBitMask = wallCategory
startingBall.fillColor = SKColor.red
startingBall.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
startingBall.physicsBody = SKPhysicsBody(circleOfRadius: 10)
startingBall.physicsBody?.affectedByGravity = false
startingBall.physicsBody?.isDynamic = true
startingBall.physicsBody?.restitution = 1.0
startingBall.physicsBody?.friction = 0.0
startingBall.physicsBody?.linearDamping = 0.0
startingBall.physicsBody?.angularDamping = 0.0
self.addChild(startingBall)
startingBall.physicsBody?.applyImpulse(CGVector(dx: 3.0, dy: 3.0))
}
}
thanks all i've fixed it
actually mutable path myPath defines line between touch begin and touch end lines to draw shape node only ... while physics edge created for each touch series from node to node ...
in steps
1- i added start point and end point variables
2- pick up start node in touch begin and end node in touch end
3- i set physical body for from point to point start node and end node
1-
var startPoint : CGPoint = CGPoint.zero
var endPoint : CGPoint = CGPoint.zero
2- ..
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
startPoint = t.location(in: self)
myPath.move(to: t.location(in: self))
}
}
endPoint = t.location(in: self)
3- ..
wallNode.physicsBody = SKPhysicsBody(edgeFrom: startPoint, to: endPoint)
for help you can set show physics to true in skiver
skview.showsPhysics = true
thanks
I am currently creating a 2D game in which I have a cannon staying at the bottom of the screen that fires projectiles to some targets in the upper part of the screen. The user can rotate the cannon in order to aim for the targets. I want the projectile to be fired to the location that the canon faces. I have no idea how to do that and I need your help... Heres my code ->
import SpriteKit
class GameScene: SKScene {
var canon = SKSpriteNode()
var projectile = SKSpriteNode()
var touching = false
var locked = false
var location = CGPoint()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
projectile.position = canon.position
projectile.color = UIColor.whiteColor()
projectile.size = CGSize(width: 50, height: 50)
projectile.physicsBody?.affectedByGravity = false
projectile.physicsBody?.linearDamping = 1
projectile.physicsBody?.dynamic = true
addChild(projectile)
canon.zPosition = 2
canon = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 200))
canon.position = CGPoint(x: self.size.width/2.0, y: 10)
addChild(canon)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
touching = true
let touch = touches.first as UITouch!
location = touch.locationInView(self.view)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
rotateCanon(location.x)
projectile.zRotation = canon.zRotation
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = false
}
func rotateCanon(locationx:CGFloat) {
if locationx <= frame.width/5 && touching {
print(1)
canon.runAction(SKAction.rotateByAngle(0.01, duration: 0.1))
}
if locationx > frame.width/5 && touching {
print(2)
canon.runAction(SKAction.rotateByAngle(-0.01, duration: 0.1))
}
}
}`
Thanks a lot if you can help that would be awesome :D
Please help. I am a beginner on Swift. I've added sprites for player and left/right buttons. But how can I add an action for every button and apply these actions to the player?
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player: SKSpriteNode!
var leftMove: SKSpriteNode!
var rightMove: SKSpriteNode!
override func didMoveToView(view: SKView) {
player = SKSpriteNode (imageNamed: "player")
player.position = CGPoint(x:127, y:125)
addChild(player)
leftMove = SKSpriteNode (imageNamed: "leftMove")
leftMove.position = CGPoint(x:80, y:35)
leftMove.size = CGSize (width: 55, height: 55)
addChild(leftMove)
rightMove = SKSpriteNode (imageNamed: "rightMove")
rightMove.position = CGPoint(x:160, y:35)
rightMove.size = CGSize (width: 55, height: 55)
addChild(rightMove)
physicsWorld.contactDelegate = self
}
Give the buttons a unique name like:
leftMove.name = "Left"
rightMove.name = "Right"
Implement the logic in touchesBegan:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if (node.name == "Left") {
// Implement your logic for left button touch here:
player.position = CGPoint(x:player.position.x-1, y:player.position.y)
} else if (node.name == "Right") {
// Implement your logic for right button touch here:
player.position = CGPoint(x:player.position.x+1, y:player.position.y)
}
}
}
Welcome to StackOverflow. My recommendation is that for each button that you would like to have actions on, that you subclass SKSpriteNode for those objects. This way you can leverage the touchesBegan:, touchesMoved: and touchesEnded: functions for each of these objects individually. For example:
As promised, here is a more robust version of a subclassed SKSpriteNode. Also, I tested it in one of my projects and I can attest to it working correctly:
import Foundation
import SpriteKit
class SubclassedSKSpriteNode: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "whateverImage.png")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
userInteractionEnabled = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let location = (touches.first! ).locationInNode(scene!)
position = scene!.convertPoint(location, toNode: parent!)
print("touchesBegan: \(location)")
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let location = (touches.first! ).locationInNode(scene!)
position = scene!.convertPoint(location, toNode: parent!)
print("touchesMoved: \(location)")
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let location = (touches.first! ).locationInNode(scene!)
position = scene!.convertPoint(location, toNode: parent!)
print("touchesEnded: \(location)")
}
}
Also, to add this button to your SKScene (or any other SKNode):
let button = SubclassedSKSpriteNode()
button.position = CGPointMake(420, 300)
addChild(button)
HTH!
Hi I have a custom class, Ship, that has a property 'Parrent' that is an SKNode. When the class is initialized a bunch of nodes are added to that 'Parrent' making up the appearance of the class ship. 'Ship' has a method 'Explode' that shoots all of the nodes in different directions. This part works great, on to my issue.
I am trying to get the Ships to explode when I touch them, but I am having trouble passing which 'Ship' is to explode. Right now when I touch I use nodeAtPoint but this only gets me one of the appearance nodes. From there I need a way to go up a parent to 'Parrent' node and then from there up to Ship object as a whole.
AKA touch->nodeAtPoint->Appearance node->.parrent->'Parrent'->(get stuck here)->Ship->Ship.explode
I hope what is I am trying to accomplish makes sense, thanks for any help.
Ship class:
class Ship: NSObject {
var Position: CGPoint!
var Scene: SKNode!
var Parrent = SKNode();
let Parts = [
SKSpriteNode(imageNamed: "Ship/Ship1.png"),
SKSpriteNode(imageNamed: "Ship/Ship2.png"),
SKSpriteNode(imageNamed: "Ship/Ship3.png"),
SKSpriteNode(imageNamed: "Ship/Ship4.png"),
SKSpriteNode(imageNamed: "Ship/Ship5.png"),
SKSpriteNode(imageNamed: "Ship/Ship6.png"),
];
init(position : CGPoint, parrent : SKNode, scaleFactor: CGFloat) {
self.Position = position;
self.Scene = parrent;
self.Parrent.position = self.Position
var x = 0;
for part in Parts {
x++;
part.xScale = scaleFactor
part.yScale = abs(scaleFactor)
part.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ship/Ship\(x).png"), size: part.size)
part.physicsBody?.collisionBitMask = PhysicsCategory.Ship
part.physicsBody?.categoryBitMask = PhysicsCategory.Something
part.position = CGPoint(x: 0, y: 0)
self.Parrent.addChild(part)
}
self.Scene.addChild(self.Parrent)
}
//other functions including explode
}
Touches began in SKScene
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let targetShip = nodeAtPoint(location)
let targetParrent = targetShip.parent
//decide if it is a 'Ship' here
}
}
Thanks! Id greatly appreciate any help!
This is what finally worked. I changed the ship class to inherit from SKNode and edited touches began as such:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let targetShip = nodeAtPoint(location)
if targetShip.isMemberOfClass(Ship){
print("Ship")
Ship1.shootLazer(targetShip as! Ship)
}else if targetShip.parent!.isMemberOfClass(Ship){
print("Ship parrent")
Ship1.shootLazer(targetShip.parent! as! Ship)
}
}
}