Mad*_*mer 6 math ios sprite-kit swift
我一直在捣乱我的大脑几天试图想出一种方法,使用Swift和SpriteKit将玩家从当前位置移动到新位置.听起来相对容易.
现在,我知道我可以使用a CGPath和a SKAction来沿着路径移动玩家,但我需要知道的是如何创建玩家移动的路径.
我需要玩家移动预定的半径,因为它在移动时首先转向新点,让我演示......

因此,红色圆圈是玩家及其当前方向,大圆圈是转弯半径,红色十字架是玩家想要移动的可能点(显然你在任何时间点只有一个,但是想法是证明一个可能的点和另一个点之间的运动差异)
此外,玩家可以向左或向右移动,这取决于哪条路径最短到目标点.
我尝试了什么(对不起,列表有点简短)......
基本上,我知道玩家当前的位置/方向; 我知道转弯圈的半径,我知道我要移动的点.我需要计算玩家最初需要通过哪个弧来定位自己到新点(CGPathAddLineToPoint在弧的末端加上应该是微不足道的)
除了花费大量时间阅读文档,谷歌搜索,阅读博客文章和教程之外,我还尝试从开始角度到给定迭代级别(例如+/- 0.5度)循环一系列角度并计算当前点与圆上下一个点之间的角度,并将其与当前点与目标点的角度进行比较,并基本上选择具有最小差值/ delta的角度...

