美文网首页Swiftios编程技术收集项目代码
一个小清新 Swift 游戏的开发全过程(Part 1)

一个小清新 Swift 游戏的开发全过程(Part 1)

作者: vulgur | 来源:发表于2016-01-23 23:33 被阅读3449次

    转自我自己的 blog

    Last Circle

    # 写在最前面

    Last Circle 是我上架的第一个独立开发的 App(App Store 地址),针对这个 App 的开发全过程,我准备写三到四篇 blog 来介绍详细的实现,1元的定价就当是这一系列 blog 的辛苦费吧。

    这个游戏并不是我原创的,而是根据多年前偶然看到的一个 Flash 小游戏改造的,源地址已不可考……。自打我学习移动开发以后,一直都拿这个小游戏练手,一开始是用 Android 实现了一个简单的 Demo,后来学习 iOS 开发又用 Objective-C 实现了一次,再后来为了学习 Swift 又用 Swift 重写了一遍,并增加了许多交互,在多个朋友的帮助下,最终提交到 App Store,圆了自己一个上架梦。

    # 游戏理念(玩法)

    Last Circle 的定位是一个极简的小游戏,也有点虐心+虐眼……。游戏的玩法非常简单,但是为了保持极简的风格,App 中我没有加入游戏说明和新手引导(我其实是写在了 App Store 的介绍里,但是估计是没有人仔细看了),导致了很多人一开始都不会玩。Last Circle 是一个测试并锻炼短时记忆力的小游戏,每一回合都会新产生一个圆(位置上),正确点击就会进入下一回合,所以需要的就是在所有的圆中找出最新的一个。原版游戏是黑底白圆,为了增加难度我把所有的圆变成了随机的彩色,并且在显示顺序上也是随机的。说起来可能很简单,但是玩起来还是比较困难的,黑白模式下我可以玩到27回合,但是彩色模式下我自己最多玩到21回合。

    Last Circle 运行示例

    #基础建设

    九层之台,起于累土。这个游戏做得最多的就是画圆,所以要先把这部分搞定。用 Objective-C 写的时候,我是用 UIView- (void)drawRect:(CGRect)rect 来画圆的,后来考虑到针对每个圆要有动画的交互,所以重写的时候就改为用一个 UIView 来表示一个圆。

    本篇主要就是讲如何设计一个圆的 model,并根据 model 来布局 UI,其他的交互放在下一篇。

    这里涉及的类有三个:

    1. 圆的 model 就是 Circle
    2. 圆对应的 view 就是 CircleView
    3. 还有一个单例的工厂类 CircleFactory
    4. 最后是负责展示并处理操作的 CircleViewController

    #Circle

    Circle 类的作用是代表一个圆的实体信息,包括三个属性:颜色,圆心坐标和半径。还有一个 class function 是用来生成一个随机的圆,首先计算一个随机的半径(在一定的范围内,不同的屏幕尺寸下这个范围是不一样的),然后在合理的屏幕区域内随机一个点作为圆心。代码如下:

    class Circle {
    
        // MARK: Properties
        static var minRadius: Int {
            switch ScreenUtils.screenWidthModel() {
            case .Width320, .Width375, .Width414, .Other:
                return 20
            case .Width768:
                return 40
            case .Width1024:
                return 60
            }
        }
        static var maxRadius: Int {
            switch ScreenUtils.screenWidthModel() {
            case .Width320, .Width375, .Width414, .Other:
                return 50
            case .Width768:
                return 100
            case .Width1024:
                return 150
            }
        }
        var color: UIColor
        let radius: Int
        let center: CGPoint
    
        init(color:UIColor, radius:Int, center:CGPoint){
            self.color = color
            self.radius = radius
            self.center = center
        }
    
        class func randomCircle() -> Circle {
            let screenRect = UIScreen.mainScreen().bounds
            let screenWidth:CGFloat = screenRect.width
            let screenHeight:CGFloat = screenRect.height
            let randomRadius = minRadius + Int(arc4random_uniform(UInt32(maxRadius - minRadius + 1)))
    
            let areaWidth = Int(screenWidth) - (randomRadius << 1);
            let areaHeight = Int(screenHeight) - (randomRadius << 1) - 20;
    
            let x = randomRadius + Int(arc4random_uniform(100000)) % areaWidth
            let y = 20 + randomRadius + Int(arc4random_uniform(100000)) % areaHeight // below the status bar
            let randomPoint = CGPoint(x: x, y: y)
    
            let randomColor = ColorUtils.randomColor()
            let circle = Circle(color: randomColor, radius: randomRadius, center: randomPoint)
            return circle
        }
    
    }
    

    #CircleView

    CircleView 类是 Circle 对象在 UI 上的体现,实现上只是增加了一个自定义的初始化方法,参数是一个 Circle 对象,根据其信息设置 view 的大小、位置和颜色,并且通过 layer 将 view 设置成圆形。代码如下:

    class CircleView: UIView {
    
        // MARK: Init
        override init(frame: CGRect) {
            super.init(frame: frame)
        }
    
        init(circle: Circle) {
            let frame = CGRectMake(0, 0, CGFloat(circle.radius*2), CGFloat(circle.radius*2))
            super.init(frame: frame)
            self.backgroundColor = circle.color
            self.center = circle.center
            self.layer.cornerRadius = CGFloat(circle.radius)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    #CircleFactory

    CircleFactory 类是一个单例工厂类,维持一个 Circle 的数组,在每次游戏重新开始的时候会清空这个数组,并且通过 addCircle 向这个数组中添加新的 Circle 对象。添加新圆的逻辑很简单,就是一个无限循环,直到算出来一个合格的圆为止。判断圆是否可用的方法是 isCircleAvailable,就是判断两个圆是否相交。但是屏幕区域毕竟是有限的,不可能会一直找到一个可用的圆,经过我的测试,圆的个数大于40的时候每次计算的耗时就会显著增加,界面也会因为计算而卡住,好在这个游戏我还没发现有那个超级大脑可以通过30回合(我朋友靠作弊才到26),所以游戏目前来说还不会遇到上述的极端情况。有个常量 MaxCircleCount 我设置成了40,期初的想法是游戏的终结就是第40回合,不过目前还没有用到。代码如下:

    class CircleFactory: NSObject{
    
        // MARK: Properties
        static let MaxCircleCount = 40
        static let sharedCircleFactory = CircleFactory()
        let RadiusGap:Float = 10
    
        var circles = [Circle]()
    
        private override init() {
            self.circles.removeAll()
        }
    
        func addCircle() {
            while true {
                let aCircle = Circle.randomCircle()
                if isCircleAvailable(aCircle) {
                    self.circles.append(aCircle)
                    break
                }
                continue
            }
        }
    
        func isCircleAvailable(aCircle: Circle) -> Bool {
            for circle in self.circles {
                let distance = hypotf(
                    Float(aCircle.center.x - circle.center.x),
                    Float(aCircle.center.y - circle.center.y))
                let radiusLength = Float(aCircle.radius + circle.radius)
                if distance <= radiusLength + RadiusGap {
                    return false
                }
            }
            return true
        }
    }
    

    #CircleViewController

    CircleViewController 在加载的时候遍历 CircleFactoryCircle 数组,生成相对应的 CircleView,并加入到 view hierarchy 中。同时,CircleViewController 有一个保存 CircleView 的数组 circleViews,这个数组的用处主要是给所有的 circle view 添加交互的动画。CircleViewController 还有一个 lastCircleView 用来保存最后一个 circle view。这里的代码有一个 Swift 相较于 Objective-C 的新操作符 ===,它比较的就是两个对象的引用是否相等,其相反的操作符是 !==

    override func viewDidLoad() {
           super.viewDidLoad()
    
           ...
    
           for circle in CircleFactory.sharedCircleFactory.circles {
               let color = ColorUtils.randomColor()
               circle.color = color
               let cv = CircleView(circle: circle)
               if circle === CircleFactory.sharedCircleFactory.circles.last! {
                   self.lastCircleView = cv
               }
               self.view.addSubview(cv)
               circleViews.append(cv)
    
               ...
           }
     }
    

    判断是否正确点击了最新的圆,就是在 func touchesEnded(_ touches: Set<UITouch>, withEvent event: UIEvent?) 判断点击是否在 lastCircleView 中,同时我也做了一定的容错,毕竟手指不是鼠标。这里为了防止多次点击,用了一个 bool 变量 touched 来判断是否已经点击过。

    // MARK: Touch
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    
            ...
    
            if touched {
                let touch = touches.first!
                let point = touch.locationInView(self.view)
                let sizeSide = CGFloat(CircleFactory.sharedCircleFactory.circles.last!.radius) + tolerance
                var roundedRect = lastCircleView.frame
                roundedRect.origin.x -= tolerance
                roundedRect.origin.y -= tolerance
                roundedRect.size.width += 2*tolerance
                roundedRect.size.height += 2*tolerance
    
                let maskPath = UIBezierPath(roundedRect: roundedRect,
                    byRoundingCorners: UIRectCorner.AllCorners,
                    cornerRadii: CGSizeMake(sizeSide, sizeSide))
                if maskPath.containsPoint(point) {
                    // go to next round
                } else {
                    // go to game over
                }
                touched = false
            }
    }
    

    #后记

    这篇 blog 主要是介绍了整个游戏的核心模型,代码贴的比文字还多,可能比较枯燥。下一篇就不会这样了,我准备在 Part 2 着力介绍这个游戏的各种动画效果,肯定是图文并茂,敬请期待!

    相关文章

      网友评论

      • 雪之音:开源吗,工程可以参考下么:blush:
      • Jon1993:挺有创意的直接用ui就可以做,不错

      本文标题:一个小清新 Swift 游戏的开发全过程(Part 1)

      本文链接:https://www.haomeiwen.com/subject/hourkttx.html