Player / CameraNode Position Issue - xcode

I have a camerNode position issue. I have included the code below + an attempt of resolving this with no progress. Essentially in my GameScene I have the camera locked perfectly onto the player when moving through the scene, I am actually trying to amend this so that the camera is slightly ahead of the player, this meaning my player is actually positioned on the left side of the screen, almost like an offset (+ 200) :) if this existed.
The Code:
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create a constant cam as a SKCameraNode:
let cam = SKCameraNode()
override func didMove(to view: SKView) {
// vertical center of the screen:
screenCenterY = self.size.height / 2
// Assign the camera to the scene
self.camera = cam
//Add the camera itself to the scene's node tree"
self.addChild(self.camera!)
// Position the camera node above the game elements:
self.camera!.zPosition = 50
}
override func didSimulatePhysics() {
// Keep the camera locked at mid screen by default:
var cameraYPos = screenCenterY
cam.yScale = 1
cam.xScale = 1
// Follow the player:
if (player.position.y > screenCenterY) {
cameraYPos = player.position.y
cam.yScale = newScale
cam.xScale = newScale
}
// Camera Adjustment:
self.camera!.position = CGPoint(x: player.position.x, y: cameraYPos)
I initially thought that I could overcome this by changing the player to another SKSpriteNode.. i'e in my HUD class I could add a node and apply this code around? I could then refer back to my player class in which I already defined the position above my override func didMove.
let initialPlayerPosition = CGPoint(x: 50, y: 350)
I did try this and the GameScene started playing up, is there a better method for achieving this result? I assume the code could be changed to accommodate but I am still in the learning grounds of Xcode.

Never mind - solved it!
Camera adjustment statement - player.position.x + 200) :)
This does the trick!

Related

Why Does Autoreversing the Added Animation "Mess Up" the Final Result?

I have an application that animates a small, red subview using added animations. The first animation moves the view down by 100 points. The second animation kicks in at the half way point and moves the view to the right by 100 points. The down and the right animations are additive resulting in a diagonal movement toward the lower right. Additionally, both animations are autoreversed so that the view is restored to its original position over the top of the small, green "marker" subview.
From the above gif one can see that the view animation behaves as expected until the autoreversing has completed whereupon the view jumps to the left (apparently by 100 points). Typically such a jump would be the result of not having set the state of the view to match the final frame of the animation. However, the view state is indeed being correctly(?) set by the animator's completion method - this seems to be borne out by the information provided by the print statements.
What is the cause of the view's final, leftward jump?
See the relevant code below. A complete project can be downloaded from GitHub
I have a hunch that the calls to UIView.setAnimationRepeatAutoreverses in each of the two animation closures might be the culprit. It is not clear to me that what I have done in that regard is in alignment with the framework's expectations.
class ViewController: UIViewController {
private var markerView: UIView!
private var animationView: UIView!
override func viewDidLoad() {
view.backgroundColor = .black
view.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(tapGestureHandler)))
let frame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 10, height: 10)
markerView = UIView(frame: frame)
markerView.backgroundColor = .green
view.addSubview(markerView)
animationView = UIView(frame: frame)
animationView.backgroundColor = .red
view.addSubview(animationView)
}
#objc private func tapGestureHandler(_ sender: UITapGestureRecognizer) {
animate()
}
// Move down; half way through begin moving right while still moving down.
// Autoreverse it.
private func animate() {
let distance: CGFloat = 100
let down = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
print("\nCenter: \(self.animationView.center)")
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
self.animationView.center.x -= distance
self.animationView.center.y -= distance
print("Center: \(self.animationView.center)")
}
animator.startAnimation()
}
}
Update 1
I have added two other versions of the animate method which are informative. Based upon what I learned from these two versions, I altered the title of this question.
animate2: The calls to UIView.setAnimationRepeatAutoreverses have been commented out in each of the down and right animation functions. This modified code functions as expected. Of course we do not get the smooth autoreversing effect.
extension ViewController {
private func animate2() {
let distance: CGFloat = 100
let down = {
//UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
//UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
print("\nCenter: \(self.animationView.center)")
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
self.animationView.center.x -= distance
self.animationView.center.y -= distance
print("Center: \(self.animationView.center)")
}
animator.startAnimation()
}
}
animate3: The addition of the second (i.e. right) animation is commented out. This modified code functions as expected. Of course we do not get the movement to the right.
extension ViewController {
private func animate3() {
let distance: CGFloat = 100
let down = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
print("\nCenter: \(self.animationView.center)")
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
//animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
self.animationView.center.y -= distance
//self.animationView.center.x -= distance
print("Center: \(self.animationView.center)")
}
animator.startAnimation()
}
}
It appears that calling UIView.setAnimationRepeatAutoreverses in both of the animation functions is "messing things up". It is not clear to me if this can be rightly be regarded as an iOS bug.
Just comment the line under your animate() method ::
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
//self.animationView.center.x -= distance //<--- Comment or remove this line.
self.animationView.center.y -= distance
print("Center: \(self.animationView.center)")
}

NSView drawing explained

I am trying to create a object that can be dragged and rotated in an NSView and have been successful in doing so using NSBezierPath. I am creating multiple objects and storing them in a class and using NSBezierPath.transform(using: AffineTransform) to modify the path in response to drag and rotation inputs.
This all works fine but I now want to add text to the shape and it seems there are a different set of rules for dealing with text.
I have tried using Core Text by creating a CTFrame but have no idea how to move or rotate this.
Is there a good reason for why the handling of text is so different from NSBezierPath.
And then there is the difference between AffineTransform and CGAffineTransform. The whole thing is pretty confusing and good documentation explaining the difference seems hard to come by.
Below is the code for creating and moving the shape which seems work perfectly. I have no idea how to move the text, ideally without having to recreate it. Is there any way to translate and rotate the CTFrame?
var path: NSBezierPath
var location: NSPoint {
didSet {
// move()
}
}
var angle: CGFloat {
didSet {
let dx = angle - oldValue
rotate(dx)
}
}
func createPath(){
// Create a simple path with a rectangle
self.path = NSBezierPath(rect: NSRect(x: -1*width/2.0, y: -1*height/2.0, width: width, height: height))
let line = NSBezierPath()
line.move(to: NSPoint(x: width/2.0, y:0))
line.line(to: NSPoint(x: width/2.0+leader, y:0))
self.path.append(line)
// Label !!
let rect = NSRect(x: width/2.0, y: 0, width: leader, height: height/2.0)
let attrString = NSAttributedString(string: assortmentLabel, attributes: attributesForLeftText)
self.labelFrame = textFrame(attrString: attrString, rect: rect)
// ??? How to rotate the CTFrame - is this even possible
move()
rotate(angle)
}
func rotate(_ da: CGFloat){
// Move to origin
let loc = AffineTransform(translationByX: -location.x, byY: -location.y)
self.path.transform(using: loc)
let rotation = AffineTransform(rotationByDegrees: da)
self.path.transform(using: rotation)
// Move back
self.path.transform(using: AffineTransform(translationByX: location.x, byY: location.y))
}
func move(){
let loc = AffineTransform(translationByX: location.x, byY: location.y)
self.path.transform(using: loc)
}
func draw(){
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
color.set()
path.stroke()
if isSelected {
path.fill()
}
if let frame = self.labelFrame {
CTFrameDraw(frame, context)
}
}
// -----------------------------------
// Modify the item location
// -----------------------------------
func offsetLocationBy(x: CGFloat, y:CGFloat)
{
location.x=location.x+x
location.y=location.y+y
let loc = AffineTransform(translationByX: x, byY: y)
self.path.transform(using: loc)
}
EDIT:
I have changed things around a bit to now draw the shape at origin 0,0 and to then apply the transformation to CGContext prior to drawing the shape.
This does the job and using CTDrawFrame now works correctly...
Well almost...
On my test app it works perfectly but when I integrated the exact same code to the production app the text appears upside-down and all characters shown on top of each other.
As far as I can tell there is nothing different about the views the drawing is taking place in - uses the same NSView subclass.
Is there something else that could upset the drawing of the text - seems like an isFlipped issue but why would this happen in the one app and not the other.
Everything else seems to draw correctly. Tearing my hair out on this.
After much struggling it seems I got lucky with the test app working at all and needed to set the textMatrix on BOTH apps to ensure things work properly under all conditions.
It also seems one can't create the CTFrame and later just scale it and redraw it - well it didn't work for me so I had to recreate it in the draw() method each time!

Clock minute-hand disappears when attempting to rotate it

