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)
}
}
Related
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)
}
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/
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
I have been using UIPanGestureRecognizer to recognise touches but I want to replace it with a fixed start to end position for my animation. Please see the code below:
panGestureDidMove:
func panGestureDidMove(gesture: UIPanGestureRecognizer) {
if gesture.state == .Ended || gesture.state == .Failed || gesture.state == .Cancelled {
} else {
let additionalHeight = max(gesture.translationInView(view).y, 0)
let waveHeight = min(additionalHeight * 0.6, maxWaveHeight)
let baseHeight = minimalHeight + additionalHeight - waveHeight
let locationX = gesture.locationInView(gesture.view).x
layoutControlPoints(baseHeight: baseHeight, waveHeight: waveHeight, locationX: locationX)
updateShapeLayer()
}
}
layoutControlPoints:
private func layoutControlPoints(baseHeight baseHeight: CGFloat, waveHeight: CGFloat, locationX: CGFloat) {
let width = view.bounds.width
let minLeftX = min((locationX - width / 2.0) * 0.28, 0.0)
let maxRightX = max(width + (locationX - width / 2.0) * 0.28, width)
let leftPartWidth = locationX - minLeftX
let rightPartWidth = maxRightX - locationX
l3ControlPointView.center = CGPoint(x: minLeftX, y: baseHeight)
l2ControlPointView.center = CGPoint(x: minLeftX + leftPartWidth * 0.44, y: baseHeight)
l1ControlPointView.center = CGPoint(x: minLeftX + leftPartWidth * 0.71, y: baseHeight + waveHeight * 0.64)
cControlPointView.center = CGPoint(x: locationX , y: baseHeight + waveHeight * 1.36)
r1ControlPointView.center = CGPoint(x: maxRightX - rightPartWidth * 0.71, y: baseHeight + waveHeight * 0.64)
r2ControlPointView.center = CGPoint(x: maxRightX - (rightPartWidth * 0.44), y: baseHeight)
r3ControlPointView.center = CGPoint(x: maxRightX, y: baseHeight)
}
I am trying to replace the panGestureDidMove with CABasicAnimation to animate the start to end position, something like the code below:
let startValue = CGPointMake(70.0, 50.0)
let endValue = CGPointMake(90.0, 150.0)
CATransaction.setDisableActions(true) //Not necessary
view.layer.bounds.size.height = endValue
let positionAnimation = CABasicAnimation(keyPath:"bounds.size.height")
positionAnimation.fromValue = startValue
positionAnimation.toValue = endValue
positionAnimation.duration = 2.0
view.layer.addAnimation(positionAnimation, forKey: "bounds")
A lot of things are affected as the position changes, how can I achieve this?
If you want fine control of animation, you can use CADisplayLink to do any custom layout or drawing prior to the screens pixel refresh. The run loop attempts to draw 60 frames per second, so with that in mind you can modify your code to simulate touch events.
You'll need to add some properties:
var displayLink:CADisplayLink? // let's us tap into the drawing run loop
var startTime:NSDate? // while let us keep track of how long we've been animating
var deltaX:CGFloat = 0.0 // how much we should update in the x direction between frames
var deltaY:CGFloat = 0.0 // same but in the y direction
var startValue = CGPointMake(70.0, 50.0) // where we want our touch simulation to start
var currentPoint = CGPoint(x:0.0, y:0.0)
let endValue = CGPointMake(90.0, 150.0) // where we want our touch simulation to end
Then whenever we want the animation to run we can call:
func animate()
{
let duration:CGFloat = 2.0
self.currentPoint = self.startValue
self.deltaX = (endValue.x - startValue.x) / (duration * 60.0) // 60 frames per second so getting the difference then dividing by the duration in seconds times 60
self.deltaY = (endValue.y - startValue.y) / (duration * 60.0)
self.startTime = NSDate()
self.displayLink = CADisplayLink(target: self, selector: #selector(self.performAnimation))
self.displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
}
This will set up the display link and determine my how much our touch simulation should move between frames and begin calling the function that will be called prior to every drawing of the screen performAnimation:
func performAnimation(){
if self.startTime?.timeIntervalSinceNow > -2 {
self.updateViewsFor(self.currentPoint, translation: CGPoint(x:self.currentPoint.x - self.startValue.x, y: self.currentPoint.y - self.startValue.y))
self.currentPoint.x += self.deltaX
self.currentPoint.y += self.deltaY
}
else
{
self.displayLink?.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
self.currentPoint = self.startValue
}
}
Here we check if we are within the duration of the animation. Then call updateViewsFor(point:CGPoint,translation:CGPoint) which is what you had in your gesture target and then update our touch simulation if we're within it, else we just reset our properties.
Finally,
func updateViewsFor(point:CGPoint,translation:CGPoint)
{
let additionalHeight = max(translation.y, 0)
let waveHeight = min(additionalHeight * 0.6, maxWaveHeight)
let baseHeight = minimalHeight + additionalHeight - waveHeight
let locationX = point.x
layoutControlPoints(baseHeight: baseHeight, waveHeight: waveHeight, locationX: locationX)
updateShapeLayer()
}
You could also change your panGestureDidMove to:
#objc func panGestureDidMove(gesture: UIPanGestureRecognizer) {
if gesture.state == .Ended || gesture.state == .Failed || gesture.state == .Cancelled {
} else {
self.updateViewsFor(gesture.locationInView(gesture.view), translation: gesture.translationInView(view))
}
}
Edit
There is an easier way of doing this using keyframe animation. But I'm not sure it will animate your updateShapeLayer(). But for animating views we can write a function like:
func animate(fromPoint:CGPoint, toPoint:CGPoint, duration:NSTimeInterval)
{
// Essentually simulates the beginning of a touch event at our start point and the touch hasn't moved so tranlation is zero
self.updateViewsFor(fromPoint, translation: CGPointZero)
// Create our keyframe animation using UIView animation blocks
UIView.animateKeyframesWithDuration(duration, delay: 0.0, options: .CalculationModeLinear, animations: {
// We really only have one keyframe which is the end. We want everything in between animated.
// So start time zero and relativeDuration 1.0 because we want it to be 100% of the animation
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 1.0, animations: {
// we want our "touch" to move to the endValue and the translation will just be the difference between end point and start point in the x and y direction.
self.updateViewsFor(toPoint, translation: CGPoint(x: toPoint.x - fromPoint.x, y: toPoint.y - fromPoint.y))
})
}, completion: { _ in
// do anything you need done after the animation
})
}
This will move the views into place then create a keyframe for where the views end up and animate everything in between. We could call it like:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.animate(CGPointMake(70.0, 50.0), toPoint: CGPointMake(90.0, 150.0), duration: 2.0)
}
I'm creating a game, and I have a canon that shoots bullets.
I have already the code to change the orientation of the canon, :
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self {
if location.x < self.size.width / 2 {
let x = location.x
let y = location.y
let AB = self.size.width / 2 - x
let angleRadians = atan(AB/y)
let angleDegrees = angleRadians * (180 / CGFloat(M_PI))
canon.zRotation = angleDegrees * (CGFloat(M_PI) / 180)
}
if location.x > self.size.width / 2 {
let x = location.x
let y = location.y
let AB = x - self.size.width / 2
let angleRadians = atan(AB/y)
let angleDegrees = angleRadians * (180 / CGFloat(M_PI))
canon.zRotation = -(angleDegrees * (CGFloat(M_PI) / 180))
}
}
}
and I want to determine the direction of the bullet when the user shoots,
I tried with an action (moveBy) but I didn't find a relation between the orientation of the canon, and the vector of the bullet's direction.
If the location of the touch is CGPoint(x : 100, y : 100), the vector cannot be CGVectorMake(100, 100), because it is too big values !
I tested with a moveTo action, the direction of the bullet was perfect, but the bullet stopped on the touch location, and I want that the bullet continue to move forever !
Thanks for your help !