Skip to content

Instantly share code, notes, and snippets.

@AchrafKassioui
Last active May 29, 2024 02:03
Show Gist options
  • Save AchrafKassioui/efd92aa28c46be10620dc0ab98c9284e to your computer and use it in GitHub Desktop.
Save AchrafKassioui/efd92aa28c46be10620dc0ab98c9284e to your computer and use it in GitHub Desktop.
A SpriteKit scene to investigate how physics joints work across various camera scales.
/**
# Physics Joints and Camera
A scene to investigate how physics joints work across various camera scales.
Achraf Kassioui
Created: 28 May 2024
Updated: 29 May 2024
*/
import SwiftUI
import SpriteKit
struct JointsWithCameraView: View {
var scene = JointsWithCameraScene()
@State var showPhysics = false
var body: some View {
VStack(spacing: 0) {
SpriteView(
scene: scene,
options: [.ignoresSiblingOrder, .shouldCullNonVisibleNodes]
)
.ignoresSafeArea()
VStack {
menuBar()
}
}
.background(.black)
}
private func menuBar() -> some View {
HStack (spacing: 2) {
Spacer()
/// Zoom Button 1
Button(action: {
scene.setCameraScale(2)
}, label: {
Text("50%")
})
.buttonStyle(.borderedProminent)
/// Zoom Button 2
Button(action: {
scene.setCameraScale(1)
}, label: {
Text("100%")
})
.buttonStyle(.borderedProminent)
/// Zoom Button 3
Button(action: {
scene.setCameraScale(0.5)
}, label: {
Text("200%")
})
.buttonStyle(.borderedProminent)
Spacer()
/// Debug Button
Button(action: {
showPhysics.toggle()
scene.toggleDebugView(showPhysics)
}, label: {
Text(showPhysics ? "Hide Physics" : "Show Physics")
})
.buttonStyle(.borderedProminent)
Spacer()
}
.padding([.top, .leading, .trailing], 10)
}
}
#Preview {
JointsWithCameraView()
}
class JointsWithCameraScene: SKScene {
override func didMove(to view: SKView) {
size = view.bounds.size
backgroundColor = .darkGray
scaleMode = .resizeFill
view.isMultipleTouchEnabled = true
createCamera()
createSceneLayers()
createZoomLabel(view: view, parent: uiLayer)
createPhysicalBoundaryForUIBodies(view: view, parent: uiLayer)
createObjectWithJoints(parent: uiLayer)
createSceneObjects(parent: objectsLayer)
}
// MARK: - Variables
let uiLayer = SKNode()
let objectsLayer = SKNode()
let zoomLabel = SKLabelNode()
// MARK: - Scene Setup
func createSceneLayers() {
guard let camera = self.camera else {
print("No camera in scene")
return
}
uiLayer.zPosition = 9999
camera.addChild(uiLayer)
objectsLayer.zPosition = 1
self.addChild(objectsLayer)
}
func toggleDebugView(_ show: Bool) {
guard let view = self.view else { return }
view.showsFPS = show
view.showsPhysics = show
view.showsNodeCount = show
view.showsDrawCount = show
view.showsFields = show
view.showsQuadCount = show
}
// MARK: - Camera
func createZoomLabel(view: SKView, parent: SKNode) {
zoomLabel.fontName = "Menlo-Bold"
zoomLabel.fontSize = 16
zoomLabel.fontColor = .white
zoomLabel.horizontalAlignmentMode = .center
zoomLabel.verticalAlignmentMode = .center
zoomLabel.position.y = view.bounds.height/2 - zoomLabel.calculateAccumulatedFrame().height/2 - view.safeAreaInsets.top - 20
parent.addChild(zoomLabel)
}
func updateZoomLabel() {
if let camera = self.camera {
let zoomPercentage = 100 / (camera.xScale)
zoomLabel.text = String(format: "Zoom: %.0f%%", zoomPercentage)
}
}
func setCameraScale(_ scale: CGFloat) {
if let camera = self.camera {
let zoomAction = SKAction.scale(to: scale, duration: 0.2)
zoomAction.timingMode = .easeInEaseOut
camera.run(zoomAction)
}
}
/**
# Uncomment this if you want to use InertialCamera
Inertial Camera allows camera control with gestures.
Requires the InertialCamera class to be added to the project.
*/
/*
func createInertialCamera(scene: SKScene) {
let inertialCamera = InertialCamera(scene: scene)
inertialCamera.lockRotation = true
inertialCamera.minScale = 0.2
inertialCamera.maxScale = 20
scene.camera = inertialCamera
scene.addChild(inertialCamera)
}
*/
func createCamera() {
let myCamera = SKCameraNode()
self.camera = myCamera
addChild(myCamera)
}
// MARK: - UI Physics
struct PhysicsBitMasks {
static let sceneBody: UInt32 = 0x1 << 0
static let sceneField: UInt32 = 0x1 << 1
static let sceneParticle: UInt32 = 0x1 << 2
static let sceneParticleCollider: UInt32 = 0x1 << 3
static let sceneBoundary: UInt32 = 0x1 << 4
static let uiBody: UInt32 = 0x1 << 5
static let uiField: UInt32 = 0x1 << 6
static let uiParticle: UInt32 = 0x1 << 7
static let uiBoundary: UInt32 = 0x1 << 8
}
func createPhysicalBoundaryForUIBodies(view: SKView, parent: SKNode) {
let margin: CGFloat = 0
let uiArea = CGRect(
x: -view.bounds.width/2 - margin/2,
y: -view.bounds.height/2 - margin/2,
width: view.bounds.width - view.safeAreaInsets.left - view.safeAreaInsets.right + margin,
height: view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom + margin
)
let uiFrame = SKShapeNode(rect: uiArea)
uiFrame.alpha = 0
uiFrame.physicsBody = SKPhysicsBody(edgeLoopFrom: uiArea)
uiFrame.physicsBody?.categoryBitMask = PhysicsBitMasks.uiBoundary
uiFrame.physicsBody?.collisionBitMask = PhysicsBitMasks.uiBody
uiFrame.physicsBody?.fieldBitMask = 0
uiFrame.physicsBody?.restitution = 0
uiFrame.physicsBody?.friction = 0
parent.addChild(uiFrame)
}
// MARK: - UI Objects
func createObjectWithJoints(parent: SKNode) {
/// The visible sprite
let rectangle = SKSpriteNode(color: .systemYellow, size: CGSize(width: 140, height: 60))
rectangle.physicsBody = SKPhysicsBody(rectangleOf: rectangle.size)
rectangle.physicsBody?.categoryBitMask = PhysicsBitMasks.uiBody
rectangle.physicsBody?.collisionBitMask = PhysicsBitMasks.uiBoundary | PhysicsBitMasks.uiBody
rectangle.physicsBody?.fieldBitMask = PhysicsBitMasks.uiField
rectangle.physicsBody?.affectedByGravity = true
rectangle.physicsBody?.allowsRotation = true
rectangle.physicsBody?.linearDamping = 1
rectangle.physicsBody?.charge = 1
rectangle.position = CGPoint(x: 0, y: -200)
parent.addChild(rectangle)
/// The first anchor point
let anchorPoint1 = SKNode()
anchorPoint1.position = CGPoint(x: -50, y: 100)
anchorPoint1.physicsBody = SKPhysicsBody(circleOfRadius: 1)
anchorPoint1.physicsBody?.isDynamic = false
parent.addChild(anchorPoint1)
/// The second anchor point
let anchorPoint2 = SKNode()
anchorPoint2.position = CGPoint(x: 50, y: 200)
anchorPoint2.physicsBody = SKPhysicsBody(circleOfRadius: 1)
anchorPoint2.physicsBody?.isDynamic = false
parent.addChild(anchorPoint2)
/// Spring joints
let springJoint1 = SKPhysicsJointSpring.joint(
withBodyA: rectangle.physicsBody!,
bodyB: anchorPoint1.physicsBody!,
anchorA: rectangle.position,
anchorB: anchorPoint1.position
)
springJoint1.frequency = 1
springJoint1.damping = 0
let springJoint2 = SKPhysicsJointSpring.joint(
withBodyA: rectangle.physicsBody!,
bodyB: anchorPoint2.physicsBody!,
anchorA: rectangle.position,
anchorB: anchorPoint2.position
)
springJoint2.frequency = 1
springJoint2.damping = 0
/**
# Uncomment/comment this to toggle the Spring Joints
*/
physicsWorld.add(springJoint1)
physicsWorld.add(springJoint2)
/// Pin joints
let pinJoint1 = SKPhysicsJointPin.joint(
withBodyA: rectangle.physicsBody!,
bodyB: anchorPoint1.physicsBody!,
anchor: anchorPoint1.position
)
pinJoint1.frictionTorque = 0
pinJoint1.rotationSpeed = 0
let pinJoint2 = SKPhysicsJointPin.joint(
withBodyA: rectangle.physicsBody!,
bodyB: anchorPoint2.physicsBody!,
anchor: anchorPoint2.position
)
pinJoint2.frictionTorque = 0
pinJoint2.rotationSpeed = 0
/**
# Uncomment/comment this to toggle the Pin Joints
*/
//physicsWorld.add(pinJoint1)
//physicsWorld.add(pinJoint2)
}
// MARK: - Scene Objects
func createSceneObjects(parent: SKNode) {
let sprite0 = SKSpriteNode(color: SKColor(white: 0, alpha: 0.1), size: CGSize(width: 800, height: 800))
parent.addChild(sprite0)
let sprite = SKSpriteNode(color: SKColor(white: 0, alpha: 0.2), size: CGSize(width: 400, height: 400))
parent.addChild(sprite)
let sprite2 = SKSpriteNode(color: SKColor(white: 0, alpha: 0.3), size: CGSize(width: 100, height: 100))
parent.addChild(sprite2)
}
// MARK: - Update
override func update(_ currentTime: TimeInterval) {
updateZoomLabel()
}
}
@AchrafKassioui
Copy link
Author

Here is a video of how spring joints attached to the camera behave when the camera is scaled:

SpriteKit.-.Joints.with.Camera.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment