Osheep

时光不回头,当下最重要。

SpriteKit学习笔记22-做个台球游戏吧

《SpriteKit学习笔记22-做个台球游戏吧》

台球效果示意.gif

主要利用的就是物理模拟系统,调整球体的重量、摩擦力、线性阻力和弹性系数来让表现尽量拟合台球的实际情况,另外一个重点就是操作方式的设定。

如果你看过我关于物理世界的前序文章那么本文应该很容易理解,下面开始coding。

    override func didMove(to view: SKView) {
        //建立物理世界
        self.backgroundColor = SKColor.white
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: CGRect(x: 15, y: 15, width: self.view!.frame.size.width - 30, height: self.view!.frame.size.height - 30))
        let backnode = SKSpriteNode.init(color: SKColor.init(colorLiteralRed: 153.0/255.0, green: 204.0/255.0, blue: 51.0/255.0, alpha: 1.0), size: CGSize(width: self.view!.frame.size.width - 30, height: self.view!.frame.size.height - 30))
        backnode.anchorPoint = CGPoint.zero
        backnode.position = CGPoint(x: 15, y: 15)
        addChild(backnode)
        self.physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
        self.physicsWorld.contactDelegate = self
 
    }

这里把重力设定为零,然后做出一个背景为绿色,距离各边都是15的举行作为物理世界主体(球桌),并将其摆好。

第二步,放台球。我使用了阿里妈妈图标库里的球素材变换颜色做了这十个球,你有更合适的素材可以自行更换。主要设定为这样:

    func buildBall(textureName:String, position:CGPoint){
        let ball1 = SKSpriteNode(texture: SKTexture(imageNamed: textureName))
        ball1.position = position
        ball1.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        ball1.physicsBody = SKPhysicsBody(circleOfRadius: 16, center: CGPoint(x: 0.5, y: 0.5))
        ball1.physicsBody?.isDynamic = true
        ball1.physicsBody?.restitution = 0.85
        ball1.physicsBody?.linearDamping = 0.90
        ball1.physicsBody?.friction = 0.20
        ball1.physicsBody?.angularDamping = 0.15
        ball1.physicsBody?.mass = 7.5
        ball1.physicsBody?.contactTestBitMask = 3
        ball1.physicsBody?.categoryBitMask = 1
        ball1.name = textureName
        self.BallsArr.add(ball1)
        let info = BallInfo.setBallInfo(Position: ball1.position, Name: textureName)
        self.LastPositionArr.add(info)
        if textureName == "球球 (0).png" {
            self.ball = ball1  //白球
        }
        addChild(ball1)
    }

BallInfo是一个自建的Model,包含两个信息:对象的名字和对象的位置属性。BallsArr是一个NSMutableArray,用来保存所有的球类SKSpriteNode对象。原本是想直接保存CGPoint类型的位置属性的(Objective-C不能在array保存非对象成员但是swift可以),可是实现过程中感觉查序列很麻烦,干脆连名字一起封成一个数组反而省心一点。

然后是设置球袋,这里我选在SKShapeNode来用贝塞尔曲线勾画,也可以用贴图,相应的球袋的物理体就根据贝塞尔曲线或者纹理来设置,代码如下:

        let path6 = UIBezierPath.init()
        path6.move(to: CGPoint(x: self.view!.frame.size.width - 15, y: self.view!.frame.size.height / 2 - 15))
        path6.addLine(to: CGPoint(x: self.view!.frame.size.width - 15, y: self.view!.frame.size.height / 2 + 15))
        path6.addArc(withCenter: CGPoint(x: self.view!.frame.size.width - 15, y: self.view!.frame.size.height / 2), radius: 15, startAngle:1.5, endAngle: -1.5, clockwise: true)
        
        
        let pocket6 = SKShapeNode(path: path6.cgPath)
        pocket6.physicsBody = SKPhysicsBody(polygonFrom: path6.cgPath)
        pocket6.fillColor = SKColor.black
        pocket6.strokeColor = SKColor.black
        pocket6.physicsBody?.isDynamic = false
        pocket6.physicsBody?.categoryBitMask = 3
        pocket6.physicsBody?.contactTestBitMask = 1
        addChild(pocket6)

