Rotating a UIView to point at another UIView - swift

I'm trying to get a UIView called object to rotate to point at the center of another UIView called orig. I can't seem to be able to calculate the angle correctly. My trig is a bit rusty so I can't figure out how my math is wrong.
let y0 = object.center.y
let y1 = orig?.center.y
let x0 = object.center.x
let x1 = orig?.center.x
let angle = atan2((y1! - y0), (x1! - x0)) * 180 / CGFloat.pi
rotateTo(object: object, degrees: angle, time: deplexed[1] as! CGFloat)

To make the top of rotator view point at the target point.
let direction = CGPoint(x: targetPoint.x - rotatorView.center.x, y: targetPoint.y - rotatorView.center.y)
var angle = atan2(direction.y, direction.x)
rotatorView.transform = CGAffineTransform(rotationAngle: angle + .pi/2)
atan2 returns zero if point is to the right.
If you want to convert the atan2 result to degrees:
if angle < 0 {
angle += .pi * 2
}
let degrees = angle * 180.0/.pi
You add a full circle if the angle is negative. 0 degrees points to the right.

Create a default object and suppose it's a button :
let button = UIButton()
if button.transform == CGAffineTransform.identity {
UIView.animate(withDuration: 0.5, animations: {
button.transform = CGAffineTransform(rotationAngle: self.radians(degrees: 180))
})
} else {
UIView.animate(withDuration: 0.5, animations: {
button.transform = CGAffineTransform.identity
})
}
func radians(degrees: CGFloat) -> CGFloat {
return CGFloat(degrees * CGFloat.pi / degrees)
}

Related

SpriteNode movement and rotation in Swift 4.1

I am trying to get my sprite to move and rotate to the location of my touch and follow as I move my finger around the screen. The movement part is working fine but I can't seem to get it to rotate. My moveAndRotate function is below.
func moveAndRotate(spriteNode: SKSpriteNode, toPosition position: CGPoint) {
let angle = atan2(position.y - player.position.y, position.x - player.position.x)
let rotateAction = SKAction.rotate(toAngle: angle - -(CGFloat(Double.pi / 2)), duration: 0.05, shortestUnitArc: true)
player.run(rotateAction)
let offsetX = position.x - player.position.x
let offsetY = position.y - player.position.y
let normal = simd_normalize(simd_double2(x: Double(offsetX), y: Double(offsetY)))
velocity = CGVector(dx: CGFloat(normal.x) * movePointsPerSecond, dy: CGFloat(normal.y) * movePointsPerSecond)
}
The duration was set to o.5 on the rotateAction, change it to 0 and it works fine.

How to properly transform UIView's scale on UIScrollView movement

To have a similar effect to Snapchat's HUD movement, I have created a movement of the HUD elements based on UIScollView's contentOffset. Edit: Link to the Github project.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.view.layoutIfNeeded()
let factor = scrollView.contentOffset.y / self.view.frame.height
self.transformElements(self.playButton,
0.45 + 0.55 * factor, // 0.45 = desired scale + 0.55 = 1.0 == original scale
Roots.screenSize.height - 280, // 280 == original Y
Roots.screenSize.height - 84, // 84 == minimum desired Y
factor)
}
func transformElements(_ element: UIView?,
_ scale: CGFloat,
_ originY: CGFloat,
_ desiredY: CGFloat,
_ factor: CGFloat) {
if let e = element {
e.transform = CGAffineTransform(scaleX: scale, y: scale) // this line lagging
let resultY = desiredY + (originY - desiredY) * factor
var frame = e.frame
frame.origin.y = resultY
e.frame = frame
}
}
With this code implemented the scroll as well as the transition appeared to be "laggy"/not smooth. (Physical iPhone 6S+ and 7+).
Deleting the following line: e.transform = CGAffineTransform(scaleX: scale, y: scale) erased the issue. The scroll as well as the Y-movement of the UIView object is smooth again.
What's the best approach to transform the scale of an object?
There are no Layout Constraints.
func setupPlayButton() {
let rect = CGRect(x: Roots.screenSize.width / 2 - 60,
y: Roots.screenSize.height - 280,
width: 120,
height: 120)
self.playButton = UIButton(frame: rect)
self.playButton.setImage(UIImage(named: "playBtn")?.withRenderingMode(.alwaysTemplate), for: .normal)
self.playButton.tintColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
self.view.addSubview(playButton)
}
This is happening because you are applying both: transform and frame. It will be smoother, if you apply only transform. Update your transformElements function as below:
func transformElements(_ element: UIView?,
_ scale: CGFloat,
_ originY: CGFloat,
_ desiredY: CGFloat,
_ factor: CGFloat) {
if let e = element {
e.transform = CGAffineTransform(scaleX: scale, y: scale).translatedBy(x: 0, y: desiredY * (1 - factor))
}
}
You can make these kinds of animation smoother by creating an animation then setting the speed of the layer to 0 and then changing the timeOffset of the layer.
first add the animation in the setupPlayButton method
let animation = CABasicAnimation.init(keyPath: "transform.scale")
animation.fromValue = 1.0
animation.toValue = 0.45
animation.duration = 1.0
//Set the speed of the layer to 0 so it doesn't animate until we tell it to
self.playButton.layer.speed = 0.0;
self.playButton.layer.add(animation, forKey: "transform");
next in the scrollViewDidScroll change the timeOffset of the layer and move the center of the button.
if let btn = self.playButton{
var factor:CGFloat = 1.0
if isVertically {
factor = scrollView.contentOffset.y / self.view.frame.height
} else {
factor = scrollView.contentOffset.x / Roots.screenSize.width
var transformedFractionalPage: CGFloat = 0
if factor > 1 {
transformedFractionalPage = 2 - factor
} else {
transformedFractionalPage = factor
}
factor = transformedFractionalPage;
}
//This will change the size
let timeOffset = CFTimeInterval(1-factor)
btn.layer.timeOffset = timeOffset
//now change the positions. only use center - not frame - so you don't mess up the animation. These numbers aren't right I don't know why
let desiredY = Roots.screenSize.height - (280-60);
let originY = Roots.screenSize.height - (84-60);
let resultY = desiredY + (originY - desiredY) * (1-factor)
btn.center = CGPoint.init(x: btn.center.x, y: resultY);
}
I couldn't quite get the position of the button correct - so something is wrong with my math there, but I trust you can fix it.
If you want more info about this technique see here: http://ronnqvi.st/controlling-animation-timing/

