How to prevent distorted images? - swift

I have the problem that the images I add are distorted. I have created a pixel accurate background for the iPhone X at (1125 x 2436), so I don't have to use .aspectFill and .aspectFit because I want a screen without black borders.
I use the following code to create the images:
func animateDeck() {
let chip = SKSpriteNode(imageNamed: "Chip")
chip.position = CGPoint(x: 300, y: 400)
chip.zPosition = 2
chip.setScale(1)
gameScene2.addChild(chip)
print("test")
}
Is there a way to display the images in their correct size without using .aspectFit or .aspectFill?
now (left) and how it should be (right)
Thank you in advance!

Check out this project I just made to show you how to create a texture and apply it to a node. All you need should be in GameScene.swift.
Also, in your ViewController, make sure that your GameScene is initialised properly as shown in my project, or how you did it with this:
gameScene2 = GameScene(size: view, bounds: size)

Related

Viewport? or Camera? of Field of View? changes on rotating iOS device

I have a 3d world using a SceneKit view in my app.
I need to support all iOS devices and orientations.
I do not understand 3d maths, matrices nor really understand cameras and stuff, yet I've built a 3d world, and I have a camera that works, and orbits with a Pan Gesture an object at the centre of the world.
My problem is that when I rotate the device from portrait to landscape the SceneKit view automatically changes it's perspective (of what I'm not sure - the camera? or the world itself?) and the object that looked like it was close, now goes way back into the distance.
What I want to happen is that regardless of rotation the 'viewport'? or camera? or world perspective? stays the same and the distance the camera is from the object doesn't "look" different i.e. the object visually stays at the same place.
I have not done any matrix manipulations or set up anything complicated.
I understand that the units of SceneKit are metres, so my object is in the correct size, and the camera is the right number of metres from it... it looks 'correct'
What I don't understand is why it all changes in landscape.
Yes, the SceneKit view is using AutoLayout constraints to hug the edges of the screen.
Here is my camera setup:
private func setupCamera() -> SCNNode {
let camera = SCNCamera()
camera.usesOrthographicProjection = false
camera.orthographicScale = 9
camera.zNear = 0.01
camera.zFar = 9500
camera.focalLength = 50
camera.focusDistance = 33
camera.fieldOfView = 100
let cameraNode = SCNNode()
cameraNode.position = SCNVector3(x:0, y:0, z: 500.0)
cameraNode.camera = camera
self.cameraNode = cameraNode
let cameraOrbit = SCNNode()
cameraOrbit.addChildNode(self.cameraNode!)
return cameraOrbit
}
The only way I've found to 'fix' this is to make the SceneKit view always be a square and sized so that it's always the largest of the devices width or height and keep it's centre in the screen's centre.
But this approach isn't working with other parts of the app, so instead I know I need to do this 'properly'.
So would someone please provide the code or tell me what I need to understand so that in the function that gets called when the device rotates i.e. override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) then I can correct this automatic change and keep the view's 'perspective' the same.
Thank you.
After 4 days of playing with constraints, manually moving the frame and all sorts of stuff, I have fixed it.
I constrained the scenekitview to the superview with 0 constants, so it's always the size of the screen.
Then ALL I had to do to solve this is ONE LINE... sigh.
I just had to halve the 'sensorHeight' and then it worked perfectly.
if UIDevice.current.orientation == .landscapeLeft ||
UIDevice.current.orientation == .landscapeRight
{
self.cameraNode?.camera?.sensorHeight = 12
}
else
{
self.cameraNode?.camera?.sensorHeight = 24
}
EDIT:
Because of the constraints to the screen size, the default Field of View meant that you have a fish-eye effect.
Dropping the FoV to almost half cured this and now objects rotate without distortions:
camera.fieldOfView = 50
From ios11 you can also tell scenekit which projection to use.
This solved it for me and is aspect ratio independent.
camera.projectionDirection = isPortrait ? .vertical : .horizontal

SpriteKit: using SKView in UIView instead of initializing a Game project

Completely new to SpriteKit. Currently I have a UIView, and I want to add a sprite node to it (like a small UIImageView, but I want animation for it so using SpriteKit). Therefore I didn't initialize my project to be a game project, as found in almost all of tutorials for SpriteKit. I've found a note here: link and what I have now is sth like:
func initializeImage() {
let imageView = SKView()
imageView.frame = CGRect(x: self.frame.width / 2 - Constants.imageWidth / 2, y: self.frame.height - Constants.imageHeight, width: Constants.imageWidth, height: Constants.imageHeight)
// so place it somewhere in the bottom middle of the whole frame
let sheet = SpriteSheet(texture: ...)
let sprite = SKSpriteNode(texture: sheet.itemFor(column: 0, row: 0))
sprite.position = imageView.center //basically the same position as the imageView.frame's x and y value
let scene = SKScene(size: imageView.frame.size)
scene.backgroundColor = SKColor.clear
scene.addChild(sprite)
imageView.presentScene(scene)
self.frame.addSubview(imageView)
}
The SpriteSheet is similar to this: sprite sheet; it's essentially cutting an image atlas and divide it into smaller images. I tracked the process and this step is indeed giving the smaller image (the var 'sprite'). But if running I only have a black square now (should be the size as defined by Constants). If I set scene.backgroundColor to be white then it's white. May I know how I should proceed from here, as how should I make the sprite showing up?
All of your code looks good except for this:
sprite.position = imageView.center // basically the same position as the imageView.frame's x and y value
That is basically not the position you think it is. The coordinate system in SpriteKit is a) relative to the (SK)scene, not to whatever view the SKView is contained in, and b) flipped vertically relative to the UIKit coordinate system. If you want a sprite centered in the scene, you probably want to set its position based on the scene's size:
sprite.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
By the way, the external SpriteSheet code might not be needed (and you're more likely to benefit from Apple's optimizations) if you slice up your sprite sheet and put it in an Xcode asset catalog.

