How to move SpriteNode by its angle? - swift

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.

Related

Air Hockey circle-circle impulse on collision in Swift/SpriteKit?

Background: I'm working on a simple game/application in SpriteKit (Swift). It's similar to Air Hockey but with magnets (like tabletop game called Klask). I have already spent a lot of time adding a store, ads, menu, etc. It's been a fun learning project for me so far.
Current method: I am applying an impulse to a ball when the player/mallet strikes the ball. This impulse (which takes in a CGVector(dx:.., dy:..)) is calculated by the touch movement/position frame to frame. So for instance, if the player moved the mallet in the north east direction and a collision between the ball and the player is detected, the ball will move in that northeast direct (up and to the right).
Issue: This doesn't take into account when the player clips the ball, which causes a warpy effect.The player would expect the ball to move in a direction based on where the ball strikes on the players mallet (a round circle) rather than the direction of the touch (which does usually work pretty well).
Desired implementation (demo from acceleroto's Air Hockey):
Current implementation/code:
// In player class
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
bottomTouchIsActive = true
var releventTouch:UITouch!
//convert set to known type
let touchSet = touches
//get array of touches so we can loop through them
let orderedTouches = Array(touchSet)
for touch in orderedTouches
{
//if we've not yet found a relevent touch
if releventTouch == nil
{
//look for a touch that is in the activeArea (Avoid touches by opponent)
if activeArea.contains(CGPoint(x: touch.location(in: parent!).x, y: touch.location(in: parent!).y + frame.height * 0.24))
{
isUserInteractionEnabled = true
releventTouch = touch
}
else
{
releventTouch = nil
}
}
}
if (releventTouch != nil) && lastTouchTimeStamp != nil
{
//get touch position and relocate player
let location = CGPoint(x: releventTouch!.location(in: parent!).x, y: releventTouch!.location(in: parent!).y + frame.height * 0.24)
position = location
//find old location and use pythagoras to determine length between both points
let oldLocation = CGPoint(x: releventTouch!.previousLocation(in: parent!).x, y: releventTouch!.previousLocation(in: parent!).y + frame.height * 0.24)
let xOffset = location.x - oldLocation.x
let yOffset = location.y - oldLocation.y
let vectorLength = sqrt(xOffset * xOffset + yOffset * yOffset)
let seconds = releventTouch.timestamp - lastTouchTimeStamp!
let velocity = 0.015 * Double(vectorLength) / seconds
//to calculate the vector, the velcity needs to be converted to a CGFloat
let velocityCGFloat = CGFloat(velocity)
// NSUserDefaults for more direct access in collision detection
let forceSaveDX = UserDefaults.standard
forceSaveDX.set(velocityCGFloat * xOffset / vectorLength, forKey: "BottomForceDX")
forceSaveDX.synchronize()
let forceSaveDY = UserDefaults.standard
forceSaveDY.set(velocityCGFloat * yOffset / vectorLength, forKey: "BottomForceDY")
forceSaveDY.synchronize()
//Only apply an impulse if the touch is active.
delegate?.bottomTouchIsActive(bottomTouchIsActive, fromBottomPlayer: self)
//update latest touch time for next calculation
lastTouchTimeStamp = releventTouch.timestamp
}
else if (releventTouch != nil) && lastTouchTimeStamp == nil
{
//get touch position and relocate player
let location = CGPoint(x: releventTouch!.location(in: parent!).x, y: releventTouch!.location(in: parent!).y + frame.height * 0.24)
position = location
//find old location and use pythagoras to determine length between both points
let oldLocation = CGPoint(x: releventTouch!.previousLocation(in: parent!).x, y: releventTouch!.previousLocation(in: parent!).y + frame.height * 0.24)
let xOffset = location.x - oldLocation.x
let yOffset = location.y - oldLocation.y
let vectorLength = sqrt(xOffset * xOffset + yOffset * yOffset)
//update latest touch time for next calculation
lastTouchTimeStamp = releventTouch.timestamp
}
}
// In main gameScene
func didBegin(_ contact: SKPhysicsContact)
{
if (contact.bodyA.categoryBitMask == BodyType.ball.rawValue && contact.bodyB.categoryBitMask == BodyType.player.rawValue) && (contact.bodyA.contactTestBitMask == 25 || contact.bodyB.contactTestBitMask == 25) && bottomTouchForCollision == true
{
ball!.physicsBody!.applyImpulse(CGVector(dx: CGFloat(UserDefaults.standard.float(forKey: "BottomForceDX")), dy: CGFloat(UserDefaults.standard.float(forKey: "BottomForceDY"))))
}
}
I apologize for the long question, it's hard to explain what the issue is. But essentially I just need to make the impulse be calculated using where the ball hits the player mallet rather than by the direction of the touch. If this can be done directly in the collision detection function, that would be even better as there would be no extra frame dedicated to calculating the touch direction. I've been stuck for a while.
I think it would be best to incorporate the speed of the touch and the position where the ball strikes the mallet to have the most realistic impulse. I just don't know how to calculate the vector this way. I'm open to all ideas!

Rotate SKSpritenode by a changing number of degrees

