Skip to content

Instantly share code, notes, and snippets.

@jweinst1
Last active April 22, 2025 05:44
Show Gist options
  • Save jweinst1/28e8137a10572ed01e48e5d0b0645ff7 to your computer and use it in GitHub Desktop.
Save jweinst1/28e8137a10572ed01e48e5d0b0645ff7 to your computer and use it in GitHub Desktop.
playground game of blocks in swift with contacts and jumps and joints
//: A SpriteKit based Playground
import PlaygroundSupport
import SpriteKit
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
class GameOverScene : SKScene {
var label = SKLabelNode(fontNamed: "Chalkduster")
override func didMove(to view: SKView) {
self.scaleMode = .aspectFill
label.position = CGPoint(x:0,y:0)
label.text = "Game Over"
addChild(label)
}
}
struct Platform {
let height:Int
let width:Int
let x:Int
let y:Int
init(height: Int, width: Int, x: Int, y: Int) {
self.height = height
self.width = width
self.x = x
self.y = y
}
}
struct PlatformGen {
let height:Int
let width:Int
let spaceMin:Int
let spaceMax:Int
var lastX:Int
var lastY:Int
init(height: Int, width: Int, spaceMin: Int, spaceMax: Int, startX:Int = 0, startY:Int = 0) {
self.height = height
self.width = width
self.spaceMin = spaceMin
self.spaceMax = spaceMax
self.lastX = startX
self.lastY = startY
}
mutating func next() -> Platform {
// print(Int.random(in: 1..<100))
let nextX = Int.random(in: (self.lastX + self.spaceMin)...(self.lastX + self.spaceMax))
let nextY = Int.random(in: (self.lastY + self.spaceMin)...(self.lastY + self.spaceMax))
self.lastX = nextX;
self.lastY = nextY;
return Platform(height: self.height, width: self.width, x: nextX, y: nextY)
}
}
class GameScene: SKScene, @preconcurrency SKPhysicsContactDelegate {
private var touchingGround : Bool = true
private var timeLabel : SKLabelNode!
private var playerNode : SKShapeNode!
private var ground : SKShapeNode!
private var spinnyNode4 : SKShapeNode!
private var cameraNode : SKCameraNode!
private var scoreCount: Int = 0
private var platforms: [SKShapeNode] = []
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeB.physicsBody?.categoryBitMask == 0b1 {
print("began ground contact")
touchingGround = true
// use contact point to make a joint
let newJoint = SKPhysicsJointFixed.joint(withBodyA: nodeA.physicsBody!, bodyB: nodeB.physicsBody!, anchor: contact.contactPoint)
physicsWorld.add(newJoint)
//nodeA.run(.moveTo(y: 200, duration: 0))
}
/*if nodeA.name == "me" && nodeB.name == "op" {
//nodeB.removeFromParent()
// causes a fall through
nodeB.physicsBody?.collisionBitMask = 0
}*/
}
func didEnd(_ contact: SKPhysicsContact) {
// todo, an ending contact
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeB.physicsBody?.categoryBitMask == 0b1 {
// ground contact
print("ended ground contact")
touchingGround = false
}
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
cameraNode = SKCameraNode()
addChild(cameraNode)
camera = cameraNode
// Get label node from scene and store it for use later
/*let fadeInOut = SKAction.sequence([.fadeIn(withDuration: 2.0),
.wait(forDuration: 0.8),
.fadeOut(withDuration: 2.0)])*/
//label.run(.repeatForever(fadeInOut))
playerNode = SKShapeNode(rectOf: CGSize(width:40, height:40))
playerNode.lineWidth = 5
playerNode.name = "me"
playerNode.position = CGPoint(x:0, y:0)
playerNode.strokeColor = NSColor(white: 0.0, alpha: 1.0)
playerNode.fillColor = NSColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
playerNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:40, height:40))
//spinnyNode2.physicsBody?.affectedByGravity = false;
playerNode.physicsBody?.friction = 0.1;
playerNode.physicsBody!.contactTestBitMask = 0b011
playerNode.physicsBody!.collisionBitMask = 0b1
addChild(playerNode)
timeLabel = SKLabelNode(fontNamed: "Chalkduster")
timeLabel.fontSize = 50
timeLabel.text = String(scoreCount)
timeLabel.horizontalAlignmentMode = .right
timeLabel.color = SKColor.white
timeLabel.position = CGPoint(x:0, y:playerNode.position.y + 300)
addChild(timeLabel)
ground = SKShapeNode(rectOf: CGSize(width:300, height:40))
ground.lineWidth = 5
ground.position = CGPoint(x:0, y:-120)
ground.strokeColor = NSColor(white: 0.0, alpha: 1.0)
ground.fillColor = NSColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
ground.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:300, height:40))
//spinnyNode3.physicsBody?.isDynamic = false;
ground.physicsBody?.isDynamic = false;
ground.physicsBody?.categoryBitMask = 0b1 // for the ground
addChild(ground)
// causes ground to move over time
ground.run(.repeatForever(.move(by: CGVector(dx: -5, dy: 0), duration: 0.1)))
spinnyNode4 = SKShapeNode(rectOf: CGSize(width:40, height:40))
spinnyNode4.name = "op"
spinnyNode4.lineWidth = 5
spinnyNode4.position = CGPoint(x:100, y:0)
spinnyNode4.strokeColor = NSColor(white: 1.0, alpha: 1.0)
spinnyNode4.fillColor = NSColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0)
spinnyNode4.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:40, height:40))
spinnyNode4.physicsBody!.categoryBitMask = 0b010
spinnyNode4.physicsBody!.collisionBitMask = 0b1
//spinnyNode4.physicsBody?.mass = 100.0
addChild(spinnyNode4)
spinnyNode4.run(.repeatForever(.move(by: CGVector(dx: -2, dy: 0), duration: 0.1)))
/* can be used for back and forth movement
let fadeAndRemove = SKAction.sequence([.wait(forDuration: 0.5),
.fadeOut(withDuration: 0.5),
.removeFromParent()])
spinnyNode.run(.repeatForever(.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
spinnyNode.run(fadeAndRemove)*/
}
@objc static override var supportsSecureCoding: Bool {
// SKNode conforms to NSSecureCoding, so any subclass going
// through the decoding process must support secure coding
get {
return true
}
}
func touchDown(atPoint pos : CGPoint) {
/*guard let n = spinnyNode.copy() as? SKShapeNode else { return }
n.position = pos
n.strokeColor = SKColor.green
addChild(n)*/
if touchingGround {
//spinnyNode2.physicsBody?.applyImpulse(CGVector(dx:0, dy:80))
physicsWorld.remove(playerNode.physicsBody!.joints[0])
playerNode.run(.applyForce(CGVector(dx:0, dy:300), duration: 0.2))
}
}
func touchMoved(toPoint pos : CGPoint) {
/*guard let n = self.spinnyNode.copy() as? SKShapeNode else { return }
n.position = pos
n.strokeColor = SKColor.blue
addChild(n)*/
}
func touchUp(atPoint pos : CGPoint) {
/*guard let n = spinnyNode.copy() as? SKShapeNode else { return }
n.position = pos
n.strokeColor = SKColor.red
addChild(n)*/
}
override func mouseDown(with event: NSEvent) {
touchDown(atPoint: event.location(in: self))
}
override func mouseDragged(with event: NSEvent) {
touchMoved(toPoint: event.location(in: self))
}
override func mouseUp(with event: NSEvent) {
touchUp(atPoint: event.location(in: self))
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
cameraNode.position = playerNode.position
timeLabel.position = CGPoint(x:playerNode.position.x, y:playerNode.position.y + 300)
}
}
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment