A bit about the implementation of the puzzle "Cubes of Catfish" (Swift & SceneKit)
About a year ago, I noticed that in mobile stores there is no Pete Hain puzzle “Soma Cubes”, invented back in 1933. The desire to try to write a game for iOS bored a sore brain for a long time and I finally decided, especially since I didn’t really need a design (drawing a cube in Blender doesn’t count). In the puzzle there are 7 elements from cubes from which various other shapes are assembled ( Wikipedia ).
The requirements immediately presented to the game came down to two points:
1. Do not use third-party frameworks in development.
2. Buttons should not be used to control figures and scenes - only Recognizers.
According to claim 2, I mean that the scenario when you need to select an object and then click on the control buttons does not fit.
We export the cube drawn in Blender to the .dae file and put it in the “art.scnassets” folder of our game project, during the creation of which SceneKit was specified as Game Tech. Access to the imported scene and the object on the scene are obtained as follows:
The third line simply paints the edges of the cube in the desired color. Now you can clone the object, set the coordinates and add it to the parent figure, which is simply an object of the SCNNode class. Similarly, we place the figure for assembly in the center of the scene, after making the cube a little smaller.
For the game, it is necessary to ensure that the figure can rotate 90 degrees around all three axes, which at first caused some difficulties, but then it suddenly dawned on me (after a week) that combinations of rotations around two axes are enough for all cases. UISwipeGestureRecognizer was chosen to implement the plan. So, swipe in a figure left-right rotates around a vertical axis (Y) regardless of the position of the camera, swipe up and down rotates a figure either around X or around Z (depending on the position of the camera).
It is natural to use UIPanGestureRecognizer to drag objects along the XZ plane, however, you need to specify the relationship between “swipe” and “drag and drop” for both handlers to work.
To raise and lower one unit along the Y axis, two instances of UITapGestureRecognizer were used, only one of them had numberOfTapsRequired = 2, and also showed envy:
The camera is added to the SCNNode in the center of coordinates at some distance from it and is directed towards it. That is, the camera is somehow on the surface of the sphere, and to rotate the camera, it is enough to rotate the parent SCNNode by a certain angle. To zoom in and out, we use UIPinchGestureRecognizer and it is enough to do the “SCNNode» in the handler:
I used the eulerAngles property of the cameraOrbit object to increment the rotation angle in the same handlers as for the shapes, previously identifying the object in SCNHitTestResult.
To test collisions, the coordinates of each cube of the current figure (which was released, rotated, etc.) coincide with the cubes of the remaining six figures. Levels are just a text file with coordinates.
As a result of the development of this puzzle, I want to say that iOS left a good impression on me, especially the implementation of UIGestureRecognizers. Thank you all for your attention and I hope someone written above will help.
The requirements immediately presented to the game came down to two points:
1. Do not use third-party frameworks in development.
2. Buttons should not be used to control figures and scenes - only Recognizers.
According to claim 2, I mean that the scenario when you need to select an object and then click on the control buttons does not fit.
1. Preparation
We export the cube drawn in Blender to the .dae file and put it in the “art.scnassets” folder of our game project, during the creation of which SceneKit was specified as Game Tech. Access to the imported scene and the object on the scene are obtained as follows:
let cubeScene = SCNScene(named: "art.scnassets/cube.dae")
let cubeNode1 = cubeScene!.rootNode.childNodeWithName("Cube", recursively: false)
cubeNode1?.geometry?.materialWithName(CUBE_MATERIAL_NAME)?.diffuse.contents = COLORS_FOR_PRIMITIVES[1]
The third line simply paints the edges of the cube in the desired color. Now you can clone the object, set the coordinates and add it to the parent figure, which is simply an object of the SCNNode class. Similarly, we place the figure for assembly in the center of the scene, after making the cube a little smaller.
2. Rotate, raise, lower, and drag objects
For the game, it is necessary to ensure that the figure can rotate 90 degrees around all three axes, which at first caused some difficulties, but then it suddenly dawned on me (after a week) that combinations of rotations around two axes are enough for all cases. UISwipeGestureRecognizer was chosen to implement the plan. So, swipe in a figure left-right rotates around a vertical axis (Y) regardless of the position of the camera, swipe up and down rotates a figure either around X or around Z (depending on the position of the camera).
It is natural to use UIPanGestureRecognizer to drag objects along the XZ plane, however, you need to specify the relationship between “swipe” and “drag and drop” for both handlers to work.
panGestureRecognizer.requireGestureRecognizerToFail(swipeGestureRecognizer)
Code for dragging objects for those interested
func handlePanGestures(recogniser: UIPanGestureRecognizer){
if recogniser.state == .Began {
let location = recogniser.locationInView(recogniser.view)
let hits = sceneView.hitTest(location,options: nil) as! [SCNHitTestResult]
for hit in hits {
if Utils.nodeHasPrefix(hit.node.parentNode!, prefix: "fig"){
selectedNode = hit.node.parentNode
saveOldPosition(selectedNode)
let worldCoord = selectedNode.position
let projectedOrigin = sceneView.projectPoint(worldCoord)
curZ = projectedOrigin.z
let unProj = sceneView.unprojectPoint(SCNVector3Make(Float(location.x), Float(location.y), projectedOrigin.z))
ofset = SCNVector3Make(selectedNode.position.x - unProj.x, selectedNode.position.y - unProj.y, selectedNode.position.z - unProj.z)
break
}
}
}
if recogniser.state == .Changed {
let curScreenPoint = SCNVector3Make(Float(location.x), Float(location.y), curZ)
let curWorld = sceneView.unprojectPoint(curScreenPoint)
let posPlusOffset = SCNVector3Make(curWorld.x + ofset.x, curWorld.y + ofset.y , curWorld.z + ofset.z)
let newPosition = SCNVector3Make(posPlusOffset.x , selectedNode.position.y , posPlusOffset.z )
let projectedOrigin2 = sceneView.projectPoint(newPosition)
curZ = projectedOrigin2.z
selectedNode.position = newPosition
}
if recogniser.state == .Ended || recogniser.state == .Failed{
selectedNode.position.x = Utils.clamp(selectedNode.position.x, min : -9 , max: 9)
selectedNode.position.y = round(selectedNode.position.y)
selectedNode.position.z = Utils.clamp(selectedNode.position.z, min : -9 , max: 9)
if testCollision(selectedNode){
selectedNode.position = selectedNodeOldPosition
}else{
testGameOver(selectedNode as! SimpleFigure)
}
selectedNode = nil
}
}
To raise and lower one unit along the Y axis, two instances of UITapGestureRecognizer were used, only one of them had numberOfTapsRequired = 2, and also showed envy:
tapRecognizer.requireGestureRecognizerToFail(doubleTapRecognizer)
3. Camera control
The camera is added to the SCNNode in the center of coordinates at some distance from it and is directed towards it. That is, the camera is somehow on the surface of the sphere, and to rotate the camera, it is enough to rotate the parent SCNNode by a certain angle. To zoom in and out, we use UIPinchGestureRecognizer and it is enough to do the “SCNNode» in the handler:
let cameraNode = SCNNode()
cameraNode.name="mainCamera"
cameraNode.camera = SCNCamera()
cameraNode.camera!.zFar = 100
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 8.0)
let cameraOrbit = SCNNode()
cameraOrbit.position = SCNVector3(x: 0.0, y: 0.0, z: 0.0)
cameraOrbit.addChildNode(cameraNode)
cameraOrbit.eulerAngles.x = -Float(M_PI_4)
I used the eulerAngles property of the cameraOrbit object to increment the rotation angle in the same handlers as for the shapes, previously identifying the object in SCNHitTestResult.
4. Other stuff
To test collisions, the coordinates of each cube of the current figure (which was released, rotated, etc.) coincide with the cubes of the remaining six figures. Levels are just a text file with coordinates.
As a result of the development of this puzzle, I want to say that iOS left a good impression on me, especially the implementation of UIGestureRecognizers. Thank you all for your attention and I hope someone written above will help.