Delay with touchBegan Cocos2d iOS - ios

I am attempting to make it so in my CCScene, if a user taps to shoot a projectile there's a delay at the end of the bullet shot from allowing them to shoot another, maybe 2 seconds.
I've attempted this:
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CCSprite *arrow = [CCSprite spriteWithImageNamed:#"arrow.png"];
arrow.position = player.position;
[self addChild:arrow];
CCActionMoveTo *actionStart = [CCActionMoveTo actionWithDuration:1.3f position:targetPosition];
CCActionRemove *actionEnd = [CCActionRemove action];
CCActionDelay *delay = [CCActionDelay actionWithDuration:2.0];
[arrow runAction:[CCActionSequence actions: actionStart, actionEnd, delay, nil]];
}
but I am still able to repeatedly click to fire projectiles with no delay. Any ideas how I could fix this, and more importantly what I'm doing wrong?

Why are you creating a new arrow each time? You can instead just do this:
Create an arrow property:
#property (nonatomic, strong) CCSprite* arrow;
#property (nonatomic, strong) CCSprite* player;
#property (nonatomic, assign) CGPoint targetPosition;
Create the arrow sprite:
- (void)onEnter
{
[super onEnter];
self.player = ...
self.targetPosition = ...
self.arrow = [CCSprite spriteWithImageNamed:#"arrow.png"];
self.arrow.position = self.player.position;
self.arrow.visible = FALSE;
[self addChild:self.arrow];
}
Then in touches began:
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
if (!self.arrow.numberOfRunningActions)
{
CCActionMoveTo* move = [CCActionMoveTo actionWithDuration:1.3f
position:self.targetPosition];
CCActionShow* show = [CCActionShow action];
CCActionRemove* hide = [CCActionHide action];
CCActionDelay* delay = [CCActionDelay actionWithDuration:2.0];
CCActionSequence* seq = [CCActionSequence actions:show, move, hide, delay, nil];
[self.arrow runAction:seq];
}
}
Hope this helped.

It's not working because the action you applied applies to your arrow CCSprite.
This means that the delay is in effect but on the arrow sprite that you created before, effectively having no effect.
I think for this use case you might just have a NSDate property called something like lastShootingTime and in touchBegan test the time and if the timeElapsedSinceNow is larger than what you want start the new action sequence and reset the lastShootingTime.

Related

How can I measure sprite's jump strength?

Hello,
I am new to spriteKit and I am trying to make a game. In the game I have a player that jumps from stair to stair, which comes from the top of the screen infinitely (Like in Doodle Jump, only the jump is controlled by the player's touch). I am trying to make a jump, by applying an impulse on the player, but I want to control the jump strength by the player's touch duration. How can I do so? The jump executes when the player STARTS touching the screen so I can't measure the jump intensity (By calculating the touch duration) ... Any ideas?
Thanks in advance!!! (:
Here's a simple demo to apply the impulse on a node with the duration of touch. The method is straightforward: set a BOOL variable YES when the touch began, and NO when the touch ended. When touching, it will apply a constant impulse in update method.
To make the game more natural, you might want to refine the impulse action, or scroll the background down as the node is ascending.
GameScene.m:
#import "GameScene.h"
#interface GameScene ()
#property (nonatomic) SKSpriteNode *node;
#property BOOL touchingScreen;
#property CGFloat jumpHeightMax;
#end
#implementation GameScene
- (void)didMoveToView:(SKView *)view
{
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
// Generate a square node
self.node = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(50.0, 50.0)];
self.node.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.node.size];
self.node.physicsBody.allowsRotation = NO;
[self addChild:self.node];
}
const CGFloat kJumpHeight = 150.0;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.touchingScreen = YES;
self.jumpHeightMax = self.node.position.y + kJumpHeight;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
self.touchingScreen = NO;
self.jumpHeightMax = 0;
}
- (void)update:(CFTimeInterval)currentTime
{
if (self.touchingScreen && self.node.position.y <= self.jumpHeightMax) {
self.node.physicsBody.velocity = CGVectorMake(0, 0);
[self.node.physicsBody applyImpulse:CGVectorMake(0, 50)];
} else {
self.jumpHeightMax = 0;
}
}
#end

