Skip to content

Instantly share code, notes, and snippets.

@MatthewWaller
Created May 24, 2025 22:12
Show Gist options
  • Save MatthewWaller/d273d8a0cb3f70796b7fffb12617182f to your computer and use it in GitHub Desktop.
Save MatthewWaller/d273d8a0cb3f70796b7fffb12617182f to your computer and use it in GitHub Desktop.
ReailtyKit clock hand entity system, useful for visionOS, in Swift
import Foundation
import RealityKit
// First, let's create a component to mark our clock hands
struct ClockHandComponent: Component {
var handType: HandType
enum HandType {
case hour
case minute
case second
var rotationSpeed: Float {
switch self {
case .second:
return -(2 * .pi / 60) // Negative for clockwise rotation
case .minute:
return -(2 * .pi / 3600)
case .hour:
return -(2 * .pi / 43200)
}
}
}
}
// Create the system to handle rotation
class ClockSystem: System {
required init(scene: Scene) { }
static let query = EntityQuery(where: .has(ClockHandComponent.self))
func update(context: SceneUpdateContext) {
for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) {
guard let clockHand = entity.components[ClockHandComponent.self] else { continue }
// Calculate rotation based on current time
let elapsedTime = Float(context.deltaTime)
let rotationAmount = clockHand.handType.rotationSpeed * elapsedTime
// Create rotation quaternion for this frame
let rotation = simd_quatf(angle: rotationAmount, axis: SIMD3<Float>(0, 0, 1))
// Apply rotation
entity.transform.rotation = entity.transform.rotation * rotation
}
}
}
// Extension to help set up the clock
// You'll want to create your own TimepieceObject which has properties for the handnames as shown below.
extension Entity {
func setupAsClock(forTimepiece timepiece: TimepieceObject) {
if let secondHand = self.findEntity(named: timepiece.secondHandName) {
secondHand.components[ClockHandComponent.self] = ClockHandComponent(handType: .second)
}
if let minuteHand = self.findEntity(named: timepiece.minuteHandName) {
minuteHand.components[ClockHandComponent.self] = ClockHandComponent(handType: .minute)
}
if let hourHand = self.findEntity(named: timepiece.hourHandName) {
hourHand.components[ClockHandComponent.self] = ClockHandComponent(handType: .hour)
}
// Set initial rotations based on current time
setInitialHandPositions(forTimepiece: timepiece)
}
private func setInitialHandPositions(forTimepiece timepiece: TimepieceObject) {
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute, .second], from: Date())
let totalSeconds = Float(components.second ?? 0)
let totalMinutes = Float(components.minute ?? 0) * 60 + totalSeconds
let totalHours = Float(components.hour ?? 0) * 3600 + totalMinutes
if let hand = self.findEntity(named: timepiece.secondHandName) {
let angle = -(totalSeconds / 60) * 2 * .pi // Negative for clockwise
switch timepiece {
case .steampunkClock, .woodenDeskClock:
hand.transform.rotation = simd_quatf(angle: angle, axis: SIMD3<Float>(0, 0, 1))
case .grandfatherClock:
hand.transform.rotation = simd_quatf(angle: -angle, axis: SIMD3<Float>(0, 1, 0))
default:
break
}
}
if let hand = self.findEntity(named: timepiece.minuteHandName) {
let angle = -(totalMinutes / 3600) * 2 * .pi // Negative for clockwise
switch timepiece {
case .steampunkClock, .woodenDeskClock:
hand.transform.rotation = simd_quatf(angle: angle, axis: SIMD3<Float>(0, 0, 1))
case .grandfatherClock:
hand.transform.rotation = simd_quatf(angle: -angle, axis: SIMD3<Float>(0, 1, 0))
default:
break
}
}
if let hand = self.findEntity(named: timepiece.hourHandName) {
let angle = -(totalHours / 43200) * 2 * .pi // Negative for clockwise
switch timepiece {
case .steampunkClock, .woodenDeskClock:
hand.transform.rotation = simd_quatf(angle: angle, axis: SIMD3<Float>(0, 0, 1))
case .grandfatherClock:
hand.transform.rotation = simd_quatf(angle: -angle, axis: SIMD3<Float>(0, 1, 0))
default:
break
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment