将SpriteKit SKScene overlaySKScene中的2D点映射到SceneKit SCNScene中的3D点

时间:2015-12-23 21:06:41

标签: objective-c swift sprite-kit scenekit

我正在尝试将SpriteKit中的触摸转换为SceneKit并且遇到一些困难。我的目的是移动一个3D“怪物”,使它看起来好像在2D晶体的同一位置(虽然在后面)。 2D晶体被添加到SpriteKit提供的overlaySKScene并定位。我们的“怪物”是坐在SCNNode中的完整3D SCNScene,如下所示。

我尝试了各种方法,其中最接近我在makeMonsterEatCrystalAtLocation方法中包含的方法虽然我知道这是错误的,因为它只在SpriteKit世界中起作用 - 它没有' t从SpriteKit转换为SceneKit。我还查看了unprojectPoint协议中的SCNSceneRenderer,但该方法仅适用于SceneKit本身。任何帮助将不胜感激!我不偏爱Objective-C或Swift,即使下面是Obj-C。

- (void)setupSceneKit {

    _sceneView = [[SCNView alloc] initWithFrame:[[UIScreen mainScreen] bounds] options:@{@"SCNPreferredRenderingAPIKey": @(SCNRenderingAPIOpenGLES2)}];
    [self.view insertSubview:_sceneView aboveSubview:backgroundView];

    _sceneView.scene = [SCNScene scene];
    _sceneView.allowsCameraControl = YES;
    _sceneView.autoenablesDefaultLighting = NO;
    _sceneView.backgroundColor = [UIColor clearColor];

    camera = [SCNCamera camera];
    [camera setXFov:20];
    [camera setYFov:20];
    camera.zFar = 10000.0f;

    cameraNode = [SCNNode node];
    cameraNode.camera = camera;
    cameraNode.position = SCNVector3Make(0, 3, 700);
    [_sceneView.scene.rootNode addChildNode:cameraNode];
}

- (void)createMonster {

    _monsterNode = [SCNNode node];
    [self.sceneView.scene.rootNode addChildNode:_monsterNode];
    [SCNTransaction begin];
    [SCNTransaction setAnimationDuration:0.5f];
    _monsterNode.position = SCNVector3Zero;
    [SCNTransaction commit];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    CGPoint crystalLocation = [[touches anyObject] locationInNode:self.sceneView.overlaySKScene];
    SKNode *touchedCrystalNode = [self.sceneView.overlaySKScene nodeAtPoint:crystalLocation];

    // determine that this is the SKNode touchedCrystalNode we're looking for...

    [self makeMonsterEatCrystalAtLocation:crystalLocation];
}

- (void)makeMonsterEatCrystalAtLocation:(CGPoint)location {

    // Attempt to move the "monster" (SCNNode in SceneKit) to appear as if its in the same 
    // location as the "crystal" (which is presented in the SpriteKit overlay: self.sceneView.overlaySKScene) and eventually
    // play an animation of the monster eating the crystal.

    CGPoint scnPoint = [self.sceneView.overlaySKScene convertPointToView:location];

    [SCNTransaction begin];
    [SCNTransaction setAnimationDuration:0.5f];
    self.monsterNode.position = SCNVector3Make(scnPoint.x, scnPoint.y, 0);
    [SCNTransaction commit];

    // ...
}

1 个答案:

答案 0 :(得分:3)

一种方法是将你的怪物放入SK3DNode。然后你可以将大部分时间都花在SpriteKit上,而不必在坐标系之间进行转换。

投影时,你必须处理几个不同的坐标系:SpriteKit场景(左下角的原点),SceneKit视图的屏幕坐标(左上角或左下角的原点,取决于操作系统),视图的相机局部坐标系统,以及SceneKit场景的世界坐标系。

让我们从SceneKit方面开始。您的相机有自己的3D坐标系,表示view frustum的内容。 屏幕上的单个点对应于平截头体内的线段,从近平面到远平面;相机空间中的Z轴对应于该点在nearar和far平面之间的相对位置。您SCNView凭借符合协议SCNSceneRenderer,可以在屏幕坐标和3D世界空间之间进行转换。

要移动怪物,您将使用projectPoint获取当前相机的x / y / z坐标,找出要移动到的屏幕位置,然后使用unprojectPoint进行计算该屏幕位置的世界位置。像这样:

