I am trying to make my 'Enemy' bounce off all the boundaries. For example, when the Enemy collides with the left, right, top and bottom of the screen, it should change direction.
This is my current code:
import SpriteKit
import GameplayKit
struct Physics {
static let Enemy: UInt32 = 0x1 << 1
let BorderCategory : UInt32 = 0x1 << 2
let BottomCategory : UInt32 = 0x1 << 3
let BallCategory : UInt32 = 0x1 << 4
}
class GameScene: SKScene {
var Enemy = SKSpriteNode()
var gameStarted = Bool()
var gameState = "running"
var destX : CGFloat = 0.0
var destY : CGFloat = 0.0
var score = 0
override func didMove(to view: SKView) {
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
let screenSize: CGRect = UIScreen.main().bounds // get the screen size
let screenWidth = screenSize.width //get the width
let screenHeight = screenSize.height //get the height
Enemy = SKSpriteNode(imageNamed: "red2")
Enemy.size = CGSize(width: 60, height: 70)
Enemy.position = (CGPoint(x: self.frame.width / 6 - Enemy.frame.width, y: self.frame.height / 10))
Enemy.physicsBody = SKPhysicsBody(circleOfRadius: Enemy.frame.height / 2)
Enemy.physicsBody?.categoryBitMask = Physics.Enemy
//Enemy.physicsBody?.categoryBitMask = Physics.Ground | Physics.wall
//Enemy.physicsBody?.contactTestBitMask = Physics.Ground | Physics.wall
Enemy.physicsBody?.affectedByGravity = false
Enemy.physicsBody?.isDynamic = true
self.addChild(Enemy)
Enemy.physicsBody?.velocity = CGVector(dx: 50, dy: 50)
if (Enemy.position.x == screenWidth) {
Enemy.physicsBody?.velocity = CGVector(dx: -50, dy: 0) // change direction at edge DOESN'T WORK
}
if(Enemy.position.y == screenHeight){
Enemy.physicsBody?.velocity = CGVector(dx: 0, dy: -50) //change direction at edge DOESN'T WORK
}
}
OK, based on your code I've written a working bouncing sprite. It uses the physics-engine to achieve this, rather than manually changing the direction (which I guess is how you should go about doing this).
override func didMove(to view: SKView) {
let borderBody = SKPhysicsBody(edgeLoopFrom: frame)
borderBody.friction = 0
borderBody.categoryBitMask = Physics.wall
physicsBody = borderBody
let enemy = enemySprite(size: CGSize(width: 60, height: 70), position: CGPoint(x: frame.size.width/2, y: frame.size.height/2))
addChild(enemy)
let force = SKAction.applyForce(CGVector(dx: 300, dy: 300) , duration: 0.1)
enemy.run(force)
}
func enemySprite(size: CGSize, position: CGPoint) -> SKSpriteNode {
let enemy = SKSpriteNode(color: SKColor.red(), size: CGSize(width: 60, height: 80))
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.frame.height / 2) // A bit silly with circle, but you seem to have an image for this use
enemy.physicsBody?.categoryBitMask = Physics.enemy
enemy.physicsBody?.restitution = 1
enemy.physicsBody?.friction = 0
enemy.physicsBody?.collisionBitMask = Physics.wall
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.angularDamping = 0
enemy.physicsBody?.linearDamping = 0
return enemy
}
A question to consider is also how you set up the scene? Is the scene-size the same as the view-size? Otherwise the scene's physicsBody will not make the ball bounce where you expect it to...
It won't (in most cases) because of your conditions - using equal to operators instead of greater/lower than.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// Create enemy with physics body attached and add it to the scene
let enemy = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 20, height: 20))
enemy.name = "enemy"
enemy.position = CGPoint(x: size.width / 2, y: size.height / 2)
enemy.physicsBody = SKPhysicsBody(rectangleOfSize: enemy.size)
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.linearDamping = 0
enemy.physicsBody!.mass = 0.1
enemy.physicsBody!.velocity = CGVector(dx: 150, dy: 0)
addChild(enemy)
}
override func didSimulatePhysics() {
let enemy = childNodeWithName("enemy")!
if (enemy.position.x > size.width && enemy.physicsBody!.velocity.dx > 0)
|| (enemy.position.x < 0 && enemy.physicsBody!.velocity.dx < 0) {
enemy.physicsBody!.velocity.dx *= -1
}
}
}
Related
I'm new to swift 5.
I printed out the self.size.width in GameScene and the result is 677.0
I printed out the self.size.width from another class - lets say Ground and the result is 4002.0
I'm confused, please help.
Thanks a lot.
GameScene.swift:
import SpriteKit
class GameScene: SKScene {
let cam = SKCameraNode()
let bee = SKSpriteNode()
let ground = Ground()
override func didMove(to view: SKView) {
self.anchorPoint = .zero
//self.backgroundColor = UIColor(red: 0.4, green: 0.6, blue: 0.95, alpha: 1.0)
self.camera = cam
self.addingTheFlyingBee()
self.addBackground()
let bee2 = Bee()
bee2.position = CGPoint(x: 325, y: 325)
self.addChild(bee2)
let bee3 = Bee()
bee3.position = CGPoint(x: 200, y: 325)
self.addChild(bee3)
ground.position = CGPoint(x: -self.size.width * 2, y: 0)
ground.size = CGSize(width: self.size.width * 6, height: 0)
ground.createChildren()
self.addChild(ground)
}
override func didSimulatePhysics() {
self.camera!.position = bee.position
}
func addBackground() {
let bg = SKSpriteNode(imageNamed: "background-menu")
bg.position = CGPoint(x: 220, y: 220)
bg.zPosition = -1
self.addChild(bg)
}
func addingTheFlyingBee() {
bee.position = CGPoint(x: 250, y: 250)
bee.size = CGSize(width: 38, height: 34)
self.addChild(bee)
let beeAtlas = SKTextureAtlas(named: "Enemies")
let beeFrames : [SKTexture] = [
beeAtlas.textureNamed("bee"),
beeAtlas.textureNamed("bee-fly")
]
let flyAction = SKAction.animate(with: beeFrames, timePerFrame: 0.14)
let beeAction = SKAction.repeatForever(flyAction)
bee.run(beeAction)
let pathLeft = SKAction.moveBy(x: -200, y: -10, duration: 2)
let pathRight = SKAction.moveBy(x: 200, y: 10, duration: 2)
let flipTextureNegative = SKAction.scaleX(to: -1, duration: 0)
let flipTexturePositive = SKAction.scaleX(to: 1, duration: 0)
let flightOfTheBee = SKAction.sequence([ pathLeft, flipTextureNegative, pathRight, flipTexturePositive])
let neverEndingFlight = SKAction.repeatForever(flightOfTheBee)
bee.run(neverEndingFlight)
}
Ground.swift:
import Foundation
import SpriteKit
class Ground: SKSpriteNode, GameSprite {
var textureAtlas: SKTextureAtlas = SKTextureAtlas(named: "Environment")
var initialSize = CGSize.zero
func createChildren() {
self.anchorPoint = CGPoint(x: 0, y: 1)
let texture = textureAtlas.textureNamed("ground")
var tileCount: CGFloat = 0
let tileSize = CGSize(width: 35, height: 300)
while tileCount * tileSize.width < self.size.width {
let tileNode = SKSpriteNode(texture: texture)
tileNode.size = tileSize
tileNode.position.x = tileCount * tileSize.width
tileNode.anchorPoint = CGPoint(x: 0, y: 1)
self.addChild(tileNode)
tileCount += 1
}
}
func onTap() {}
}
I don't really understand the question. Since your scene width is 677.0px and your ground
width should be 6 * scene width so 4062px.
Is your question "Why is ground node width 4002px instead of 4062px?" or "What is the difference between those properties?"
If you question is the second one then this should be an answer:
SKScene.size.width will return width of the current scene while SKSpriteNode.size.width will return width of the target node in your case "ground" node.
self.size.width will return width of the current class you're editing.
So in GameScene.swift file if you call self.size.width inside a class GameScene: SKScene it will return GameScene width.
In Ground.swift file calling self.size.width inside a class Ground: SKSpriteNode, GameSprite will return Ground width.
I hope this answers your question.
I'm working on a 2-d Platformer style game. I'm very new to IOS and Swift development. I'm attempting to use a button (a different node) to move my character across the screen from left to right. It works fine until I reach the halfway point then it speeds up dramatically and letting go of the touch of the button doesn't always stop it. Sometimes it requires another touch. Also the background doesn't appear to keep up with the player. Once the player hits mid screen the background should shift as the player continues moving.
I've pieced what I've done together from multiple tutorials SO examples but I'm stuck on this point.
class StoryModeScene: SKScene, SKPhysicsContactDelegate {
var tileMap = JSTileMap(named: "legend1Level1.tmx")
var tileSize:CGSize!
var xPointsToMovePerSecond:CGFloat = 0
var rightMoveButton = SKSpriteNode(imageNamed: "right-move")
var leftMoveButton = SKSpriteNode(imageNamed: "left-move")
var jumpButton = SKSpriteNode(imageNamed: "a-button")
var fireButton = SKSpriteNode(imageNamed: "b-button")
var forwardMarch:Bool = false
var mightAsWellJump:Bool = false
var onGround:Bool = true
//CREATE THE PLAYER ATLAS FOR ANIMATION
let playerAtlas = SKTextureAtlas(named:"legend1")
var playerSprites = Array<Any>()
var player = SKSpriteNode(imageNamed: "legend1")
var repeatActionPlayer = SKAction()
override func didMove(to view: SKView) {
/* Setup your scene here */
setupScene()
addPlayer()
//PREPARE TO ANIMATE THE PLAYER AND REPEAT THE ANIMATION FOREVER
let animatedPlayer = SKAction.animate(with: self.playerSprites as! [SKTexture], timePerFrame: 0.1)
self.repeatActionPlayer = SKAction.repeatForever(animatedPlayer)
leftMoveButton.position.x = 64
leftMoveButton.position.y = 64
leftMoveButton.name = "moveLeft"
addChild(leftMoveButton)
rightMoveButton.position.x = 124
rightMoveButton.position.y = 64
rightMoveButton.name = "moveRight"
addChild(rightMoveButton)
jumpButton.position.x = 771
jumpButton.position.y = 64
jumpButton.name = "jumpButton"
addChild(jumpButton)
fireButton.position.x = 836
fireButton.position.y = 64
fireButton.name = "fireButton"
addChild(fireButton)
}
override func update(_ currentTime: TimeInterval) {
if (forwardMarch) {
//let moveAction = SKAction.moveBy(x: 3, y: 0, duration: 1)
//let repeatForEver = SKAction.repeatForever(moveAction)
//let seq = SKAction.sequence([moveAction, repeatForEver])
//run the action on your ship
//player.run(seq)
player.position.x = player.position.x + 3
setViewpointCenter(player.position)
}
if (mightAsWellJump) {
let jumpForce = CGPoint(x: 0.0, y: 310.0)
let jumpCutoff: Float = 150.0
if mightAsWellJump && onGround {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx + jumpForce.x, dy: player.physicsBody!.velocity.dy + jumpForce.y)
onGround = false
} else if !mightAsWellJump && player.physicsBody!.velocity.dy > CGFloat(jumpCutoff) {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: CGFloat(jumpCutoff))
}
player.position = CGPoint(x: player.position.x, y: player.position.y + 5);
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches) {
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpButton" {
mightAsWellJump = true
player.texture = SKTexture(imageNamed: "legend1_jump")
}
if name == "moveRight" {
forwardMarch = true
self.player.run(repeatActionPlayer)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
if rightMoveButton.contains(touch.location(in: self)) {
forwardMarch = false
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
}
if jumpButton.contains(touch.location(in: self)) {
mightAsWellJump = false
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
}
}
func setViewpointCenter(_ position: CGPoint) {
var x = max(position.x, size.width / 2)
var y = max(position.y, size.height / 2)
x = min(x, (tileMap!.mapSize.width * tileMap!.tileSize.width) - size.width / 2)
y = min(y, (tileMap!.mapSize.height * tileMap!.tileSize.height) - size.height / 2)
let actualPosition = CGPoint(x: CGFloat(x), y: CGFloat(y))
let centerOfView = CGPoint(x: size.width / 2, y: size.height / 2)
let viewPoint = CGPoint(x: (centerOfView.x - actualPosition.x) * 3, y: centerOfView.y - actualPosition.y)
tileMap!.position = viewPoint
}
func setupScene() {
playerSprites.append(playerAtlas.textureNamed("legend1_0"))
playerSprites.append(playerAtlas.textureNamed("legend1_1"))
playerSprites.append(playerAtlas.textureNamed("legend1_2"))
playerSprites.append(playerAtlas.textureNamed("legend1_3"))
playerSprites.append(playerAtlas.textureNamed("legend1_4"))
playerSprites.append(playerAtlas.textureNamed("legend1_5"))
backgroundColor = UIColor(red: 165.0/255.0, green: 216.0/255.0, blue: 255.0/255.0, alpha: 1.0)
physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
anchorPoint = CGPoint(x: 0, y: 0)
position = CGPoint(x: 0, y: 0)
let point = tileMap!.calculateAccumulatedFrame()
print (point)
tileMap!.position = CGPoint(x: 0, y: 0)
addChild(tileMap!)
addFloor()
}
func addFloor() {
for a in 0..<Int(tileMap!.mapSize.width) {
for b in 0..<Int(tileMap!.mapSize.height) {
let layerInfo:TMXLayerInfo = tileMap!.layers.firstObject as! TMXLayerInfo
let point = CGPoint(x: a, y: b)
let walls = tileMap!.layerNamed("Walls")
let wallInfo:TMXLayerInfo = walls!.layerInfo
let wallGIDs = wallInfo.layer.tileGid(at: wallInfo.layer.point(forCoord: point))
if wallGIDs > 0 {
//print (wallGIDs)
//let node = walls
let node = wallInfo.layer.tile(atCoord: point)
node!.physicsBody = SKPhysicsBody(rectangleOf: node!.size)
node!.physicsBody?.isDynamic = false
}
}
}
}
func addPlayer() {
tileSize = tileMap?.tileSize
player.position = CGPoint(x: tileSize.width + player.size.width/2, y: tileSize.height + player.size.height*8)
let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 50, height: 95))
player.physicsBody = SKPhysicsBody(rectangleOf: rect.size)
player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
player.physicsBody!.isDynamic = true
player.physicsBody!.restitution = 0
player.physicsBody!.allowsRotation = false
player.physicsBody!.friction = 1.0
addChild(player)
}
}
Holding the rightMoveButton should move at a consistent pace to the right. As the player gets to mid screen the view point of the background should shift until it reaches the end of the background at which point the player can move off screen and complete the level. Releasing the button should allow the player to stop.
You can move the scene as the character moves by creating an SKCameraNode. You then can choose when to move the camera to create the right affect. Make sure to set the camera as your game’s camera. You can learn about cameras here. As for the speeding up, I assume it has something to do with your physicsBody. If you’re interested about player movement, you could look here or here. There are many other great videos and sites online, which you could find by searching “SpriteKit player movement.”
Eli Front's answer pointed me in the right direction. I still have some issues with this code but the answer to my question was using SKCamera node as Eli pointed out. I just wanted to post the code that works so if anyone has a similar question there's a code example as far as camera moving with the player. The speed issue was essentially an optical illusion with the background moving too fast.
Note that the controls are children of the camera. That is so they move with the camera as well otherwise as your node moves the controls would move off screen.
import UIKit
import SpriteKit
import GameKit
class StoryModeScene: SKScene, SKPhysicsContactDelegate {
var tileMap = JSTileMap(named: "legend1Level1.tmx")
var tileSize:CGSize!
var xPointsToMovePerSecond:CGFloat = 0
var rightMoveButton = SKSpriteNode(imageNamed: "right-move")
var leftMoveButton = SKSpriteNode(imageNamed: "left-move")
var jumpButton = SKSpriteNode(imageNamed: "a-button")
var fireButton = SKSpriteNode(imageNamed: "b-button")
var forwardMarch:Bool = false
var mightAsWellJump:Bool = false
var onGround:Bool = true
//CREATE THE Player ATLAS FOR ANIMATION
let playerAtlas = SKTextureAtlas(named:"legend1")
var playerSprites = Array<Any>()
var player = SKSpriteNode(imageNamed: "legend1")
var repeatActionPlayer = SKAction()
let cam = SKCameraNode()
var previousUpdateTime: TimeInterval = 0
override func didMove(to view: SKView) {
// SETUP CAMERA
self.camera = cam
scene?.addChild(cam)
cam.position.x = 448
cam.position.y = 212
setupScene()
addPlayer()
//PREPARE TO ANIMATE THE PLAYER AND REPEAT THE ANIMATION FOREVER
let animatedPlayer = SKAction.animate(with: self.playerSprites as! [SKTexture], timePerFrame: 0.1)
self.repeatActionPlayer = SKAction.repeatForever(animatedPlayer)
// SETUP CONTROLS
leftMoveButton.position.x = -338
leftMoveButton.position.y = -112
leftMoveButton.name = "moveLeft"
leftMoveButton.zPosition = 5
cam.addChild(leftMoveButton)
rightMoveButton.position.x = -278
rightMoveButton.position.y = -112
rightMoveButton.name = "moveRight"
cam.addChild(rightMoveButton)
jumpButton.position.x = 278
jumpButton.position.y = -112
jumpButton.name = "jumpButton"
jumpButton.zPosition = 5
cam.addChild(jumpButton)
fireButton.position.x = 338
fireButton.position.y = -112
fireButton.name = "fireButton"
cam.addChild(fireButton)
}
override func update(_ currentTime: TimeInterval) {
if (forwardMarch) {
if player.position.x > 448 && player.position.x < 1800 {
cam.position.x = player.position.x
} else if player.position.x >= 1800 {
cam.position.x = 1800
}
setViewpointCenter(player.position)
player.position.x = player.position.x + 3
}
if (mightAsWellJump) {
let jumpForce = CGPoint(x: 0.0, y: 310.0)
let jumpCutoff: Float = 150.0
if mightAsWellJump && onGround {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx + jumpForce.x, dy: player.physicsBody!.velocity.dy + jumpForce.y)
onGround = false
} else if !mightAsWellJump && player.physicsBody!.velocity.dy > CGFloat(jumpCutoff) {
player.physicsBody!.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: CGFloat(jumpCutoff))
}
player.position = CGPoint(x: player.position.x, y: player.position.y + 5);
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches) {
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpButton" {
mightAsWellJump = true
player.texture = SKTexture(imageNamed: "legend1_jump")
}
if name == "moveRight" {
player.physicsBody!.velocity.dy = 0.0
forwardMarch = true
self.player.run(repeatActionPlayer)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches) {
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpButton" {
mightAsWellJump = false
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
}
if name == "moveRight" {
player.removeAllActions()
player.texture = SKTexture(imageNamed: "legend1")
forwardMarch = false
}
}
}
}
func setViewpointCenter(_ position: CGPoint) {
var x = max(position.x, size.width / 2)
var y = max(position.y, size.height / 2)
x = min(x, (tileMap!.mapSize.width * tileMap!.tileSize.width) - size.width / 2)
y = min(y, (tileMap!.mapSize.height * tileMap!.tileSize.height) - size.height / 2)
let actualPosition = CGPoint(x: CGFloat(x), y: CGFloat(y))
let centerOfView = CGPoint(x: size.width / 2, y: size.height / 2)
let viewPoint = CGPoint(x: centerOfView.x - actualPosition.x, y: centerOfView.y - actualPosition.y)
if (actualPosition.x > 1800) {
tileMap!.position = tileMap!.position
} else {
tileMap!.position = viewPoint
}
}
func setupScene() {
// ADD PLAYER SPRITES #TODO MAKE THIS DYNAMIC
playerSprites.append(playerAtlas.textureNamed("legend1_0"))
playerSprites.append(playerAtlas.textureNamed("legend1_1"))
playerSprites.append(playerAtlas.textureNamed("legend1_2"))
playerSprites.append(playerAtlas.textureNamed("legend1_3"))
playerSprites.append(playerAtlas.textureNamed("legend1_4"))
playerSprites.append(playerAtlas.textureNamed("legend1_5"))
backgroundColor = UIColor(red: 165.0/255.0, green: 216.0/255.0, blue: 255.0/255.0, alpha: 1.0)
anchorPoint = CGPoint(x: 0, y: 0)
position = CGPoint(x: 0, y: 0)
// let point = tileMap!.calculateAccumulatedFrame()
tileMap!.position = CGPoint(x: 0, y: 0)
addChild(tileMap!)
addFloor()
}
func addFloor() {
for a in 0..<Int(tileMap!.mapSize.width) {
for b in 0..<Int(tileMap!.mapSize.height) {
// let layerInfo:TMXLayerInfo = tileMap!.layers.firstObject as! TMXLayerInfo
let point = CGPoint(x: a, y: b)
let walls = tileMap!.layerNamed("Walls")
let wallInfo:TMXLayerInfo = walls!.layerInfo
let wallGIDs = wallInfo.layer.tileGid(at: wallInfo.layer.point(forCoord: point))
if wallGIDs > 0 {
let node = wallInfo.layer.tile(atCoord: point)
node!.physicsBody = SKPhysicsBody(rectangleOf: node!.size)
node!.physicsBody?.isDynamic = false
}
}
}
}
func addPlayer() {
tileSize = tileMap?.tileSize
player.position = CGPoint(x: tileSize.width + player.size.width/2, y: tileSize.height + player.size.height*8)
let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 50, height: 95))
player.physicsBody = SKPhysicsBody(rectangleOf: rect.size)
player.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)
player.physicsBody!.isDynamic = true
player.physicsBody!.restitution = 0
player.physicsBody!.allowsRotation = false
player.physicsBody!.friction = 1.0
player.physicsBody!.mass = 1.0
addChild(player)
}
}
As I mentioned I still have some issues like the player node floating/flying when I jump/run at the same time. The 1st jump is the correct height and all subsequent jumps are smaller etc but I'm working on figuring those out.
I have a pong game in a Swift Playground that I made by following a tutorial online but my assistant editor won't show anything! Where is my pong game!? My code is below the image, which shows that nothing is showing up.
import SpriteKit
import PlaygroundSupport
// Declare some global constants
let width = 800 as CGFloat
let height = 1200 as CGFloat
let racketHeight = 150 as CGFloat
let ballRadius = 20 as CGFloat
// Three types of collision objects possible
enum CollisionTypes: UInt32 {
case Ball = 1
case Wall = 2
case Racket = 4
}
// Racket direction
enum Direction: Int {
case None = 0
case Up = 1
case Down = 2
}
// Make a SpriteKit scene
class gameScene: SKScene, SKPhysicsContactDelegate {
let racketSpeed = 500.0
var direction = Direction.None
var score = 0
var gameRunning = false
// Screen elements
var racket: SKShapeNode?
var ball: SKShapeNode?
let scoreLabel = SKLabelNode()
// Initialize objects during first start
override func sceneDidLoad() {
super.sceneDidLoad()
scoreLabel.fontSize = 40
scoreLabel.position = CGPoint(x: width/2, y: height - 100)
self.addChild(scoreLabel)
createWalls()
createBall(position: CGPoint(x: width / 2, y: height / 2))
createRacket()
startNewGame()
self.physicsWorld.contactDelegate = self
}
// Create the ball sprite
func createBall(position: CGPoint) {
let physicsBody = SKPhysicsBody(circleOfRadius: ballRadius)
ball = SKShapeNode(circleOfRadius: ballRadius)
physicsBody.categoryBitMask = CollisionTypes.Ball.rawValue
physicsBody.collisionBitMask = CollisionTypes.Wall.rawValue | CollisionTypes.Ball.rawValue | CollisionTypes.Racket.rawValue
physicsBody.affectedByGravity = false
physicsBody.restitution = 1
physicsBody.linearDamping = 0
physicsBody.velocity = CGVector(dx: -500, dy: 500)
ball!.physicsBody = physicsBody
ball!.position = position
ball!.fillColor = SKColor.white
}
// Create the walls
func createWalls() {
createWall(rect: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: ballRadius, height: height)))
createWall(rect: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: width, height: ballRadius)))
createWall(rect: CGRect(origin: CGPoint(x: 0, y: height - ballRadius), size: CGSize(width: width, height: ballRadius)))
}
func createWall(rect: CGRect) {
let node = SKShapeNode(rect: rect)
node.fillColor = SKColor.white
node.physicsBody = getWallPhysicsbody(rect: rect)
self.addChild(node)
}
// Create the physics objetcs to handle wall collisions
func getWallPhysicsbody(rect: CGRect) -> SKPhysicsBody {
let physicsBody = SKPhysicsBody(rectangleOf: rect.size, center: CGPoint(x: rect.midX, y: rect.midY))
physicsBody.affectedByGravity = false
physicsBody.isDynamic = false
physicsBody.collisionBitMask = CollisionTypes.Ball.rawValue
physicsBody.categoryBitMask = CollisionTypes.Wall.rawValue
return physicsBody
}
// Create the racket sprite
func createRacket() {
racket = SKShapeNode(rect: CGRect(origin: CGPoint.zero, size: CGSize(width: ballRadius, height: racketHeight)))
self.addChild(racket!)
racket!.fillColor = SKColor.white
let physicsBody = SKPhysicsBody(rectangleOf: racket!.frame.size, center: CGPoint(x: racket!.frame.midX, y: racket!.frame.midY))
physicsBody.affectedByGravity = false
physicsBody.isDynamic = false
physicsBody.collisionBitMask = CollisionTypes.Ball.rawValue
physicsBody.categoryBitMask = CollisionTypes.Racket.rawValue
physicsBody.contactTestBitMask = CollisionTypes.Ball.rawValue
racket!.physicsBody = physicsBody
}
// Start a new game
func startNewGame() {
score = 0
scoreLabel.text = "0"
racket!.position = CGPoint(x: width - ballRadius * 2, y: height / 2)
let startLabel = SKLabelNode(text: "Game Over")
startLabel.position = CGPoint(x: width / 2, y: height / 2)
startLabel.fontSize = 160
self.addChild(startLabel)
// Animated countdown
let fadeIn = SKAction.fadeIn(withDuration: 0.5)
let fadeOut = SKAction.fadeOut(withDuration: 0.5)
startLabel.text = "3"
startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: {
startLabel.text = "2"
startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: {
startLabel.text = "1"
startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: {
startLabel.text = "0"
startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: {
startLabel.removeFromParent()
self.gameRunning = true
self.ball!.position = CGPoint(x: 30, y: height / 2)
self.addChild(self.ball!)
})
})
})
})
}
// Handle touch events to move the racket
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if location.y > height / 2 {
direction = Direction.Up
} else if location.y < height / 2{
direction = Direction.Down
}
}
}
// Stop racket movement
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
direction = Direction.None
}
// Game loop:
var dt = TimeInterval(0)
override func update(_ currentTime: TimeInterval) {
if gameRunning {
super.update(currentTime)
checkGameOver()
if dt > 0 {
moveRacket(dt: currentTime - dt)
}
dt = currentTime
}
}
// Move the racket up or down
func moveRacket(dt: TimeInterval) {
if direction == Direction.Up && racket!.position.y < height - racketHeight {
racket!.position.y = racket!.position.y + CGFloat(racketSpeed * dt)
} else if direction == Direction.Down && racket!.position.y > 0 {
racket!.position.y = racket!.position.y - CGFloat(racketSpeed * dt)
}
}
// Check if the ball is still on screen
// Game Over animation
func checkGameOver() {
if ball!.position.x > CGFloat(width) {
gameRunning = false
ball!.removeFromParent()
let gameOverLabel = SKLabelNode(text: "Game Over")
gameOverLabel.position = CGPoint(x: width / 2, y: height / 2)
gameOverLabel.fontSize = 80
self.addChild(gameOverLabel)
// Game Over animation
let rotateAction = SKAction.rotate(byAngle: CGFloat(M_PI), duration: 1)
let fadeInAction = SKAction.fadeIn(withDuration: 2)
gameOverLabel.run(SKAction.repeat(rotateAction, count: 2))
gameOverLabel.run(SKAction.scale(to: 0, duration: 2.5), completion: {
gameOverLabel.removeFromParent()
self.startNewGame()
})
}
}
// Detect collisions between ball and racket to increase the score
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == CollisionTypes.Racket.rawValue || contact.bodyB.categoryBitMask == CollisionTypes.Racket.rawValue {
score += 1
scoreLabel.text = String(score)
}
}
}
//Initialize the playground and start the scene:
let skView = SKView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height)))
let scene = gameScene(size: skView.frame.size)
skView.presentScene(scene)
PlaygroundPage.current.liveView = skView
This happens to me sometimes. Playgrounds with SpriteKit just would not show anything in the Timeline view - you do have the Timeline view open, right? If you don't, tap the "Show the Assistant editor" button on the toolbar to open the Timeline view.
If you have the Timeline view open and nothing shows, try shutting down Xcode and restarting it. That generally resolves this issue for me.
I want to build a game similar to snooker, and at the beginning I already have some problems. I want to build four walls first (that will be the size of the screen-self.frame), and the first one I made like this:
let ground = SKNode()
ground.position = CGPointMake ( 0, 0)
ground.physicsBody = SKPhysicsBody( rectangleOfSize:CGSizeMake(self.frame.size.width * 3, 1))
ground.physicsBody!.dynamic = false
self.addChild(ground)
However, I don't known how to manipulate the values of CGPointMake to do the left/right/up walls. I first imagined that the (0,0) point was the left down corner, but it seems to not be like that. Can someone please help me with this or just explain how this works? (since in https://developer.apple.com/library/prerelease/ios/documentation/GraphicsImaging/Reference/CGGeometry/index.html#//apple_ref/c/func/CGPointMake they dont explain very much =/)
Here is a basic example of how you can restrict the ball from leaving the screen:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let BallCategory : UInt32 = 0x1 << 1
let WallCategory : UInt32 = 0x1 << 2
let ball = SKShapeNode(circleOfRadius: 40)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
physicsWorld.contactDelegate = self
setupWalls()
setupBall()
}
func setupWalls(){
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
physicsBody?.categoryBitMask = WallCategory
physicsBody?.contactTestBitMask = BallCategory
physicsBody?.collisionBitMask = BallCategory
}
func setupBall(){
ball.physicsBody = SKPhysicsBody(circleOfRadius: 40)
ball.fillColor = SKColor.whiteColor()
ball.name = "ball"
ball.physicsBody?.categoryBitMask = BallCategory
ball.physicsBody?.contactTestBitMask = WallCategory
ball.physicsBody?.collisionBitMask = WallCategory
ball.physicsBody?.dynamic = true //In order to detect contact between two bodies, at least on body has to be dynamic
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.restitution = 0.5
ball.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame)) // placing to ball in the middle of the screen
addChild(ball)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
ball.physicsBody?.applyImpulse(CGVector(dx: 200, dy: 200))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Try this for adding four walls with a thickness of twenty:
// Create walls
let leftWall = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 20, height: self.frame.height))
leftWall.fillColor = UIColor.whiteColor()
let physicsBodyLW = SKPhysicsBody(rectangleOfSize: CGSize(width: 20, height: self.frame.size.height), center: CGPoint(x:10, y:self.frame.height/2))
physicsBodyLW.affectedByGravity = false
physicsBodyLW.dynamic = false
leftWall.physicsBody = physicsBodyLW
self.addChild(leftWall)
let rightWall = SKShapeNode(rect: CGRect(x: self.frame.width - 20, y: 0, width: 20, height: self.frame.height))
rightWall.fillColor = UIColor.whiteColor()
let physicsBodyRW = SKPhysicsBody(rectangleOfSize: CGSize(width: 20, height: self.frame.size.height), center: CGPoint(x:10, y:self.frame.height/2))
physicsBodyRW.affectedByGravity = false
physicsBodyRW.dynamic = false
rightWall.physicsBody = physicsBodyRW
self.addChild(rightWall)
let topWall = SKShapeNode(rect: CGRect(x: 0, y: self.frame.height-20, width: self.frame.width, height: 20))
topWall.fillColor = UIColor.whiteColor()
let physicsBodyTW = SKPhysicsBody(rectangleOfSize: CGSize(width: self.frame.size.width, height: 20), center: CGPoint(x:self.frame.width/2, y:10))
physicsBodyTW.affectedByGravity = false
physicsBodyTW.dynamic = false
topWall.physicsBody = physicsBodyTW
self.addChild(topWall)
let bottomWall = SKShapeNode(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: 20))
print(bottomWall.frame)
bottomWall.fillColor = UIColor.whiteColor()
let physicsBodyBW = SKPhysicsBody(rectangleOfSize: CGSize(width: self.frame.size.width, height: 20), center: CGPoint(x:self.frame.width/2, y:10))
physicsBodyBW.affectedByGravity = false
physicsBodyBW.dynamic = false
bottomWall.physicsBody = physicsBodyBW
self.addChild(bottomWall)
I just created a very basic scene in SpriteKit and first problem just appeared.
When two SKShapeNode get in contact, the player node is overlapping the ground node, I can't see why.
Here is my code and a screenshot:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var contentCreated = false
var player: SKShapeNode?
override func didMoveToView(view: SKView) {
if !contentCreated {
createSceneContents()
contentCreated = true
}
}
func createSceneContents() {
self.backgroundColor = SKColor(white: 1.0, alpha: 1.0)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsWorld.gravity = CGVectorMake(0, -10)
self.physicsWorld.contactDelegate = self
let groundCategory: UInt32 = 0x1 << 0
let playerCategory: UInt32 = 0x1 << 0
let ground = SKShapeNode(rect: CGRectMake(0, 0, self.frame.width, 50))
ground.fillColor = UIColor.blueColor()
ground.lineWidth = 0
ground.position = CGPoint(x: 0, y: 0)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.frame.size)
ground.physicsBody?.dynamic = false
ground.physicsBody?.categoryBitMask = groundCategory
ground.physicsBody?.collisionBitMask = groundCategory
ground.physicsBody?.contactTestBitMask = groundCategory
self.addChild(ground)
player = SKShapeNode(rect: CGRectMake(0, 0, 30, 30))
player?.fillColor = UIColor.blackColor()
player?.lineWidth = 0
player?.position = CGPoint(x: 20, y: 400)
player?.physicsBody = SKPhysicsBody(rectangleOfSize: player!.frame.size)
player?.physicsBody?.mass = 1.0
player?.physicsBody?.categoryBitMask = playerCategory
player?.physicsBody?.collisionBitMask = playerCategory
player?.physicsBody?.contactTestBitMask = playerCategory
self.addChild(player!)
}
}
Thanks.
You might want to have a different bitmask for your ground and player :
let groundCategory: UInt32 = 0x1
let playerCategory: UInt32 = 0x1 << 1
Then you might also want to set the collision / contact bitmask differently :
ground.physicsBody?.categoryBitMask = groundCategory
ground.physicsBody?.collisionBitMask = playerCategory
ground.physicsBody?.contactTestBitMask = playerCategory
player?.physicsBody?.categoryBitMask = playerCategory
player?.physicsBody?.collisionBitMask = groundCategory
player?.physicsBody?.contactTestBitMask = groundCategory
Even so, you'll have something like you already have. However if you turn on the showPhysics (as suggested by #Skoua) :
You can see that the physics body is attached to the bottom left anchor point.
Here is how to fix that :
func createSceneContents() {
self.backgroundColor = SKColor(white: 1.0, alpha: 1.0)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsWorld.gravity = CGVectorMake(0, -10)
self.physicsWorld.contactDelegate = self
let groundCategory: UInt32 = 0x1
let playerCategory: UInt32 = 0x1 << 1
let ground = SKShapeNode(rectOfSize: CGSize(width: self.frame.width, height: 50)) // HERE
ground.fillColor = UIColor.blueColor()
ground.lineWidth = 0
ground.position = CGPoint(x: ground.frame.size.width/2, y: ground.frame.size.height/2) // HERE
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.frame.size)
ground.physicsBody?.dynamic = false
ground.physicsBody?.categoryBitMask = groundCategory
ground.physicsBody?.collisionBitMask = playerCategory
ground.physicsBody?.contactTestBitMask = playerCategory
self.addChild(ground)
player = SKShapeNode(rectOfSize: CGSize(width: 30, height: 30)) // HERE
player?.fillColor = UIColor.blackColor()
player?.lineWidth = 0
player?.position = CGPoint(x: 20, y: 400)
player?.physicsBody = SKPhysicsBody(rectangleOfSize: player!.frame.size)
player?.physicsBody?.mass = 1.0
player?.physicsBody?.categoryBitMask = playerCategory
player?.physicsBody?.collisionBitMask = groundCategory
player?.physicsBody?.contactTestBitMask = groundCategory
self.addChild(player!)
}
Don't forget, when placing your node, that the default anchor point is the bottom left of the scene.