I am developing a game that requires a character SKNode *character to "randomly" move around an SKScene by following a randomly generated path. My question, is how can I effectively and efficiently edit or extend this path so that the character is always following a never ending path?
Here is an outline with some code snippets of how I am approaching this:
1. Create a CGMutablePathRef that will represent the path for the character to follow.
Here is the method that I am using to generate the path:
- (void)createPath {
// cgpath declared outside of this method to be retained
cgpath = CGPathCreateMutable();
// For simplicity, I am only dealing with positive random numbers for now
CGPoint s = CGPointMake(character.position.x, character.position.y);
CGPoint e = CGPointMake([self randomNumber] + character.position.x, [self randomNumber] + character.position.y);
CGPoint cp1 = CGPointMake([self randomNumber] + character.position.x, [self randomNumber] + character.position.y);
CGPoint cp2 = CGPointMake([self randomNumber] + character.position.x, [self randomNumber] + character.position.y);
CGPathMoveToPoint(cgpath, NULL, s.x, s.y);
CGPathAddCurveToPoint(cgpath, NULL, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);
SKAction *planeDestroy = [SKAction followPath:cgpath asOffset:NO orientToPath:NO duration:5];
[character runAction:planeDestroy];
CGPathRelease(cgpath);
}
- (int)randomNumber {
return (int)(arc4random_uniform(100) + randPointRad);
}
2. Run an SKAction in the scene to run this method and make the character follow the path.
Here is the code that I am using:
SKAction *wait = [SKAction waitForDuration:5];
SKAction *bla = [SKAction runBlock:^{
[self createPath];
}];
SKAction *randomMovement = [SKAction sequence:#[wait,bla]];
[self runAction:[SKAction repeatActionForever: randomMovement] withKey:#"randomMovementAction"];
3.* (This is step I am having trouble with) Edit / extend the current cgpath with newly generated random points to continuously move the character randomly around the scene.
I understand that the path that I am creating utilizes the logic shown in the below image
Providing Points of Control (POC), I am able to generate a path from defined starting and stopping points.
MY GOAL was to add new random control points to this path, in tern redefining the end point each addition (to some new random point some X distance from the previous point, giving some sense of continuity and coverage), then removing the previous control points (to conserve memory). I am trying to use the following method:
CGPathApply(<#CGPathRef _Nullable path#>, <#void * _Nullable info#>, <#CGPathApplierFunction _Nullable function#>)
to edit the path by adding new points, and extending the overall path, but I am having trouble since the path is being executed by an SKAction, so it's already been set.
Essentially, I am trying to repeat an action that adds a new POC to the already defined path, forcing the path to grow without demonstrating possible sharp turns, velocity changes, or direction changes.
Currently, the character does indeed float around the map randomly and continuously with the provided code I have shown, but at times the character has quite unnatural movements as cgpath(1) ends, and cgpath(2) begins, and so on to cgpath(n).
So does anyone know if there is some way to achieve what I am asking (extend and cgpath without having to stop, then start a completely new path)? OR at least prevent the character from performing any of these sharp unappealing movements. Thanks in advance for any help at all.
UPDATE: My only solution that I have been able to think of as of now is to track the angle between the last control point B1, and the current end point E1, then when creating the new path, make sure the angle between S2 and A2 are exact opposite (same direction of motion). Like such:
Related
I am having trouble plotting a new CGPoint when given an origin, distance and angle. The task is pretty simple: I have a line with three edit handles attached to it - one on each end and one in the center. When the the end handles are dragged, the line is moved relative to the handle being dragged. That functionality is working properly. When the center handle is dragged, the two endpoint handles should maintain their relationship to one another as shown in the image below. So when dragging the center handle the two other handles should move with it.
Here is my current code to plot the points:
func pointFromPoint(origin:CGPoint, distance:Double, degrees:Double) -> CGPoint {
var endPoint = CGPoint()
endPoint.x = CGFloat(distance * cos(degrees) + Double(origin.x))
endPoint.y = CGFloat(distance * sin(degrees) + Double(origin.y))
return endPoint
}
When using this function, the new CGPoint locations seems to fall in random locations. Can anyone spot anything wrong in my math? Thanks!
Most trigonometry functions need radians, not degrees.
Also, as #Putz1103 pointed out, you can probably use the delta x and delta y of the UITouch of the movement instead of calculating the new position of the ends based on the center point's movement.
Let's say I have two circles respectively at (0,0) and (0,1).
I have a SKPhysicsJoint between them and it is working good, now I want to separate them with a distance of 2 on runtime, meaning while physics are working in-game. How can I achieve this?
I've tried setting anchor points to (0,0) and (0,2) but something is bugged, although I see the joint it doesn't have any affect.
I want the circles smoothly push each other, as if the length of the spring has increased.
Everything works if I make a circle 'teleport' to a distance of 2 and then anchor the spring to it, but making a physics object teleport cause other bugs as you can guess.
Before adding the joint I 'teleported' the second object to the desired position, then added the joint and then 'teleported' back to the original position.
Here is the code piece:
SKSpriteNode* node1 = [_bodies objectAtIndex:loop];
SKSpriteNode* node2 = [_bodies objectAtIndex:loop-1];
CGPoint prev1 = node1.position;
CGPoint prev2 = node2.position;
node1.position = [((NSValue*)[positions objectAtIndex:loop]) CGPointValue];
node2.position = [((NSValue*)[positions objectAtIndex:loop-1]) CGPointValue];
[self AttachPoint:node1 secondPoint:node2 pos1:node1.position pos2:node2.posiiton] ;
node1.position = prev1;
node2.position = prev2;
it is working as it is, but I'm not sure how efficient this is. I wish SKPhysicsJointSpring had a 'length' parameter that can be changed over time.
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 trying to create a game, something slightly similar to Jetpack Joyride. At certain points it will have an object that the player has to fly between - like the pipes on flappy birds. I have managed to create the pipe objects however I wish for the objects to move up and down on the screen, making it harder for the player to jump between. My code for placing the objects is:
// Maths
float availableSpace = HEIGHT(self) - HEIGHT(floor);
float maxVariance = availableSpace - (2*OBSTACLE_MIN_HEIGHT) - VERTICAL_GAP_SIZE;
float variance = [Math randomFloatBetween:0 and:maxVariance];
// Bottom object placement
float minBottomPosY = HEIGHT(floor) + OBSTACLE_MIN_HEIGHT - HEIGHT(self);
float bottomPosY = minBottomPosY + variance;
bottomPipe.position = CGPointMake(xPos,bottomPosY);
bottomPipe.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0,0, WIDTH(bottomPipe) , HEIGHT(bottomPipe))];
bottomPipe.physicsBody.categoryBitMask = blockBitMask;
bottomPipe.physicsBody.contactTestBitMask = playerBitMask;
// Top object placement
topPipe.position = CGPointMake(xPos,bottomPosY + HEIGHT(bottomPipe) + VERTICAL_GAP_SIZE);
topPipe.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0,0, WIDTH(topPipe), HEIGHT(topPipe))];
topPipe.physicsBody.categoryBitMask = blockBitMask;
topPipe.physicsBody.contactTestBitMask = playerBitMask;
How would I go about moving the objects up and down?
Thanks for any help :)
All such operations on a node are done using SKAction objects. Read up on the SKAction class here.
In your case, using an SKAction to move the nodes up and down would go something like this:
SKAction *actionMoveUp = [SKAction moveByX:0 y:20 duration:0.5];
SKAction *actionMoveDown = [actionMoveUp reversedAction];
SKAction *actionMoveUpDown = [SKAction sequence:#[actionMoveUp, actionMoveDown]];
SKAction *actionMoveDownRepeat = [SKAction repeatActionForever:actionMoveUpDown];
[bottomPipe runAction:actionMoveDownRepeat];
[topPipe runAction:actionMoveDownRepeat];
The code given above will make your top and bottom pipe go up and down by a factor of 20 pixels repeatedly.
I am making a small SpriteKit game. I want the "enemies" in this game to move on random paths around the player (which is static).
If I just select a random point on the screen and animate a motion to there and then repeat (e.g.: every 2 seconds), this will give a very jagged feel to the motion.
How do I make this random motion to be very smooth (e.g.: if the enemy decides to turn around it will be on a smooth U turn path not a jagged sharp angle).
PS: The enemies must avoid both the player and each other.
You can create an SKAction with a CGPathRef that the node should follow.
Here is an example how to make a node make circles:
SKSpriteNode *myNode = ...
CGPathRef circlePath = CGPathCreateWithEllipseInRect(CGRectMake(0,
0,
400,
400), NULL);
SKAction *followTrack = [SKAction followPath:circle
asOffset:NO
orientToPath:YES
duration:1.0];
SKAction *forever = [SKAction repeatActionForever:followTrack];
[myNode runAction:forever];
You can also create a random UIBezierPath to define more sophisticated paths and make your objects follow them.
For example:
UIBezierPath *randomPath = [UIBezierPath bezierPath];
[randomPath moveToPoint:RandomPoint(bounds)];
[randomPath addCurveToPoint:RandomPoint(YourBounds)
controlPoint1:RandomPoint(YourBounds)
controlPoint2:RandomPoint(YourBounds)];
CGPoint RandomPoint(CGRect bounds)
{
return CGPointMake(CGRectGetMinX(bounds) + arc4random() % (int)CGRectGetWidth(bounds),
CGRectGetMinY(bounds) + arc4random() % (int)CGRectGetHeight(bounds));
}
You make a node follow the path using an SKAction and when the action completes (node is at the end of the path), you calculate a new path.
This should point you to the right direction.
If you want to generate random path, I highly suggest you to use "CGPathAddCurveToPoint", it is simple to understand and easy to use
Go to title 2. Add & Move Enemies
http://code.tutsplus.com/tutorials/build-an-airplane-game-with-sprite-kit-enemies-emitters--mobile-19916
It is coded by Jorge Costa and Orlando Pereira