let screenDestinationX = 100.0
let screenDestinationY = 200.0
let monsterCoordinates = sceneView.projectPoint(MonsterNode.position)
let monsterDestinationCoordinates = SCNVector3D(x: screenDestinationX,
                                                y: screenDestinationY,
                                                z: monsterCoordinates.z)
let monsterDestinationPosition = sceneView.unprojectPoint(monsterDestinationCoordinates)

通过保持怪物开始和结束的相同Z坐标,你将使怪物与相机保持相同的距离。

现在为SpriteKit方面。如果您的sceneView.overlaySKScenescaleMode设置为SKSceneScaleMode.ResizeFill,则您的SKScene将与屏幕尺寸相同。当然,Y轴的方向可能不同。所以问你的水晶的SpriteKit坐标,必要时翻转Y(高度减Y而不是Y),你有目的地X和Y你可以传递到SceneKit进行投影。

我有一个示例项目,显示SceneKit和覆盖SpriteKit坐标https://github.com/halmueller/ImmersiveInterfaces/tree/master/Tracking%20Overlay之间的转换。它并没有完全符合您的要求,但确实显示了相互作用。

最后,下面是一个Playground,显示世界和相机坐标之间的转换:

import SceneKit
import SpriteKit
import XCPlayground

let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 800, height: 600))

XCPlaygroundPage.currentPage.liveView = sceneView

var scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = SKColor.greenColor()
sceneView.debugOptions = .ShowWireframe

// default lighting
sceneView.autoenablesDefaultLighting = true

// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
print (cameraNode.camera?.zFar, cameraNode.camera?.zNear)

cameraNode.camera?.automaticallyAdjustsZRange = false
cameraNode.position = SCNVector3(x: 5, y: 5, z: 50)
scene.rootNode.addChildNode(cameraNode)
let center = SCNNode()
center.position = SCNVector3(x:0, y:0, z:0)
scene.rootNode.addChildNode(center)
let centerConstraint = SCNLookAtConstraint(target: center)
cameraNode.constraints = [centerConstraint]

let sphere = SCNSphere(radius: 2)
sphere.geodesic = true
let sphereNode1 = SCNNode(geometry: sphere)
sphereNode1.position = SCNVector3(x:-8, y:5, z:10)
scene.rootNode.addChildNode(sphereNode1)

let sphereNode2 = SCNNode(geometry: sphere)
sphereNode2.position = SCNVector3(x:8, y:-5, z:-30)
scene.rootNode.addChildNode(sphereNode2)

let sphere1Coordinates = sceneView.projectPoint(sphereNode1.position)
print ("sphere1 position", sphereNode1.position, "screen coordinates",sphere1Coordinates)
let sphere2Coordinates = sceneView.projectPoint(sphereNode2.position)
print ("sphere2 position", sphereNode2.position, "screen coordinates",sphere2Coordinates)

let sphere2NearCoordinates = SCNVector3(x: sphere2Coordinates.x, y: sphere2Coordinates.y, z: sphere1Coordinates.z)
let sphere2FarPosition = sphereNode2.position
let sphere2NearPosition = sceneView.unprojectPoint(sphere2NearCoordinates)

let sphere1FarCoordinates = SCNVector3(x: sphere1Coordinates.x, y: sphere1Coordinates.y, z: sphere2Coordinates.z)
let sphere1NearPosition = sphereNode1.position
let sphere1FarPosition = sceneView.unprojectPoint(sphere1FarCoordinates)

let sphere1LowerCoordinates = SCNVector3(x: sphere1Coordinates.x, y: sphere1Coordinates.y - 100, z: sphere2Coordinates.z)
let sphere1LowerPosition = sceneView.unprojectPoint(sphere1LowerCoordinates)


let forwardAction = SCNAction.moveTo(sphere2NearPosition, duration: 3)
let backAction = SCNAction.moveTo(sphere2FarPosition, duration: 3)
let shiftAction = SCNAction.moveTo(sphere1LowerPosition, duration: 3)
let forwardBackShift = SCNAction.sequence([forwardAction, backAction, forwardAction, shiftAction])
sphereNode2.runAction(forwardBackShift) {
    print ("sphere1 position", sphereNode1.position, "screen coordinates",sceneView.projectPoint(sphereNode1.position))
    print ("sphere2 position", sphereNode2.position, "screen coordinates",sceneView.projectPoint(sphereNode2.position))
}