Xcode Swift SpriteKit SkSpriteNode Y value isn't moving but X is

In the move Action, the Y value isn't changing and i don't know why because the X value is changing. The changeY variable is doing its job with the correct value of 5.0 or -5.0 but the Y value on the "player" SkSpriteNode isn't changing. Please help.
override func update(_ currentTime: TimeInterval) {
let percent = touchLocation.x / size.width
let newAngle = percent * 180 - 180
print(newAngle)
if playButtonPressed{
var changeY = 0.0
if newAngle >= -180{
changeY = 5.0
}
else{
changeY = -5.0
}
player.zRotation = CGFloat(newAngle) * CGFloat(M_PI) / 180.0
print(changeY)
var move = SKAction.moveBy(x: -(player.position.x+1), y: CGFloat(changeY), duration: 2000.0)
player.run(move)
}
}
It's not entirely clear to me what you ultimate goal is, but your calculations seems of. Especially the fact that the duration for the movement is 2000 seconds. That's more than 30 minutes(!) The x-calculations also suggests that you actually planned to use moveTo rather than moveBy as you're currently doing.
The below is based on your code but as I'm not sure what your actually aiming for this does the following: Moves left+up if the touch is on the left-hand side of the screen, moves down+right if the touch is on the right-hand side of the screen...
override func update(_ currentTime: TimeInterval) {
if playButtonPressed {
let percent = touchLocation.x / size.width
let newAngle = percent * 180 - 180
player.zRotation = CGFloat(newAngle) * CGFloat(M_PI) / 180.0
let moveRightAndUp = touchLocation.x>size.width/2
let changeY: CGFloat = moveRightAndUp ? 5.0 : -5.0
let changeX: CGFloat = moveRightAndUp ? 1.0 : -1.0
let move = SKAction.moveBy(x: changeX, y: changeY, duration: 1/60)
player.run(move)
}
}

Applying an impulse to bullets

I have player (SKSpriteNode) he can move and rotate, and I want to shoot five bullets from him, but with another angle. I use this code:
let sinus = sin(player.zRotation)
let cosinus = cos(player.zRotation)
bullet.physicsBody!.applyImpulse(CGVector(dx: -sinus * 100, dy: cosinus * 100))
But, i do not know how to correctly set the vector with angle. I try to make something like this:
Can anyone help me please!
According with this diagram:
you must change the zRotation of your bullets.
Remember that zRotation is expressed in radians so if you need:
extension CGFloat {
var radiansToDegrees: CGFloat {
return self * CGFloat(180.0 / M_PI)
}
var degreesToRadians: CGFloat {
return self * CGFloat(M_PI / 180.0)
}
}
To rotate your bullets you can also use (also this one is expressed in radians) :
let rotate = SKAction.rotateByAngle(angle, duration: 0.01)
You can calculate also the speed and the angle of your bullet impulses :
extension CGVector {
func speed() -> CGFloat {
return sqrt(dx*dx+dy*dy)
}
func angle() -> CGFloat {
return atan2(dy, dx)
}
}
If you need you can connect these element with the screen touches as explained in detail here
About your comments you can use this formula (already explained in my link):
let angle = atan2(location.y - cannon.position.y , location.x - cannon.position.x)
cannon.zRotation = angle - CGFloat(M_PI_2)

