How to make a sprite follow a trail? - iphone

I'm making a game where the player draws a line and then a sprite should follow and run on it. I have a mutable array and also a draw method which works well. but I'm having trouble figuring out a way to move the sprite. I have tried different approaches but I can't get the iterator to work.
It's supposed to work by iterating through the array. which is populated with CGPoint locations previously stored. I try to move the sprite in ccTouchedEnded but it highlights [toucharray objectAtIndex:0] and says "passing 'id' to parameter of incompatible type 'CGPoint (aka 'struct CGPoint')"
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//remove objects each time the player makes a new path
[toucharray removeAllObjects];
}
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [ touches anyObject];
CGPoint new_location = [touch locationInView: [touch view]];
new_location = [[CCDirector sharedDirector] convertToGL:new_location];
CGPoint oldTouchLocation = [touch previousLocationInView:touch.view];
oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];
oldTouchLocation = [self convertToNodeSpace:oldTouchLocation];
// add touches to the touch array
[toucharray addObject:NSStringFromCGPoint(new_location)];
[toucharray addObject:NSStringFromCGPoint(oldTouchLocation)];
}
-(void)draw
{
glEnable(GL_LINE_SMOOTH);
for(int i = 0; i < [toucharray count]; i+=2)
{
CGPoint start = CGPointFromString([toucharray objectAtIndex:i]);
CGPoint end = CGPointFromString([toucharray objectAtIndex:i+1]);
ccDrawLine(start, end);
}
}
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
// here is the line I can't get to work to move the sprite
_sprite.position = ccpAdd(ccpMult([toucharray objectAtIndex:0], progress), ccpMult([toucharray objectAtIndex:0+1], 1-progress));
}