Modus Operandi:
1) Use an UIImageView of a base Clock Image.
2) Add MinuteHand & HourHand sublayers (containing their respective images) to the UIImageView layer.
Problem: both sublayers disappear when attempting to perform a rotation transformation.
Note: 1) I've removed the 'hour' code & ancillary radian calculations to simplify code.
2) The 'center' is the center of the clock. I had adjusted the coordinates to actually pin the hands to the clock's center.
3) The ViewDidLayoutSubviews() appear to be okay. I got the clock + hands.
class ClockViewController:UIViewController {
private let minuteLayer = CALayer()
#IBOutlet weak var clockBaseImageView: UIImageView!
#IBOutlet weak var datePicker: UIDatePicker!
override func viewDidLayoutSubviews() {
guard var minuteSize = UIImage(named: "MinuteHand")?.size,
var hourSize = UIImage(named: "HourHand")?.size
else {
return
}
var contentLayer:CALayer {
return self.view.layer
}
var center = clockBaseImageView.center
// Minute Hand:
minuteLayer.setValue("*** Minute Hand ***", forKey: "id")
minuteSize = CGSize(width: minuteSize.width/3, height: minuteSize.height/3)
minuteLayer.contents = UIImage(named: "MinuteHand")?.cgImage
center = CGPoint(x: 107.0, y: 40.0)
var handFrame = CGRect(origin: center, size: minuteSize)
minuteLayer.frame = handFrame
minuteLayer.contentsScale = clockBaseImageView.layer.contentsScale
minuteLayer.anchorPoint = center
clockBaseImageView.layer.addSublayer(minuteLayer)
}
Here's my problem: Attempting to rotate the minute hand via 0.01 radians:
func set(_ time:Date) {
minuteLayer.setAffineTransform(CGAffineTransform(rotationAngle: .01)) // random value for test.
}
Before rotation attempt:
After attempting to rotate minute hand:
The hand shifted laterally to the right vs rotate.
Why? Perhaps due to the pivot point?
I think this will solve your problem, Take a look and let me know.
import GLKit // Importing GLKit Framework
func set(_ time:Date) {
minuteLayer.transform = CGAffineTransformMakeRotation(CGFloat(GLKMathDegreesToRadians(0.01)))
}
Note: this solution doesn't solve the issue about rotating a CALayer. Instead, it bypasses the issue by replacing the layer with a subview and rotating the subview via:
func set(_ time:Date) {
minuteView.transform = CGAffineTransform(rotationAngle: 45 * CGFloat(M_PI)/180.0)
}
Here's the result:
Still, it would be nice to know how to rotate a CALayer.

Changing scene causes zoom

When going from GameScene to PlayScene using this code below, all works fine:
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.playButton1 {
var scene = PlayScene(size: self.size)
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
But when i am going from PlayScene to GameScene using this code below, it gets zoomed in:
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
Tried to Updated code:
var scene = GameScene(size: self.size)
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
How come? Any suggestions?
In the first code snippet, you create the new scene of self.size which keeps the same size as the old one. But in the second one, you use another method to do that, and the size of the new scene is initialized from the file GameScene.sks whose size is 1024x768 by default.
EDIT:
Did you use
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {...}
to create your first scene in your GameViewController.swift? If so, try to replace this line with
let scene = GameScene(size: view.bounds.size)
I'm afraid the reason why your buttons get resized(zoomed) is that they are not in the right size at the first time you load GameScene, actually they are in a scene sized 1024x768. Your button is hard-coded as 400 width which is nearly the 1/3 of the scene width (1024pt). When you load GameScene again, its width now becomes the screen width, so you find buttons get resized. Print scene.size.width for more info.
I had the same problem.
For me the problem was that the scene size had changed to be much smaller when going back to the first scene.
Scene 1) GameMap Scene
Original Scene Size: (1024.0, 4096.0)
Original View Size : ( 320.0, 480.0)
Scene 2) GameLevel Scene
Original Scene Size: ( 320.0, 480.0)
Original View Size : ( 320.0, 480.0)
When Moving from Scene 1 to Scene 2 everything looked fine but when moving back from Scene 2 to Scene 1 The "Scene 1" size is changed to Scene 2's size
Scene 1) GameMap Scene -> After Going Back
Original Scene Size: ( 320.0, 480.0)
Original View Size : ( 320.0, 480.0)
My solution was to specifically change the size of the scene in "didMoveToView" and set the scaleMode of the "Scene 1" see below:
self.view?.bounds = CGRectMake(0, 0, Constants.screenWidth, Constants.screenHeight)
self.scene?.size = CGSizeMake(1024, 4096)
self.scaleMode = SKSceneScaleMode.AspectFill
Hope it helps
BR
BuD

SKSpriteNode that the correct size

I am trying to draw a SKSpriteNode that is 30 tall and has the width of the viewport. This is the code (inside SKScene):
func floor() -> SKSpriteNode{
let floor = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(self.size.width, 20))
floor.position = CGPointMake(0, 0)
floor.physicsBody = SKPhysicsBody(rectangleOfSize: floor.size)
floor.physicsBody.dynamic = false
return floor
}
The sprite is added to the scene like this:
override func didMoveToView(view: SKView!){
if (!contentCreated){
self.createContents()
contentCreated = true
}
}
func createContents() {
self.backgroundColor = SKColor.blackColor()
self.scaleMode = SKSceneScaleMode.AspectFill
self.addChild(self.floor())
}
The sprite is 30 tall (seemingly), but the length seems to be half of the viewport in width instead of the full width. The code that creates this scene is:
var mainScene = MainScene(size: self.view.frame.size)
spriteView.presentScene(mainScene)
This code is inside a ViewController.
Does anyone know what might be going on?
The default anchorPoint of a sprite node is { 0.5, 0.5 }, which could result in the code above positioning only half of your sprite on the screen. Try setting the anchorPoint to { 0.0, 0.0 } and see if that helps.

Resources