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 + } + } + } +} diff --git a/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift b/QuackFight/Sources/Systems/Render/TrajectoryRenderSystem.swift index a454e5e..5636e64 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(dashingWithPhase: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(dashingWithPhase: 0, lengths: [10.0, 10.0]) + trajectoryNode.path = dashedPath } }