Hi everyone i am making a sprite-kit app in which the user must be able to draw multiple lines. To do this I made the following code.
import SpriteKit
class GameScene: SKScene {
var line = SKShapeNode()
var path = CGPathCreateMutable()
var touch: UITouch!
var location:CGPoint!
override func didMoveToView(view: SKView) {
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
touch = touches.anyObject() as UITouch!
location = touch.locationInNode(self)
drawLine()
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
self.delete(line)
}
func drawLine() {
CGPathMoveToPoint(path, nil, location.x, location.y)
CGPathAddLineToPoint(path, nil, 500.0, 500.0)
line.path = path
line.strokeColor = UIColor.redColor()
line.lineWidth = 5.0
self.addChild(line)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
This works but when the users tries the draw the second line the application crashes.
Can someone explain to me why that is happening?
------Edit------
I think i need to use an array of lines so I did something like this:
var line: [[SKShapeNode]] = []
which gives me the following error:
[[SKShapeNode]] does not have a member named 'path'.
So I though I had to make the path variable an array as well but that only gave me a second error: GameScene does not have a member named 'path'. So I changed it all back but I still think I have to use an array although I can't really figure out how to make it work.
Copy and paste this..now you'll redefine your line every time
import SpriteKit
class GameScene: SKScene {
var line = SKShapeNode()
var path = CGPathCreateMutable()
var touch: UITouch!
var location:CGPoint!
override func didMoveToView(view: SKView) {
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
touch = touches.anyObject() as UITouch!
location = touch.locationInNode(self)
drawLine()
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
self.delete(line)
}
func drawLine() {
CGPathMoveToPoint(path, nil, location.x, location.y)
CGPathAddLineToPoint(path, nil, 500.0, 500.0)
SKShapeNode *line = [SKShapeNode node]; //redfine line every time
line.path = path
line.strokeColor = UIColor.redColor()
line.lineWidth = 5.0
self.addChild(line)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Related
I am having a problem formatting the scene for a simple game using SKSpriteKit.
In the simulator, my scene looks exactly how I want, but on a physical device it does not.
This is what is looks like in the Simulator
This is what it looks like on my Ipad
Once I run this on a physical device(my iPad), all i get is a zoomed in picture of the background.
Here is my GameViewController.swift
import UIKit
import SpriteKit
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
//Configure the view
let skView = view as! SKView
skView.multipleTouchEnabled = false
//Create and configure the scene
//create scene within size of skview
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
//present the scene
skView.presentScene(scene)
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .Landscape
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Additionally Here is my GameScene.swift
import SpriteKit
class GameScene: SKScene {
//////////////////////////////////////////////////////////////////////
//background
var background = SKSpriteNode(imageNamed: "Basketball")
//isStarted & GameOver
var isStarted = false
var isGameOver = false
//
var Ball: Basketball_Ball!
var Rim1: Rim!
var Rim2: Rim!
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
func addBackground(){
//background
background.zPosition = 0
background.size = self.frame.size
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background)
}
/////////////////////////////////////////////////////////////////////
func addBall() {
Ball = Basketball_Ball()
addChild(Ball)
Ball.loadAppearance()
}
/////////////////////////////////////////////////////////////////////
func addRims() {
Rim1 = Rim()
addChild(Rim1)
Rim1.loadAppearance()
Rim2 = Rim()
addChild(Rim2)
Rim2.loadAppearance_Rim2()
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
override func didMoveToView(view: SKView) {
addBackground()
addBall()
addRims()
}
/////////////////////////////////////////////////////////////////////
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Does the problem lie in my use of scene.scaleMode in my GameViewController file?
Do I need to upload different sizes for each image in my Assets Folder?
I have these facebook interstitial ads and when I call them in my GameScene they dont show up. They work perfectly when I call them in the GameViewController but not in my GameScene. Can someone tell me what Im doing wrong with my code?
//GameViewController.swift
class GameViewController: UIViewController, FBInterstitialAdDelegate {
let interstitialFBAD: FBInterstitialAd = FBInterstitialAd(placementID: "65543456456456_454345345454534")
override func viewDidLoad() {
super.viewDidLoad()
}
//CALL THIS IN MY GAMESCENE.
func loadFBInterstitialAd() {
interstitialFBAD.delegate = self
interstitialFBAD.loadAd()
}
func interstitialAdDidLoad(interstitialAd: FBInterstitialAd!) {
interstitialFBAD.showAdFromRootViewController(self)
}
//GameScene.swift
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "retry" {
GameViewController().loadFBInterstitialAd()
}
}
}
In viewDidLoad of GameViewController register for a notification, and later from the GameScene post a notification when you want the ad to appear.
so your viewDidLoad will have the following extra line
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadFBInterstitialAd", name: "loadAd", object: nil)
then write a method in Gamescene
func showAdNow() {
NSNotificationCenter.defaultCenter().postNotificationName("loadAd", object: nil)
}
now in Gamescene.swift replace the line
GameViewController().loadFBInterstitialAd()
with
showAdNow()
You try:
//GameViewController.swift
class GameViewController: UIViewController, FBInterstitialAdDelegate {
let interstitialFBAD: FBInterstitialAd = FBInterstitialAd(placementID: "65543456456456_454345345454534")
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as! SKView
skView.multipleTouchEnabled = false
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.view?.userInteractionEnabled = true
skView.presentScene(scene)
scene.gvcDelegate = self
}
//GameScene.swift
class GameScene: SKScene {
gvcDelegate: GameViewController?
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "retry" {
gvcDelegate.loadFBInterstitialAd()
}
}
}
Goodluck!
I have an app that has two ViewControllers. On the first there is a count of current speed in realtime through CLLocationManager. Also there is a label that shows current speed with update by timer (NSTimer). In second ViewController there is another Label, where this current speed has to be shown too. It shows it, but don't update. I tried to set second timer (different ways: in first VC, in second VC - there is always was an error or just nothing).
Will be grateful for help, thanks!
First VC
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var currentSpeedLabel: UILabel!
var manager = CLLocationManager()
var currentSpeed: CLLocationSpeed = CLLocationSpeed()
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
mapView.mapType = MKMapType.Hybrid
trackingMe()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func HUDMapView(sender: AnyObject) {
speedCount()
}
#IBAction func findMe(sender: AnyObject) {
trackingMe()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
let location = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
}
func trackingMe() {
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
mapView.showsUserLocation = true
currentSpeedUpdate()
}
func currentSpeedUpdate() {
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("speedCount"), userInfo: nil, repeats: true)
}
func speedCount() {
currentSpeed = manager.location!.speed
currentSpeedLabel.text = String(format: "%.0f km/h", currentSpeed * 3.6)
}
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
let speedController = segue.destinationViewController as! speedViewController
currentSpeed = manager.location!.speed
speedController.showSpeed = currentSpeedLabel.text
}
}
Second VC
import UIKit
class speedViewController: UIViewController {
#IBOutlet weak var secondSpeedLabel: UILabel!
var showSpeed: String!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
secondSpeedLabel.text = showSpeed
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func back(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Project Link
You could use a Singleton to hold the LocationManager. Then you can access it from all over your app. When you move to a second VC you can either change the delegate to the second VC or just get the needed data manually.
Remember that a delegate can only point to one "receiver". Changing the delegate will stop updates in the first VC. but since it is now a Singleton you can also store information in there about past locations / speeds. When dismissing the second VC get the stored data and update.
This will keep running until you call stop()
The code was simplified a bit to illustrate the idea.
VC Code:
import UIKit
import CoreLocation
class ViewController: UIViewController, TrackerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
Tracker.shared.delegate = self
Tracker.shared.start()
}
func speedUpdate(speed: CLLocationSpeed) {
print(speed)
}
}
Singleton Code:
import UIKit
import MapKit
import CoreLocation
class Tracker: NSObject, CLLocationManagerDelegate {
static var shared = Tracker()
private var manager = CLLocationManager()
private var timer = NSTimer()
var region : MKCoordinateRegion?
var currentSpeed: CLLocationSpeed = CLLocationSpeed()
weak var delegate : TrackerDelegate?
private override init() {
super.init()
manager.delegate = self
manager.requestWhenInUseAuthorization()
}
internal func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
let userLocation: CLLocation = locations[0] as CLLocation
let coordinates2D = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.05, 0.05)
region = MKCoordinateRegion(center: coordinates2D, span: span)
currentSpeed = userLocation.speed
guard let del = delegate else {
return
}
del.speedUpdate(currentSpeed)
}
func start() {
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.startUpdatingLocation()
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("loopUpdate"), userInfo: nil, repeats: true)
}
func stop() {
timer.invalidate()
}
internal func loopUpdate() {
// restart updating
manager.startUpdatingLocation()
}
}
Delegate for the Singleton:
Add more functions, or more values to the current function to get more feedback.
protocol TrackerDelegate : class {
func speedUpdate(speed:CLLocationSpeed)
}
I'm trying to drag a SKSpirteNode around the screen by touching the screen. But I want to be able to do a constant movement of the Sprite, currently my code only moves the sprite to the location of my touch but if I hold and move the sprite will not follow. Moreover I don't want to "have" to touch the SKSpriteNode to activate the movement, I want to touch anywhere on the screen and to have a movement response from that SKSpriteNode.
Here is my current code:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// SpriteNode I want to drag around
basket = SKSpriteNode(texture: basketTexture)
self.addChild(basket)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
var nodeTouched = SKNode()
var currentNodeTouched = SKNode()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
nodeTouched = self.nodeAtPoint(location)
basket.position = location
}
Thank you any help appreciated.
I solved this by using the func touchesMoved instead of touchesBegan and works perfectly and smoothly. here is the final code:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// SpriteNode I want to drag around
basket = SKSpriteNode(texture: basketTexture)
self.addChild(basket)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
var nodeTouched = SKNode()
var currentNodeTouched = SKNode()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
nodeTouched = self.nodeAtPoint(location)
basket.position = location
}
File GameScene have class GameScene, class GameScene have CirclePhysicsDefault:
func circlePhysicsDefault() {
var Circle = SKShapeNode(circleOfRadius: 40)
Circle.position = CGPointMake(500, 500)
Circle.name = "defaultCircle"
Circle.strokeColor = SKColor.blackColor()
Circle.glowWidth = 10.0
Circle.fillColor = SKColor.yellowColor()
Circle.physicsBody = SKPhysicsBody(circleOfRadius: 40)
Circle.physicsBody.dynamic = true
self.addChild(Circle)
}
In file GameViewController i type:
#IBAction func addOneCircle(sender: AnyObject) {
GameScene().circlePhysicsDefault()
}
I linked this to button.
Run application, push the button - nothing happens.
If in GameScene function didMoveToView i call function circlePhysicsDefault, then app launch circle will be placed.
File GameViewController:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
/* Pick a size for the scene */
let scene = GameScene(fileNamed:"GameScene")
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
#IBAction func addOneCircle(sender: AnyObject) {
GameScene(fileNamed:"GameScene").circlePhysicsDefault()
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.toRaw())
} else {
return Int(UIInterfaceOrientationMask.All.toRaw())
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return false }
}
File GameScene:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
circlePhysicsDefault()
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func sceneSetting() {
self.backgroundColor = SKColor.grayColor()
self.physicsWorld.gravity = CGVectorMake(0.01, -2)
}
func circlePhysicsDefault() {
var Circle = SKShapeNode(circleOfRadius: 40)
Circle.position = CGPointMake(500, 500)
Circle.name = "defaultCircle"
Circle.strokeColor = SKColor.blackColor()
Circle.glowWidth = 10.0
Circle.fillColor = SKColor.yellowColor()
Circle.physicsBody = SKPhysicsBody(circleOfRadius: 40)
Circle.physicsBody.dynamic = true
self.addChild(Circle)
}
}
you are creating a new instance on key press, store a weak reference to the scene and call the method from the wished instance.
#IBAction func addOneCircle(sender: AnyObject) {
myGameSceneRef.circlePhysicsDefault()
}
So now please try the following:
#IBAction func addOneCircle(sender: AnyObject) {
let scene = ((self.view as SKScene).scene as GameScene)
scene.circlePhysicsDefault()
}
Or maybe change "let" to "var"