I want to essentially draw a line of width 10 from one spot on the screen to another (where the touch started and where the touch currently is) and I figured the best way to do that was to find the height and width and then rotate it to be in the right spot. Well I got it eventually but some weird things were happening and I don't know a ton about Swift so I'm hoping someone here has an explanation. Here is the relevant code
var touchBox: UIImageView!
var touchOrigin: CGPoint!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let position = touch?.location(in: self.view)
touchOrigin = CGPoint(x: (position?.x)!, y: floors[fromFloor].image.frame.origin.y + 10)
touchBox = UIImageView(image: UIImage(named: "lineImg"))
touchBox.frame = CGRect(x: (position?.x)!, y: (position?.y)!, width: 20, height: 20)
touchBox.alpha = 0.5
self.view.addSubview(touchBox)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let position = touch?.location(in: self.view)
let x = min(touchOrigin!.x, (position?.x)!)
let y = min(touchOrigin!.y, (position?.y)!)
let w = max(touchOrigin!.x, (position?.x)!) - x
let h = max(touchOrigin!.y, (position?.y)!) - y
let angle = atan(h / w)
touchBox.removeFromSuperview()
touchBox = UIImageView(image: UIImage(named: "lineImg"))
let hyp = sqrt(pow(h, 2) + pow(w, 2))
// since it rotates around the center I have to find a new x, y
let x1 = (2 * x + w) / 2
let y1 = (2 * y + h) / 2 - (hyp / 2)
touchBox.frame = CGRect(x: x1, y: y1, width: 10, height: hyp)
// my image has arrows which is why I need to flip it sometimes
if (touchOrigin!.y < (position?.y)!) {
touchBox.transform = CGAffineTransform.init(scaleX: -1, y: -1)
}
if ((touchOrigin!.x < (position?.x)!) == (touchOrigin!.y < (position?.y)!)) {
touchBox.transform = touchBox.transform.rotated(by: -1 * (CGFloat(M_PI_2) - angle))
} else {
touchBox.transform = touchBox.transform.rotated(by: (CGFloat(M_PI_2) - angle))
}
touchBox.alpha = 0.5
self.view.addSubview(touchBox)
}
This version works the way I want it to. However, what is weird is that every time through touchesMoved I must redefine touchBox in order for it to work correctly. If I remove this (it has already been defined anyways) and the other lines that concern it (removeFromSuperview, alpha, addSubview), the width of the resulting image on the screen is way larger than 10 (and when I print the width I can confirm this). Alternatively, if I do this and remove the lines that rotate the image, it doesn't rotate obviously but the width does stay at 10.
I have tried rotating it with this instead
touchBox.transform = CGAffineTransform(rotationAngle: (CGFloat(M_PI_2) - angle))
but the same problem happens. Does anyone have any idea? Thanks in advance!
Could it be you're over-thinking this? There's no need for an image view in order to do this, and replacing the image view on every touchesMoved call is just plain madness. This is just a self-drawing view that implements the three touches methods:
Related
I currently have the code to move the UIImage to wherever I tap on the screen, however my app requires the user to be able to drag the image about on the screen and my current code doesn't do that. I am new to the language so any help would be appreciated. Here is my current code for getting the location of the touch:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?{ if let touch = touches.first { let location = touch.location(in: self.view)
You could do it yourself by implementing both touchesBegan() and touchesMoved(), but you'd be better off using a UIPanGestureRecognzier. I'd suggest finding a sample project that lets you drag views using UIPanGestureRecognzier. It will save you some head-scratching.
I had made cube which is basically a UIView that can be dragged around and the info changes inside the cube. I used the following function to drag the cube in the view. See if it helps
var draggableCube = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
#objc func panGestureDetected(panGestureRecognizer: UIPanGestureRecognizer) {
let translation = panGestureRecognizer.translation(in: self.view)
var changeX : CGFloat = 0
var changeY : CGFloat = 0
if translation.x + self.draggableCube.frame.maxX > self.view.bounds.maxX {
// prevents it to go outside of the bounds from right side
changeX = self.view.bounds.maxX - self.draggableCube.frame.maxX
} else if translation.x + self.draggableCube.frame.minX < self.view.bounds.minX{
// prevents it to go outside of the bounds from right side
changeX = self.view.bounds.minX - self.draggableCube.frame.minX
} else {
// translation is within limits
changeX = translation.x
}
if translation.y + self.draggableCube.frame.maxY > self.view.bounds.maxY {
// prevents it to go outside of the bounds from bottom
changeY = self.view.bounds.maxY - self.draggableCube.frame.maxY
} else if translation.y + self.draggableCube.frame.minY < self.view.bounds.minY {
// prevents it to go outside of the bounds from top
changeY = self.view.bounds.minY - self.draggableCube.frame.minY
} else {
// translation is within limits
changeY = translation.y
}
self.draggableCube.center = CGPoint(x: self.draggableCube.center.x + changeX, y: self.draggableCube.center.y + changeY)
panGestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
if panGestureRecognizer.state == .ended {
// implement action what you want to do after the gragging ended
}
}
I made a simple app with Xcode in swift in which a circular Imageview in the middle of the screen can be moved with swipe-gesture and changes color when touching the outside of the bigger ring (image for reference) and at the same time changes location to the middle of the screen. Change of color and respawn is achieved by an if statement that changes the file of the picture when the euclidian distance between the middle-points of the smaller Imageview and the middle coordinates of the screen exceeds a certain length. However, when the Imageview spawns in what seems to me to be fixed middle coordinates, the spawning-position is slighty moved in the direction of the swipe. And even more so if the swipe-gesture is faster. Thanks for any hints.
Edit: I removed every part that keeps this from being a minimal example for the problem I have and also uploaded the files I for the colored balls. Can anyone tell me how else I could improve my question to get an answer?
Edit: Now I added an animated gif which firstly shows the functionality and later, when I increase dragging speed, also shows the problem that I'm facing.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBOutlet weak var colorImg: UIImageView!
#IBAction func panMade(_ sender: UIPanGestureRecognizer) {
if sender.state == .began || sender.state == .changed {
let translation = sender.translation(in: sender.view)
let changeX = (sender.view?.center.x)! + translation.x
let changeY = (sender.view?.center.y)! + translation.y
sender.view?.center = CGPoint(x: changeX, y: changeY)
sender.setTranslation(CGPoint.zero, in: sender.view)
}
let coordX = colorImg.frame.origin.x+60
let coordY = colorImg.frame.origin.y+60
let centerX = UIScreen.main.bounds.size.width*0.5
let centerY = UIScreen.main.bounds.size.height*0.5
let a = (coordX-centerX) ; let b = (coordY-centerY) ; let c = (a*a) ; let d = (b*b) ; let j = Double(c+d)
if (j.squareRoot() > 112) {
let n = Int.random(in: 1..<4)
colorImg.image = UIImage(named: String(n) + ".png")
sleep(1)
colorImg.center = CGPoint(x: centerX, y: centerY)
}
}
}
I am trying to create a small game where SpriteNode (aka Player) moves up vertically on constant speed. I want to use its angle for steering left or right. However, I am not able to move the Player properly using its angle.
Thank you for your time.
Here is the partial code I wrote:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.previousLocation(in: self)
if location.x < self.size.width / 2 && location.y < self.size.height / 2 {
// Turn Left
print("TURNING LEFT")
turn(left: true)
} else if location.x >= self.size.width / 2 && location.y < self.size.height / 2 {
// Turn Right
print("TURNING RIGHT")
turn(left: false)
} else if location.y > self.size.height / 2 {
// Go Up
print("GOING UP!")
move()
}
}
}
func turn(left: Bool) {
if left {
// Turn Left
let turnAction = SKAction.rotate(byAngle: 0.1, duration: 0.05)
let repeatAction = SKAction.repeatForever(turnAction)
player?.run(repeatAction)
} else {
// Turn Right
let turnAction = SKAction.rotate(byAngle: -0.1, duration: 0.05)
let repeatAction = SKAction.repeatForever(turnAction)
player?.run(repeatAction)
}
}
func move() {
// Move Up
let moveAction = SKAction.moveBy(x: 0, y: 15, duration: 0.5)
let repeatAction = SKAction.repeatForever(moveAction)
player?.run(repeatAction)
}
Using Trigonometry you can determine the sprite's x and y speed in either direction create an angle for the sprite to point towards. A great article that sums up how to do this can be found here.
If you simply wish to literally rotate the sprite it can be done by creating an SKAction for the rotation and running the action on the node.
// Create an action, duration can be changed from 0 so the user can see a smooth transition otherwise change will be instant.
SKAction *rotation = [SKAction rotateByAngle: M_PI/4.0 duration:0];
//Simply run the action.
[myNode runAction: rotation];
Thanks to #makertech81 link, I was able to write below code which works:
func move() {
// Move Up
let playerXPos = sin((player?.zRotation)!) * -playerSpeed
let moveAction = SKAction.moveBy(x: playerXPos, y: playerSpeed, duration: 0.5)
let repeatAction = SKAction.repeatForever(moveAction)
player?.run(repeatAction)
}
Basically, because I know the angle through zRotation and also I know how much Player will move toward Y, I was able to calculate its sin (which is X value). Therefore, moveAction will be properly calculated toward its destination.
Hope it helps.
From the image above, given an initial position of b0(x, y), an end position of b1(x, y) and positions a(x, y) and c(x, y). How can I predetermine if square B0 will move from b0(x, y) to b1(x, y) without getting into contact with rectangle A and C? I believe that the angle will be needed.
Some observations...
If box B's initial position is to the right of the ending position (in the gap), then the box can successfully move to the ending position without colliding with the other boxes only if theta is a counter-clockwise angle (see Figure below). For this test, use the box B's top-right corner and the bottom-left corner of C.
Similarly, If box B's initial position is to the left of the ending position, then it can successfully move to the ending position without colliding with the other boxes only if theta is a counter-clockwise angle (see Figure below). For this test, use the box B's top-left corner and the bottom-right corner of A.
Some code...
First, extend CGPoint to determine the corners of a box.
extension CGPoint {
func bottomLeftCorner(size:CGSize) -> CGPoint {
return CGPoint (x:x - size.width/2.0, y:y - size.height/2.0)
}
func bottomRightCorner(size:CGSize) -> CGPoint {
return CGPoint(x:x + size.width/2.0, y:y - size.height/2.0)
}
func topLeftCorner(size:CGSize) -> CGPoint {
return CGPoint (x:x - size.width/2.0, y:y + size.height/2.0)
}
func topRightCorner(size:CGSize) -> CGPoint {
return CGPoint(x:x + size.width/2.0, y:y + size.height/2.0)
}
}
The following code allows the user to drop/drag box B. While the user moves the box, the code performs an on-the-fly test to see if the box can move into the gap without colliding with the other boxes.
class GameScene: SKScene {
let size1 = CGSize(width: 100, height: 50)
let size2 = CGSize(width: 50, height: 50)
let size3 = CGSize(width: 100, height: 50)
var boxA:SKSpriteNode!
var boxB:SKSpriteNode!
var boxC:SKSpriteNode!
var center:CGPoint!
override func didMove(to view: SKView) {
// This is box B's ending position
center = CGPoint (x:0,y:0)
// Define and add the boxes to the scene
boxA = SKSpriteNode(color: SKColor.yellow, size: size1)
boxB = SKSpriteNode(color: SKColor.red, size: size2)
boxC = SKSpriteNode(color: SKColor.blue, size: size3)
boxA.position = CGPoint(x: -size1.width, y: 0)
boxB.position = CGPoint(x: 0, y: 0)
boxC.position = CGPoint(x: size3.width, y: 0)
boxB.zPosition = 1
addChild(boxA)
addChild(boxB)
addChild(boxC)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
// Allow user to drag box to a new location
boxB.position = location
// Find the appropriate corners
var cornerA:CGPoint!
var cornerB:CGPoint!
var cornerC:CGPoint!
if (boxB.position.x < center.x) {
cornerA = boxA.position.bottomRightCorner(size: boxA.size)
cornerB = boxB.position.topLeftCorner(size: boxB.size)
cornerC = center.topLeftCorner(size: boxB.size)
}
else {
cornerA = center.topRightCorner(size: boxB.size)
cornerB = boxB.position.topRightCorner(size: boxB.size)
cornerC = boxC.position.bottomLeftCorner(size: boxC.size)
}
// Test if box B can move in the gap without colliding
if isCounterClockwise(A: cornerA, B: cornerB, C: cornerC) {
boxB.color = SKColor.green
}
else {
boxB.color = SKColor.red
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Move box B to the ending position
let action = SKAction.move(to: center, duration: 2)
boxB.run(action)
}
// Test direction of angle between line segments AB and AC
func isCounterClockwise (A:CGPoint, B:CGPoint, C:CGPoint) -> Bool {
return (C.y-A.y)*(B.x-A.x) > (B.y-A.y)*(C.x-A.x)
}
}
and a video clip...
Box B turns green if it can move into the gap without colliding and turns red if not.
Is there a way to give an SKNode its own physics? I have an SKShapeNode call "backGround" which I use for the parent node of most of my other nodes. I am constantly moving "background" to the left, to give the illusion that the player is moving forward. However, one of the objects that has "backGround" as a parent node is a pin with a rope hanging from it. When background accelerates to the left, is there a way to make it so the rope doesn't swing back and forth, as ropes tend to do when accelerating or decelerating?
EDIT: Here is my code:
func createRopeNode(pos: CGPoint) -> SKSpriteNode{
let ropeNode = SKSpriteNode(imageNamed: "Ball")
ropeNode.size = CGSize(width: 5, height: 5)
ropeNode.physicsBody = SKPhysicsBody(rectangleOfSize: ropeNode.size)
ropeNode.physicsBody?.affectedByGravity = true
ropeNode.physicsBody?.collisionBitMask = 0
ropeNode.alpha = 1
ropeNode.position = CGPoint(x: pos.x + 0, y: pos.y)
ropeNode.name = "RopePiece"
let text = SKSpriteNode(imageNamed: "RopeTexture")
ropeNode.zPosition = -5
text.runAction(SKAction.rotateByAngle(atan2(-dx!, dy!), duration: 0))
ropeNode.addChild(text)
return ropeNode
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
if (!playerIsConnected){
playerIsConnected = true
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
dx = pin.position.x - playerPoint!.x
dy = pin.position.y - playerPoint!.y
let length = sqrt(pow(dx!, 2) + pow(dy!, 2))
let distanceBetweenRopeNodes = 5
let numberOfPieces = Int(length)/distanceBetweenRopeNodes
var ropeNodes = [SKSpriteNode]()
//adds the pieces to the array at respective locations
for var index = 0; index < numberOfPieces; ++index{
let point = CGPoint(x: pin.position.x + CGFloat((index) * distanceBetweenRopeNodes) * sin(atan2(dy!, -dx!) + 1.5707), y: pin.position.y + CGFloat((index) * distanceBetweenRopeNodes) * cos(atan2(dy!, -dx!) + 1.5707))
let piece = createRopeNode(point)
ropeNodes.append(piece)
world.addChild(ropeNodes[index])
}
let firstJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[0].physicsBody, bodyB: pin.physicsBody, anchor:
CGPoint(x: (ropeNodes[0].position.x + pin.position.x)/2, y: (ropeNodes[0].position.y + pin.position.y)/2))
firstJoint.frictionTorque = 1
self.physicsWorld.addJoint(firstJoint)
for var i = 1; i < ropeNodes.count; ++i{
let nodeA = ropeNodes[i - 1]
let nodeB = ropeNodes[i]
let middlePoint = CGPoint(x: (nodeA.position.x + nodeB.position.x)/2, y: (nodeA.position.y + nodeB.position.y)/2)
let joint = SKPhysicsJointPin.jointWithBodyA(nodeA.physicsBody, bodyB: nodeB.physicsBody, anchor: middlePoint)
joint.frictionTorque = 0.1
self.physicsWorld.addJoint(joint)
}
finalJoint?.frictionTorque = 1
finalJoint = SKPhysicsJointPin.jointWithBodyA(ropeNodes[ropeNodes.count - 1].physicsBody, bodyB: player.physicsBody, anchor:
CGPoint(x: (ropeNodes[ropeNodes.count - 1].position.x + playerPoint!.x)/2, y: (ropeNodes[ropeNodes.count - 1].position.y + playerPoint!.y)/2))
self.physicsWorld.addJoint(finalJoint!)
}
}
else{
physicsWorld.removeJoint(finalJoint!)
playerIsConnected = false
}
}
Anchor points are what you are looking for. Move the anchor point of the scene to only move the "camera" of the scene (what is displayed onscreen). This will not jostle the pin and rope. Keep in mind that the anchor point is on a slightly different scale from the scene.
Where the width of the scene could be 1024, the "width" of the of the anchor point for one scene length is 1 (basically counting as one width of the node). Same for the height, where it could be 768, the "height" would still be 1 in the anchor point coordinate space. So to move half a screen width, move the anchor point 0.5
The anchor point is a CGPoint, so you can go vertically as well. Here's a quick example:
var xValue : Float = 0.75
var yValue : Float = 0.0
self.scene?.anchorPoint = CGPointMake(xValue, yValue);
And for further reading, here's a link to the documentation on anchor points for sprites.