我想在SwiftUI中写一个简单的游戏,用户可以拖动一个圆圈来躲避树木,它应该绘制一个轨迹,但是轨迹非常奇怪,是矩形而不是平滑的曲线.有人能帮我吗?

以下是我的代码:

import SwiftUI

struct ContentView: View {

@State private var track = [CGFloat]()

@State private var offset: CGFloat = 0
let timer = Timer.publish(every: 0.001, on: .current, in: .common).autoconnect()

var body: some View {
    GeometryReader { geo in
        ZStack {
            Color.black
            Path { path in
                
                let height = (geo.size.height / 2) / CGFloat(track.count)
                
                
                
                for i in 0..<track.count {
                    path.addLine(to: CGPoint(x: geo.size.width * track[i], y: height * CGFloat(i)))
                    
                    path.move(to: CGPoint(x: geo.size.width * track[i], y: height * CGFloat(i)))
                }
            } .stroke(lineWidth: 1)
                .foregroundStyle(.yellow)
                
            Circle()
                .frame(width: 25)
                .foregroundStyle(.red)
                .position(x: offset, y: geo.size.height / 2)
                .gesture (
                    DragGesture()
                        .onChanged { value in
                            offset = min(geo.size.width - 15, max(15 ,value.location.x))
                        }
                ) 
                .onAppear {
                    offset = geo.size.width / 2
                }
                .onReceive(timer, perform: { _ in
                    track.append((offset / geo.size.width))
                    if track.count >= 100 {
                        track.removeFirst()
                    }
                })
            
        }
    }
}
}

推荐答案

计时器发布得太快,比Gesture更新其值的速度还要快.

每隔0.001秒,你就会采集一次圆周偏移量的样本.这些样例中的许多可能是相同的值,这使得轨迹看起来是"矩形"的.

请注意,计时器的速度也代表了圆圈的速度,您只在屏幕的上半部分显示了geo.size.height / 2个样本.这意味着每0.1秒,圆圈就会"垂直移动"geo.size.height / 2秒.我不确定你想不想这么快就绕个圈.

我建议在手势更新的速度附近设置一个计时器间隔.在我的实验中,像1 / 30这样的值工作得很好.

另外,您的绘图代码有一点不正确.我会先计算所有的分数,然后用addLines.你应该以相反的顺序画tracks,从下到上.

Path { path in
    let height = (geo.size.height / 2) / 100
    let points = track.reversed().enumerated().map { (i, value) in
        CGPoint(x: geo.size.width * value, y: geo.size.height / 2 - height * CGFloat(i))
    }
    path.addLines(points)
}
// a round line join might look better than the default miter
.stroke(style: StrokeStyle(lineJoin: .round))
.foregroundStyle(.yellow)

使用您当前的控制,您不能自由控制圆的垂直速度.

一种更灵活的方法是记录time以及手势onChanged的偏移量.与其存储onChanged个这样的常量,不如存储过go X秒内的所有内容.

当画这条线时,你可以利用时间戳之间的差异来计算出这个圆在这段时间内垂直移动了多少(根据你 Select 的速度).这就给出了两点之间的y坐标差.

以下是一个粗略的草图:

struct Sample: Hashable {
    
    let timestamp: Date
    let offset: CGFloat
}

struct ContentView: View {
    
    @State private var track = [Sample]()
    
    let timer = Timer.publish(every: 1 / 30, on: .current, in: .common).autoconnect()
    
    let speed: CGFloat = 100 // points per second
    
    var body: some View {
        GeometryReader { geo in
            ZStack {
                Color.black
                Path { path in
                    var points = [CGPoint]()
                    var currentY = geo.size.height / 2
                    let reversed = track.sorted(using: KeyPathComparator(\.timestamp, order: .reverse))
                    for (sample, nextSample) in zip(reversed, reversed.dropFirst()) {
                        points.append(CGPoint(x: sample.offset, y: currentY))
                        let elapsedTime = sample.timestamp.timeIntervalSince(nextSample.timestamp)
                        currentY -= speed * elapsedTime
                    }
                    if let last = reversed.last {
                        points.append(CGPoint(x: last.offset, y: currentY))
                    }
                    path.addLines(points)
                }
                .stroke(style: StrokeStyle(lineJoin: .round))
                .foregroundStyle(.yellow)
                
                Circle()
                    .frame(width: 25)
                    .foregroundStyle(.red)
                    .position(x: track.last?.offset ?? geo.size.width / 2, y: geo.size.height / 2)
                    .gesture (
                        DragGesture()
                            .onChanged { value in
                                let offset = min(geo.size.width - 15, max(15 ,value.location.x))
                                track.append(Sample(timestamp: Date(), offset: offset))
                            }
                    )
                    .onAppear {
                        track = [Sample(timestamp: Date(), offset: geo.size.width / 2)]
                    }
                    .onReceive(timer, perform: { time in
                        if let lastSample = track.last,
                            time.timeIntervalSince(lastSample.timestamp) >= 1 / 30 {
                            track.append(Sample(timestamp: time, offset: lastSample.offset))
                        }
                        let timeThreshold = geo.size.height / 2 / speed + 0.1 // add 0.1 seconds just to be safe
                        track.removeAll(where: { time.timeIntervalSince($0.timestamp) > timeThreshold })
                    })
                
            }
        }
    }
}

Swift相关问答推荐

如何将CodingKeys作为数组而不是枚举传递到类外部的函数中?

如何在AudioKit中实现自定义音效 node ?

如何在macOS中延迟退出应用程序的退出操作?

是否可以使用一个 NSRegularExpression 同时判断字符串格式并提取子字符串?

如何使 onKeyPress 与 TextField 快速配合使用?

使用`JSONSerialiser`时,省略通用可选项

从 Obj-C 函数返回 swift 类的不兼容指针类型

(Swift)混淆了函数内 for-in 循环的返回值和循环后(函数内)的返回值)

如何将变量添加到不属于可解码部分的 struct ?

格式化大货币数字

如何在 Swift 中使用子定义覆盖父类扩展

供应和普通数组中具有空值的 map 函数的行为不一致?

响应 UITextField (UIViewRepresentable) 中的特定按键

Swift Components.Url 返回错误的 URL

使 Swift 并发中的任务串行运行

如何删除桥头而不出错?

如何在 SwiftUI 中实现 PageView?

Swift 协议只能设置?

Swift:创建 UIImage 数组

在 Swift 中为 UIPickerView 设置默认值?