I did this before, by (what you have so far) creating an array
Mine was with multiple sprites on screen, so I also had 1 definition "Touched sprite"
ontouchstart empty array and add first point (start point) + set TouchedSprite to the Sprite that was closest to the start point)
ontouchmove add point to array
ontouchend (run the array on the sprite by actions (get back to that later)
A few problems is what I got, (1) I could only control 1 sprite at a time and (2) the drawn line had way too many points.
Solution to number 1:
Create a subclass Object of a sprite and create your sprites like that. Within that object create an array (make it accessible with #property and #synthesize) which will allow you to place the drawn path points in. Also create a public method called "runPathAction".
So onTouchStart you clean the Array and set the selected spriteObject. OnTouchMove add the items, OntouchEnd set the array of the selected spriteObject to the local array and then run the "runPathAction" method. (You could have passed it to the method, but I like doing it this way, just in case I want to access the array)
Solution to number 2:
I found that drawing the line creates WAY to many points. So, i created a boolean operator called "CanDraw" and a schedule with a time interval of 0.1 (you can play around with it) to a method which sets canDraw to YES. and then in the onTouchMove you check for "canDraw == YES", add the point and set canDraw=NO;
This way you will have an interval of 0.1 seconds to add the points. Don't forget to remove the schedule onTouchEnd!!!
Now how to run the actions.
You will want a steady speed, so you will need to set a speed variable.
Walk through the array of points and calculate the distance between each point to create a total distance. (PS Don't forget that the first pint is from CurrentLocation to Point0 in the array)
When you have the total distance you can figure out how much delay time you should set in your actions per step. (if you don't do this, you don't know the delay time and if you fix that you get weird movement).
Create a method,
check the "count" of the array. If count = 0 return; (finished!!! do cleanup or whatever)
otherwise run an actionsequence, which at the end calls itself. (the check for count will handle the "break".
grab 1 item of the array CGPoint p = (objectAtIndex:0); and remove that item from the array (removeAtIndex:0).
Run the action with the delay time and there you go!

Record the positions of the path in an array or a list and iterate through that to move your sprite along the trail. I did this in a game I made to create a particle trail behind the player. I used a size 20 array and iterated through that on an interval, updating the array at the iterator's position with the location of my character and then moving the particle effect to the location stored in the array at the position of the iterator plus 1.
You'll need to seed the array with the starting location so that you don't have null values and you'll need a special case for when you're at the end of the array because you don't want to read from an out of bounds location, instead have your code read from the 0 position.

Related

SpriteKit SKFieldNode: Why can't I move the field during runtime?

I am trying to move a SKFieldNode (radialGravity) during runtime:
let movingField = SKFieldNode.radialGravityField(
movingField.position = location //location is a touch position
addChild(movingField)
It crashes as soon as I command the field node to change positions. I have no problem placing the field node in a static position at didMoveToView.
I'm guessing SKFieldNodes simply have to stay in place, but maybe theres a solution.
The main idea is to attach the radial fields to sprites (which have physics bodies attached) so that they have attraction/repulsion interactions. I realize this can be done in a more manual approach, but I was trying to take advantage of the physics available in SpriteKit.
Thanks!
I can easily move field nodes by making them move to follow touch events. Works fine.
Inside of GameScene.m
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
self.backgroundColor = SKColorWithRGB(0, 0, 0);
// This uses SKTUtils to load my simple SKEmitter
SKEmitterNode *nd = [SKEmitterNode skt_emitterNamed:#"bokeh"];
nd.particleZPosition += 100;
nd.position = CGPointMake(self.size.width/2,self.size.height/2);
nd.fieldBitMask = 1;
field = [SKFieldNode radialGravityField];
field.position = CGPointMake(self.size.width/2,self.size.height/2);;
field.minimumRadius = 50;
field.categoryBitMask = 1;
[self addChild:field];
[self addChild:nd];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint t = [touch locationInNode:self];
field.position = t;
}
In my app I have a very similar approach like you. I can set new positions of field nodes just fine. Here is an excerpt:
// _targetPos is the touch location in UIView coordinates
CGPoint pos = [k.world convertToScenePoint:_targetPos]; // convert to scene coordinates
self.gravityField.position = pos;
self.dragField.position = pos;
I can only assume the crash you have mentioned occurs somewhere else in your code. Which line of code does Xcode show when your app crashes?

Sprite Kit Creating Selection Menu

i have just finished implementing core functionality in my sprite kit tower defence game, and now am trying to refine the game. Firstly, i was thinking of doing a selection menu to select which tower to place. This is done in games such as Kingdom Rush where you can select between 4 different towers. However, i am kinda lost as to how to do this.
http://i.ytimg.com/vi/ihc4G76jA_Q/0.jpg // image of tower selection
any suggestions would be greatly appreciated!
thanks
As promised, heres a little code to get you going, I don't have my mac available to me at the moment so there may be a couple of typos but the concept is there:
Start out by defining which areas on your map are able to hold towers, this can be done in a few ways but as it's SpriteKit I'll use a sprite called towerPlot to represent an area that can take a tower.
Define a method to add a plot to a location with something like:
-(void)addTowerPlotAt:(CGPoint)location{
SKSpriteNode* plot = [SKSpriteNode spriteNodeWithImageNamed: "#plotImage";
plot.location = location;
plot.name = #"towerPlot";
plot.zPosition = 1; //if needed
[self addChild: plot];
}
And your viewDidLoad method add something like:
[self addTowerPlotAt:CGPointMake(someX, someY)];
[self addTowerPlotAt:CGPointMake(otherX, otherY)];
In order to add your plots in the correct positions.
Next up is to check whether a touch was located on a plot;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//if the node is a plot, bring up the menu
if ([node.name isEqualToString:#"towerPlot"]) {
//add the menu frame
SKSpriteNode* towerMenu = [SKSpriteNode spriteNodeWithImageNamed: "#TowerMenu";
towerMenu.position = node.position;
SKSpriteNode* towerType1= [SKSpriteNode spriteNodeWithImageNamed: "#Tower1";
towerType1.position = CGPointMake(someX, someY); /* remember this is relative to the menu, the next tower type has an example */
towerType1.name = #"towerType1";
[towerMenu addChild:towerType1];
SKSpriteNode* towerType2= [SKSpriteNode spriteNodeWithImageNamed: "#Tower2";
towerType2.position = CGPointMake((towerType2.size.width/2)-(towerMenu.size.width/2), 0); /* would add it to to the middle left of the menu */
towerType1.name = #"towerType2";
[towerMenu addChild:towerType2];
//add and position as many tower types as you need
[self addChild: towerMenu];
}
if ([node.name isEqualToString:#"towerType1"]) {
//code to build the tower here
}
}
There are other little bits you would need such as removing the menu after selecting a tower and ensuring that a tap returns a nodes name if they are layered oddly, but that's the gist of it I think.

applyImpulse towards CGPoint SpriteKit

I'm kind of a newb in SpriteKit, game dev, as a matter of fact I'm just learning. So I got to a point where I what to move a bunch of nodes towards a users tap location. So far I fought that I might calculate a virtual right triangle and get sin and cos of angles based on the sides. Unfortunately that left me with a very strong impulse that doesn't really consider the user tap location.
Any ideas?
Thanks in advance.
Look up the shooting projectiles section in the tutorial by Ray Wenderlich here:
http://www.raywenderlich.com/42699/spritekit-tutorial-for-beginners
Change the code from the tutorial as follows:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 1 - Choose one of the touches to work with
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
// 2 - Set up initial location of projectile
SKSpriteNode * projectile = [self childNodeWithName:#"desirednode"];
//make projectile point to your desired node.
// 3- Determine offset of location to projectile
CGPoint offset = rwSub(location, projectile.position);
// 4 - Bail out if you are shooting down or backwards. You can ignore this if required.
if (offset.x <= 0) return;
// 5 - OK to add now - we've double checked position
[self addChild:projectile];
// 6 - Get the direction of where to shoot
CGPoint direction = rwNormalize(offset);
// 7 - Make it shoot far enough to be guaranteed off screen
float forceValue = 200; //Edit this value to get the desired force.
CGPoint shootAmount = rwMult(direction, forceValue);
//8 - Convert the point to a vector
CGVector impulseVector = CGVectorMake(shootAmount.x, shootAmount.y);
//This vector is the impulse you are looking for.
//9 - Apply impulse to node.
[projectile.physicsBody applyImpulse:impulseVector];
}
The projectile object in the code represents your node. Also, you will need to edit the forceValue to get the desired impulse.

Finding the array indexes of objects on screen

I have a pie chart made up of 12 slices, which are all within an array. The idea is to be able to touch any particular slice, receive its correct index position within the array, and scale that slice up and down. I have the creation of the slices done, as well as the scaling, but the math behind selecting the correct index has stumped me.
The current code (slicesArray = 12) I have that returns the incorrect index number is:
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];
int index = atan2f(point.x-self.view.center.x, point.y-self.view.center.y) * self.slicesArray.count / (2*M_PI);
This image shows the slices, with the black text representing the correct index numbers, while the white text represents what the above code returns.
Thanks for any help.
If you are using an UIImageView to show all images, then Here's my answer will help you.
What you need to do is setTag for individual UIImageView. It will identify touched UIImageView. Its tag will index for your array, and you can also pass it to NSArray to get value from it.
use a for loop to find out which UIView is being touched
UIView *selectedPie;
for (UIView *pie in slicesArray) {
if(CGRectContainsPoint(pie.frame, point)) {
selectedPie = pie;
break;
}
}
and then scale the selectedPie. Hope this helps

Smoothly drag a Sprite in cocos2d - iPhone

I have implemented a drag on a sprite object as follows..
-(BOOL)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [[Director sharedDirector] convertCoordinate: [touch locationInView:touch.view]];
[diskSprite setPosition:ccp(location.x , location.y )];
return kEventHandled;
}
but this dragging is not smooth.....
when i drag fast with my thumb the object left from the path.
Thanks
Probably a little bit late but I was searching for a similar thing.
I found this great Tutorial which explained everything:
http://www.raywenderlich.com/2343/how-to-drag-and-drop-sprites-with-cocos2d
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
CGPoint oldTouchLocation = [touch previousLocationInView:touch.view];
oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];
oldTouchLocation = [self convertToNodeSpace:oldTouchLocation];
CGPoint translation = ccpSub(touchLocation, oldTouchLocation);
CGPoint newPos = ccpAdd(mySpriteToMove.position, translation);
mySpriteToMove.position = newPos;
}
I had this same issue with my game. Dragging operations appeared jerky. I believe the reason is that touch events aren't generated fast enough to give a smooth appearance.
To solve the problem I smoothed the motion out by running an action on the sprite toward the desired location, instead of setting the position immediately.
I'm not exactly sure what you mean by "the object left from the path". I suppose what you mean is that if you drag your finger over the screen in an arc or circle, that the sprite will "jump" from point to point, instead of follow your finger precisely. Is this correct?
If you want your sprite to follow an exact path, you will have to create a path and then set the sprite to follow it. What you do now is simply set the sprite's position to the touch position, but a "dragged" touch will not create an event for every pixel it touches.
It is fairly easy to create a path for touches received, and code samples can be found here and there. However, if the sprite's speed (in pixels per frame) is too high, you will always see it "jump", even if you use a smooth path.
Example:
You can animate a sprite over a circular path. If you animate this to complete the path in 1 second, you will likely see smooth animation. But if it runs at a high speed, like a full circle in 4 frames, you will just see your sprite at 4 places, not in a smooth circle.
If you wish to 'correct' that, you will need to look into blending, or determine what the maximum speed is for acceptable motion, and slow your sprite down when it's too fast.
I hope that answers your question. If it's not clear, feel free to edit your question, or add a comment to my answer.
look here, what I suggest you to try in such case:
-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
if (_binCaptured) {
CGPoint location = [self convertTouchToNodeSpace:touch];
[_sprite stopAllActions];
id move = [CCEaseIn actionWithAction:[CCMoveTo actionWithDuration:0.1 position:ccp(location.x, _sprite.position.y)]];
[_sprite runAction:move];
}
}
And it really work smoothly.
I enjoyed this easy way.