主要利用的就是物理模拟系统,调整球体的重量、摩擦力、线性阻力和弹性系数来让表现尽量拟合台球的实际情况,另外一个重点就是操作方式的设定。
如果你看过我关于物理世界的前序文章那么本文应该很容易理解,下面开始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查看,本文到此结束。
网友评论