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?
2. 初始化和销毁DisplayLink
在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
相关产品推荐
相关产品推荐

