Playing around with SKSprites in the IOS SpriteKit, and I basically want to have a sprite randomly move a certain direction for a certain distance, then pick a new random direction and distance.. Simple enough, create a method that generates the random numbers then creates the animation and in the completion block of the animation have it callback the same routine.
THis does work, but it also keeps the animation from moving at the same velocity since the animations are all based on duration.. If the object has to move 100 it moves at 1/2 the speed it moves if the next random tells it to move 200... So how the heck do I have it move with a consistent speed?
#Noah Witherspoon is correct above - use Pythagorus to get a smooth speed:
//the node you want to move
SKNode *node = [self childNodeWithName:#"spaceShipNode"];
//get the distance between the destination position and the node's position
double distance = sqrt(pow((destination.x - node.position.x), 2.0) + pow((destination.y - node.position.y), 2.0));
//calculate your new duration based on the distance
float moveDuration = 0.001*distance;
//move the node
SKAction *move = [SKAction moveTo:CGPointMake(destination.x,destination.y) duration: moveDuration];
[node runAction: move];
Using Swift 2.x I've solved with this little method:
func getDuration(pointA:CGPoint,pointB:CGPoint,speed:CGFloat)->NSTimeInterval {
let xDist = (pointB.x - pointA.x)
let yDist = (pointB.y - pointA.y)
let distance = sqrt((xDist * xDist) + (yDist * yDist));
let duration : NSTimeInterval = NSTimeInterval(distance/speed)
return duration
}
With this method i can use an ivar myShipSpeed and directly call my actions for example :
let move = SKAction.moveTo(dest, duration: getDuration(self.myShip.position,pointB: dest,speed: myShipSpeed))
self.myShip.runAction(move,completion: {
// move action is ended
})
As an extension to #AndyOS's answer, if you're going to move only along one axis (X for example) you can simplify the math by doing this instead:
CGFloat distance = fabs(destination.x - node.position.x);
Related
I'm currently developing a game with a cannon that shoots a projectile, I'm having trouble figuring out how to get the projectile to fire at the angle based on the touch location and how far away it is from the cannon. this is the code i'm using below.
let dx = cannon.position.x - (touchLocation.x)
let dy = cannon.position.y - (touchLocation.y)
let angle = atan2(dy, dx)
bullet.zRotation = angle
bulletspeed = Double.random(in: 1...6)
//let angle1 = Double.random(in: 0.2...5); let angle2 = Double.random(in: 1...4)
// dx must be somewhere between 0.2 to 5
bullet.physicsBody?.applyImpulse(CGVector(dx: -angle , dy: -angle))
this doesn't seem to work and i've resorted to using the angle as my x and y values which works but not well.
I'm trying to get the cannon to fire based on the angle of the touch location and to change the speed/power of the projectile by how far way the touch location is from the cannon. How can I do this?
The vector you need to use takes in dx and dy as parameters. You already have these but you say the speed is too fast. That’s because the length of the vector is the speed.
So in your example the speed can be calculated like...
sqrt(dx*dx+dy*dy)
What you need to do is calculate a ‘unit vector’ that is, a vector with length equal to one.
You can do this by dividing dx and dy by the length of the vector.
So...
touchDX = //your calculation
touchDY = //your calculation
touchLength = sqrt(touchDX*touchDX+touchDY*touchDY)
unitVectorDX = touchDX / touchLength
unitVectorDY = touchDY / touchLength
// now put the speed you want in...
speed = 10
vector = CGVector(dx: unitVectorDX * speed, dy: unitVectorDY * speed)
Now, if you use the vector in your impulse it will have the correct direction and speed.
Quick side note, I’m typing on my iPad so don’t have access to code completion etc... you may be able to do this using APIs on CGVector. I think I remember a ‘unitVector’ property that returns a unit vector. But I may be mistaken.
I'm trying to make an air hockey game using SpriteKit. I trying to make the pucks draggable but I can't make them continue to move after the touch has ended. Right now I am binding the touch and the puck and setting it's position when the touch moves.
using the physics system:
override func update(currentTime: NSTimeInterval) {
for (touch, node) in draggingNodes {
let targetPosition = touch.locationInNode(self)
let distance = hypot(node.position.x - targetPosition.x, node.position.y - targetPosition.y)
var damping = sqrt(distance * 100)
if (damping < 0) {
damping = 0.0
}
node.physicsBody!.linearDamping = damping
node.physicsBody!.angularDamping = damping
let translation = CGPointMake(targetPosition.x - node.position.x, targetPosition.y - node.position.y)
node.physicsBody!.velocity = CGVectorMake(translation.x * 100, translation.y * 100);
}
}
You're likely going to need to do a lot more reading. Either you'll use the physics system:
In which case you'll impart an impulse onto the puck on the touch end event, having calculated the speed based on a delta in position and delta in time from last frame to current frame (or some type of average over more than 1 frame).
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Physics/Physics.html
[OR]
You'll manually set velocity on the puck (not using the physics system), and then manually update the puck's position per frame based on that velocity, then recalculate its vector when it comes into contact with another object based on angle of of incidence.
I essentially want the "sprites" to collide when they stick together. However, I don't want the "joint" to be rigid; I essentially want the sprites to be able to move around as long as they are in contact with each other. Imagine two circles connected, and you can move one circle around the other, as long as it remains in contact.
I found this question: How to make one body stick to another moving object in SpriteKit and a lot of other resources that explain how to make sprites stick upon collision, but they all use SKJoints, which are rigid are not really flexible.
I guess another way to phrase it would be to say that I want the sprites to stick, but I want them to be able to "slide" on each other.
Well, I can think of one workaround, but this wouldn't work with non-normal polygons.
Sticking (pun unintended) with your circles example, what if you lock the position of the circle?
let circle1 = center circle
let circle2 = movable circle
Knowing the width of both circles, you can place in the update function that the position should be exactly the distance of:
((circle1.frame.width / 2) + (circle2.frame.width / 2))
If you're up to it, here's some code to help you on your way.
override func update(currentTime: CFTimeInterval) {
{
let distance = hypotf(Float(circle1.position.x - circle2.position.x), Float(circle1.position.y - circle2.position.y))
//calculate circle distances from each other
let radius = ((circle1.frame.width / 2) + (circle2.frame.width / 2))
//distance of circle positions
if distance != radius
{
//if distance is less or more than radius
let pointA = circle1.position
let pointB = circle2.position
let pointC = CGPointMake(pointB.x + 2, pointB.y)
let angle_ab = atan2(pointA.y - pointB.y, pointA.x - pointB.x)
let angle_cb = atan2(pointC.y - pointB.y, pointC.x - pointB.x)
let angle_abc = angle_ab - angle_cb
//get angle of circles from each other using atan2
let vectorx = cos(angle_abc)
let vectory = sin(angle_abc)
//convert angle into vectors
let x = circle1.position.x + radius * vectorx
let y = circle1.position.y + radius * vectory
//get new coordinates from vector, radius and center circle position
circle2.position = CGPointMake(x, y)
//set new position
}
}
Well you need to write code to make sure the movable circle, is well movable.
But, this should work.
I haven't tested this yet though, and I haven't even learned geometry let alone trig in school yet.
If I'm reading your question as you intended it, you can still use joints- just create actions with Inverse Kinematic constraints that allow rotation and translation around the contacting circles' joint.
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKAction_Ref/index.html#//apple_ref/doc/uid/TP40013017-CH1-SW72
This is language-agnostic question, more about model of my game.
I have a snake game with elements, but I move the elements smoothly, they don't just move 1 block each time, but instead they move some amount of pixels every frame.
I have an update loop that calculates the positions of the element, but I am stuck on correct calculations.
I have heading for each element:
typedef NS_ENUM(int, kElementHeading)
{
kElementHeadingNorth = 1,
kElementHeadingSouth,
kElementHeadingEast,
kElementHeadingWest
};
I also have velocity (x, y) that determines in what direction snake is going. I have problem with snake movement, because my elements are in wrong positions. I managed to localize the thing for 2 elements, but my solution fails on more elements.
First solution I tried is to save point of rotation where the head changes direction. This worked, but due to different circumstances element can move different amount of pixels each turn. Often the element would skip the point. I tried increasing the zone where it should rotate, but it adds up error. I tried fixing this error, but element would still separate from snake (quite often).
On the second try I decided to keep the snake head in center of the screen and move the world around it. It worked good for 2 elements, as I just smoothly move the next element to desired position relatively to head. But this fails badly on more elements. If you make fast turns they start dancing and not following the path.
Third thing that I tried is leaving a path for other elements to follow. But that didn't work because I intend to keep my snake on center of the screen and technically it never moves to create a path.
I'm looking to replicate the movement pattern like in Nimble Quest (or any snake).
How should I implement snake elements moving to have no errors?
Here is my code for the first method, problem with it is that often the elements would fall off. The code is pretty self-explanatory. Rotation points are the places where to change direction.
CFTimeInterval delta = self.lastTime - currentTime;
CGPoint currentPosition = self.playerSnake.head.sprite.position;
CGPoint velocity = self.playerSnake.velocity;
self.playerSnake.head.sprite.position = CGPointMake(currentPosition.x + velocity.x * delta * CONSTANTSPEEDFACTOR , currentPosition.y + velocity.y * delta * CONSTANTSPEEDFACTOR);
for (SnakeElement *element in self.playerSnake.elements) {
CGPoint currentPositionE = element.sprite.position;
CGPoint velocityE = element.velocity;
element.sprite.position = CGPointMake(currentPositionE.x + velocityE.x * delta * CONSTANTSPEEDFACTOR , currentPositionE.y + velocityE.y * delta * CONSTANTSPEEDFACTOR);
}
BOOL markToDelete = NO;
NSDictionary *deleteDictionary;
for (NSDictionary *dict in self.playerSnake.rotationPoints) {
CGPoint positionCoordinate = CGPointFromString(dict[#"position"]);
CGPoint velocityNew = CGPointFromString(dict[#"velocity"]);
double newAngle = [dict[#"angle"] doubleValue];
for (SnakeElement *element in self.playerSnake.elements) {
int xDifference = element.sprite.position.x - positionCoordinate.x;
int yDifference = element.sprite.position.y - positionCoordinate.y;
if ((xDifference > -2 && xDifference < 2) && (yDifference > -2 && yDifference < 2) ) {
element.velocity = velocityNew;
element.sprite.position = CGPointMake(element.sprite.position.x + xDifference, element.sprite.position.y + yDifference);
SKAction *action = [SKAction rotateToAngle:newAngle duration:0.2 shortestUnitArc:YES];
[element.sprite runAction:action];
if ([element isEqual:[self.playerSnake.elements lastObject]]) {
markToDelete = YES;
deleteDictionary = dict;
}
}
}
}
[self.playerSnake.rotationPoints removeObject:deleteDictionary];
If I try increase the catch zone for the turning point, the elements tend to fall off more often then when it is 1 or 2 pixels wide. I'm not sure why this happens.
This is what I was suggesting you do in the comments in terms of handling your turning on points :
1.. calculate the distance that the element should move that frame based on speed and your elapsed time since last frame. (delta)
2.. calculate distance from element's current position to the turn point. This is the beforeDistance I spoke of in the comments
3.. calculate the distance the element should move towards the NEW target turning point AFTER the turn
afterDistance = distanceToMoveThisFrame - beforeDistance
4.. Calculate the new position for your element, starting at the current turning point towards the next target turning point of the element using afterDistance
If you follow this logic, you will NEVER overshoot or undershoot the turning point.
I am making a game using Cocos2D and Kobold2D. In my game, I have a ship that I want to move to where the player taps, using this code:
KKInput * input = [KKInput sharedInput];
CGPoint tap = [input locationOfAnyTouchInPhase:KKTouchPhaseBegan];
if (tap.x != 0 && tap.y != 0)
{
[ship stopAllActions]; // Nullifies previous actions
int addedx = tap.x - ship.position.x;
int addedy = tap.y - ship.position.y;
int squaredx = pow(addedx, 2);
int squaredy = pow(addedy, 2);
int addedSquares = squaredx + squaredy;
int distance = pow(addedSquares, 0.5);
[ship runAction: [CCMoveTo actionWithDuration:distance/100 position:tap]];//makes ship move at a constant speed
}
The ship generally moves as I expect it to. However, if I tap near the ship, instead of smoothly moving to the tap location, it jumps to that location. How do I fix this?
Your logic is right but your calculation is lack precision.
Especially when you calc the fly time as distance/100. That means when distance < 100 the result is 0, so the ship is jumping to the destination.
Basically, you should use float instead of int when dealing with positions in cocos2d. And there are couple functions can do the work nicely.
You may change your code to this:
[ship stopAllActions]; // Nullifies previous actions
float distance = ccpDistance(tap, ship.position);
[ship runAction: [CCMoveTo actionWithDuration:distance/100.0f position:tap]];
First of All you can use the ccpDistance function of cocos2d to calculate distance between two point.
And it jumps because if u tap too near the distance is too small suppose 2 or 10 ... the you are dividing it on 100
e.g 5/100 = 0.05 which is too low that's why it jumps.
You need to handle it manually i think some thing like if(speed<1) speed++; adding 1 to speed. shortcut solution :)