只放出一个,因为基本大同小异。
注意这里将球的categoryBitMask设置为1,球袋的设置为3,碰撞比特码请看前序教程。

针对碰撞代理,通过bitMask来判断碰撞体类型进而做出判断,代码如下:

    func didBegin(_ contact: SKPhysicsContact) {
        print("发生了碰撞")
        let bodyA = contact.bodyA
        let bodyB = contact.bodyB
        if ((bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 3) || (bodyA.categoryBitMask == 3 && bodyB.categoryBitMask == 1)) {
            print("碰撞了球袋")
            var disappearBall:SKPhysicsBody? = nil
            if bodyA.categoryBitMask == 1 {
                bodyA.node?.removeFromParent()
                disappearBall = bodyA
            }else{
                bodyB.node?.removeFromParent()
                disappearBall = bodyB
            }
            if disappearBall?.node?.name == "球球 (0).png" {
                disappearBall?.node?.position = CGPoint(x: kwidth / 2, y: 100)
                addChild(disappearBall!.node!)
            }else{
                //可以考虑剔除已经进了的球的信息,to do list
            }
        }
    }

最后是操控部分:

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if self.GameState == 0 {  //角度
            let touch = touches.first
            let ballPosition = self.ball?.position
            let touchPosition = touch!.location(in: (self.ball?.parent)!)
            let touchpoint = CGPoint(x: (ballPosition?.x)! - touchPosition.x, y:(ballPosition?.y)! - touchPosition.y)
            let ballPoint = self.ball!.position
            let arc = -touchpoint.x / touchpoint.y
            self.cue?.zRotation = atan(arc)
            let a4 = sqrt(1 / (touchpoint.x * touchpoint.x + touchpoint.y * touchpoint.y))
            let PointBaseBall = CGPoint(x: 150 * touchpoint.x * a4, y: 150 * touchpoint.y * a4)
            let truePoint = CGPoint(x: PointBaseBall.x + ballPoint.x, y: PointBaseBall.y + ballPoint.y)
            self.cue?.position = truePoint
            self.AnglePoint = CGPoint(x: 115 * touchpoint.x * a4, y: 115 * touchpoint.y * a4)
        } else if self.GameState == 1 {   //力度
            let touch = touches.first
            let touchpoint = touch!.location(in: self.ball!)
            self.HitPower = sqrt(touchpoint.x * touchpoint.x + touchpoint.y * touchpoint.y) //击球力度
            let ballPoint = self.ball!.position
            let cuePosition = CGPoint(x: ballPoint.x + self.AnglePoint.x / 115 * (HitPower + 100), y: ballPoint.y + self.AnglePoint.y / 115 * (HitPower + 100))
            self.cue?.position = cuePosition
        }
        
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if self.GameState == 0 {
            self.GameState = 1
        }else if self.GameState == 1 {
            self.GameState = 2
            let cueAction = SKAction.move(to: CGPoint(x: (self.ball?.position.x)! + self.AnglePoint.x, y: (self.ball?.position.y)! + self.AnglePoint.y), duration: 0.15)
            self.cue?.run(cueAction, completion: {
                self.cue?.isHidden = true
                self.ball?.physicsBody?.applyImpulse(CGVector(dx: -self.AnglePoint.x * self.HitPower, dy: -self.AnglePoint.y * self.HitPower))
            })
        }
    }

基本就是这样。如果你对物理系统理解比较透彻那么本文对你应该没什么难度,如果很多不明白的地方请关注本系列教程的前序文章,本文的代码可以点击我的github查看,本文到此结束。

点赞