You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift实现无SpriteKit的Pong游戏:如何计算小球运动角度

问题:Swift开发无SpriteKit的Pong游戏,小球弹跳逻辑实现

我正在尝试使用Swift开发Pong游戏,但不使用SpriteKit。目前已成功在视图中绘制了一个可拖拽的paddle,当前代码如下:

import UIKit
class PongView: UIView {
    lazy var paddle: CGRect = {
        return CGRect(x: 200, y: 200, width: self.frame.width / 6, height: self.frame.height / 60)
    }()
    var movePaddle = false
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragPaddle(recognizer:)))
        addGestureRecognizer(gesture)
    }
    
    override func draw(_ rect: CGRect) {
        drawPaddle(rect)
    }
    
    func drawPaddle(_ rect: CGRect) {
        let path = UIBezierPath(rect: paddle)
        UIColor.black.set()
        path.fill()
    }
    
    func paddleInBounds() -> Bool {
        //padding vars are calculated in another file
        return (paddle.minX >= frame.minX + leftPadding) && (paddle.maxX <= frame.maxX - rightPadding) && (paddle.minY >= frame.minY + topPadding) && (paddle.maxY <= frame.maxY - bottomPadding)
    }
    
    func setPaddleInBounds() {
        if (paddle.minX < frame.minX + leftPadding) {
            paddle.origin.x = frame.minX + leftPadding
        }
        if (paddle.maxX > frame.maxX - rightPadding) {
            paddle.origin.x = frame.maxX - rightPadding - paddle.width
        }
        if (paddle.minY < frame.minY + topPadding) {
            paddle.origin.y = frame.minY + topPadding
        }
        if (paddle.maxY > frame.maxY - bottomPadding) {
            paddle.origin.y = frame.maxY - bottomPadding - paddle.height
        }
    }
    
    @objc func dragPaddle(recognizer: UIPanGestureRecognizer) {
        print(paddle.origin)
        print(paddle.minX)
        print(paddle.minY)
        if paddleInBounds() {
            let translation = recognizer.translation(in: self)
            paddle.origin = CGPoint(x: paddle.minX + translation.x, y: paddle.minY + translation.y)
            recognizer.setTranslation(CGPoint.zero, in: self)
            setNeedsDisplay()
        } else {
            setPaddleInBounds()
        }
    }
}

现在我想要创建小球,但不知从何入手。碰撞检测的思路大致如下:

if (ball coordinates are within boundaries) || (ball coordinates touch paddle coordinates) {
    //collision detected
}

但不使用SpriteKit的情况下,我不清楚如何计算小球的弹跳角度并相应地移动小球,这是游戏的核心部分。请问有什么建议?


回答:一步步实现小球与弹跳逻辑

不用SpriteKit完全没问题,我来给你拆解核心的实现步骤,都是基础的UIKit和几何计算:

1. 先给小球添加基础属性

首先在PongView里加小球的相关变量:

  • 小球的矩形框ball
  • 小球的速度向量ballVelocity(dx是水平速度,dy是垂直速度,正负代表方向)
  • 用CADisplayLink来驱动每帧的动画更新(替代只靠手势触发重绘,因为小球是持续移动的)
// 小球属性
lazy var ball: CGRect = {
    let ballSize = min(frame.width, frame.height) / 30
    return CGRect(x: frame.midX - ballSize/2, y: frame.midY - ballSize/2, width: ballSize, height: ballSize)
}()
var ballVelocity = CGVector(dx: 5, dy: -5) // 初始速度,可根据屏幕大小调整
private var displayLink: CADisplayLink?

在init里启动DisplayLink,deinit里销毁,避免内存泄漏:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragPaddle(recognizer:)))
    addGestureRecognizer(gesture)
    
    // 启动动画循环
    displayLink = CADisplayLink(target: self, selector: #selector(updateGameState))
    displayLink?.add(to: .main, forMode: .common)
}

deinit {
    displayLink?.invalidate()
}

3. 绘制小球

在draw(_:)里调用绘制小球的方法:

override func draw(_ rect: CGRect) {
    drawPaddle(rect)
    drawBall(rect)
}