How can I define an SKAction, and then update the number of degrees my node will rotate? I am trying to define it with variables, but when I update the variable values the action doesn't update.
var degreesToRotate = 4
var direction = 1
let action = SKAction.rotate(byAngle: CGFloat(degreesToRotate * direction), duration: TimeInterval(2))
charector.run(SKAction.repeatForever(action))
direction = -1
import SpriteKit
import GameplayKit
//Extensions borrowed from here : http://stackoverflow.com/a/29179878/3402095
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
let kActionKey = "rotate"
class GameScene:SKScene {
let purpleCube = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let yellowCube = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
addChild(purpleCube)
purpleCube.position.y = purpleCube.size.height
purpleCube.name = "purple"
addChild(yellowCube)
yellowCube.position.y = -yellowCube.size.height
yellowCube.name = "yellow"
let rotate = SKAction.rotate(byAngle: CGFloat(-M_PI * 2.0), duration: 5)
let loop = SKAction.repeatForever(rotate)
purpleCube.run(loop, withKey: kActionKey)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touch = touches.first {
let location = touch.location(in: self)
if let cube = atPoint(location) as? SKSpriteNode {
if let name = cube.name {
switch name {
case "purple":
if let action = purpleCube.action(forKey: kActionKey){
purpleCube.run(action.reversed(), withKey: kActionKey)
}
case "yellow":
if action(forKey: "rotating") == nil{
yellowCube.run(SKAction.rotate(byAngle: CGFloat(4.degreesToRadians), duration: 0.1), withKey: kActionKey)
}
default:
break
}
}
}
}
}
}
In this example, there are two nodes that have been rotated in a two different ways. Purple node is rotated constantly at certain speed in clockwise direction. To achieve this, I've created an action that rotates a sprite by 360 degrees... That would be one revolution, which will be repeated forever, thus the sprite will be rotated forever.
About the yellow node...It will be rotated by 4 degrees every time you tap on it. Currently you have to wait that sprite stop rotating so you can rotate it more. This is optional of course, I just wanted to show you the usefulness of action keys.
Rotation Direction
Since in SpriteKit 0 degrees specifies positive x-axis and a positive angle is in counterclockwise direction, I rotated purple cube by -360 degrees, which rotates the sprite in clockwise direction. To find out more about SpriteKit coordinate system, read this documentation section.
Radians Vs Degrees
As you can see, I am talking in degrees, rather than in radians... That is because it would be really hard to say, I rotated the sprite by 6.2831853072 radians :) That is why I used extensions which does conversion from degrees to radians and vice-versa. You might use this often so I added them for you.

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

shooting moving location in swift

I'm working in a game for my college and i stuck in one thing.
when i try to shoot with my hero the bullet go's up only or side i need my bullet go's where ever my hero rotate plz help me this is my code
func spownBullet() {
let bullet = SKSpriteNode(imageNamed: "Bullet")
let hero = self.childNodeWithName("hero") as! SKSpriteNode
bullet.zPosition = 1
bullet.position = CGPointMake(hero.position.x, hero.position.y)
bullet.size = CGSize(width: 20, height: 30)
let bulletact = SKAction.moveToY(self.size.height + 300, duration: 1)
bullet.runAction(SKAction.repeatActionForever(bulletact))
self.addChild(bullet)
}
If I understand correctly you want your bullet to go a certain distance (e.g. 300) in the same angle as your "hero" sprite is rotated.
Here's a function to compute the destination point:
// destination point moving at angle
// ---------------------------------
func endPointMovingSpriteFrom(origin:CGPoint, atAngle angle:CGFloat,forDistance distance:CGFloat ) -> CGPoint
{
let deltaX = distance / sin(angle + CGFloat(M_PI)/2)
let deltaY = distance / cos(angle + CGFloat(M_PI)/2)
return CGPoint(x: origin.x - deltaX, y:origin.y - deltaY)
}
You can use it to compute the destination and then use the destination in a moveTo action:
let bulletDestination = endPointMovingSpriteFrom(hero.position, atAngle:hero.zRotation, forDistance:300)
let bulletact = SKAction.moveTo(bulletDestination, duration: 1)

Rotate SkSpriteNode around internal point that is not .anchorPoint

So I'm using this code to rotate an object either clockwise or anti-clockwise.
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let touchPosition = touch.locationInNode(self)
let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise
if currentRotationDirection != newRotationDirection && currentRotationDirection != .none {
reverseRotation()
currentRotationDirection = newRotationDirection
}
else if (currentRotationDirection == .none) {
setupRotationWith(direction: newRotationDirection)
currentRotationDirection = newRotationDirection
}
}
func reverseRotation(){
let oldRotateAction = ship.actionForKey("rotate")
let newRotateAction = SKAction.reversedAction(oldRotateAction!)
ship.runAction(newRotateAction(), withKey: "rotate")
}
func stopRotation(){
ship.removeActionForKey("rotate")
}
func setupRotationWith(direction direction: rotationDirection){
let angle : CGFloat = (direction == .clockwise) ? CGFloat(M_PI) : -CGFloat(M_PI)
let rotate = SKAction.rotateByAngle(angle, duration: 2)
let repeatAction = SKAction.repeatActionForever(rotate)
ship.runAction(repeatAction, withKey: "rotate")
}
My question is, this creates clockwise or anticlockwise rotation around the SKSpriteNode's anchor point.
However I would like the object to rotate about another point inside of the SKSprite (something like 4/5's of the way up from the base of the sprite image). I assume I cannot use a set point defined through CGPointMake as I would like the sprite to independently move around the screen and this would not work?
Any suggestions anyone?
Have you tried setting the anchorPoint property of your sprite to the point around which you want the sprite to rotate?
ship.anchorPoint = CGPoint(x: 0, y: 4/5)
Is this what you were looking for?