I am building a Sprite Kit game where the player shoots a particle whenever the screen is pressed. How can I limit the touch inputs (let's say 2 recognized touch inputs per second) so that the player can't just quickly tap the screen to get unlimited shots?
Another solution:
Create a BOOL (I prefer to work with properties myself, so):
#property (nonatomic, assign) BOOL touchEnabled;
Set it to YES in the scene's init. Then it is fairly simple from thereon:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.touchEnabled){
self.touchEnabled = NO;
[self shootParticle];
[self performSelector:#selector(enableTouch) withObject:nil afterDelay:2.0];
}
...
- (void)shootParticle{
// whatever...
}
- (void)enableTouch{
self.touchEnabled = YES;
}
One of many possibilities:
#interface YourSceneName (){
int _amountBullets; //increase every time you shot and just shoot when _fireStop = NO
BOOL _fireStop; // init as NO at start
BOOL _needStartTime; // init as YES at start
CFTimeInterval _startTime;
}
#end
-(void)update:(CFTimeInterval)currentTime {
//set starttime
if(_needStartTime){
_startTime = currentTime;
_needStartTime = NO;
}
//timeinterval if 2 seconds, renew everything
if(currentTime - _startTime > 2){
_startTime = currentTime;
_amountBullets = 0
_fireStop = NO;
}
//set firestop to yes, method should be executed
if(_amountBullets = 2){
_fireStop = YES;
}
}
I am new in SpriteKit but this should work. I bet there are better possibilities. Also i havenĀ“t test the code. It shell show you the logic of how you could do it, hope I could help.
Here is a great Tutorial for working with time in SpriteKit.
Related
I have a paused SKScene. When the user asks to do so, I would like to resume the scene. However, I would like to give the user a couple of seconds to prepare before the game begins. To do so, when the user asks to resume the game, I would like to first countdown from 3, and then resume the scene.
I currently have a SKLabel to indicate the count. When the user clicks on resume, I use an NSTimer to countdown from 3, updating the contents of the label every second, and resuming the game when the count is up.
However, because the game is paused, the SKLabel doesn't update every second; it only updates once at the very end, once the game is resumed. I am looking for a way around this.
Use a common variable in your GameScene that indicates if the game is paused, for example isGamePaused. In your update: method, you will have:
if(!isGamePaused){
//Do all game logic
}
You can use isGamePaused to pause and unpause the game. Now, let's make the countdown. I would make a subclass of SKNode, add the SKLabel there and set a delegate so we know when the CountDown finished. For example, in CountDown.h:
#protocol SKCountDownDelegate <NSObject>
-(void)countDown:(id)countDown didFinishCounting:(BOOL) didFinishCounting;
#end
#interface SKCountDown : SKNode
#property (assign, atomic) id<SKCountDownDelegate> delegate;
-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *) color andFontName:(NSString *)fontName;
#end
And in CountDown.m:
#import "SKCountDown.h"
#interface SKCountDown()
#property int secondsToGo;
#property SKLabelNode *numberLabel;
#property NSTimer *countTimer;
#end
#implementation SKCountDown
-(instancetype)initCountDownWithSeconds:(int)seconds andColor:(UIColor *)color andFontName:(NSString *)fontName{
if (self = [super init]) {
self.numberLabel = [[SKLabelNode alloc] initWithFontNamed:fontName];
[self.numberLabel setFontColor:color];
[self.numberLabel setFontSize:110];
[self.numberLabel setText:#"3"];
self.secondsToGo = seconds;
[self addChild:self.numberLabel];
self.countTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(count) userInfo:nil repeats:YES];
}
return self;
}
-(void)count{
if (self.secondsToGo > 1) {
self.secondsToGo -= 1;
self.numberLabel.text = [NSString stringWithFormat:#"%i", self.secondsToGo];
}else{
[self.countTimer invalidate];
self.countTimer = nil;
self.numberLabel.text = #"GO!";
[self performSelector:#selector(finish) withObject:nil afterDelay:1];
}
}
-(void)finish{
[self removeFromParent];
[self.delegate countDown:self didFinishCounting:YES];
}
#end
So, anywhere you want to add this CountDown, you can do the following:
-(void)startCountDown{
self.countDown = [[SKCountDown alloc] initCountDownWithSeconds:3 andColor:self.countdownFontColor andFontName:self.countdownFontName];
self.countDown.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.countDown.delegate = self;
self.countDown.zPosition = 20;
[self addChild:self.countDown];
}
-(void)countDown:(id)countDown didFinishCounting:(BOOL)didFinishCounting{
isGamePaused = NO;
}
This is an example of a way to do it. Hope it helps!
Pausing an entire SKScene during gameplay seems a bit too blunt for me, because you typically want to update something in the node hierarchy.
A simple solution would be to make a distinction between game node and UI nodes. You can simply add two nodes (game and ui) to the scene and whenever the scene would call [self addChild] choose which node to add them to.
Later, you can pause the game node and still be able to update the UI.
SKScene
- SKNode "game" // <-- pause this one
- SKSpriteNode "goodGuy"
- SKSpriteNode "badGuy"
- SKSpriteNode "mehGuy"
- ...
- SKNode "ui"
- SKLabelNode "countdownLabel"
- SKSpriteNode "resumeButton"
- SKSpriteNode "pauseButton"
- ...
Do take note that the paused property only determines if actions are processed on the node and its descendants. If you are performing physics this will not suffice.
Pausing physics can be done by pausing the SKView but that is definitely not what you're looking for. You can always try setting the speed of the physicsWorld to 0 (I haven't tested this, though).
I prefer to simply pause sections of the update method instead of pausing the whole scene. Create a BOOL property:
#property (nonatomic) BOOL runUpdateMethod;
Structure your update method to determine what pieces get paused:
-(void)update {
if(runUpdateMethod) {
// code to be paused
}
// code not to be paused
}
You can use a SKAction block to run a countdown and resume regular updates:
-(void)countdown {
__block SKLabelNode *labelNode0;
SKAction *wait0 = [SKAction waitForDuration:1.0];
SKAction *block0 = [SKAction runBlock:^{
labelNode0 = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
labelNode0.text = #"5";
labelNode0.fontSize = 100;
labelNode0.fontColor = [SKColor redColor];
labelNode0.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
labelNode0.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
labelNode0.position = CGPointMake(self.screenWidth/2, self.screenHeight/2);
labelNode0.zPosition = 955;
labelNode0.alpha = 0.4;
[self addChild:labelNode0];
}];
SKAction *block1 = [SKAction runBlock:^{
labelNode0.text = #"4";
}];
SKAction *block2 = [SKAction runBlock:^{
labelNode0.text = #"3";
}];
SKAction *block3 = [SKAction runBlock:^{
labelNode0.text = #"2";
}];
SKAction *block4 = [SKAction runBlock:^{
labelNode0.text = #"1";
}];
SKAction *block5 = [SKAction runBlock:^{
[labelNode0 removeFromParent];
self.runUpdateMethod = YES;
}];
[self runAction:[SKAction sequence:#[block0, wait0, block1, wait0, block2, wait0, block3, wait0, block4, wait0, block5]]];
}
Instead of pausing the game, I set SKScene's speed property to 0, along with setting SKPhysicsWorld's speed property to 0. I also had to modify my update function slightly by only executing certain portions of the code if the game isn't paused.
This created the same effect as the entire game being paused, while also allowing me to update the content of labels.
When I pause the game and wait some time, then resume the game, an alien ship is immediately added. How do I make it so that the 'timer' doesn't continue once the game is paused?
My Code:
#implementation GamePlayScene
-(void) didMoveToView:(SKView *)view {
...
self.addAlienTimeInterval = [Util randomWithMin:10 max:25];
self.timeSinceLastAlien = 0;
...
}
-(void) update:(NSTimeInterval)currentTime{
if (!_isGamePaused){
if (self.lastUpdateTimeInterval){
self.timeSinceLastAlien += currentTime - self.lastUpdateTimeInterval;
}
if (self.timeSinceLastAlien > self.addAlienTimeInterval && !self.gameOver){
[self addAlienShip];
self.timeSinceLastAlien = 0;
}
self.lastUpdateTimeInterval = currentTime;
...
}
}
#end
Your update: process isn't assigning lastUpdateTimeInterval while the "paused" condition is true, so when you resume, timeSinceLastAlien is augmented based on the lastUpdateTimeInterval that was seen way back when you first hit the pause button.
You need to keep updating the lastUpdateTimeInterval property while _isGamePaused is true in order for the interval currentTime - self.lastUpdateTimeInterval to reflect un-paused game time.
I have to place functionality where CCButton can be dragged to proper position, wherever user needs to place them.
I have create a custom class for this but the issue is click method of the button is not being called when user clicks on the button.
touchyButton.h
#import "cocos2d.h"
#import "cocos2d-ui.h"
#interface touchyButton : CCButton { ... }
#property (nonatomic, assign) BOOL touchMoved;
#end
touchyButton.m
#import "touchyButton.h"
#implementation touchyButton
- (void) touchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
NSLog(#"touchMoved...", nil);
self.touchMoved = YES;
self.anchorPoint = ccp(0.5, 0.5);
CGPoint touchLoc = [touch locationInNode:self.parent];
//CGPoint inTouchLoc = [self convertToNodeSpace:self.anchorPoint];
//CGPoint touchP = ccpAdd(touchLoc, inTouchLoc);
//self.position = [self.parent convertToNodeSpace: touchP];
self.position = touchLoc;
}
- (void) touchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
self.touchMoved = NO;
NSLog(#"touchEnded...", nil);
}
#end
As the code explains, we are just trying to move the button wherever on the screen when user drags on the button.
Calling of the button in main code where it needs to be displayed.
touchyButton *btnRight = [touchyButton buttonWithTitle: #"" spriteFrame:[[CCSprite spriteWithImageNamed: #"arrR.png"] spriteFrame]];
[btnRight setBackgroundOpacity:0.5f forState: CCControlStateNormal];
[btnRight setAnchorPoint: ccp(1, 0.5)];
[btnRight setPosition: ccp(viewS.width - 10.f, viewS.height/2)];
[self addChild: btnRight];
[btnRight setTarget:self selector: #selector(performRightJump:)];
Now, when user clicks on the button, the button goes into selected state but performRightJump never fires. Can anyone suggest any alternative how I can implement the button with dragging behaviour with target action working..? Any hint would be appreciated as well.
One more thing is, in current code I can only be able to move the button's anchor point to the new touch point. Any idea how I can move the button in real fashion? The current method causes problem of tapping of first time for move, button's anchor point jumps to the tapped point.
this code is cocos2dx but useful for you
create a new class classA and classB
add this code
classA.h
{
//here implement
CC_SYNTHESIZE_RETAIN(classB *, _ classB, _ ClassB);
}
classA.cpp
bool classA::init()
{
set_classB(classB::initwithclass());
this->addChild(get_ClassB(),0);
//here create button
return true;
}
bool classA:: onTouchBegan (Touch *touch ,Event *event)
{
_classB->setposition(touchLoc);
return true;
}
void classA:: onTouchMoved(Touch *touch, Event *event)
{
}
void classA:: onTouchEnded(Touch *touch ,Event *event)
{
}
//as well as this code use in cocos2d
I have an issue at the moment. I am making a game. When two imageViews collide the game will end. I am using CGRECTIntersects rect to detect if the two images collide.
The issue is that when i restart the game the collision will happen when in fact the two images have not actually touched one another?
Any suggestions would be much appreciated.
Thank you
-(void)collision {
if (CGRectIntersectsRect(object.frame, object2.frame)) {
object.hidden=YES;
object2.hidden=YES;
retry.hidden=NO;
[timer invalidate];
}
}
/- (void)viewDidLoad
{
retry.hidden=YES;
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(object2) userInfo:nil repeats:YES];
objectSpeed1 = CGPointMake(3.0, 2.0);
[super viewDidLoad];
}
/- (IBAction)retry:(id)sender {
[self performSegueWithIdentifier:#"restart" sender:sender];
}
-(void)object2 {
object2.center = CGPointMake(object2.center.x + object2.x, object2.center.y + objectspeed1.y);
if (object2.center.x > 310 || object2.center.x < 10) {
objectspeed1.x = - objectspeed1.x;
}
if (object2.center.y > 558 || object2.center.y < 10) {
objectspeed1.y = - objectspeed1.y;
}
Just consider this case on the code. You can check if image views are on their start position or they came to this position from another. It's enough to keep BOOL property on viewcontroller's class like this:
// ViewController.m
#interface ViewController ()
#property (readonly) BOOL isStarted;
#end
#implementation
- (void)viewDidLoad {
[super viewDidLoad];
_isStarted = NO;
// Make some preparations for app's needs
_isStarted = YES;
}
#end
_isStarted is a flag which shows whether viewcontroller is ready to handle the data.
if(_isStarted) {
// Analyze image views' frame
}
else {
// Do nothing
}
Hi Im making a cocos2d game that has a sprite flying and falling, Im trying to have a first tap touch event for example when the user touches the screen for the first time it'll start the game animation and start the physics engine. Whats happening is that when the user starts the game the sprite falls down right away, can anyone give me a hand with this?
right now Im using something like this but Im not sure how to get the physics engine to wait until the user touches the screen for the first time.
CCSprite *_pixie
CCNode *_start;
BOOL *_firstTap;
CCPhysicsNode *_physicsNode;
-(void)didLoadFromCCB{
_physicsNode.collisionDelegate = self;
_firstTap = True;
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
if(_firstTap == TRUE){
_start.visible = FALSE;
_firstTap = False;
}
//flying sounds & so on
if (!_gameOver) {
[[OALSimpleAudio sharedInstance] playEffect:MAGIC volume:0.4 pitch:1 pan:0 loop:NO];
[_pixie.physicsBody applyImpulse:ccp(0, 420.f)];
[_pixie.physicsBody applyAngularImpulse:11000.f];
_sinceTouch = 0.f;
}
}
- (void)update:(CCTime)delta {
if(_firstTap == FALSE){
float yVelocity = clampf(_pixie.physicsBody.velocity.y, -1 * MAXFLOAT, 200.f);
if ((_sinceTouch > .5f)) {
[_pixie.physicsBody applyAngularImpulse:-40000.f*delta];
}
}
}
Change
BOOL *_firstTap;
to
BOOL _firstTap; //No asterisk
And also make sure that you set _firsttap = YES in viewDidLoad functions
- (void)viewDidLoad
{
[super viewDidLoad];
_firstTap = YES;
}
Looks to me like the first touch Boolean value may not be defined until after the update code is called. You are also mixing BOOL values with bool values.
Objective-C : BOOL vs bool