I was learning about SKNodes in Swift and have encountered a problem. App was supposed to be very simple: touching screen and getting node to appear in the touched position. I don't know why but the rectangle keeps showing in different place on the screen but sn.position is the same as touch.location(in: view) position. What's wrong?
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var spinnyNode : SKShapeNode?
private var touchPoint : CGPoint!
override func didMove(to view: SKView) {
let size = CGSize(width: 50, height: 50)
spinnyNode = SKShapeNode(rectOf: size, cornerRadius: CGFloat(0.6)) //init
if let spinnyNode = self.spinnyNode{
spinnyNode.lineWidth = 3
let rotate = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI), duration: 1))
let fade = SKAction.fadeOut(withDuration: 0.5)
let remove = SKAction.removeFromParent()
spinnyNode.run(rotate)
spinnyNode.run(SKAction.sequence([fade, remove]))
}
}
func touchDown(point pos : CGPoint){
if let sn = self.spinnyNode?.copy() as! SKShapeNode? {
sn.position = CGPoint(x: touchPoint.x , y: touchPoint.y)
print("touchDown x:\(touchPoint.x), y:\(touchPoint.y)")
self.addChild(sn)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard touches.count == 1 else{
return
}
if let touch = touches.first{
touchPoint = touch.location(in: view)
touchDown(point: touchPoint)
print("touchesBegan -> x: \(touchPoint.x) y: \(touchPoint.y)")
}
}
Try doing something like this
import SpriteKit
import GameplayKit
class GameScene: SKScene {
//use whichever image for your node
var ballNode = SKSpriteNode(imageNamed: "ball")
var fingerLocation = CGPoint()
override func didMove(to view: SKView) {
ballNode.size = CGSize(width: 100, height: 100)
ballNode.position = CGPoint(x: self.size.width*0.5, y: self.size.height*0.5)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let location = touches.first?.location(in: self){
if let copy = ballNode.copy() as? SKSpriteNode {
copy.position = location
addChild(copy)
}
}
}
}
Every time I click on a random spot, the ball appears. Hope this helps.
Related
I have created Circle using "draw" and can place it anywhere on view. but when I want to move a specific circle by clicking it. it selects "LAST" created circle.
how can I select a specific circle? please guide me for the same.
if anything else you need let me know.
**CircleView**
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init(coder aDecoder : NSCoder) {
fatalError("init(coder : ) has not been implemented")
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext(){
context.setLineWidth(2)
UIColor.yellow.set()
let circleCenter = CGPoint(x: frame.size.width / 2, y: frame.self.height / 2)
let circleRadius = (frame.size.width - 10) / 2
context.addArc(center: circleCenter, radius: circleRadius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
context.strokePath()
}
}
}
**ViewController**
import UIKit
class ViewController: UIViewController {
var circleView = CircleView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
let circleWidth = CGFloat(100)
var lastCircleCenter = CGPoint()
var currentCenter = CGPoint()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
circleView.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
lastCircleCenter = touch.location(in: view)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let circleCenter = touch.location(in: view)
if circleCenter == lastCircleCenter{
let circleHeight = circleWidth
circleView = CircleView(frame: CGRect(x: circleCenter.x - circleWidth / 2, y: circleCenter.y - circleWidth / 2, width: 100, height: 100))
view.addSubview(circleView)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let location = touch.location(in: view)
circleView.center = location
}
}
how can I select a specific circle? please guide me for the same.
if anything else you need let me know.
how can I select a specific circle? please guide me for the same.
if anything else you need let me know.
If you are creating multiple circles and adding them to your view, I would suggest to keep track of the created circles in a collection. That way on each touch you can check if the coordinate matches any of the created circles. Based on that you can determine if to create a new circle or to move an existing one.
Example:
class ViewController: UIViewController {
var circleViews: [CircleView] = []
let circleWidth = CGFloat(100)
var draggedCircle: CircleView?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Do nothing if a circle is being dragged
// or if we do not have a coordinate
guard draggedCircle == nil, let point = touches.first?.location(in: view) else {
return
}
// Do not create new circle if touch is in an existing circle
// Keep the reference of the (potentially) dragged circle
if let draggedCircle = circleViews.filter({ UIBezierPath(ovalIn: $0.frame).contains(point) }).first {
self.draggedCircle = draggedCircle
return
}
// Create new circle and store in an array
let offset = circleWidth / 2
let rect = CGRect(x: point.x - offset, y: point.y - offset, width: circleWidth, height: circleWidth)
let circleView = CircleView(frame: rect)
circleViews.append(circleView)
view.addSubview(circleView)
// The newly created view can be immediately dragged
draggedCircle = circleView
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// If touches end then a circle is never dragged
draggedCircle = nil
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let draggedCircle = draggedCircle, let point = touches.first?.location(in: view) else {
return
}
draggedCircle.center = point
}
}
import SpriteKit
class GameScene: SKScene {
var cam: SKCameraNode?
var player: SKSpriteNode?
override func didMove(to view: SKView) {
super.didMove(to: view)
cam = SKCameraNode()
self.camera = cam
self.addChild(cam!)
player = self.childNode(withName: "player") as? SKSpriteNode
}
func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch:UITouch = touches.anyObject() as! UITouch
let touchLocation = touch.location(in: self)
if touchLocation.x < self.frame.size.width / 2 {
let moveRight = SKAction.moveBy(x: 300, y: 0, duration: 1)
player?.run(moveRight)
} else {
let moveLeft = SKAction.moveBy(x: -300, y: 0, duration: 1)
player?.run(moveLeft)
}
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
if let camera = cam, let pl = player {
camera.position = pl.position
}
}
}
That is my whole game scene and the sprites I have dragged and dropped onto the screen using the sk scene. I have added a camera to the scene if that affects anything. Thanks for any help.
You need to override the touches began function. It also helps to put in a print statement to show that touches are being received.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
print("touch detected")
let touchLocation = touch.location(in: self)
if touchLocation.x < self.frame.size.width / 2 {
let moveRight = SKAction.moveBy(x: 300, y: 0, duration: 1)
player?.run(moveRight)
} else {
let moveLeft = SKAction.moveBy(x: -300, y: 0, duration: 1)
player?.run(moveLeft)
}
}
}
import SpriteKit
class GameScene: SKScene {
var cam: SKCameraNode?
var player: SKSpriteNode?
override func didMove(to view: SKView) {
super.didMove(to: view)
cam = SKCameraNode()
self.camera = cam
self.addChild(cam!)
player = self.childNode(withName: "player") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
print("touch detected")
let touchLocation = touch.location(in: self)
if touchLocation.x < (player?.position.x)! {
let moveRight = SKAction.moveBy(x: 300, y: 0, duration: 1)
print("right")
player?.run(moveRight)
} else {
let moveLeft = SKAction.moveBy(x: -300, y: 0, duration: 1)
print("Left")
player?.run(moveLeft)
}
}
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
if let camera = cam, let pl = player {
camera.position = pl.position
}
}
}
I figured it out with the help of JohnL. By adding if touchLocation.x < (player?.position.x)! instead of if touchLocation.x < self.frame.size.width / 2
I am trying to make a game that has five simple objects that the user can touch and move these have physics on them but having trouble getting them to move separately any help would be appreciated.
This is what I have so far:
import SpriteKit
class GameScene: SKScene {
let starSprite = SKSpriteNode(imageNamed: "starSprite")
let circleSprite = SKSpriteNode(imageNamed: "circleSprite")
let squareSprite = SKSpriteNode(imageNamed: "squareSprite")
let triangleSprite = SKSpriteNode(imageNamed: "triangleSprite")
let moonSprite = SKSpriteNode(imageNamed: "moonSprite")
var touchLocation = CGPoint()
override func didMove(to view: SKView) {
layoutScene()
}
func layoutScene() {
backgroundColor = UIColor(red: 44/255, green: 62/255, blue:
80/255, alpha: 1.0)
starSprite.size = CGSize(width: 350, height: 350)
starSprite.position = CGPoint(x: frame.midX, y: frame.midY)
starSprite.physicsBody = SKPhysicsBody(texture:
starSprite.texture!, size: starSprite.size)
addChild(starSprite)
circleSprite.size = CGSize(width: 350, height: 350)
circleSprite.position = CGPoint(x: 0, y: 100)
circleSprite.physicsBody = SKPhysicsBody(texture:
circleSprite.texture!, size: circleSprite.size)
addChild(circleSprite)
squareSprite.size = CGSize(width: 350, height: 350)
squareSprite.position = CGPoint(x: 0, y: 200)
squareSprite.physicsBody = SKPhysicsBody(texture:
squareSprite.texture!, size: squareSprite.size)
addChild(squareSprite)
triangleSprite.size = CGSize(width: 350, height: 350)
triangleSprite.position = CGPoint(x: 0, y: 300)
triangleSprite.physicsBody = SKPhysicsBody(texture:
triangleSprite.texture!, size: triangleSprite.size)
addChild(triangleSprite)
moonSprite.size = CGSize(width: 350, height: 350)
moonSprite.position = CGPoint(x: 0, y: 400)
moonSprite.physicsBody = SKPhysicsBody(texture:
moonSprite.texture!, size: moonSprite.size)
addChild(moonSprite)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
touchLocation = touch.location(in: self)
moonSprite.position.x = (touchLocation.x)
moonSprite.position.y = (touchLocation.y)
starSprite.position.x = (touchLocation.x)
starSprite.position.y = (touchLocation.y)
}
}
}
you should first detect which object has been selected in touchDown, then change the position in touchesMoved, and release the object in touchUp!
define a value as a temp and use that!
In addition , and you don't need to use this
moonSprite.position.x = (touchLocation.x)
moonSprite.position.y = (touchLocation.y)
it's better
moonSprite.position = touchLocation
Edited Code
//
// GameScene.swift
// aa
//
// Created by ehsan on 1/30/19.
// Copyright © 2019 ehsan. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var spinnyNode1 = SKSpriteNode(imageNamed: "1")
private var spinnyNode2 = SKSpriteNode(imageNamed: "2")
private var movingSprite:SKSpriteNode?
override func didMove(to view: SKView) {
addChild(spinnyNode1)
addChild(spinnyNode2)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
let node = self.atPoint(location)
if node is SKSpriteNode
{
movingSprite = node as? SKSpriteNode
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if movingSprite != nil
{
movingSprite!.position = touchLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
movingSprite = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
I would like to have the following interplay between line and ball in my game: a line gives direction and speed to the ball. The longer the line, the faster the ball.
What I have now: a ball is following the line and stops at the end of it. But it shouldn't stop here. Of course, I understand that the ball is only following the path I made. But how could I change it?
Here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// Basic for dynamic sizes step01
var width = CGFloat()
var height = CGFloat()
var ball:SKSpriteNode!
var line:SKShapeNode!
var startPoint: CGPoint!
var location = CGPoint()
override func didMove(to view: SKView) {
self.backgroundColor = .purple
//declare dynamic size of the screen
width = self.frame.size.width
height = self.frame.size.height
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.physicsWorld.gravity = CGVector.zero
createBall()
}
func createBall(){
ball = SKSpriteNode(imageNamed: "yellowBtn")
ball.position = CGPoint(x:0, y: -(height/2.5))
ball.size = CGSize(width: width/6, height: width/6)
self.addChild(ball)
}
func drawLine(endPoint:CGPoint){
if(line != nil ){ line.removeFromParent() }
startPoint = ball.position
let path = CGMutablePath()
path.move(to: startPoint)
path.addLine(to: endPoint)
line = SKShapeNode()
line.path = path
line.lineWidth = 5
line.strokeColor = UIColor.white
self.addChild(line)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(line != nil ){ line.removeFromParent()
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let moveAction = SKAction.move(to: location, duration: 10)
ball.run(moveAction)
}
override func update(_ currentTime: TimeInterval) {
}
}
I have found at least a part of answer to my question: I need to calculate CGVector manually (lastPoint - firstPoint) and then apply impulse to the ball:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let dx = location.x - startPoint.x
let dy = location.y - startPoint.y
let movement = CGVector(dx: dx, dy: dy)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 50)
ball.physicsBody?.applyImpulse(movement)
}
Now I should find the second part: how to set the speed of the ball accordingly to the length of the line.
im currently working on an app in which your player which is a small round ball, gets dragged around the screen by your finger. Then every 10 seconds a enemy ball gets added in which automatically tracks your ball and follows it until it runs into it. this is the code I have for this so far. you can drag your ball around the screen, bu the enemy ball which is only showing up as a red x even though its file is in the assets gets spawned every frame and they just move to the position of where your ball was when it was spawned. is there any way I can fix this, any help is appreciated. the contact between the balls I will add later on.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var me = SKSpriteNode()
let enemy = SKSpriteNode (imageNamed: "ellipse 1")
override func didMove(to view: SKView) {
me = self.childNode(withName: "me") as! SKSpriteNode
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func update(_ currentTime: TimeInterval) {
let enemy = SKSpriteNode (imageNamed: "ball 2")
enemy.position = CGPoint(x:667, y: -200)
enemy.run(SKAction.moveTo(x: me.position.x, duration: 1.5))
enemy.run(SKAction.moveTo(y: me.position.y, duration: 1.5))
enemy.zPosition = +1
addChild(enemy)
}
}
So the problem you're having is that your enemy ball images aren't loading, and you're seeing a red X instead?
If so you need to break your code that creates your sprite into steps. First load the image into a temporary variable, print it, and then use the image to create your sprite:
let enemyImage = imageNamed: "ellipse 1"
print("enemyImage = \(enemyImage)")
let enemy = SKSpriteNode (enemyImage)
If enemyImage is nil, you need to figure out why that is.
Here is a modified version of your code that'll spawn an enemy once every 10 seconds and have them follow you. I used the Spaceship graphic provided by Apple's SpriteKit template for the enemy. As for your issue with the enemy graphic not showing up I'd make sure that the image is named "ball 2" in the assets folder.
import SpriteKit
import GameplayKit
class Enemy: SKSpriteNode {
var enemySpeed: CGFloat = 4
}
class GameScene: SKScene {
var me: SKSpriteNode!
var enemies = [Enemy]()
override func didMove(to view: SKView) {
me = self.childNode(withName: "me") as! SKSpriteNode
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
spawnEnemy() // Spawn an enemy to begin with
Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(spawnEnemy), userInfo: nil, repeats: true) // Spawns an enemy every 10 seconds
}
func spawnEnemy() {
let enemy = Enemy(imageNamed: "Spaceship")
enemy.xScale = 0.25
enemy.yScale = 0.25
enemy.position = CGPoint(x: CGFloat(arc4random() % UInt32(size.width)) - size.width / 2, y: CGFloat(arc4random() % UInt32(size.height)) - size.height / 2)
enemy.zPosition = 1
addChild(enemy)
enemies.append(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
me.run(SKAction.move(to: location, duration: 0.05))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
me.run(SKAction.move(to: location, duration: 0.05))
}
}
override func update(_ currentTime: TimeInterval) {
for enemy in enemies {
let angle = CGFloat(atan2f(Float(me.position.y - enemy.position.y), Float(me.position.x - enemy.position.x)))
enemy.zRotation = angle - CGFloat.pi / 2
enemy.position = CGPoint(x: enemy.position.x + cos(angle) * enemy.enemySpeed, y: enemy.position.y + sin(angle) * enemy.enemySpeed)
}
}
}
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var me = SKSpriteNode()
override func didMove(to view: SKView) {
me = self.childNode(withName: "me") as! SKSpriteNode
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 10.0)])))
}
func createEnemy () {
let enemy = SKSpriteNode(imageNamed: "ball 1")
enemy.name = "enemy"
enemy.position = CGPoint(x:667, y: -200)
enemy.run(SKAction.moveTo(x: me.position.x, duration: 1.5))
enemy.run(SKAction.moveTo(y: me.position.y, duration: 1.5))
enemy.zPosition = +1
addChild(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
}
}
override func update(_ currentTime: TimeInterval) {
}
}