How to Create "identical" SpriteNodes that hold the same methods? - swift

I'm super new to coding (like I started yesterday new). I'm trying to make the same SpriteNode appear multiple times that follow the same methods. For example, the game starts off with one Node that reacts to touch and after you reach 5 points another appears, after 15 another and so on... I have been able to spawn a spriteCopy at 5 points; however I cannot get it to respond to touch like the original node. I'm sure it's something super simple I just don't have the experience. Thanks.
Here is my code so far:
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
var score: Int = 0
let scoreTextNode = SKLabelNode(fontNamed: "Helvetica Neue UltraLight")
override func didMoveToView(view: SKView) {
sprite = SKSpriteNode(imageNamed: "PongBall")
sprite.position = CGPoint(x: 500,y: 500)
sprite.size = CGSize(width: 75, height: 75)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 37.5)
self.addChild(sprite)
scoreTextNode.text = "\(score)"
scoreTextNode.fontSize = 70
scoreTextNode.fontColor = SKColor.blackColor()
scoreTextNode.position = CGPoint (x: 680.734, y: 700.941)
self.addChild(scoreTextNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
if touchPoint == location {
self.score += 1
self.scoreTextNode.text = "\(score)"
}
let spriteCopy = sprite.copy() as! SKSpriteNode
if score == 5 {
self.addChild(spriteCopy)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
touchPoint = location
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = false
}
override func update(currentTime: CFTimeInterval) {
if touching {
let dt:CGFloat = 1.0/30.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity=velocity
}
}
}

Your problem is you have multiple sprites appearing on your screen, but you do not do anything with them. To keep things real simple for you, take this approach.
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
var score: Int = 0
let scoreTextNode = SKLabelNode(fontNamed: "Helvetica Neue UltraLight")
var spriteList = [SKSpriteNode]() //Add this line
override func didMoveToView(view: SKView) {
sprite = SKSpriteNode(imageNamed: "PongBall")
sprite.position = CGPoint(x: 500,y: 500)
sprite.size = CGSize(width: 75, height: 75)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 37.5)
self.addChild(sprite)
spriteList.append(sprite) //Add This line
scoreTextNode.text = "\(score)"
scoreTextNode.fontSize = 70
scoreTextNode.fontColor = SKColor.blackColor()
scoreTextNode.position = CGPoint (x: 680.734, y: 700.941)
self.addChild(scoreTextNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
for sprite in spriteList{ //Add This line
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
if touchPoint == location {
self.score += 1
self.scoreTextNode.text = "\(score)"
}
let spriteCopy = sprite.copy() as! SKSpriteNode
if score == 5 {
self.addChild(spriteCopy)
spriteList.append(spriteCopy) //Add this line
}
} //Add this line
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
touchPoint = location
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touching = false
}
override func update(currentTime: CFTimeInterval) {
if touching {
for sprite in spriteList{ //Add this line
let dt:CGFloat = 1.0/30.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity=velocity
} //Add this line
}
}
}
The basic concept is you maintain it in an array. This should be a good starting point for a beginner. After you learn how to override classes, you can learn that the touch code can be done inside of the sprite instead of inside of the scene. This would actually be my preferred method.

Related

draw a straight line with a starting touch point