因此,两个红色圆圈代表圆圈上的两个点,蓝色线条代表它们之间的角度,绿色线条代表从第一个点到目标点的角度.
我们只是说,虽然这可能有用,但我对这个想法感到震惊,并希望有可能提出更好/更快的解决方案.
我不确定是否CGPathAddArcToPoint会有所帮助,因为它会从我的球员当前位置到目标点产生弧线,而不是让玩家通过转弯圈.
一旦玩家离开转弯圈,如果直线移动或不移动(即它们可以略微弯曲到目标点),我不会特别讨厌,但我现在专注于尝试计算所需的弧所需让玩家开始.
对不起,我的数学很差,所以请你好
代码"当前"看起来像(一个完整的混乱)
func pointTowards(point thePoint: CGPoint) {
// Need to calculate the direction of the turn
//let angle = atan2(thePoint.y - self.position.y, thePoint.x - self.position.x) - CGFloat(180.0.toRadians());
let angle = angleBetween(startPoint: self.position, endPoint: thePoint) - CGFloat(180.0.toRadians())
if (self.zRotation < 0) {
// self.zRotation
// self.zRotation = self.zRotation + M_PI * 2;
}
let rotateTo: SKAction = SKAction.rotateToAngle(angle, duration: 1, shortestUnitArc: true)
rotateTo.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(rotateTo)
let offset = CGPoint(x: rotorBlur.position.x, y: rotorBlur.position.y + (rotorBlur.size.width / 2))
let radius = rotorBlur.size.width / 2.0
var points: [AnglesAndPoints] = self.pointsOnCircleOf(
radius: radius,
offset: offset);
let centerPoint = CGPoint(x: offset.x + radius, y: offset.y + radius)
var minAngle = CGFloat.max
var minDelta = CGFloat.max
for var p: Int = 1; p < points.count; p++ {
let p1 = points[p - 1].point
let p2 = points[p].point
let point = angleBetween(startPoint: p1, endPoint: p2) - CGFloat(180.0.toRadians())
let target = angleBetween(startPoint: p1, endPoint: thePoint) - CGFloat(180.0.toRadians())
let delta = target - point
if delta < minDelta {
minDelta = delta
minAngle = points[p - 1].angle
}
}
println("projected: \(minAngle); delta = \(minDelta)")
if let pathNode = pathNode {
pathNode.removeFromParent()
}
//points = self.pointsOnCircleOf(
// radius: rotorBlur.size.width / 2.0,
// offset: CGPoint(x: 0, y: rotorBlur.size.width / 2));
let path = CGPathCreateMutable()
CGPathAddArc(
path,
nil,
0,
rotorBlur.size.width / 2,
rotorBlur.size.width / 2,
CGFloat(-180.0.toRadians()),
minAngle,
true)
pathNode = SKShapeNode()
pathNode?.path = path
pathNode?.lineWidth = 1.0
pathNode?.strokeColor = .lightGrayColor()
addChild(pathNode!)
}
func pointsOnCircleOf(radius r : CGFloat, offset os: CGPoint) -> [AnglesAndPoints] {
var points: [AnglesAndPoints] = []
let numPoints = 360.0 * 2.0
let delta = 360.0 / numPoints
for var degrees: Double = 0; degrees < numPoints; degrees += delta {
var point: CGPoint = pointOnCircle(angle: CGFloat(degrees.toRadians()), radius: r)
point = CGPoint(x: point.x + os.x, y: point.y + os.y)
points.append(AnglesAndPoints(angle: CGFloat(degrees.toRadians()), point: point))
}
return points
}
func pointOnCircle(angle radians:CGFloat, radius theRadius:CGFloat) -> CGPoint {
return CGPointMake((cos(radians) * theRadius),
(sin(radians) * theRadius));
}
func angleBetween(startPoint p1: CGPoint, endPoint p2: CGPoint) -> CGFloat {
return atan2(p2.y - p1.y, p2.x - p1.x) //- CGFloat(180.0.toRadians());
}
Run Code Online (Sandbox Code Playgroud)
基本上,我开始计算给定半径的圆上的点与给定的偏移,这是非常可怕的,如果我现在有时间,将重新工作,以便动态创建点(或我可以缓存价值一些如何并简单地翻译它们,但正如我所说,这是一个非常可怕的想法我真的想找到一种不同的方式并放弃这种方法
我很确定当前的代码没有考虑到玩家的当前方向,它应该提供一个开始角度和方向(计数器/顺时针方向)来迭代,但我已经到了我想要的地步在尝试解决任何更多问题之前,看看他们是否只是一个更好的解决方案
有趣的是,实际上我的游戏中的运动几乎与您描述的完全相同,只是在右侧时总是顺时针方向而在左侧时逆时针方向,它会选择更近的路径。
因此,我抓取了一些代码并对其进行了粗略的修改以符合您的描述。当目标点在玩家左侧时它会向左移动,否则它会向右移动。您还可以设置节点的速度,以及“轨道”的半径和位置。
然而,我的实现不使用 SKActions 和路径来移动。一切都是实时动态完成的,这允许与移动物体的碰撞和更好的运动控制。但是,如果您绝对需要将路径与 SKActions 一起使用,请告诉我,我会尝试提出解决方案。本质上它归结为找到切点的弧(代码已经在一定程度上做到了)。
实现的工作方式是首先确定最终目标点,以及使用辅助圆找到切点的最佳切点的角距离。然后使用向心运动,节点沿着路径移动到切点,然后切换到直线运动完成移动到最终目的地。
下面是 GameScene 的代码:
import SpriteKit
enum MotionState { case None, Linear, Centripetal }
class GameScene: SKScene {
var node: SKShapeNode!
var circle: SKShapeNode!
var angularDistance: CGFloat = 0
var maxAngularDistance: CGFloat = 0
let dt: CGFloat = 1.0/60.0 //Delta Time
var centripetalPoint = CGPoint() //Point to orbit.
let centripetalRadius: CGFloat = 60 //Radius of orbit.
var motionState: MotionState = .None
var invert: CGFloat = 1
var travelPoint: CGPoint = CGPoint() //The point to travel to.
let travelSpeed:CGFloat = 200 //The speed at which to travel.
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
circle = SKShapeNode(circleOfRadius: centripetalRadius)
circle.strokeColor = SKColor.redColor()
circle.hidden = true
self.addChild(circle)
}
func moveToPoint(point: CGPoint) {
travelPoint = point
motionState = .Centripetal
//Assume clockwise when point is to the right. Else counter-clockwise
if point.x > node.position.x {
invert = -1
//Assume orbit point is always one x radius right from node's position.
centripetalPoint = CGPoint(x: node.position.x + centripetalRadius, y: node.position.y)
angularDistance = CGFloat(M_PI)
} else {
invert = 1
//Assume orbit point is always one x radius left from node's position.
centripetalPoint = CGPoint(x: node.position.x - centripetalRadius, y: node.position.y)
angularDistance = 0
}
}
final func calculateCentripetalVelocity() {
let normal = CGVector(dx:centripetalPoint.x + CGFloat(cos(self.angularDistance))*centripetalRadius,dy:centripetalPoint.y + CGFloat(sin(self.angularDistance))*centripetalRadius);
let period = (CGFloat(M_PI)*2.0)*centripetalRadius/(travelSpeed*invert)
self.angularDistance += (CGFloat(M_PI)*2.0)/period*dt;
if (self.angularDistance>CGFloat(M_PI)*2)
{
self.angularDistance = 0
}
if (self.angularDistance < 0) {
self.angularDistance = CGFloat(M_PI)*2
}
node.physicsBody!.velocity = CGVector(dx:(normal.dx-node.position.x)/dt ,dy:(normal.dy-node.position.y)/dt)
//Here we check if we are at the tangent angle. Assume 4 degree threshold for error.
if abs(maxAngularDistance-angularDistance) < CGFloat(4*M_PI/180) {
motionState = .Linear
}
}
final func calculateLinearVelocity() {
let disp = CGVector(dx: travelPoint.x-node.position.x, dy: travelPoint.y-node.position.y)
let angle = atan2(disp.dy, disp.dx)
node.physicsBody!.velocity = CGVector(dx: cos(angle)*travelSpeed, dy: sin(angle)*travelSpeed)
//Here we check if we are at the travel point. Assume 15 point threshold for error.
if sqrt(disp.dx*disp.dx+disp.dy*disp.dy) < 15 {
//We made it to the final position! Code that happens after reaching the point should go here.
motionState = .None
println("Node finished moving to point!")
}
}
override func update(currentTime: NSTimeInterval) {
if motionState == .Centripetal {
calculateCentripetalVelocity()
} else if motionState == .Linear {
calculateLinearVelocity()
}
}
func calculateMaxAngularDistanceOfBestTangent() {
let disp = CGVector(dx: centripetalPoint.x - travelPoint.x, dy: centripetalPoint.y - travelPoint.y)
let specialCirclePos = CGPoint(x: (travelPoint.x+centripetalPoint.x)/2.0, y: (travelPoint.y+centripetalPoint.y)/2.0)
let specialCircleRadius = sqrt(disp.dx*disp.dx+disp.dy*disp.dy)/2.0
let tangentPair = getPairPointsFromCircleOnCircle(centripetalPoint, radiusA: centripetalRadius, pointB: specialCirclePos, radiusB: specialCircleRadius)
let tangentAngle1 = (atan2(tangentPair.0.y - centripetalPoint.y,tangentPair.0.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
let tangentAngle2 = (atan2(tangentPair.1.y - centripetalPoint.y,tangentPair.1.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
if invert == -1 {
maxAngularDistance = tangentAngle2
} else {
maxAngularDistance = tangentAngle1
}
}
//Not mine, modified algorithm from /sf/ask/234438781/
func getPairPointsFromCircleOnCircle(pointA: CGPoint, radiusA: CGFloat, pointB: CGPoint, radiusB: CGFloat) -> (CGPoint,CGPoint) {
let dX = (pointA.x - pointB.x)*(pointA.x - pointB.x)
let dY = (pointA.y - pointB.y)*(pointA.y - pointB.y)
let d = sqrt(dX+dY)
let a = (radiusA*radiusA - radiusB*radiusB + d*d)/(2.0*d);
let h = sqrt(radiusA*radiusA - a*a);
let pointCSub = CGPoint(x:pointB.x-pointA.x,y:pointB.y-pointA.y)
let pointCScale = CGPoint(x: pointCSub.x*(a/d), y: pointCSub.y*(a/d))
let pointC = CGPoint(x: pointCScale.x+pointA.x, y: pointCScale.y+pointA.y)
let x3 = pointC.x + h*(pointB.y - pointA.y)/d;
let y3 = pointC.y - h*(pointB.x - pointA.x)/d;
let x4 = pointC.x - h*(pointB.y - pointA.y)/d;
let y4 = pointC.y + h*(pointB.x - pointA.x)/d;
return (CGPoint(x:x3, y:y3), CGPoint(x:x4, y:y4));
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touchPos = (touches.first! as! UITouch).locationInNode(self)
node = SKShapeNode(circleOfRadius: 10)
node.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
node.physicsBody = SKPhysicsBody(circleOfRadius: 10)
self.addChild(node)
moveToPoint(touchPos)
calculateMaxAngularDistanceOfBestTangent() //Expensive!
circle.hidden = false
circle.position = centripetalPoint
}
}
Run Code Online (Sandbox Code Playgroud)

请注意,您看到的圆圈是我添加到场景中的另一个节点,以使运动更加明显;您可以轻松地将其删除。调试时,您可能还会发现在切点添加节点很有用。calculateMaxAngularDistanceOfBestTangent 函数中的 tangentPair 元组包含两个切点。
另外请注意,找到切点/角度很昂贵,但只有在每次需要移动到新点时才会发生。但是,如果您的游戏需要不断移动到一个新点,则在许多节点上重复使用此算法的成本可能会很高(尽管如此,请务必先进行分析)。检查何时从向心运动移动到直线运动的另一种方法是检查速度矢量是否接近终端位置,如下所示。这不太准确,但允许您完全删除 calculateMaxAngularDistanceOfBestTangent 函数。
let velAngle = atan2(node.physicsBody!.velocity.dy,node.physicsBody!.velocity.dx)
let disp = CGVector(dx: travelPoint.x-node.position.x, dy: travelPoint.y-node.position.y)
let dispAngle = atan2(disp.dy,disp.dx)
//Here we check if we are at the tangent angle. Assume 4 degree threshold for error.
if velAngle != 0 && abs(velAngle - dispAngle) < CGFloat(4*M_PI/180) {
motionState = .Linear
}
Run Code Online (Sandbox Code Playgroud)
最后让我知道您是否需要在 SKActions 中使用路径,不管我想我会更新最后一部分,展示这是如何完成的(除非有人打败了我!正如我之前提到的,我发布的代码在一定程度上做到了这一点。 )我现在没有时间,但希望我很快就有机会!我希望这个答案中提到的东西对你有帮助。祝你游戏好运。
下面的代码显示了获得相同的效果,除了这次使用 SKActions 将 CGPath 动画化到切线角度然后到最终目标点。由于不再需要手动计算向心和线性运动,因此它要简单得多,但是因为它是一个动画,您将失去上述解决方案提供的动态实时运动控制。
class GameScene: SKScene {
var centripetalPoint = CGPoint() //Point to orbit.
let centripetalRadius: CGFloat = 60 //Radius of orbit.
var travelPoint: CGPoint = CGPoint() //The point to travel to.
var travelDuration: NSTimeInterval = 1.0 //The duration of action.
var node: SKShapeNode!
var circle: SKShapeNode!
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
circle = SKShapeNode(circleOfRadius: centripetalRadius)
circle.strokeColor = SKColor.redColor()
circle.hidden = true
self.addChild(circle)
}
//Not mine, modified algorithm from /sf/ask/234438781/
func getPairPointsFromCircleOnCircle(pointA: CGPoint, radiusA: CGFloat, pointB: CGPoint, radiusB: CGFloat) -> (CGPoint,CGPoint) {
let dX = (pointA.x - pointB.x)*(pointA.x - pointB.x)
let dY = (pointA.y - pointB.y)*(pointA.y - pointB.y)
let d = sqrt(dX+dY)
let a = (radiusA*radiusA - radiusB*radiusB + d*d)/(2.0*d);
let h = sqrt(radiusA*radiusA - a*a);
let pointCSub = CGPoint(x:pointB.x-pointA.x,y:pointB.y-pointA.y)
let pointCScale = CGPoint(x: pointCSub.x*(a/d), y: pointCSub.y*(a/d))
let pointC = CGPoint(x: pointCScale.x+pointA.x, y: pointCScale.y+pointA.y)
let x3 = pointC.x + h*(pointB.y - pointA.y)/d;
let y3 = pointC.y - h*(pointB.x - pointA.x)/d;
let x4 = pointC.x - h*(pointB.y - pointA.y)/d;
let y4 = pointC.y + h*(pointB.x - pointA.x)/d;
return (CGPoint(x:x3, y:y3), CGPoint(x:x4, y:y4));
}
func moveToPoint(point: CGPoint) {
travelPoint = point
//Assume clockwise when point is to the right. Else counter-clockwise
if point.x > node.position.x {
centripetalPoint = CGPoint(x: node.position.x + centripetalRadius, y: node.position.y)
} else {
centripetalPoint = CGPoint(x: node.position.x - centripetalRadius, y: node.position.y)
}
let disp = CGVector(dx: centripetalPoint.x - travelPoint.x, dy: centripetalPoint.y - travelPoint.y)
let specialCirclePos = CGPoint(x: (travelPoint.x+centripetalPoint.x)/2.0, y: (travelPoint.y+centripetalPoint.y)/2.0)
let specialCircleRadius = sqrt(disp.dx*disp.dx+disp.dy*disp.dy)/2.0
let tangentPair = getPairPointsFromCircleOnCircle(centripetalPoint, radiusA: centripetalRadius, pointB: specialCirclePos, radiusB: specialCircleRadius)
let tangentAngle1 = (atan2(tangentPair.0.y - centripetalPoint.y,tangentPair.0.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
let tangentAngle2 = (atan2(tangentPair.1.y - centripetalPoint.y,tangentPair.1.x - centripetalPoint.x)+CGFloat(2*M_PI))%CGFloat(2*M_PI)
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, node.position.x, node.position.y)
if travelPoint.x > node.position.x {
CGPathAddArc(path, nil, node.position.x+centripetalRadius, node.position.y, centripetalRadius, CGFloat(M_PI), tangentAngle2, true)
} else {
CGPathAddArc(path, nil, node.position.x-centripetalRadius, node.position.y, centripetalRadius, 0, tangentAngle1, false)
}
CGPathAddLineToPoint(path, nil, travelPoint.x, travelPoint.y)
let action = SKAction.followPath(path, asOffset: false, orientToPath: false, duration: travelDuration)
node.runAction(action)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touchPos = (touches.first! as! UITouch).locationInNode(self)
node = SKShapeNode(circleOfRadius: 10)
node.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(node)
moveToPoint(touchPos)
circle.hidden = false
circle.position = centripetalPoint
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1188 次 |
| 最近记录: |