diff --git a/MiniBlocks.swiftpm/Sources/Component/CrosshairHUDPositioningComponent.swift b/MiniBlocks.swiftpm/Sources/Component/CrosshairHUDPositioningComponent.swift new file mode 100644 index 0000000..0891562 --- /dev/null +++ b/MiniBlocks.swiftpm/Sources/Component/CrosshairHUDPositioningComponent.swift @@ -0,0 +1,15 @@ +import SpriteKit +import GameplayKit + +/// Positions the crosshair (in the center). +class CrosshairHUDPositioningComponent: GKComponent, FrameSizeDependent { + private var node: SKNode? { + entity?.component(ofType: SpriteNodeComponent.self)?.node + } + + func onUpdateFrame(to frame: CGRect) { + DispatchQueue.main.async { [self] in + node?.position = CGPoint(x: frame.midX, y: frame.midY) + } + } +} diff --git a/MiniBlocks.swiftpm/Sources/Component/DebugHUDLoadComponent.swift b/MiniBlocks.swiftpm/Sources/Component/DebugHUDLoadComponent.swift index 3a92dd6..8a15bda 100644 --- a/MiniBlocks.swiftpm/Sources/Component/DebugHUDLoadComponent.swift +++ b/MiniBlocks.swiftpm/Sources/Component/DebugHUDLoadComponent.swift @@ -1,8 +1,9 @@ import SpriteKit import GameplayKit -class DebugHUDLoadComponent: GKComponent { +class DebugHUDLoadComponent: GKComponent, FrameSizeDependent { private var lastEnabled: Bool = false + private let padding: CGFloat = 5 private var node: SKLabelNode? { entity?.component(ofType: SpriteNodeComponent.self)?.node as? SKLabelNode @@ -52,6 +53,12 @@ class DebugHUDLoadComponent: GKComponent { lastEnabled = isEnabled } + func onUpdateFrame(to frame: CGRect) { + DispatchQueue.main.async { [self] in + node?.position = CGPoint(x: frame.minX + padding, y: frame.maxY - padding) + } + } + private func format(pos: BlockPos3) -> String { "x \(pos.x), y \(pos.y), z \(pos.z)" } diff --git a/MiniBlocks.swiftpm/Sources/Component/FrameSizeDependent.swift b/MiniBlocks.swiftpm/Sources/Component/FrameSizeDependent.swift new file mode 100644 index 0000000..8ccc27b --- /dev/null +++ b/MiniBlocks.swiftpm/Sources/Component/FrameSizeDependent.swift @@ -0,0 +1,6 @@ +import SpriteKit + +/// Indicates that the implementing component is dependent on the frame's size. +protocol FrameSizeDependent { + func onUpdateFrame(to frame: CGRect) +} diff --git a/MiniBlocks.swiftpm/Sources/Component/HotbarHUDLoadComponent.swift b/MiniBlocks.swiftpm/Sources/Component/HotbarHUDLoadComponent.swift index 98e4691..87a15ea 100644 --- a/MiniBlocks.swiftpm/Sources/Component/HotbarHUDLoadComponent.swift +++ b/MiniBlocks.swiftpm/Sources/Component/HotbarHUDLoadComponent.swift @@ -2,7 +2,7 @@ import SpriteKit import GameplayKit /// Renders the hotbar for a player from the world model to a SpriteKit node. -class HotbarHUDLoadComponent: GKComponent { +class HotbarHUDLoadComponent: GKComponent, FrameSizeDependent { /// The inventory last rendered to a SpriteKit node. private var lastInventory: Inventory? /// The slot selection last rendered to a SpriteKit node. @@ -73,4 +73,10 @@ class HotbarHUDLoadComponent: GKComponent { private func slotLineThickness(for i: Int) -> CGFloat { playerInfo?.selectedHotbarSlot == i ? selectedSlotLineThickness : normalSlotLineThickness } + + func onUpdateFrame(to frame: CGRect) { + DispatchQueue.main.async { [self] in + node?.position = CGPoint(x: frame.midX, y: frame.minY) + } + } } diff --git a/MiniBlocks.swiftpm/Sources/Component/PauseHUDPositioningComponent.swift b/MiniBlocks.swiftpm/Sources/Component/PauseHUDPositioningComponent.swift new file mode 100644 index 0000000..52be376 --- /dev/null +++ b/MiniBlocks.swiftpm/Sources/Component/PauseHUDPositioningComponent.swift @@ -0,0 +1,20 @@ +import SpriteKit +import GameplayKit + +/// Positions the pause overlay. +class PauseHUDPositioningComponent: GKComponent, FrameSizeDependent { + private var node: SKNode? { + entity?.component(ofType: SpriteNodeComponent.self)?.node + } + + private var backgroundNode: SKShapeNode? { + node?.childNode(withName: "pauseHUDBackground") as? SKShapeNode + } + + func onUpdateFrame(to frame: CGRect) { + DispatchQueue.main.async { [self] in + node?.position = CGPoint(x: frame.midX, y: frame.midY) + // TODO: Update size + } + } +} diff --git a/MiniBlocks.swiftpm/Sources/Entity/CrosshairHUDEntity.swift b/MiniBlocks.swiftpm/Sources/Entity/CrosshairHUDEntity.swift index 62133e2..0816d36 100644 --- a/MiniBlocks.swiftpm/Sources/Entity/CrosshairHUDEntity.swift +++ b/MiniBlocks.swiftpm/Sources/Entity/CrosshairHUDEntity.swift @@ -4,12 +4,12 @@ import GameplayKit func makeCrosshairHUDEntity(size: CGFloat = 20, thickness: CGFloat = 2, in frame: CGRect) -> GKEntity { // Create node let node = makeCrosshairHUDNode(size: size, thickness: thickness) - node.position = CGPoint(x: frame.midX, y: frame.midY) // Create entity let entity = GKEntity() entity.addComponent(SpriteNodeComponent(node: node)) entity.addComponent(MouseCaptureVisibilityComponent(visibleWhenCaptured: true)) + entity.addComponent(CrosshairHUDPositioningComponent()) return entity } diff --git a/MiniBlocks.swiftpm/Sources/Entity/DebugHUDEntity.swift b/MiniBlocks.swiftpm/Sources/Entity/DebugHUDEntity.swift index 46eedc3..465056a 100644 --- a/MiniBlocks.swiftpm/Sources/Entity/DebugHUDEntity.swift +++ b/MiniBlocks.swiftpm/Sources/Entity/DebugHUDEntity.swift @@ -3,12 +3,10 @@ import GameplayKit func makeDebugHUDEntity(in frame: CGRect, playerEntity: GKEntity, fontSize: CGFloat = 15) -> GKEntity { // Create node - let padding: CGFloat = 5 let node = SKLabelNode() node.fontColor = .white node.fontName = NodeConstants.fontName node.fontSize = fontSize - node.position = CGPoint(x: frame.minX + padding, y: frame.maxY - padding) node.numberOfLines = 0 node.verticalAlignmentMode = .top node.horizontalAlignmentMode = .left diff --git a/MiniBlocks.swiftpm/Sources/Entity/HotbarHUDEntity.swift b/MiniBlocks.swiftpm/Sources/Entity/HotbarHUDEntity.swift index 058af07..68bd91f 100644 --- a/MiniBlocks.swiftpm/Sources/Entity/HotbarHUDEntity.swift +++ b/MiniBlocks.swiftpm/Sources/Entity/HotbarHUDEntity.swift @@ -4,7 +4,6 @@ import GameplayKit func makeHotbarHUDEntity(in frame: CGRect, playerEntity: GKEntity) -> GKEntity { // Create node let node = SKNode() - node.position = CGPoint(x: frame.midX, y: frame.minY) // Create entity let entity = GKEntity() diff --git a/MiniBlocks.swiftpm/Sources/Entity/PauseHUDEntity.swift b/MiniBlocks.swiftpm/Sources/Entity/PauseHUDEntity.swift index b40e350..4344e84 100644 --- a/MiniBlocks.swiftpm/Sources/Entity/PauseHUDEntity.swift +++ b/MiniBlocks.swiftpm/Sources/Entity/PauseHUDEntity.swift @@ -4,12 +4,12 @@ import GameplayKit func makePauseHUDEntity(in frame: CGRect, fontSize: CGFloat = 28) -> GKEntity { // Create node let node = makePauseHUDNode(size: frame.size, fontSize: fontSize) - node.position = CGPoint(x: frame.midX, y: frame.midY) // Create entity let entity = GKEntity() entity.addComponent(SpriteNodeComponent(node: node)) entity.addComponent(MouseCaptureVisibilityComponent(visibleWhenCaptured: false)) + entity.addComponent(PauseHUDPositioningComponent()) return entity } diff --git a/MiniBlocks.swiftpm/Sources/Node/PauseHUDNode.swift b/MiniBlocks.swiftpm/Sources/Node/PauseHUDNode.swift index 34fbc82..6f2da12 100644 --- a/MiniBlocks.swiftpm/Sources/Node/PauseHUDNode.swift +++ b/MiniBlocks.swiftpm/Sources/Node/PauseHUDNode.swift @@ -2,6 +2,7 @@ import SpriteKit private func makeBackgroundNode(size: CGSize) -> SKNode { let node = SKShapeNode(rect: CGRect(center: CGPoint(x: 0, y: 0), size: size)) + node.name = "pauseHUDBackground" node.strokeColor = .clear node.lineWidth = 0 node.fillColor = .black.withAlphaComponent(0.92) diff --git a/MiniBlocks.swiftpm/Sources/View/MiniBlocksViewController.swift b/MiniBlocks.swiftpm/Sources/View/MiniBlocksViewController.swift index 8f124a7..4fed34c 100644 --- a/MiniBlocks.swiftpm/Sources/View/MiniBlocksViewController.swift +++ b/MiniBlocks.swiftpm/Sources/View/MiniBlocksViewController.swift @@ -26,7 +26,7 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel // MARK: View properties private var sceneView: MiniBlocksSceneView! - private let sceneFrame: CGRect? + private var sceneFrame: CGRect? private var inputSensivity: SceneFloat = 1 #if canImport(AppKit) @@ -122,7 +122,6 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel // Create overlay scene overlayScene = sceneFrame.map { SKScene(size: $0.size) } ?? SKScene() - overlayScene.scaleMode = .aspectFill overlayScene.isUserInteractionEnabled = false // Add light @@ -267,6 +266,13 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel } #endif + // Perform initial layout if frame size dependent + if let sceneFrame = sceneFrame { + for case let component as FrameSizeDependent in entity.components { + component.onUpdateFrame(to: sceneFrame) + } + } + // Add components to their corresponding systems playerControlComponentSystem.addComponent(foundIn: entity) playerPositioningComponentSystem.addComponent(foundIn: entity) @@ -737,5 +743,38 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel } #endif + + // MARK: View resizing + + private func onRelayout() { + let frame = view.frame + + if frame.size != overlayScene.size { + sceneFrame = frame + overlayScene.size = frame.size + + for entity in entities { + for case let component as FrameSizeDependent in entity.components { + component.onUpdateFrame(to: frame) + } + } + } + } + + #if canImport(AppKit) + + public override func viewWillLayout() { + onRelayout() + } + + #endif + + #if canImport(UIKit) + + public override func viewWillLayoutSubviews() { + onRelayout() + } + + #endif } diff --git a/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj b/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj index 9558171..d30c857 100644 --- a/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj +++ b/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj @@ -178,6 +178,12 @@ 35D8D9EE278B3F470056ACAA /* TextureLeaves.png in Resources */ = {isa = PBXBuildFile; fileRef = 35D8D9ED278B3F470056ACAA /* TextureLeaves.png */; }; 35D9E8A1278887960058F0E6 /* SunEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D9E8A0278887960058F0E6 /* SunEntity.swift */; }; 35D9E8A3278888190058F0E6 /* AmbientLightEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D9E8A2278888190058F0E6 /* AmbientLightEntity.swift */; }; + 35DDED6F2817703C007E9CFD /* FrameSizeDependent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DDED6E2817703C007E9CFD /* FrameSizeDependent.swift */; }; + 35DDED702817703C007E9CFD /* FrameSizeDependent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DDED6E2817703C007E9CFD /* FrameSizeDependent.swift */; }; + 35DDED72281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DDED71281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift */; }; + 35DDED73281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DDED71281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift */; }; + 35DDED7528177B50007E9CFD /* PauseHUDPositioningComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DDED7428177B50007E9CFD /* PauseHUDPositioningComponent.swift */; }; + 35DDED7628177B50007E9CFD /* PauseHUDPositioningComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DDED7428177B50007E9CFD /* PauseHUDPositioningComponent.swift */; }; 35E7F3EA27867ED10067F0F7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E7F3E927867ED10067F0F7 /* AppDelegate.swift */; }; 35E7F3F027867ED20067F0F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35E7F3EF27867ED20067F0F7 /* Assets.xcassets */; }; 35E7F40C278682C60067F0F7 /* MiniBlocksScene.scn in Resources */ = {isa = PBXBuildFile; fileRef = 35E7F40A278682C60067F0F7 /* MiniBlocksScene.scn */; }; @@ -306,6 +312,9 @@ 35D8D9ED278B3F470056ACAA /* TextureLeaves.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TextureLeaves.png; path = ../../MiniBlocks.swiftpm/Resources/TextureLeaves.png; sourceTree = ""; }; 35D9E8A0278887960058F0E6 /* SunEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SunEntity.swift; path = ../../MiniBlocks.swiftpm/Sources/Entity/SunEntity.swift; sourceTree = ""; }; 35D9E8A2278888190058F0E6 /* AmbientLightEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AmbientLightEntity.swift; path = ../../MiniBlocks.swiftpm/Sources/Entity/AmbientLightEntity.swift; sourceTree = ""; }; + 35DDED6E2817703C007E9CFD /* FrameSizeDependent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FrameSizeDependent.swift; path = ../../MiniBlocks.swiftpm/Sources/Component/FrameSizeDependent.swift; sourceTree = ""; }; + 35DDED71281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CrosshairHUDPositioningComponent.swift; path = ../../MiniBlocks.swiftpm/Sources/Component/CrosshairHUDPositioningComponent.swift; sourceTree = ""; }; + 35DDED7428177B50007E9CFD /* PauseHUDPositioningComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PauseHUDPositioningComponent.swift; path = ../../MiniBlocks.swiftpm/Sources/Component/PauseHUDPositioningComponent.swift; sourceTree = ""; }; 35E7F3E627867ED10067F0F7 /* MiniBlocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MiniBlocks.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35E7F3E927867ED10067F0F7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35E7F3EF27867ED20067F0F7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -421,9 +430,12 @@ 35D3491F27FF3AB1000F0CC2 /* HotbarHUDControlComponent.swift */, 35D3492827FF45EB000F0CC2 /* ControlPadHUDControlComponent.swift */, 355006CD27A0C0710021A0BE /* DebugHUDLoadComponent.swift */, + 35DDED71281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift */, + 35DDED7428177B50007E9CFD /* PauseHUDPositioningComponent.swift */, 3554CB0427F51F0E000EE620 /* AchievementHUDLoadComponent.swift */, 3540752127F2A6EE00887E01 /* MouseCaptureVisibilityComponent.swift */, 35D3491C27FF3A07000F0CC2 /* TouchInteractable.swift */, + 35DDED6E2817703C007E9CFD /* FrameSizeDependent.swift */, ); name = Component; sourceTree = ""; @@ -749,6 +761,8 @@ 35391BC8278BC5D800B22954 /* ChunkConstants.swift in Sources */, 35D3492A27FF45EB000F0CC2 /* ControlPadHUDControlComponent.swift in Sources */, 357EB0CD279640B00004CA6E /* Debouncer.swift in Sources */, + 35DDED7628177B50007E9CFD /* PauseHUDPositioningComponent.swift in Sources */, + 35DDED702817703C007E9CFD /* FrameSizeDependent.swift in Sources */, 35F2680427F76C7D00CC458C /* HandNodeComponent.swift in Sources */, 35A5552D278C3AE0006FA70D /* Item.swift in Sources */, 35A55529278C3AE0006FA70D /* EmptyWorldGenerator.swift in Sources */, @@ -803,6 +817,7 @@ 35391BE3278BC5F400B22954 /* PlayerEntity.swift in Sources */, 35391BE2278BC5F400B22954 /* AmbientLightEntity.swift in Sources */, 3540751A27F2A06300887E01 /* PauseHUDEntity.swift in Sources */, + 35DDED73281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift in Sources */, 3572145127957B56003C640F /* Vec3Convertible.swift in Sources */, 35391BE6278BC5F700B22954 /* MiniBlocksSceneView.swift in Sources */, 35391BAF278BC5A800B22954 /* AppDelegate.swift in Sources */, @@ -844,6 +859,8 @@ 35EA353E2788A57C0066DD82 /* WorldAssociationComponent.swift in Sources */, 35D3492927FF45EB000F0CC2 /* ControlPadHUDControlComponent.swift in Sources */, 357EB0CC279640B00004CA6E /* Debouncer.swift in Sources */, + 35DDED7528177B50007E9CFD /* PauseHUDPositioningComponent.swift in Sources */, + 35DDED6F2817703C007E9CFD /* FrameSizeDependent.swift in Sources */, 35F2680327F76C7D00CC458C /* HandNodeComponent.swift in Sources */, 35A55530278C3AE0006FA70D /* WorldGeneratorType.swift in Sources */, 35A55526278C3AE0006FA70D /* NatureWorldGenerator.swift in Sources */, @@ -898,6 +915,7 @@ 35A55528278C3AE0006FA70D /* EmptyWorldGenerator.swift in Sources */, 35EA352627889CBE0066DD82 /* GridIterator2.swift in Sources */, 3540751927F2A06300887E01 /* PauseHUDEntity.swift in Sources */, + 35DDED72281778EC007E9CFD /* CrosshairHUDPositioningComponent.swift in Sources */, 3572145027957B56003C640F /* Vec3Convertible.swift in Sources */, 35D9E8A3278888190058F0E6 /* AmbientLightEntity.swift in Sources */, 35A0C298278753AE00FA8570 /* Throttler.swift in Sources */,