I want my swift code to draw a straight horiziontal line. Right now it selects a point and the user extends the line anchored in the first point. I just want the user to be able to draw the line left or right. I tried alternating bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y)) but that does not seem to have any effect.
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var drawingPlace: UIImageView!
var startTouch : CGPoint?
var secondTouch : CGPoint?
var currentContext : CGContext?
var prevImage : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemOrange
drawingPlace.backgroundColor = .gray
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startTouch = touch?.location(in: drawingPlace)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
secondTouch = touch.location(in: drawingPlace)
if(self.currentContext == nil){
UIGraphicsBeginImageContext(drawingPlace.frame.size)
self.currentContext = UIGraphicsGetCurrentContext()
}else{
self.currentContext?.clear(CGRect(x: 0, y: 0, width: drawingPlace.frame.width, height: drawingPlace.frame.height))
}
self.prevImage?.draw(in: self.drawingPlace.bounds)
let bezier = UIBezierPath()
bezier.move(to: startTouch!)
bezier.addLine(to: secondTouch!)
bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y))
bezier.close()
UIColor.blue.set()
self.currentContext?.setLineWidth(4)
self.currentContext?.addPath(bezier.cgPath)
self.currentContext?.strokePath()
let img2 = self.currentContext?.makeImage()
drawingPlace.image = UIImage.init(cgImage: img2!)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.currentContext = nil
self.prevImage = self.drawingPlace.image
}
}
If you want to draw a horizontal line, create a CGPoint whose x is the location of the touch and whose y is that of the starting point. That will result in a horizontal line.
That having been said, here are a few other of observations:
If you call UIGraphicsBeginImageContext, you must call UIGraphicsEndImageContext. You should do this within touchesMoved, not trying to hang on to the context beyond this call.
If you were to do this, we would generally use UIGraphicsImageRenderer nowadays.
Personally, I wouldn't try rendering an image for every touch. That is a pretty expensive operation. I would just add a CAShapeLayer and then update the path for that layer. Let CAShapeLayer take care of the rendering of the path.
I'm not quite sure why you are iterating through the array of touches. I would just grab one and use that.
I might suggest using predictive touches to minimize perceived lagginess.
The startTouch is actually a CGPoint, not a UITouch, so I might call it startPoint instead.
If you want to make a snapshot image, I'd do that in touchesEnded, not in touchesMoved.
For example:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var startPoint: CGPoint?
let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 4
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
return shapeLayer
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = .systemOrange
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startPoint = touch?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard var touch = touches.first else { return }
if let predicted = event?.predictedTouches(for: touch)?.last {
touch = predicted
}
updatePath(in: imageView, to: touch)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePath(in: imageView, to: touch)
let image = UIGraphicsImageRenderer(bounds: imageView.bounds).image { _ in
imageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
}
shapeLayer.path = nil
imageView.image = image
}
}
private extension ViewController {
func updatePath(in view: UIView, to touch: UITouch) {
let point = touch.location(in: view)
guard view.bounds.contains(point) else { return }
let bezier = UIBezierPath()
bezier.move(to: startPoint!)
bezier.addLine(to: CGPoint(x: point.x, y: startPoint!.y))
shapeLayer.path = bezier.cgPath
}
}
This just renders the current line as a CAShapeLayer, but when the stroke is done, it creates a snapshot of the image view (permanently capturing the stroke in an image), removes the shape layer’s path, and updates the image view’s image.
But hopefully this answers the question on how to make the line horizontal.

Move SKSpriteNode on Touch to create Button effect

I am currently creating the main menu of a mobile game, and it works fine however To make it look nicer I would like to add an effect when I touch one of my SKSpriteNodes which is an image of a button. I would like it to slightly move down then back up before transitioning to the game, im having some difficulty. Currently when the button is pressed it changed scenes to the game scene.
class MainMenuScene: SKScene{
let startGame = SKSpriteNode(imageNamed: "StartButton")
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "Menu")
background.size = self.size
background.position = CGPoint(x: self.size.width/2, y:self.size.height/2)
background.zPosition = 0
self.addChild(background)
startGame.position = CGPoint(x: self.size.width/2, y: self.size.height*0.6)
startGame.zPosition = 1
startGame.name = "startButton"
self.addChild(startGame)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
if nodeITapped.name == "startButton" {
let sceneToMoveTo = GameScene(size: self.size)
sceneToMoveTo.scaleMode = self.scaleMode
let myTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: myTransition)
}
}
}
}
You could make your logic in the function touchesEnded and then you call a SKAction in your button, passing the moveToScene logic as completion.
Something like:
var shouldMoveToNewScene: Bool = False
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
shouldMoveToNewScene = nodeITapped.name == "startButton" ? True : False
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard shouldMoveToNewScene, touces.count == 1 else { return }
startButton.run(myAnimation, completion: {
moveToScene()
})
}
private func moveToScene() {
//do your transition scene logic here
{
I haven't tried it yet, but hopefully you could get the idea.
If you're not familiar with SKAction you should consider follow this guide.

my code doesn't draw a line, it only draws a very tiny circle

My code isn't behaving the way I want it, why is the so? I don't understand what went wrong. So I'm playing around with swift, trying to transition from android to swift, I'm trying to make this simple app that draws lines, its not working as I want it, can someone please help me with what I'm doing wrong?
//
// DrawView.swift
// IOSTouch
import Foundation
import UIKit
class DrawView: UIView {
var currentLine: Line?
var finishedLines = [Line]();
//for debug
let line1 = Line(begin: CGPoint(x:50,y:50), end: CGPoint(x:100,y:100));
let line2 = Line(begin: CGPoint(x:50,y:100), end: CGPoint(x:100,y:300));
func strokeLine(line: Line){
//Use BezierPath to draw lines
let path = UIBezierPath();
path.lineWidth = 5;
path.lineCapStyle = CGLineCap.round;
path.move(to: line.begin);
path.addLine(to: line.end);
path.stroke(); //actually draw the path
}
override func draw(_ rect: CGRect) {
//draw the finished lines
UIColor.black.setStroke() //finished lines in black
for line in finishedLines{
strokeLine(line: line);
}
//for debug
strokeLine(line: line1);
strokeLine(line: line2);
//draw current line if it exists
if let line = currentLine {
UIColor.red.setStroke(); //current line in red
strokeLine(line: line);
}
}
//Override Touch Functions
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(#function) //for debugging
let touch = touches.first!; //get first touch event and unwrap optional
let location = touch.location(in: self); //get location in view co-ordinate
currentLine = Line(begin: location, end: location);
setNeedsDisplay(); //this view needs to be updated
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//TODO
setNeedsDisplay()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//TODO
setNeedsDisplay()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
//TODO
}
#IBInspectable var finishedLineColor: UIColor = UIColor.black {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var currentLineColor: UIColor = UIColor.red {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var lineThickness: CGFloat = 10 {
didSet {
setNeedsDisplay()
}
}
}
another file
import Foundation
import CoreGraphics
struct Line {
var begin = CGPoint.zero
var end = CGPoint.zero
}
This code is works, I use it:
import UIKit
class ViewController: UIViewController {
var firstPoint: CGPoint?
var secondPoint: CGPoint?
var currentLine: CAShapeLayer?
override func viewDidLoad() {
super.viewDidLoad()
}
func addLine(fromPoint start: CGPoint, toPoint end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.black.cgColor
line.lineWidth = 3
line.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(line)
}
func setCurrentLine(fromPoint start: CGPoint, toPoint end: CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
line.lineWidth = 3
line.lineJoin = kCALineJoinRound
currentLine = line
if let current = currentLine {
self.view.layer.addSublayer(current)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
firstPoint = touches.first?.location(in: self.view)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
currentLine?.removeFromSuperlayer()
currentLine = nil
if let first = firstPoint, let current = touches.first?.location(in: self.view) {
setCurrentLine(fromPoint: first, toPoint: current)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
secondPoint = touches.first?.location(in: self.view)
if let first = firstPoint, let second = secondPoint {
addLine(fromPoint: first, toPoint: second)
currentLine?.removeFromSuperlayer()
currentLine = nil
}
}
}
It should work now
override func touchesMoved(_ touches: Set, with event: UIEvent?) {
print(#function)
let touch = touches.first!
let location = touch.location(in: self);
currentLine?.end = location;
setNeedsDisplay();
}
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
print(#function) //for debugging
if var line = currentLine {
let touch = touches.first!;
let location = touch.location(in: self);
line.end = location;
finishedLines.append(line);
}
currentLine = nil;
setNeedsDisplay();
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
print(#function) //for debugging
currentLine = nil;
setNeedsDisplay();
}

How to add 1 point every time SKNode is touched

I created a game involving falling balls. Every time the player taps a ball, they're supposed to get a point. I set up the score label, but it just stays at 0.
Is there something wrong with my code?
I use a GameElements.swift file as an extension. Here is the file:
extension GameScene {
//1st Ball//
func createPlayer() -> SKNode {
let playerNode = SKNode()
playerNode.position = CGPoint(x:self.size.width / 2, y:440)
let sprite = SKSpriteNode(imageNamed: "Ball")
sprite.name = "ballPoints"
playerNode.addChild(sprite)
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: 120)
playerNode.physicsBody?.dynamic = true
playerNode.physicsBody?.allowsRotation = false
playerNode.physicsBody?.restitution = 3
playerNode.physicsBody?.friction = 0
playerNode.physicsBody?.angularDamping = 0
playerNode.physicsBody?.linearDamping = 0
playerNode.physicsBody?.usesPreciseCollisionDetection = true
playerNode.physicsBody?.categoryBitMask = CollisionBitMask.Player
playerNode.physicsBody?.categoryBitMask = 0
return playerNode
}
}
Here is the GameScene.swift file:
class GameScene: SKScene {
var foreground: SKNode!
var hud: SKNode!
var firstBall: SKNode!
var scoreLabel: SKLabelNode!
private var score = 0
override func didMoveToView(view: SKView) {
scoreLabel = SKLabelNode(fontNamed:"Geared-Slab")
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPoint( x: self.frame.midX, y: 3 * self.frame.size.height / 4 )
scoreLabel.fontSize = 100.0
scoreLabel.zPosition = 100
scoreLabel.text = String(score)
self.addChild(scoreLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if(firstBall.containsPoint(location)) {
firstBall.physicsBody?.velocity = CGVectorMake(0, 600)
firstBall.physicsBody?.applyImpulse(CGVectorMake(0, 1100))
}
if
let touch : UITouch! = touches.first,
let tappedSprite = nodeAtPoint(touch!.locationInNode(self)) as? SKSpriteNode,
let scoreLabel = childNodeWithName("scoreLabel") as? SKLabelNode
where tappedSprite.name == "ballPoints" {
score += 1
scoreLabel.text = "Score: \(score)"
}
}
}
override init(size:CGSize) {
super.init(size: size)
foreground = SKNode()
addChild(foreground)
//1st Ball//
firstBall = createPlayer()
foreground.addChild(firstBall)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I know this is a lot of code, but I want experts to be able to test the code themselves. For background, I was following this tutorial: https://www.youtube.com/watch?v=0gOi_2Jwt28 up until a certain point.
Step 1
First of all let's create a class for the Ball
class Ball: SKSpriteNode {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
}
Step 2
Then inside createPlayer() let's replace this
let sprite = SKSpriteNode(imageNamed: "Ball")
with this
let sprite = Ball(imageNamed: "Ball")
sprite.userInteractionEnabled = true
Step 3
Let's remove the touchesBegan from GameScene.
Update
This code is working as expected for me
class GameScene: SKScene {
var scoreLabel: SKLabelNode!
var ball: Ball!
private var score = 0 {
didSet {
scoreLabel.text = "\(score)"
print(score)
}
}
override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(ball)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// this is called when you tap outside of the Ball
// use self.ball to make the ball to jump
}
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Player not moving when tapped

I have it set up so that my player should jump to wherever I tap on the screen, however, it won't budge. I triple checked my code and can't find anything wrong. Can someone please help?
Here is my code for my GameScene:
import SpriteKit
var player: Player!
var snowflake: Snowflake!
var isFingerOnPlayer = false
var gameOver = false
var playerTouched = false
var touching: Bool = false
var lastTouch: CGPoint? = nil
let xPlayerForce: CGFloat = 30
let yPlayerForce: CGFloat = 40
var touchPoint: CGPoint = CGPoint()
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
player = Player()
addChild(player)
backgroundColor = UIColor.whiteColor()
snowflake = Snowflake()
addChild(snowflake)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
lastTouch = location
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
lastTouch = nil
}
override func update(currentTime: NSTimeInterval) {
if let touch = lastTouch {
var xForce: CGFloat = 0.0
var yForce: CGFloat = 0.0
let xTouchOffset = (touch.x - player.position.x)
let yTouchOffset = (touch.y - player.position.y)
if xTouchOffset > 0.0
{
xForce = xPlayerForce
}
else if xTouchOffset < 0.0
{
xForce = -xPlayerForce
} // else we do nothing
if yTouchOffset > 0.0
{
yForce = yPlayerForce
} // here you can choose whether you want it to push
// the player node down, using similar code from the
// above if statement
let impulseVector = CGVector(dx: xForce, dy: yForce)
player.physicsBody?.applyImpulse(impulseVector)
}
}
You are right by saying you don't NEED code in touches began, but from what you are saying you should probably. I would try just setting the location of the object in touches began and then updating it in touches moved.