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
Related
I want to add a SKShapeNode drawn from my SKScene to a SKSpriteNode. The SKSpriteNode may already have some transformations that automatically translate to the SKShapeNode, which I don't want to happen. I've added a screenshot of the way the shapenode jumps after adding. I have also added my code where I add the shape node as a child and apply some opposite transformations to the node (works fine in some cases, but for example when I scale, move and rotate the robot, the drawing jumps to another position).
func addMaskPath(_ path: CGPath) {
let drawing = MaskLayer(withPath: path)
drawing.zRotation = -zRotation
drawing.xScale = drawing.xScale / xScale
drawing.yScale = drawing.yScale / yScale
drawing.position.x = (drawing.position.x - position.x) / xScale
drawing.position.y = (drawing.position.y - position.y) / yScale
drawing.position.y = drawing.position.y*cos(zRotation) - drawing.position.x*sin(zRotation)
drawing.position.x = -drawing.position.y*sin(zRotation) + drawing.position.x*cos(zRotation)
cropMask?.eraserMask?.addChild(drawing)
}
MaskLayer is a SKShapeNode. How do I easily find the transformations of my SKSpriteNode and apply them the opposite way to my SKShapeNode so my drawing will visually stay exactly the same?
Screenshot
You shouldn't add your SKShapeNode to a SKSpriteNode if you don't want it to inherit its transformation.
I suggest having a parent SKNode that'll hold your SKSpriteNode and add your SKShapeNode to the parent SKNode instead of the SKSpriteNode
I have managed to draw a line with array of my random CGPoints.
-(void)drawLine
{
SKShapeNode *mainLine = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, 0, 0);
for(int i;i<pointsInMyView.count;i++)
{
CGPoint myPoint = [pointsInMyView[i] CGPointValue];
CGPathAddLineToPoint(pathToDraw, NULL, myPoint.x, myPoint.y);
mainLine.path = pathToDraw;
}
[mainLine setLineWidth:40];
[mainLine setStrokeColor:[SKColor whiteColor]];
mainLine.name = #"mainLine";
[self addChild:mainLine];
}
As you can see I am drawing a SKShapeNode. My goal is to check collision of my SKSpriteNode with my line. But of course, this shape node makes a frame that contains all points of my line, and in this case my ShapeNode is all over my view. My SpriteNode detects collision with this ShapeNode all the time.
I should draw multiple different ShapeNodes I guess, so every node would have its own frame. But if i do it this way, my line is not connected.
Is there some solution to draw this node by node and still get nice line.
Unfortunately nodes in SpriteKit cannot be concave (http://mathworld.wolfram.com/ConcavePolygon.html), with lines that cross over each other.
You will have to create separate nodes for each lines between adjacent points and then possibly join them together. you could do this either by:
Create the 1st segment and then add subsequent segments as children
of that 1st segment.
Create a non-visible node for the whole line and add wll the line
segments as children of that node.
Create physicsbodies for each segment and join them with
SKPhysicsJointFixed.
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:
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.
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.