func drawBall(_ rect: CGRect) {
    let path = UIBezierPath(ovalIn: ball)
    UIColor.red.set()
    path.fill()
}

4. 核心:每帧更新游戏状态

实现updateGameState方法,这是每帧都会执行的逻辑,包含:

  • 更新小球位置
  • 检测边界碰撞
  • 检测与Paddle的碰撞
  • 触发重绘
@objc private func updateGameState() {
    // 1. 更新小球位置
    ball = ball.offsetBy(dx: ballVelocity.dx, dy: ballVelocity.dy)
    
    // 2. 检测边界碰撞
    checkBoundaryCollision()
    
    // 3. 检测Paddle碰撞
    checkPaddleCollision()
    
    // 4. 重绘视图
    setNeedsDisplay()
}

5. 边界碰撞处理

碰到左右边界时水平速度取反,碰到上下边界时垂直速度取反,同时要把小球拉回边界内,避免卡住:

private func checkBoundaryCollision() {
    let ballRadius = ball.width / 2
    
    // 左边界
    if ball.minX <= frame.minX + leftPadding {
        ball.origin.x = frame.minX + leftPadding + ballRadius
        ballVelocity.dx = -ballVelocity.dx
    }
    
    // 右边界
    if ball.maxX >= frame.maxX - rightPadding {
        ball.origin.x = frame.maxX - rightPadding - ballRadius - ball.width
        ballVelocity.dx = -ballVelocity.dx
    }
    
    // 上边界
    if ball.minY <= frame.minY + topPadding {
        ball.origin.y = frame.minY + topPadding + ballRadius
        ballVelocity.dy = -ballVelocity.dy
    }
    
    // 下边界(这里可以加游戏结束逻辑,比如小球掉下去就重置)
    if ball.maxY >= frame.maxY - bottomPadding {
        // 示例:重置小球位置和速度
        resetBall()
    }
}

private func resetBall() {
    let ballSize = ball.width
    ball.origin = CGPoint(x: frame.midX - ballSize/2, y: frame.midY - ballSize/2)
    ballVelocity = CGVector(dx: Bool.random() ? 5 : -5, dy: -5) // 随机水平方向
}

6. Paddle碰撞的智能弹跳(重点)

不要只简单检测矩形重叠,还要根据碰撞位置调整弹跳角度,让游戏更真实:

  • 首先检测小球和paddle是否有交集
  • 计算小球碰撞点在paddle上的相对位置(比如从左到右0到1)
  • 根据这个相对位置调整水平速度,比如碰左边就向左弹,碰右边就向右弹,中间则保持垂直反弹
private func checkPaddleCollision() {
    guard ball.intersects(paddle) else { return }
    
    // 先把小球拉回paddle上方,避免重复碰撞
    ball.origin.y = paddle.minY - ball.height
    
    // 计算碰撞点在paddle上的相对X位置(0=左边缘,1=右边缘)
    let collisionX = (ball.midX - paddle.minX) / paddle.width
    // 把相对位置转换成水平方向的速度偏移(-1到1)
    let velocityOffset = (collisionX - 0.5) * 8 // 8是调整灵敏度的系数,可改
    
    // 更新速度:垂直速度取反,水平速度加上偏移
    ballVelocity.dy = -abs(ballVelocity.dy) // 保证向上弹
    ballVelocity.dx = 5 + velocityOffset // 基础水平速度加上偏移
}

7. 修复你的Paddle拖拽逻辑

原来的dragPaddle里的边界判断有点问题,应该先更新位置,再修正边界,而不是只有在边界内才移动:

@objc func dragPaddle(recognizer: UIPanGestureRecognizer) {
    let translation = recognizer.translation(in: self)
    // 先移动paddle
    paddle.origin.x += translation.x
    paddle.origin.y += translation.y
    recognizer.setTranslation(CGPoint.zero, in: self)
    
    // 然后修正到边界内
    setPaddleInBounds()
    
    setNeedsDisplay()
}

这样一套下来,小球就能正常移动、碰撞边界和paddle,还能根据碰撞paddle的位置调整弹跳方向,完全不用SpriteKit。你可以根据自己的需求调整速度、小球大小、灵敏度这些参数~


内容的提问来源于stack exchange,提问作者Xcoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:44:33