Having trouble adding multiple physics objects in Xcode cocos2d

I'm new to cocos2d and objective-c. I know I'm missing something here, but I just can't find the solution. Hope someone can help....
My goal is to be able to click any of the sprites that I've placed on the screen. To start, I've put 2 sprites on the screen. (Each sprite is in the shape of a star).
The problem is, only the second sprite placed on the screen is clickable. My guess is that when I call addNewStar, it replaces _star with the latest star sprite, and takes the previous _star out of the physics node. I want all the stars I add to be in the physics node and be clickable. No clue how to do this.
Here is my code...hopefully someone can point out my mistake(s)!
#implementation MainScene {
CCSprite *_star;
CCPhysicsNode *_physicsNode;
CCNode *_ground;
CCLabelTTF *_scoreLabel;
BOOL _gameOver;
CCButton *_restartButton;
}
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
[self addNewStar];
[self addNewStar];
// set collision txpe
_ground.physicsBody.collisionType = #"level";
// set this class as delegate
_physicsNode.collisionDelegate = self;
}
-(void)addNewStar {
//This successfully loads my star onto the screen
_star = (Star *)[CCBReader load:#"Star"];
_star.physicsBody.collisionGroup = #"starGroup";
_star.physicsBody.collisionType = #"star";
_star.position = ccp(200,300);
[_physicsNode addChild:_star];
}
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair star:(CCNode *)star level:(CCNode *)level {
[self gameOver];
return TRUE;
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
float ranNum1 = (arc4random_uniform(10000));
float ranNum2 = (arc4random_uniform(10000));
float sideForce = ranNum1 - ranNum2;
if (!_gameOver) {
CGPoint touchLocation = [touch locationInNode:_physicsNode];
if(CGRectContainsPoint([_star boundingBox], touchLocation))
{
[_star.physicsBody applyImpulse:ccp(sideForce, 1000.f)];
[_star.physicsBody applyAngularImpulse:2500.f];
}
}
}
- (void)restart {
CCScene *scene = [CCBReader loadAsScene:#"MainScene"];
[[CCDirector sharedDirector] replaceScene:scene];
}
- (void)gameOver {
if (!_gameOver) {
_gameOver = TRUE;
_restartButton.visible = TRUE;
_star.rotation = 90.f;
_star.physicsBody.allowsRotation = FALSE;
[_star stopAllActions];
CCActionMoveBy *moveBy = [CCActionMoveBy actionWithDuration:0.2f position:ccp(-2, 2)];
CCActionInterval *reverseMovement = [moveBy reverse];
CCActionSequence *shakeSequence = [CCActionSequence actionWithArray:#[moveBy, reverseMovement]];
CCActionEaseBounce *bounce = [CCActionEaseBounce actionWithAction:shakeSequence];
[self runAction:bounce];
}
}
#end
You only have one pointer to one star. So when you test if the star was touched in your touches began method then of course only the second one would do anything since your sole pointer is only pointing at the most recently created star.
To answer your question, the simple approach you could take is to add each star to an NSArray. Then in your touches began method you can loop over the array and see which star was touched.
Example:
// Recommend using properties over i-vars
#property (nonatomic, strong) NSMutableArray* starList;
// In init or onEnter...
self.starList = [NSMutableArray array];
// Each time you create a new star...
Star* star = ...;
[self addChild:star];
[self.starList addObject:star];
// In your touches began...
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
...
for (Star* star in self.starList)
{
// If star is touched...
// ...add your impulses, etc
}
}
This is touchBegan from my project. Maybe you can change code little bit for your need. What you can see here? After getting location of touch code searching b2Body which was touched. In your example you need to search your _physicsNode for _child that was touched.
for(UITouch *touch in allTouches)
{
CGPoint location = [touch locationInView:touch.view];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 worldLoc = b2Vec2(ptm(location.x), ptm(location.y));
// SEARCH YOUR CHILD FROM NODE HERE
for (b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetType() == b2_dynamicBody)
if (b->IsBullet() == false)
{
for (b2Fixture *f = b->GetFixtureList(); f; f = f->GetNext())
{
// Hit!
if (f->TestPoint(worldLoc))
{
//do stuff
}
break;
}
}
}
}

SpriteKit: Change texture based on direction

I'm using an on-screen DPAD to move my character but I'm having trouble with passing in my sprites based on direction. Basically what I need is if the character is moving up, use the walking up animation. Moving down, use down. Left, use left, and right, use right animations. The angles are what I'm really having trouble with. This is the code that I'm using to move my character.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//if control pad touched
if ([node.name isEqualToString:#"controlPadNode"]) {
touchX = location.x +100; //adjust for anchor
touchY = location.y +43;
controlButtonDown = YES;
}
-(void)update:(CFTimeInterval)currentTime {
if (controlButtonDown == YES) {
//the node I want to move
SKNode *character = (CSCharacter*)node;
//compute the angle between parameters pad and the horizontal
float angle = atan2f (touchY - controlPadY, touchX - controlPadX) ;
//move the character
SKAction *moveCharacter = [SKAction moveByX: 1*cosf(angle) y:1*sinf(angle) duration:0.005];
[character runAction: moveCharacter];
//add h and m file in your project
#import <SpriteKit/SpriteKit.h>
//spriteSequence
#interface spriteSheet : SKSpriteNode
{
//private variable no one
}
#property SKTexture *startingFrame;
#property SKSpriteNode *animatedsprite;
#property SKAction *Animation;
#property SKAction *currentAction;
#property NSMutableArray *spritesArray;
#property NSMutableArray *frameRateArray;
#property NSMutableArray *actionKeys;
#property NSString *previousAction;
//public mehods
-(id) initSpritesheet:(NSString*)firstFrame;
-(void)addAnimation:(NSMutableArray*)frames frameRate:(float)time withKey:(NSString*)Key;
-(void)gotoAndPlay:(int)animationindex label:(NSString*)framelabel;
-(void)removeThisAction:(int)index;
//for ground hero
#property float totalWidth;
//for zipline
#property float Hspeed;
//
#end
#import "spriteSheet.h"
#implementation spriteSheet
-(id) initSpritesheet:(SKTexture*)firstFrame{
self=[super init];
if(self)
{
//passing rect to rectangle
_startingFrame=firstFrame;
[self addRefernce];
}
return self;
}
-(void) addRefernce
{
//adding a reference
_animatedsprite=[SKSpriteNode spriteNodeWithTexture:_startingFrame];
_animatedsprite.anchorPoint=CGPointMake(0.5, 0.5);
[self addChild:_animatedsprite];
_spritesArray=[[NSMutableArray alloc] init];
_frameRateArray=[[NSMutableArray alloc] init];
_actionKeys=[[NSMutableArray alloc] init];
}
//
-(void)addAnimation:(NSMutableArray*)frames frameRate:(float)time withKey:(NSString*)Key
{
[_spritesArray addObject:frames];
[_frameRateArray addObject:[NSNumber numberWithFloat:time]];
[_actionKeys addObject:Key];
}
-(void)gotoAndPlay:(int)animationindex label:(NSString*)framelabel;
{
// [self removeAllActions];
NSMutableArray *frames=_spritesArray[animationindex];
float time=[_frameRateArray[animationindex] floatValue];
NSString *key=_actionKeys[animationindex];
_Animation = [SKAction animateWithTextures:frames timePerFrame:time resize:TRUE restore:TRUE];
if([framelabel isEqualToString:#"loop"])
{
_currentAction = [SKAction repeatActionForever:_Animation ];
}
else if([framelabel isEqualToString:#"playonce"])
{
_currentAction=_Animation;
}
[_animatedsprite runAction:_currentAction withKey:key];
}
-(void)removeThisAction:(int)index
{
[_animatedsprite removeActionForKey:_actionKeys[index]];
}
#end
adding all animation
NSString *frame =gameViewController.commanDataHolder.heroRunning[0];//add first frame
_gameHero=[[herospriteSheet alloc] initSpritesheet:frame];
//adding animation
[_gameHero addAnimation:gameViewController.commanDataHolder.heroRunning frameRate:1 withKey:#“up”]; //index 0
[_gameHero addAnimation:gameViewController.commanDataHolder.heroJumpUp frameRate:6 withKey:#“down]; //index 1
[_gameHero addAnimation:gameViewController.commanDataHolder.heroFallDown frameRate:1 withKey:#“left”];//index 2
[_gameHero addAnimation:gameViewController.commanDataHolder.heroJumpDown frameRate:0.5 withKey:#“right”];//index 3
[self addObject:_gameHero];
at any action degree of rotation or swipe etc
//play first animation in loop
[_gameHero gotoAndPlay:0 label:#"loop"];
//play first animation in loop
[_gameHero gotoAndPlay:0 label:#“playonce”];
at any action degree of rotation or swipe etc
//play second animation in loop
[_gameHero gotoAndPlay:1 label:#"loop"];
//play first animation in loop
[_gameHero gotoAndPlay:1 label:#“playonce”];
I've got the animations working but I'm not sure how to pass them in according to which direction the character is moving. I have up and down working with this:
if (touchY > 1 ) {
[leader runWalkBackTextures]; //character moving up animations
}else{
[leader runWalkFrontTextures]; //character moving down animations
}
but my issue comes in with the angles, like if someone were to press between up and right on the control pad. If they press more to the right then up I'd like it to pass in my moving right animation but if they press more to the top of the DPAD I want to pass in the move up animation. Not sure exactly how to do this.

Why isn't this SKAction moveByX: y: duration: moving this sprite at all?

I'm trying to respond to Swipe Gestures in a scene and move a sprite in response. The sprite is displaying on the left side of the screen. The logging statement in the handleSwipeRight is going to the log. But the sprite isn't moving at all. Surely I'm just missing something basic on the SKAction?
Here's my Scene code :
MFFMyScene.h :
#import <SpriteKit/SpriteKit.h>
#interface MFFMyScene : SKScene <UIGestureRecognizerDelegate>
#property (nonatomic) SKSpriteNode *player;
#property (nonatomic) UISwipeGestureRecognizer * swipeRightGesture;
#property (nonatomic) UISwipeGestureRecognizer * swipeLeftGesture;
#property (nonatomic) UISwipeGestureRecognizer * swipeUpGesture;
#property (nonatomic) UISwipeGestureRecognizer * swipeDownGesture;
#end
Relevant bits from MFFMyScene.m :
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
NSLog(#"Size: %#", NSStringFromCGSize(size));
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:#"dungeon"];
SKTexture *texture = [atlas textureNamed:#"hero_trans.png"];
_player = [SKSpriteNode spriteNodeWithTexture:texture];
_player.position = CGPointMake(10, 150);
[self addChild:_player];
}
return self;
}
-(void) didMoveToView:(SKView *)view {
_swipeRightGesture = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleSwipeRight:)];
_swipeRightGesture.direction = UISwipeGestureRecognizerDirectionRight;
[view addGestureRecognizer:_swipeRightGesture];
}
- (void)handleSwipeRight:(UISwipeGestureRecognizer *)sender {
if(sender.direction == UISwipeGestureRecognizerDirectionRight) {
NSLog(#"scene: SWIPE RIGHT");
SKAction *movePlayerRight = [SKAction moveByX:10.0
y:0.0
duration:100.0];
[_player runAction:movePlayerRight];
}
}
Make your properties strong, use setters to let system do right memory management for you, for instance
#property (nonatomic, strong) UISwipeGestureRecognizer *rightSwipe;
self.rightSwipe = /* init code here */
check for swipe gesture state:
- (void)swipeHandled:(UISwipeGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
if (sender.direction == UISwipeGestureRecognizerDirectionRight) {
/* moving code here */
}
}
}
I was using this SKAction wrong. I was looking for an SKAction that would do 'move x/y every z seconds' or 'move x/y every frame for z seconds'. The way I originally had it written, the SKAction will move the sprite 10 points total over a period of 100 seconds.
So I think the sprite was moving after all, but my X/Y/duration values were such that the movement was so small that it looked like it wasn't moving. With x=10 and duration=100 seconds, it was moving 10 points over 100 seconds. I don't even understand point vs pixel, but that was slow movement.
I changed it to x=100, y=50, duration=1.0, and it moved just fine.

How can I draw one circle around each touch location on a device?

New programmer here trying to take things step by step. I am trying to find a way to draw a circle around each currently touched location on a device. Two fingers on the screen, one circle under each finger.
I currently have the working code to draw a circle at one touch location, but once I lay another finger on the screen, the circle moves to that second touch location, leaving the first touch location empty. and when I add a third, it moves there etc.
Ideally I would like to be able to have up to 5 active circle on the screen, one for each finger.
Here is my current code.
#interface TapView ()
#property (nonatomic) BOOL touched;
#property (nonatomic) CGPoint firstTouch;
#property (nonatomic) CGPoint secondTouch;
#property (nonatomic) int tapCount;
#end
#implementation TapView
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
NSArray *twoTouch = [touches allObjects];
if(touches.count == 1)
{
self.tapCount = 1;
UITouch *tOne = [twoTouch objectAtIndex:0];
self.firstTouch = [tOne locationInView:[tOne view]];
self.touched = YES;
[self setNeedsDisplay];
}
if(touches.count > 1 && touches.count < 3)
{
self.tapCount = 2;
UITouch *tTwo = [twoTouch objectAtIndex:1];
self.secondTouch = [tTwo locationInView:[tTwo view]];
[self setNeedsDisplay];
}
}
-(void)drawRect:(CGRect)rect
{
if(self.touched && self.tapCount == 1)
{
[self drawTouchCircle:self.firstTouch :self.secondTouch];
}
}
-(void)drawTouchCircle:(CGPoint)firstTouch :(CGPoint)secondTouch
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx,0.1,0.1,0.1,1.0);
CGContextSetLineWidth(ctx,10);
CGContextAddArc(ctx,self.firstTouch.x,self.firstTouch.y,30,0.0,M_PI*2,YES);
CGContextStrokePath(ctx);
}
I do have setMultipleTouchEnabled:YES declared in my didFinishLaunchingWithOptions method in the appDelegate.m.
I have attempted to use an if statement in the drawTouchCircle method that changes the self.firstTouch.x to self.secondTouch.x based on a self.tapCount but that seems to break the whole thing, leaving me with no circles at any touch locations.
I'm having an immensely hard time trying to find my issue, and I am aware that it might be something quite simple.
I just wrote some code that seems to work. I've added an NSMutableArray property called circles to the view, which contains a UIBezierPath for each circle.
In -awakeFromNib I setup the array and set self.multipleTouchEnabled = YES - (I think you did this using a reference to the view in your appDelegate.m).
In the view I call this method in the -touchesBegan and -touchesMoved methods.
-(void)setCircles:(NSSet*)touches
{
[_circles removeAllObjects]; //clear circles from previous touch
for(UITouch *t in touches)
{
CGPoint pt= [t locationInView:self];
CGFloat circSize = 200; //or whatever you need
pt = CGPointMake(pt.x - circSize/2.0, pt.y - circSize/2.0);
CGRect circOutline = CGRectMake(pt.x, pt.y, circSize, circSize);
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:circOutline];
[_circles addObject:circle];
}
[self setNeedsDisplay];
}
Touches ended is:
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[_circles removeAllObjects];
[self setNeedsDisplay];
}
Then I loop over circles in -drawRect and call [circle stroke] on each one

Resources