Background image in gamescene.swift

What do I need to code in order to have an image (that is already in the assets.xcassets) displayed as the background of the GameScene.swift?
First of all you could call you scene with the scaleMode .resizeFill that modify the SKScene's actual size to exactly match the SKView :
scene.scaleMode = .resizeFill
.resizeFill – The scene is not scaled. It is simply resized so that its fits the view. Because the scene is not scaled, the images will all remain at their original size and aspect ratio. The content will all remain relative to the scene origin (lower left).
By default, a scene’s origin is placed in the lower-left corner of the view. So, a scene is initialized with a height of 1024 and a width of 768, has the origin (0,0) in the lower-left corner, and the (1024,768) coordinate in the upper-right corner. The frame property holds (0,0)-(1024,768).The default value for the anchor point is CGPointZero (so you don't need to change it), which places it at the lower-left corner.
Finally, you can use the code below to add your background image (called of example bg.jpg):
// Set background
let txt = SKTexture(imageNamed: "bg.jpg")
let backgroundNode = SKSpriteNode(texture: txt, size:size)
self.addChild(backgroundNode)
backgroundNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
Although this might not be the best way, but it's what I always do, and it works.
Assuming that you have an image that is exactly the same aize as your scene, you can do this:
// please declare bg as a class level variable
bg = SKSpriteNode(imageNamed: "name of your texture")
// the below two lines of code is my preference only. I want the
// background's anchor point to be the bottom left of the screen
// because IMO it's easier to add other sprites as children of the background.
bg.anchorPoint = CGPoint.zero
bg.position = CGPoint(x: self.frame.width / -2, y: self.frame.height / -2)
self.addChild(bg)
Alernatively, just do this in an sks file. It's much easier.
After that, add all your game sprites as children of bg instead of self because it is easier to manage.

SpriteKit. Why .strokeTexture does not work for SKShapeNode?

I can't understand why the "strokeTexture" option isn't working on a SKShapeNode. Or, how can I do border with texture? Thanks in advance!
let shape = SKShapeNode(circleOfRadius: 100)
shape.position = CGPoint(x: 200, y: 400)
shape.fillColor = SKColor.white
shape.fillTexture = SKTexture(imageNamed: "test")
shape.lineWidth = 50
shape.strokeColor = SKColor.white
shape.strokeTexture = SKTexture(imageNamed: "test(5)")
Output:
Test image:
Test(5) image:
It doesn't work in Simulator!
Try it on your device.
I get the feeling this stroke might be being done with metal, whereas the fill is somehow not being done with metal, so is visible in the simulator.
The fill also doesn't rotate with the object on the device, but the stroke/outline and its texture do.
This would tend to indicate that an SKShapeNode without a fill might have reasonable performance.
Set the width and height of the texture, that is used to render the stroke, multiple of 8 pixels. In your case Test(5) has dimensions 100x100. If you change this texture for another one with dimensions, e.g. 96x96 pixels, the stroke will be rendered correctly and displayed. I don't know, why there are no reference to this fact in the official documentation.

Swift 1.2 SpriteKit coordinate system origin

I don't know if it is a problem started in Swift 1.2 or not. I am new to both swift and SpriteKit. I was watching an online tutorial and the guy there was able to put a green box on the bottom-left corner of the screen by doing the following:
let greenBox = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 200, height: 200))
let somePoint = CGPointMake(0, 0)
greenBox.position = somePoint
self.addChild(greenBox)
However when I try the same thing, it does not even appear on the screen! Later, I found out that bottom left of the screen was actually something close to (300,10). Why would that happen?
Also, I found out that the self.frame.size equals to (1024.0, 768.0) which is even more confusing since it has no relation iPhone6's size. (I was testing with iPhone 6 though.)
I am stuck at this. Any help will be appreciated, thanks!
There are a few things. First of all, make sure your gameViewController.swift has this code in the viewDidLoad function:
self.screenSize = skView.frame.size.width
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
This will make sure you have the right aspect ratio and size of an iPhone screen.
To get the bottom left of the screen, you can use this:
let somePoint = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame))
Finally, I believe the greenBox has a center anchor point, so it will put the center of the box in the bottom left which means that about 75% of the block will fall off screen.
You can change the anchor point to the bottom left of the green box to make sure it shows entirely.
greenBox.anchorPoint = CGPointMake(0, 0)