SpriteKitGameSceneで弾丸が移動するパスを追跡したいと思います。「enumerateBodies(alongRayStart」を使用しているので、物理ボディとの最初の衝突を簡単に計算できます。
接触点と接触法線を考えると、反射角を計算する方法がわかりません。
5回の反射/バウンスを超えるパスを計算したいので、最初に次のことを行います。
私は何だと思う私は、通常の接点と接点との間の角度を取得していること、そしてそれに新しいポイントの反対を計算する必要があります...
var points: [CGPoint] = []
var start: CGPoint = renderComponent.node.position
var end: CGPoint = crossHairComponent.node.position
points.append(start)
var closestNormal: CGVector = .zero
for i in 0...5 {
closestNormal = .zero
var closestLength: CGFloat? = nil
var closestContact: CGPoint!
// Get the closest contact point.
self.physicsWorld.enumerateBodies(alongRayStart: start, end: end) { (physicsBody, contactPoint, contactNormal, stop) in
let len = start.distance(point: contactPoint)
if closestContact == nil {
closestNormal = contactNormal
closestLength = len
closestContact = contactPoint
} else {
if len <= closestLength! {
closestLength = len
closestNormal = contactNormal
closestContact = contactPoint
}
}
}
// This is where the code is just plain wrong and my math fails me.
if closestContact != nil {
// Calculate intersection angle...doesn't seem right?
let v1: CGVector = (end - start).normalized().toCGVector()
let v2: CGVector = closestNormal.normalized()
var angle = acos(v1.dot(v2)) * (180 / .pi)
let v1perp = CGVector(dx: -v1.dy, dy: v1.dx)
if(v2.dot(v1perp) > 0) {
angle = 360.0 - angle
}
angle = angle.degreesToRadians
// Set the new start point
start = closestContact
// Calculate a new end point somewhere in the distance to cast a ray to, so we can repeat the process again
let x = closestContact.x + cos(angle)*100
let y = closestContact.y + sin(-angle)*100
end = CGPoint(x: x, y: y)
// Add points to array to draw them on the screen
points.append(closestContact)
points.append(end)
}
}
私はあなたがこのようなものを探していると思いますか?
まず、完全に機能するコードを投稿しましょう。SpriteKitベースの新しいXcodeプロジェクトを作成して
GameViewController.swiftセットで
scene.scaleMode = .resizeFill
で見つけた通常のラベルを削除します GameScene.sks
Scene.swift
次のコードに置き換えます
>>
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
var angle: CGFloat = 0
override func update(_ currentTime: TimeInterval) {
removeAllChildren()
drawRayCasting(angle: angle)
angle += 0.001
}
private func drawRayCasting(angle: CGFloat) {
let colors: [UIColor] = [.red, .green, .blue, .orange, .white]
var start: CGPoint = .zero
var direction: CGVector = CGVector(angle: angle)
for i in 0...4 {
guard let result = rayCast(start: start, direction: direction) else { return }
let vector = CGVector(from: start, to: result.destination)
// draw
drawVector(point: start, vector: vector, color: colors[i])
// prepare for next iteration
start = result.destination
direction = vector.normalized().bounced(withNormal: result.normal.normalized()).normalized()
}
}
private func rayCast(start: CGPoint, direction: CGVector) -> (destination:CGPoint, normal: CGVector)? {
let endVector = CGVector(
dx: start.x + direction.normalized().dx * 4000,
dy: start.y + direction.normalized().dy * 4000
)
let endPoint = CGPoint(x: endVector.dx, y: endVector.dy)
var closestPoint: CGPoint?
var normal: CGVector?
physicsWorld.enumerateBodies(alongRayStart: start, end: endPoint) {
(physicsBody:SKPhysicsBody,
point:CGPoint,
normalVector:CGVector,
stop:UnsafeMutablePointer<ObjCBool>) in
guard start.distanceTo(point) > 1 else {
return
}
guard let newClosestPoint = closestPoint else {
closestPoint = point
normal = normalVector
return
}
guard start.distanceTo(point) < start.distanceTo(newClosestPoint) else {
return
}
normal = normalVector
}
guard let p = closestPoint, let n = normal else { return nil }
return (p, n)
}
private func drawVector(point: CGPoint, vector: CGVector, color: SKColor) {
let start = point
let destX = (start.x + vector.dx)
let destY = (start.y + vector.dy)
let to = CGPoint(x: destX, y: destY)
let path = CGMutablePath()
path.move(to: start)
path.addLine(to: to)
path.closeSubpath()
let line = SKShapeNode(path: path)
line.strokeColor = color
line.lineWidth = 6
addChild(line)
}
}
extension CGVector {
init(angle: CGFloat) {
self.init(dx: cos(angle), dy: sin(angle))
}
func normalized() -> CGVector {
let len = length()
return len>0 ? self / len : CGVector.zero
}
func length() -> CGFloat {
return sqrt(dx*dx + dy*dy)
}
static func / (vector: CGVector, scalar: CGFloat) -> CGVector {
return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar)
}
func bounced(withNormal normal: CGVector) -> CGVector {
let dotProduct = self.normalized() * normal.normalized()
let dx = self.dx - 2 * (dotProduct) * normal.dx
let dy = self.dy - 2 * (dotProduct) * normal.dy
return CGVector(dx: dx, dy: dy)
}
init(from:CGPoint, to:CGPoint) {
self = CGVector(dx: to.x - from.x, dy: to.y - from.y)
}
static func * (left: CGVector, right: CGVector) -> CGFloat {
return (left.dx * right.dx) + (left.dy * right.dy)
}
}
extension CGPoint {
func length() -> CGFloat {
return sqrt(x*x + y*y)
}
func distanceTo(_ point: CGPoint) -> CGFloat {
return (self - point).length()
}
static func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
}
このコードが何をするのか見てみましょう。下から始めましょう。
CGPoint
およびCGVector
拡張機能これらは、実行する幾何学的操作を単純化するための単純な拡張機能です(主にGitHubのRay Wenderlichのリポジトリから取得)。
drawVector(point:vector:color)
これは、vector
与えられたcolor
から始まる与えられたでを描く簡単な方法point
です。
ここには何も派手なものはありません。
private func drawVector(point: CGPoint, vector: CGVector, color: SKColor) {
let start = point
let destX = (start.x + vector.dx)
let destY = (start.y + vector.dy)
let to = CGPoint(x: destX, y: destY)
let path = CGMutablePath()
path.move(to: start)
path.addLine(to: to)
path.closeSubpath()
let line = SKShapeNode(path: path)
line.strokeColor = color
line.lineWidth = 6
addChild(line)
}
rayCast(start:direction) -> (destination:CGPoint, normal: CGVector)?
このメソッドはレイキャスティングを実行し、光線が物理ボディと衝突して入るALMOSTの最も近いポイントを返します。
private func rayCast(start: CGPoint, direction: CGVector) -> (destination:CGPoint, normal: CGVector)? {
let endVector = CGVector(
dx: start.x + direction.normalized().dx * 4000,
dy: start.y + direction.normalized().dy * 4000
)
let endPoint = CGPoint(x: endVector.dx, y: endVector.dy)
var closestPoint: CGPoint?
var normal: CGVector?
physicsWorld.enumerateBodies(alongRayStart: start, end: endPoint) {
(physicsBody:SKPhysicsBody,
point:CGPoint,
normalVector:CGVector,
stop:UnsafeMutablePointer<ObjCBool>) in
guard start.distanceTo(point) > 1 else {
return
}
guard let newClosestPoint = closestPoint else {
closestPoint = point
normal = normalVector
return
}
guard start.distanceTo(point) < start.distanceTo(newClosestPoint) else {
return
}
normal = normalVector
}
guard let p = closestPoint, let n = normal else { return nil }
return (p, n)
}
クローゼットのほとんどはどういう意味ですか?
これは、目的地が始点から少なくとも1ポイント離れている必要があることを意味します
guard start.distanceTo(point) > 1 else {
return
}
いいけどなんで?
このルールがないと、光線は物理体に突き刺さり、外に出ることはできません。
drawRayCasting(angle)
この方法は基本的に、ローカル変数を最新の状態に保ち、5つのセグメントを適切に生成します。
private func drawRayCasting(angle: CGFloat) {
let colors: [UIColor] = [.red, .green, .blue, .orange, .white]
var start: CGPoint = .zero
var direction: CGVector = CGVector(angle: angle)
for i in 0...4 {
guard let result = rayCast(start: start, direction: direction) else { return }
let vector = CGVector(from: start, to: result.destination)
// draw
drawVector(point: start, vector: vector, color: colors[i])
// prepare next direction
start = result.destination
direction = vector.normalized().bounced(withNormal: result.normal.normalized()).normalized()
}
}
最初のセグメントの開始点はゼロに等しく、方向は角度パラメーターをダイビングします。
セグメント2から5は、前のセグメントの最終点と「ミラーリングされた方向」を使用します。
ここでは、現在の角度値と増加する角度を0.001通過させるすべてのフレームをdrawRayCastingと呼んでいます。
var angle: CGFloat = 0
override func update(_ currentTime: TimeInterval) {
removeAllChildren()
drawRayCasting(angle: angle)
angle += 0.001
}
didMove(to view: SKView)
最後に、ここで、光線が境界を越えて跳ね返るようにするために、シーンの周りに物理ボディを作成します。
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
説明が明確であることを願っています。疑問がある場合はお知らせください。
バウンスされた関数にバグがありました。反射光線の適切な計算を妨げていました。修正されました。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加