I'm testing the possibility of the tile editor that comes with Xcode 8 (8.2.2). And I've created a PacMan-like map as shown above. There's a game character at the top-left corner in a rectangle. I wonder if there's an easy way of making the game character staying inside the blue borders? So far, I've set a (red) wall to the left like the following through the scene editor. And I have the following lines of code.
struct PhysicsCategory {
static let None: UInt32 = 0
static let Player: UInt32 = 0b1 // 1
static let Edge: UInt32 = 0b10 // 2
static let Wall: UInt32 = 0b100 // 4
}
class GameScene: SKScene {
// MARK: - Variables
var background: SKTileMapNode! // background
var player: SKNode! // player
// MARK: - DidMove
override func didMove(to view: SKView) {
setupNodes()
}
func setupNodes() {
background = self.childNode(withName: "World") as! SKTileMapNode
background.physicsBody = SKPhysicsBody(edgeLoopFrom: background.frame)
background.physicsBody?.categoryBitMask = PhysicsCategory.Edge
let wall = self.childNode(withName: "Wall")
wall?.physicsBody = SKPhysicsBody(rectangleOf: (wall?.frame.size)!)
wall?.physicsBody?.isDynamic = false
wall?.physicsBody?.categoryBitMask = PhysicsCategory.Wall
player = self.childNode(withName: "Player")
player.physicsBody = SKPhysicsBody(circleOfRadius: 32)
player.physicsBody?.categoryBitMask = PhysicsCategory.Player
player.physicsBody?.collisionBitMask = 4
player.physicsBody?.allowsRotation = false
}
}
The user will get to control the player position with CoreMotion. For now, the game character does respect the left edge. But if the map gets complicated, I could end up placing a lot of walls here and there. And that kind of kills the fun as it could be time-consuming. So, again, is there a simplier way of making the game character collide the map borders?
Take a look at GameplayKit's pathfinding classes, specifically GKGridGraph and GKGridGraphNode which allow you to specify a graph that only has these kind of rectilinear paths.
There is a good tutorial from Apple here: https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/Pathfinding.html
And a demo app here: https://developer.apple.com/library/content/samplecode/Pathfinder_GameplayKit/Introduction/Intro.html#//apple_ref/doc/uid/TP40016461
Related
I am pretty new to SpriteKit so I may be missing something quite obvious.
I am attempting to create an interactive map of the US. I have loaded PNG images for each state and placed a couple of them into the main SKScene using the scene editor.
My goal is wanting to detect when each state is tapped by the user. I initially wrote some code that would find the nodes within a touch location however, this meant that the user could tap the frame and it would be counted as a valid tap. I only want to register touches that happen within the state texture and not the frame. Reading online it was suggested to use SKPhysicsBody to register when a tap takes place. So I changed my code to the following.
class GameScene: SKScene {
override func didMove(to view: SKView) {}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location: CGPoint = self.convertPoint(fromView: touch.location(in: self.view))
let body = self.physicsWorld.body(at: location)
if let state = body?.node, let name = state.name {
state.run(SKAction.run({
var sprite = self.childNode(withName: name) as! SKSpriteNode
sprite.color = UIColor.random()
sprite.colorBlendFactor = 1
}))
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
Now, if I choose the Bounding circle body type everything works as expected (shown above in the screenshot). When I click within the boudning circle it runs the SKAction otherwise it does nothing. However, when I change the body type to Alpha mask (the body type I want) it suddenly stops detecting the state. In fact, it returns the SKPhysicsBody for the MainScene entity.
Any advice on how to fix this would be greatly appreciated.
Thanks.
i can reproduce this behavior (bug?) when using the scene editor. however it goes away if you skip the sks file and initialize your sprites in code. (i acknowledge that setting locations for 50 states is more tedious this way.)
class GameScene: SKScene {
let ok = SKSpriteNode(imageNamed: "ok")
let nm = SKSpriteNode(imageNamed: "nm")
let tx = SKSpriteNode(imageNamed: "tx")
override func didMove(to view: SKView) {
tx.name = "Texas"
nm.name = "New Mexico"
ok.name = "Oklahoma"
//set up physics bodies w/ alpha mask
tx.physicsBody = SKPhysicsBody(texture: tx.texture!, size: tx.texture!.size())
tx.physicsBody?.affectedByGravity = false
nm.physicsBody = SKPhysicsBody(texture: nm.texture!, size: nm.texture!.size())
nm.physicsBody?.affectedByGravity = false
ok.physicsBody = SKPhysicsBody(texture: ok.texture!, size: ok.texture!.size())
ok.physicsBody?.affectedByGravity = false
//then position your sprites and add them as children
}
}
I’ve created 8 paddles in gamescene.sks. I know how to control them manually but in this case, I want them all to begin automatically oscillating back and forth in their respective ‘lanes’ (meaning they won’t collide with one another) once a button is pressed (I haven’t created the button because idk if that should come before or the paddles function correctly).
I’m not asking for code but I just need a general idea of the commands that I need to implement in order to move a single paddle (positioned randomly) back and forth linearly when a button is pressed. I’m sure I can figure everything else out from there.
Thanks in Advance!!!
START button pressed → 8 paddles (placed randomly in their respective lanes) begin oscillating back and forth (out of sync with one another)
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var squareRight = SKSpriteNode()
var squareLeft = SKSpriteNode()
var paddleL1 = SKSpriteNode()
var paddleL2 = SKSpriteNode()
var paddleL3 = SKSpriteNode()
var paddleL4 = SKSpriteNode()
var paddleR1 = SKSpriteNode()
var paddleR2 = SKSpriteNode()
var paddleR3 = SKSpriteNode()
var paddleR4 = SKSpriteNode()
override func didMove(to view: SKView) {
squareRight = self.childNode(withName: "squareRight") as! SKSpriteNode
squareLeft = self.childNode(withName: "squareLeft") as! SKSpriteNode
paddleL1 = self.childNode(withName: "paddleL1") as! SKSpriteNode
paddleL2 = self.childNode(withName: "paddleL2") as! SKSpriteNode
paddleL3 = self.childNode(withName: "paddleL3") as! SKSpriteNode
paddleL4 = self.childNode(withName: "paddleL4") as! SKSpriteNode
paddleR1 = self.childNode(withName: "paddleR1") as! SKSpriteNode
paddleR2 = self.childNode(withName: "paddleR2") as! SKSpriteNode
paddleR3 = self.childNode(withName: "paddleR3") as! SKSpriteNode
paddleR4 = self.childNode(withName: "paddleR4") as! SKSpriteNode
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
As a general set of guidelines, I would:
Create a PaddleDirection enum with values of stationary,
movingRight and movingLeft
Subclass SKSpriteNode to create a PaddleNode
Add a property var currentDirection = PaddleDirection.stationary to your PaddleNode.
Make sure all your paddles are PaddleNodes and not SKSpriteNodes
When the start button is pressed, enumerate through all the
PaddleNodes and set their currentDirection properties to movingRight (or left, or randomly select one or the other)
In update() enumerate over all the paddles and adjust their
position according to whether they are moving left or right.
When each paddle reaches the limit of its movement, change its
direction from left to right or vice-versa.
PLEASE HELP!!! I have been trying to figure this out for along time. I have searched the internet and i cannot find anything that will help me.
I am currently making a game in which you are a space ship in the middle and enemy ships are moving towards you and you have to shoot them. some enemies have different lives. for example: a red ship takes one shot to explode, the blue ship takes 3, etc. I have everything to work only the lives. for example: whenever a blue ship is called on to the screen i shoot it once so its life goes down to 2. but whenever a another blue ship is called the first blue ship has its life reset back to 3 again. Is there anyway I can make it so that whenever a ship looses lives it remains that way even if other ships are called ?
this is my ship function that gets called and adds enemy space ships onto the screen:
func VillainRight(){
let TooMuch = self.size.width
let point = UInt32(TooMuch)
life = 3
let VillainR = SKSpriteNode(imageNamed: "BlueVillain")
VillainR.zPosition = 2
VillainR.position = CGPoint(x: self.frame.minX,y: CGFloat(arc4random_uniform(point)))
//This code makes the villain's Zposition point towards the SpaceShip
let angle = atan2(SpaceShip.position.y - VillainR.position.y, SpaceShip.position.x - VillainR.position.x)
VillainR.zRotation = angle - CGFloat(M_PI_2)
let MoveToCenter = SKAction.move(to: CGPoint(x: self.frame.midX, y: self.frame.midY), duration: 15)
//Physics World
VillainR.physicsBody = SKPhysicsBody(rectangleOf: VillainR.size)
VillainR.physicsBody?.categoryBitMask = NumberingPhysics.RightV
VillainR.physicsBody?.contactTestBitMask = NumberingPhysics.Laser | NumberingPhysics.SpaceShip
VillainR.physicsBody?.affectedByGravity = false
VillainR.physicsBody?.isDynamic = true
VillainR.run(MoveToCenter)
addChild(VillainR)
}
This is the code that calls this function:
_ = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(Level1.VillainRight), userInfo: nil, repeats: true)
I am using the spritekit in Swift.
Thank You Very Much in advance!
That is happening because life variable is declared as a property of a scene and it is not local to a specific node (enemy ship). You can solve this in a few ways... First way would be using node's userData property:
import SpriteKit
let kEnergyKey = "kEnergyKey"
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
let blueShip = getShip(energy: 3)
let greenShip = getShip(energy: 2)
let redShip = getShip(energy: 1)
if let blueShipEnergy = blueShip.userData?.value(forKey: kEnergyKey) as? Int {
print("Blue ship has \(blueShipEnergy) lives left")
//hit the ship
blueShip.userData?.setValue(blueShipEnergy-1, forKey: kEnergyKey)
if let energyAfterBeingHit = blueShip.userData?.value(forKey: kEnergyKey) as? Int {
print("Blue ship has \(energyAfterBeingHit) lives left")
}
}
}
func getShip(energy:Int)->SKSpriteNode{
//determine which texture to load here based on energy value
let ship = SKSpriteNode(color: .purple, size: CGSize(width: 50, height: 50))
ship.userData = [kEnergyKey:energy]
return ship
}
}
This is what the docs say about userData property:
You use this property to store your own data in a node. For example,
you might store game-specific data about each node to use inside your
game logic. This can be a useful alternative to creating your own node
subclasses to hold game data.
As you can see, an alternative to this is subclassing of a node (SKSpriteNode):
class Enemy:SKSpriteNode {
private var energy:Int
//do initialization here
}
This question already has an answer here:
Applying simple Physics to a .scn object in SceneKit XCODE SWIFT
(1 answer)
Closed 6 years ago.
Hey I have a scene kit scene and my goal is just to get the guy/character on the scene to fall do to gravity and then hit the floor. Thats it. the "Guy" is simple a node .scn node. and the ground is a also a .scn scene as shown in the code below. Ive gotten as far as the skills I have to try and add simple physics to the scene. I know how to add physics perfectly in SPRITEKIT but the .scn nodes don't let me attach the same variable that would use give the character physics. There are no errors in this code But the character is not affect by gravity when ran Thanks
Code:
// Collision bit masks
let BitmaskCollision = 1 << 2
let BitmaskCollectable = 1 << 3
let BitmaskEnemy = 1 << 4
let BitmaskFriend = 1 << 5
let BitmaskOutofBounds = 1 << 6
class GameViewController: UIViewController, ADBannerViewDelegate, SKPhysicsContactDelegate, SKSceneDelegate, SCNSceneRendererDelegate, SCNPhysicsContactDelegate{
// The character
let character = Character()
// The Playing Fields
let FieldScene = SCNScene(named: "art.scnassets/TesingCampusField.dae")!
override func viewDidLoad() {
super.viewDidLoad()
let scnView = self.view as! SCNView
scnView.scene = FieldScene
scnView.playing = true
scnView.delegate = self
scnView.scene!.physicsWorld.contactDelegate = self
//-----Adding Gravity----------------------------------------------
scnView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: 0, z: -9.8)
//------Add-the-Character-to-Scene--------------------
scnView.scene!.rootNode.addChildNode(character.node)
}
End Of code for this Controller
The Character is simply being set up from a file called "Character"
class Character {
let node = SCNNode()
init() {
let GuyScene = SCNScene(named: "art.scnassets/Guy.scn")
let characterTopLevelNode: SCNNode = GuyScene!.rootNode.childNodeWithName("Guy", recursively: true)!
node.addChildNode(characterTopLevelNode)
characterTopLevelNode.physicsBody?.contactTestBitMask = BitmaskEnemy
characterTopLevelNode.physicsBody?.collisionBitMask = BitmaskCollision
characterTopLevelNode.physicsBody?.categoryBitMask = BitmaskCollectable
characterTopLevelNode.physicsBody?.affectedByGravity = true
characterTopLevelNode.physicsBody?.friction = 0
characterTopLevelNode.physicsBody?.restitution = 0
characterTopLevelNode.physicsBody?.angularDamping = 0
// Below is the Creation of the Physics body of the character
let (min, max) = node.boundingBox
let collisionCapsuleRadius = CGFloat(max.x - min.x) * 0.4
let collisionCapsuleHeight = CGFloat(max.y - min.y)
let characterCollisionNode = SCNNode()
characterCollisionNode.name = "collider"
// position light above the floor so you dont hit it and cause a contact
characterCollisionNode.position = SCNVector3(0.0, collisionCapsuleHeight * 0.51, 0.0)
characterCollisionNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius, height: collisionCapsuleHeight), options:nil))
// Adding the Pysics body of the character called "characterCollisionNode" to the actually character
node.addChildNode(characterCollisionNode)
}
}
This seems to be an earlier version of this question Applying simple Physics to a .scn object in SceneKit XCODE SWIFT
And the answer is probably similar, you don't appear to be adding the node as a child of the scene.
I am trying to make a mouse down event, but keep getting the error "Use of undeclared type 'NSEvent'" on the "override func mouseDown( theEvent: NSEvent!) { " line. After researching and trying different things, I still got nothing. Anyone had this problem before?
import UIKit
import SpriteKit
let BallCategoryName = "ball"
let MainBallCategoryName = "mainBall"
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
// 1. Create a physics body that borders the screen
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
// 2. Set the friction of that physicsBody to 0
borderBody.friction = 0
// 3. Set physicsBody of scene to borderBody
self.physicsBody = borderBody
physicsWorld.gravity = CGVectorMake(0, 0)
let ball = childNodeWithName(BallCategoryName) as SKSpriteNode
var mainBall = childNodeWithName(MainBallCategoryName) as SKSpriteNode
ball.physicsBody!.applyImpulse(CGVectorMake(10, -10))
ball.physicsBody!.allowsRotation = false
ball.physicsBody!.friction = 0
ball.physicsBody!.restitution = 1
ball.physicsBody!.linearDamping = 0
ball.physicsBody!.angularDamping = 0
}
override func mouseDown( theEvent: NSEvent!) {
let action = SKAction.moveTo(
CGPoint(x:theEvent.locationInWindow.x,y:theEvent.locationInWindow.y),
duration:2
);
MainBallCategoryName.runAction(action)
}
}
iOS devices don't have mice, and iOS doesn't have an NSEvent class. It has a UIEvent class, but there's nothing special about a function named mouseDown on iOS. iOS events report touches, not mouse buttons.
If you copied this code from a tutorial, the tutorial was for OS X, not iOS. You should find an iOS tutorial instead.
In an OS X app you should not:
import UIKit
but instead use
import AppKit