Here's my question. I would like to have an Apple Watch game react based on the Apple Watch side. But I'm totally new and lost with it.
I think the connectivity between Apple Watch and iOS app is ok, but I don't know how can I catch the event in the Game Controller, and even less how to deal this with Game Scene.
The result of all of my research have just shown characters can move by themselves or by a click event, but nothing about how to bind to any buttons.
So I'm asking for some help regarding this, and if you have any examples, they are welcome.
Here is my viewDidLoad method to load GameScene :
override func viewDidLoad() {
super.viewDidLoad()
guard WCSession.isSupported() else {
return
}
let session = WCSession.default
session.delegate = self
session.activate()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
And my delegate GameController which, I think is supposed to link to the action button with movement logic in GameScene (but maybe I'm wrong on that point):
extension GameViewController: WCSessionDelegate {
func session(_ session: WCSession, didReceiveApplicationContext applicationContext:
[String : Any]) {
//Truly don't know what to put in there
}
And finally my GameScene 'touchesBegan' method, which contains the movement logic:
var locx = CGFloat()
var locy = CGFloat()
var character = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(locx > 0) {
print(locx)
character.position.x += 1
} else if (locx < 0) {
character.position.x -= 1
}
if(locy > 0) {
print(locy)
character.position.y += 1
}else if (locy < 0) {
character.position.y -= 1
}
}
I'm pretty sure there are some misunderstandings on my side, but I can't find any issues.
Thank for all help you could provide.
Related
Ok, exploring ARKit with Swift in iOS 11 here and I have made a very simple app that just adds nodes at the point where the user taps:
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let actualScene = SCNScene(named: "art.scnassets/ship.scn")!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return }
let hitTransform = SCNMatrix4.init(hitFeature.worldTransform) // <- if higher than beta 1, use just this -> hitFeature.worldTransform
let hitPosition = SCNVector3Make(hitTransform.m41,
hitTransform.m42,
hitTransform.m43)
createBall(hitPosition: hitPosition)
}
func createBall(hitPosition : SCNVector3) {
let newBall = SCNSphere(radius: 0.01)
let newBallNode = SCNNode(geometry: newBall)
newBallNode.position = hitPosition
self.sceneView.scene.rootNode.addChildNode(newBallNode)
}
And this works. My issue is that when first running the app, it takes 30-60 seconds of just panning the camera around where tapping does nothing.
It seems like ARKit is "loading", so that when I tap in this first minute no nodes appear in the tapped position. Nothing happens for that first minute.
Why is this? Is there a way to expedite this loading process? What is happening here?
On the overrided function viewWillAppear call this function
func setUpSceneView() {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
sceneView.session.run(configuration)
sceneView.delegate = self
}
and delete everything in viewdidload and add what you need in view will appear
So for a school project, I have been tasked with making a 2D game. The game is fine but I'm struggling with how to make a back button (In the middle of the page) so was wondering if there was specific code to make this work. I am using spriteKit so I'm trying to go back to the previous scene after clicking on a colour sprite.
I apologise if this is a stupid question but I am slightly new to Swift.
Kind Regards,
James
Here is an example of how you can create a button using a colored sprite. It shows how you can set up a button to receive touch events and how you can use those touch events to navigate between scenes.
In this example you can navigate forward to new scenes and backwards to previous scenes.
import SpriteKit
class Button: SKSpriteNode {
var tapped: (() -> Void)?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tapped?()
}
}
class GameScene: SKScene {
var parentScene: SKScene?
var sceneCount = 1
override func didMove(to view: SKView) {
if parentScene != nil {
let backButton = addButton(color: .red, position: CGPoint(x: -200, y: 0))
backButton.tapped = {
if let previousScene = self.parentScene {
view.presentScene(previousScene)
}
}
}
let nextButton = addButton(color: .blue, position: CGPoint(x: 200, y: 0))
nextButton.tapped = {
if let nextScene = SKScene(fileNamed: "GameScene") as? GameScene {
nextScene.scaleMode = self.scaleMode
nextScene.parentScene = self
nextScene.sceneCount = self.sceneCount + 1
view.presentScene(nextScene)
}
}
let label = SKLabelNode(text: "Scene \(sceneCount)")
addChild(label)
}
func addButton(color: SKColor = .white, position: CGPoint = .zero) -> Button {
let button = Button(color: color, size: CGSize(width: 200, height: 200))
button.position = position
button.isUserInteractionEnabled = true
addChild(button)
return button
}
}
Too add a button the simplest way is to detect touches on your sprite(s) in the relevant SKScene.
enum NodeName: String {
case coloredSprite1
case coloredSprite2
}
class GameScene: SKScene {
let coloredSprite = SKSpriteNode(imageNamed: "YourImageName")
/// Scene setup
override func didMove(to view: SKView) {
// set up your colored sprite if necessary
// Give your sprites unique names to identify them
coloredSprite.name = NodeName.coloredSprite1.rawValue // always use enums for things like string identifiers so you avoid typos
}
/// Touches
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
// Way 1 by node (probably less preferable)
switch touchedNode {
case coloredSprite:
// do something (e.g call loadScene method)
// see below
default:
break
}
// Way 2 by node name (probably more preferable)
// name is an optional so we have to unwrap it when using it in the switch statement.
// The easiest way is by providing an alternative string, by using the nil coalescing operator (?? "NoNodeNameFound")
switch touchedNode.name ?? "NoNodeNameFound" {
case NodeName.coloredSprite1.rawValue:
// do something (e.g call loadScene method)
// see below
default:
break
}
}
}
// Also call touchesEnded, touchesMoved and touchesCancelled and do necessary stuff
}
For a more reusable solution you ideally want to create a button subclass. There is quite a few tutorials to google on how to do this.
To than transition between SKScenes you can create a loadScene method in each scene and than call them when necessary.
// Start Scene
class StartScene: SKScene {
...
func loadGameScene() {
// If you do everything in code
let gameScene = GameScene(size: self.size)
view?.presentScene(gameScene, transition: ...)
// If you use SpriteKit scene editor
guard let gameScene = SKScene(fileNamed: "GameScene") else { return } // fileNamed is the name you gave the .sks file
view?.presentScene(gameScene, transition: ...)
}
}
// Game scene
class GameScene: SKScene {
....
func loadStartScene() {
// If you do everything in code
let startScene = StartScene(size: self.size)
view?.presentScene(startScene, transition: ...)
// If you use SpriteKit scene editor
guard let startScene = SKScene(fileNamed: "StartScene") else { return } // fileNamed is the name you gave the .sks file
view?.presentScene(startScene, transition: ...)
}
}
Hope this helps
Explanation
The title pretty much asks for itself. I'm trying to make a button activate an ad banner, but Xcode returns me this message below; so, because of the UIViewController, I saw that this banner should be called on GameViewController, not GameScene. I looked for it everywhere, but did not find how to call this banner from GameScene or another way to activate this banner by a button.
Cannot convert value of type 'GameScene' to expected argument type 'UIViewController!'
Code
This code below is pretty simple. It has a button on its attempt to call an ad banner.
import SpriteKit
import Ads
class GameScene: SKScene {
var button = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
button = SKSpriteNode(imageNamed: "button")
button.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
button.setScale(0.4)
addChild(button)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == button{
Ads.showAd(AdsShowStyle.BannerBottom, rootViewController: self) //issue line
}
}
}
}
Attempt
After some research, I found out that it might be possible by using delegation. Following the highest voted answer of this question, I came up to this code below, but I'm having many issues that I'm, unsuccessfully, struggling to solve.
GameScene.swift
GameViewController.swift
Thanks in advance,
Luiz.
Since you are using SpriteKit but we don't know what ad network you are using, I would recommend using a NSNotificationCenter command in GameViewController and use a postNotificationName command in the GameScene.swift instead of a ViewControllerDelegate. This would allow your ad code to be used in any other .swift files. Here's a better explanation of this from another post if needed
Note: This may or may not work depending on ad network
GameViewController.swift (from my project)
import UIKit
import SpriteKit
import GoogleMobileAds
class GameViewController: UIViewController, GADAdDelegate {
var adMobBanner : GADBannerView!
override func viewDidLoad() {
super.viewDidLoad()
if 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)
}
adMobBanner = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
adMobBanner.adUnitID = "your ad unit id"
adMobBanner.rootViewController = self
adMobBanner.frame = CGRectMake(0, view.bounds.height - adMobBanner.frame.size.height, adMobBanner.frame.size.width, adMobBanner.frame.size.height)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.showAdMob), name: "showAdMobKey", object: nil)
}
func showAdMob() {
let request : GADRequest = GADRequest()
adMobBanner.loadRequest(request)
self.view.addSubview(adMobBanner)
print("adMob")
}
GameScene.swift (edited yours)
import SpriteKit
import Ads
class GameScene: SKScene {
var button = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
button = SKSpriteNode(imageNamed: "button")
button.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
button.setScale(0.4)
addChild(button)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
if (button.containsPoint(location)) {
NSNotificationCenter.defaultCenter().postNotificationName("showAdMobKey", object: nil)
}
}
}
}
I'm creating a Swift project for a high school programming class. I can't seem to figure out this problem, and everyone else in my class doesn't seem to have any ideas.
To start, I created a new Swift project, and chose a game format.
I then used some basic code to make the first level for my game, a maze game where the maze moves around instead of the ball based on how the user tilts the device.
This is my GameScene.swift:
import SpriteKit
import CoreMotion
var accelupdateinterval = 0.1
var accelmultiplier = 15.0
class GameScene: SKScene {
let manager = CMMotionManager()
override func didMoveToView(view: SKView) {
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = accelupdateinterval
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * CGFloat(accelmultiplier), CGFloat((data?.acceleration.y)!) * CGFloat(accelmultiplier))
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
I want to have a main menu that the app opens into, which is mainMenu.storyboard:
I successfully have the app launching into the mainMenu.storyboard (and the level physics work well when I've tested the level1.sks), but I'm having trouble figuring out how to segue.
GOAL: I want people to be segued into the level1.sks (and the levels that I add later), when they tap the corresponding image in mainMenu.storyboard.
I can't use the method of adding a Storyboard Reference to segue it, as the Storyboard Reference won't let me choose level1.sks.
I'd also love to find out how to send users back to the main menu when the player icon touches the goal (the blue thing up near the top in this screenshot):
So to do this I think the best approach is to create another ViewController subclass, maybe named LauncherViewController, which will present your SKScene. Then in your storyboard add this viewController and have your menu segue to it on an image press.
Here is a start for the LauncherViewController
class LauncherViewController: UIViewController {
var gameScene: GameScene!
override func viewDidLoad() {
}
override func viewWillAppear() {
presentGameScene()
}
func presentGameScene(){
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
gameScene.size = skView.bounds.size
gameScene.scaleMode = .AspectFill
skView.presentScene(gameScene)
}
}
Where in your menu controller you have a prepareForSegue like this:
override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
if segue.identifier == "yourSegue" {
let destinationViewController = segue.destinationViewController as! LauncherViewController
destinationViewController.gameScene = GameScene()
}
to have your LauncherViewController dismiss the gameScene when the user finishes the maze, use a delegate pattern. So in GameScene add a protocol above your class
protocol GameDelegate {
func gameFinished()
}
have your LauncherViewController conform to this delegate and set the gameScene's delegate variable to self (see below)
class LauncherViewController: UIViewController, GameDelegate {
var gameScene: GameScene!
override func viewDidLoad() {
gameScene.delegate = self
}
override func viewWillAppear() {
presentGameScene()
}
func presentGameScene(){
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
gameScene.size = skView.bounds.size
gameScene.scaleMode = .AspectFill
skView.presentScene(gameScene)
}
func gameFinished(){
// this forces LauncherViewController to dismiss itself
dismissViewControllerAnimated(true, completion: nil)
}
}
add a variable in GameScene to hold the delegate (LauncherViewController) and add a function that calls the delegate function. You will also need to add the logic to know when the game is over as I haven't done that.
class GameScene: SKScene {
let manager = CMMotionManager()
var delegate: GameDelegate!
override func didMoveToView(view: SKView) {
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = accelupdateinterval
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * CGFloat(accelmultiplier), CGFloat((data?.acceleration.y)!) * CGFloat(accelmultiplier))
}
}
func gameOver(){
delegate.gameFinished()
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
// it's probably easiest to add the logic for a gameOver here
if gameIsOver {
gameOver()
}
}
}
There will probably be some mistakes in here as I wrote this on my phone so just comment below for anything you are unsure about or doesn't work.
I am very new to Swift and iOS development. I was watching tutorials on iOS development w/ Swift and SpriteKit. Following the tutorials I opened Xcode, new project, game, universal; and all I changed was the GameScene.swift. Here is the new code:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var node1 = SKNode()
node1.position = CGPoint(x: 100, y: 100)
self.addChild(node1)
var spr1 = SKSpriteNode(imageNamed: "Spaceship")
spr1.position = CGPointZero
spr1.zPosition = 1
node1.addChild(spr1)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Note: the Spaceship image is provided by default.
So in the tutorials, this code added a spaceship to the scene. However, when I run the simulator, the scene remains blank. What can be the problem? If more info is needed, please say so and I will provide.
It could be running correctly, and you just can't see it on the screen. Depending on the device that you're emulating, you may be showing the spaceship off screen. Try a different, smaller device, to emulate, and check if you can see it.
Try going into your GameViewController.swift and making sure that you see something along the lines of this:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
}