Shooting bullets from SKSpriteNode

I try to build 2D - top down game, and I have player (SKSpriteNode) he can move and rotate, and I want to shoot two bullets from him.
I use this code to shoot:
func setBullet(player: Player, bullet: Int)
{
let weaponPosition = scene!.convertPoint(player.weapon.position, fromNode: player)
var xPos, yPos: CGFloat!
let sinus = sin(player.zRotation)
let cosinus = cos(player.zRotation)
if bullet == 1
{
xPos = converted.x - sinus * player.size.height / 2
yPos = converted.y + cosinus * player.size.height / 2
}
else if bullet == 2
{
xPos = weaponPosition.x - sinus * player.size.height / 2
yPos = weaponPosition.y + cosinus * player.size.height / 2
}
position = CGPoint(x: xPos, y: yPos)
physicsBody!.applyImpulse(CGVector(dx: -sinus * normalSpeed, dy: cosinus * normalSpeed))
}
But, i do not know how to correctly set the position...
I try to make something like this
(Green dots - this is a bullets). Can anyone help me please!
Shooting multiple bullets in the same direction is fairly straightforward. The key is to determine the bullets' initial positions and direction vectors when the character is rotated.
You can calculate a bullet's initial position within the scene by
let point = node.convertPoint(weapon.position, toNode: self)
where node is the character, weapon.position is non-rotated position of a gun, and self is the scene.
Typically, a bullet moves to the right, CGVector(dx:1, dy:0), or up, CGVector (dx:0, dy:1), when the character is not rotated. You can calculate the direction of the impulse to apply to the bullet's physics body by rotating the vector by the character's zRotation with
func rotate(vector:CGVector, angle:CGFloat) -> CGVector {
let rotatedX = vector.dx * cos(angle) - vector.dy * sin(angle)
let rotatedY = vector.dx * sin(angle) + vector.dy * cos(angle)
return CGVector(dx: rotatedX, dy: rotatedY)
}
Here's an example of how to shoot two bullets from a rotated character:
struct Weapon {
var position:CGPoint
}
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed:"Spaceship")
let dualGuns = [Weapon(position: CGPoint(x: -15, y: 15)), Weapon(position: CGPoint(x: 15, y: 15))]
let singleGun = [Weapon(position: CGPoint(x: 0, y: 15))]
let numGuns = 1
// If your character faces up where zRotation == 0, offset by pi
let rotationOffset = CGFloat(M_PI_2)
override func didMoveToView(view: SKView) {
scaleMode = .ResizeFill
sprite.position = view.center
sprite.size = CGSizeMake(25, 25)
self.addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let _ = touches.first {
let action = SKAction.rotateByAngle(CGFloat(M_PI_4/2), duration:1)
sprite.runAction(action) {
[weak self] in
if let scene = self {
switch (scene.numGuns) {
case 1:
for weapon in scene.singleGun {
scene.shoot(weapon: weapon, from: scene.sprite)
}
case 2:
for weapon in scene.dualGuns {
scene.shoot(weapon: weapon, from: scene.sprite)
}
default:
break
}
}
}
}
}
func shoot(weapon weapon:Weapon, from node:SKNode) {
// Convert the position from the character's coordinate system to scene coodinates
let converted = node.convertPoint(weapon.position, toNode: self)
// Determine the direction of the bullet based on the character's rotation
let vector = rotate(CGVector(dx: 0.25, dy: 0), angle:node.zRotation+rotationOffset)
// Create a bullet with a physics body
let bullet = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(4,4))
bullet.physicsBody = SKPhysicsBody(circleOfRadius: 2)
bullet.physicsBody?.affectedByGravity = false
bullet.position = CGPointMake(converted.x, converted.y)
addChild(bullet)
bullet.physicsBody?.applyImpulse(vector)
}
// Rotates a point (or vector) about the z-axis
func rotate(vector:CGVector, angle:CGFloat) -> CGVector {
let rotatedX = vector.dx * cos(angle) - vector.dy * sin(angle)
let rotatedY = vector.dx * sin(angle) + vector.dy * cos(angle)
return CGVector(dx: rotatedX, dy: rotatedY)
}
}
Suppose your player is a circle maked with SKShapeNode or SKSpriteNode.
Both of them have the frame property:
let f = player.frame
So, the first bullet can be in this position:
let firstBullet.position = CGPointMake(player.position.x-(f.width/2),player.position.y)
let secondBullet.position = CGPointMake(player.position.x+(f.width/2),player.position.y)
To know it during rotation do:
let firstBulletXPos = firstBullet.position.x - sinus * bullet.size.height / 2
let firstBulletYPos = firstBullet.position.y + cosinus * bullet.size.height / 2
let secondBulletXPos = secondBullet.position.x - sinus * bullet.size.height / 2
let secondBulletYPos = secondBullet.position.y + cosinus * bullet.size.height / 2