From fb7810bb34fa875970977b66c4f5fe16efe79852 Mon Sep 17 00:00:00 2001 From: Vallykrie Date: Mon, 18 May 2026 17:40:24 +0700 Subject: [PATCH 1/3] feat: Implement RenderSystem and Bread trail/rotation (#94, #97) --- .../Sources/Entities/ProjectileEntity.swift | 22 ++++++++++++ QuackFight/Sources/Scenes/GameScene.swift | 3 ++ .../Sources/Systems/Render/RenderSystem.swift | 36 ++++++++++++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/QuackFight/Sources/Entities/ProjectileEntity.swift b/QuackFight/Sources/Entities/ProjectileEntity.swift index 13632b9..a124766 100644 --- a/QuackFight/Sources/Entities/ProjectileEntity.swift +++ b/QuackFight/Sources/Entities/ProjectileEntity.swift @@ -16,6 +16,28 @@ class ProjectileEntity: GKEntity { // Issue #38: Attach all 4 components // SpriteComponent handles the visual node let spriteComponent = SpriteComponent(imageName: imageName) + + // Issue #94: Add a visual trail + let trail = SKEmitterNode() + trail.particleTexture = SKTexture(imageNamed: "spark") // Will safely fallback to a square if "spark" is missing + trail.particleBirthRate = 60 + trail.particleLifetime = 0.4 + trail.particlePositionRange = CGVector(dx: 10, dy: 10) + trail.particleScale = 0.2 + trail.particleScaleSpeed = -0.5 + trail.particleAlpha = 0.8 + trail.particleAlphaSpeed = -2.0 + trail.particleColor = .white + trail.particleColorBlendFactor = 1.0 + trail.emissionAngleRange = .pi * 2 + trail.particleSpeed = 10 + trail.zPosition = -1 // Render behind the projectile + + // We add the trail to the sprite component so it travels with the bread + // To make particles linger in world space, the `ThrowSystem` or `GameScene` could set `trail.targetNode = scene`. + trail.name = "trailEmitter" + spriteComponent.node.addChild(trail) + addComponent(spriteComponent) // TransformComponent handles position/rotation data diff --git a/QuackFight/Sources/Scenes/GameScene.swift b/QuackFight/Sources/Scenes/GameScene.swift index 09a058d..39ab333 100644 --- a/QuackFight/Sources/Scenes/GameScene.swift +++ b/QuackFight/Sources/Scenes/GameScene.swift @@ -69,6 +69,9 @@ class GameScene: SKScene { entity.update(deltaTime: dt) } + // Render System (End of frame synchronization) + RenderSystem.shared.update(entities: self.entities, deltaTime: dt) + self.lastUpdateTime = currentTime } diff --git a/QuackFight/Sources/Systems/Render/RenderSystem.swift b/QuackFight/Sources/Systems/Render/RenderSystem.swift index 1639c79..7325b8f 100644 --- a/QuackFight/Sources/Systems/Render/RenderSystem.swift +++ b/QuackFight/Sources/Systems/Render/RenderSystem.swift @@ -25,4 +25,38 @@ import SpriteKit import GameplayKit - +final class RenderSystem { + + static let shared = RenderSystem() + private init() {} + + func update(entities: [GKEntity], deltaTime: TimeInterval) { + for entity in entities { + guard let spriteComp = entity.component(ofType: SpriteComponent.self), + let transformComp = entity.component(ofType: TransformComponent.self) else { + continue + } + + // 1. Sync Position + spriteComp.node.position = transformComp.position + + // 2. Sync Facing (PlayerEntity specific) + if let player = entity as? PlayerEntity { + spriteComp.node.xScale = abs(spriteComp.node.xScale) * player.facing + } + + // 3. Sync Rotation + // If the entity has velocity (e.g. Bread), point it along its trajectory. + if let velocityComp = entity.component(ofType: VelocityComponent.self) { + let dx = velocityComp.vector.dx + let dy = velocityComp.vector.dy + if dx != 0 || dy != 0 { + spriteComp.node.zRotation = atan2(dy, dx) + } + } else { + // Default transform rotation + spriteComp.node.zRotation = transformComp.rotation + } + } + } +} From 33bfaa42245bad2c78871f34ac8f3a377bf8581a Mon Sep 17 00:00:00 2001 From: Vallykrie Date: Mon, 18 May 2026 17:42:32 +0700 Subject: [PATCH 2/3] fix: make trajectory arc dashed as per issue 91 --- .../Sources/Systems/Render/TrajectoryRenderSystem.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift b/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift index a454e5e..20d5905 100644 --- a/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift +++ b/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift @@ -53,6 +53,11 @@ final class TrajectoryRenderSystem { trajectoryNode.lineWidth = 4.0 trajectoryNode.alpha = 0.6 // Agak transparan agar tidak menutupi karakter trajectoryNode.zPosition = 10 // Pastikan garis berada di atas background + // Make it a dashed/dotted line + trajectoryNode.strokeTexture = SKTexture(imageNamed: "spark") // Optional + let pattern: [CGFloat] = [10.0, 10.0] + // Note: SKShapeNode doesn't support lineDashPattern natively unless using CGPath stroking, + // Actually it's easier to use CGPath(dashedWithPhase:lengths:) when drawing. } /// Jangan lupa panggil ini sekali saat GameScene baru mulai dimuat (didMove) @@ -104,6 +109,7 @@ final class TrajectoryRenderSystem { } } - trajectoryNode.path = path + let dashedPath = path.copy(dashedWithPhase: 0, lengths: [10.0, 10.0]) + trajectoryNode.path = dashedPath } } From b7f084229e55622090239865375e7906216373e9 Mon Sep 17 00:00:00 2001 From: Vallykrie Date: Mon, 18 May 2026 17:46:58 +0700 Subject: [PATCH 3/3] fix: correct method name to dashingWithPhase in TrajectoryRenderSystem --- .../Sources/Systems/Render/TrajectoryRenderSystem.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift b/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift index 20d5905..5636e64 100644 --- a/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift +++ b/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift @@ -57,7 +57,7 @@ final class TrajectoryRenderSystem { trajectoryNode.strokeTexture = SKTexture(imageNamed: "spark") // Optional let pattern: [CGFloat] = [10.0, 10.0] // Note: SKShapeNode doesn't support lineDashPattern natively unless using CGPath stroking, - // Actually it's easier to use CGPath(dashedWithPhase:lengths:) when drawing. + // Actually it's easier to use CGPath(dashingWithPhase:lengths:) when drawing. } /// Jangan lupa panggil ini sekali saat GameScene baru mulai dimuat (didMove) @@ -109,7 +109,7 @@ final class TrajectoryRenderSystem { } } - let dashedPath = path.copy(dashedWithPhase: 0, lengths: [10.0, 10.0]) + let dashedPath = path.copy(dashingWithPhase: 0, lengths: [10.0, 10.0]) trajectoryNode.path = dashedPath } }