I'm trying to play a sound on touchesEnded but I'm having a problem. There are multiple objects that get moved around, so if the below code holds true when any object is moved, it constantly plays the sound. How do I only play it once?
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if(CGRectContainsRect([image1 frame], [image2 frame])){
[self playsound];
}
}
If you only want it to play for a certain object that calls touchesEnded, you first need to identify that object and then you can just do a quick if-then statement.
If you only want it to play once, then just give it a quick variable like int playCount = 0; and then set it to playCount = 1; after you're done playing and do an if-then statement on that as well (i.e. play it if playCount is 0, don't play it if playCount is 1).
Related
I am trying to implement an on/off button in Sprite Kit for the music/sound effects. Here is the code to set up the buttons:
-(void)setUpSoundIcon{
if (soundOrNoSound == YES) {
soundIcon = [SKSpriteNode spriteNodeWithImageNamed:[self imageNamed:#"sound"]];
sound = 2;
}else if (soundOrNoSound == NO) {
soundIcon = [SKSpriteNode spriteNodeWithImageNamed:[self imageNamed:#"nosound"]];
sound = 1;
}
soundIcon.name = #"Sound";
if (screenWidth == 1024 && screenHeight == 768) {
soundIcon.position = CGPointMake(screenWidth - 50, 50);
}else if(screenHeight == 320){
soundIcon.position = CGPointMake(screenWidth - 30, 30);
}
[soundIcon setZPosition:2000];
[self addChild:soundIcon];
}
Then in the touchesBegan method I have the sound icon image change to represent the music on or off. So, my problem is that I have the background music playing correctly, I just need a way to see if the user pressed the sound icon to be off, and then making sure the music and sound effects do not play unless the user pressed the sound icon on. I need a way to do it so that it works between multiple classes too. Thank you!
This is how I would do it. In touches began I would check if your On/Off button is tapped. Then I would toggle the button and change the BOOL variable for playing the sound and then start/stop music that is playing.
It would be great for you to have separate class for sound/music which will have that BOOL variable and methods for playing/pausing music and other music related stuff. But you can also store that values in the scene or anywhere else, but the playing part would be great to have in separate class.
And I would to it so you have 2 sprites on the scene, one would be soundOnIcon other soundOffIcon and you can make them hidden when they should not be visible
Here is a bit of code that I would do:
-(void)toggleMusicSound
{
if([MusicPlayingClass soundOn])
{
[MusicPlayingClass setSoundOn:NO];
[MusicPlayingClass stopAllSounds];//this method will stop playing all continuous music and set other music logic if you want
[soundOnIcon setHidden:YES];
[soundOffIcon setHidden:NO];
}
else
{
[MusicPlayingClass setSoundOn:YES];
[MusicPlayingClass playSounds];//it will start playing continuous music if it has to, and set other music logic if you want
[soundOnIcon setHidden:NO];
[soundOffIcon setHidden:YES];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
//check if your button is tapped
if(CGRectContainsPoint(soundIcon.frame, positionInScene))
{
[self toggleMusicSound];
}
}
That is how I would do it. Maybe it helps, but if you need some more tips or explanation I would be happy to help. :)
I am using touchesMoved with a coordinate system to detect and respond to user touches within certain areas of the screen. For example, if I have a virtual keyboard and the user swipes across the keys, it reads the coordinates and responds:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:touch.view];
if(point.y < 333 && point.y > 166 && point.x < 90 && point.x > 20)
{
//do something
}
}
...However, the problem is, if the user slowly drags across the keys, or the border between keys, the method is triggered several times in a row, playing the piano key sound in a stutter.
How can I prevent this stutter? I think setting a minimum delay of 0.25 seconds between each successive if statement triggering would help. Also, this delay would only be for a specific if statement -- I want the user to be able to drag across the keys quickly and trigger different key's if-statement as quick as they want.
Does anyone know how to code something like this?
Try this:
BOOL _justPressed; // Declare this in your #interface
...
- (void)unsetJustPressed {
_justPressed = NO;
}
Then, in your touchesMoved:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (_justPressed) {
// A key was just pressed, so do nothing.
return;
}
else {
_justPressed = YES;
// Do stuff here
[self performSelector:#selector(unsetJustPressed)
withObject:nil
afterDelay:0.25];
}
}
This way, you set a variable _justPressed to YES every touchesMoved:withEvent: is called (or within a specific conditional in there, depending on what you want to do), and you use performSelector:withObject:afterDelay: to set _justPressed to NO after a certain time period, and so you can just check whether _justPressed is YES when touchesMoved: is called to ascertain whether it was called recently.
Remember, you don't have to return from the method like in the example above, you can simply use _justPressed to check whether you should play the sound, but still perform your other actions. The example is just to give you a basic idea of what to do.
I asked this question on the Stack Exchange Game Development Site about how to combine the tap and long hold gesture recognizers, and received the following answer:
The way to deal with this is to set a timer once the person taps the phone. The most user friendly scenario that you'd implement would look something like this:
When you detect a tap, set a timer (t = timeToRepeat)
On each frame, decrease the timer by dt
If the timer reaches zero, move the sprite a tile and reset the timer
If the user releases their finger before the first cycle of the timer, move the sprite one tile
Obviously the amount of time that you set your timer to will determine how fast your sprite moves. There are a few variations on this theme depending on the kind of behavior you want as well. For example, you can move the sprite once immediately upon detecting a tap and ignore step #4.
I agree that this is the way to do it, so I am trying to implement this and have come up with the following code to do so:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//This records the time when the user touches the screen
self.startTime = [NSDate date];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSTimeInterval temp = [self.startTime timeIntervalSinceNow];
NSTimeInterval holdTime = temp * -1;
if(holdTime < self.threshold) {
//Tap
}
else {
//Hold
}
}
This code works but I realized that I should call the timer code while the user is holding down on the screen, not after they finish. So is there a way to call the code in touchesEnded while the user is pressing down?
Technically, if the user keeps his/her finger perfectly still there is no method that is called between them. In practice though, touchesMoved gets called a bunch. You should just use an NSTimer though instead of keeping track of the time yourself
I have a collection of six seat objects (UIViews with the alpha property set to 0) on my screen and I have player objects basically placed on top of them. The seats may or may not have a player on top of it. What I have right now is I've programmed the player's touchesMoved event so that when I drag a player on top of a seat object the seat's alpha property will go from 0 to 0.6. And then while still dragging the player, if I drag him off the seat the alpha property will go back to 0.
Instead, is there a built in UIView animation that could instead cause the alpha property to kind of fluctuate back and forth between .6 and .2? Kind of a throbbing effect? Would this require core animation or something more advanced?
I'm using the following in the Player's touchesMoved method to drag a player and to detect if it's above a seat:
UITouch *aTouch = [touches anyObject];
self.center = [aTouch locationInView:[self.superview]];
Seat *seat = [controller seatAtPoint:[aTouch locationInView:self.superview]];
if (seat) {
self.hoverSeat = seat;
seat.alpha = .6;
} else {
self.hoverSeat.alpha = 0;
}
The seatAtPoint method in my controller is as follows:
- (Seat *) seatAtPoint:(CGPoint)point {
NSMutableArray seats = [NSMutableArray arrayWithCapacity:6];
for (int i = 1; i <= 6; i++) {
Seat *aSeat = (Seat*)[self.view viewWithTag:i];
[seats addObject:aSeat];
}
for (Seat *seat in seats) {
if (CGRectContainsPoint([seat frame], point)) {
return seat;
}
}
return nil;
}
I use a hoverSeat ivar to hold the seat above which the player is hovering. And then if the seat returned is nil then it sets that seat's alpha to 0.
A bug I'm seeing with this code is if I move the player around the screen a little too quickly sometimes the alpha property won't go back to 0. Can anyone think of a more effective way to ensure that it goes back to 0?
Thank you for any suggestions.
I would look into using CoreAnimation; it's not that hard to use. Here's what it might look like for a fade:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.5];
seat.alpha = 1;
[UIView commitAnimations];
You can look into the callbacks that methods like these use to create some kind of pulsing animation ( Trigerring other animation after first ending Animation (Objetive-C) ) when combined with a condition you can probably set it up to loop forever. Also of note is that UIView Animations disable user input when they're running ( Infinitely looping animation ).
As for the bug you mentioned, if it's still happening, you could set up a timer that when fired, iterates through all the seats and checks for ones that aren't being displayed correctly. You might want to set this interval to be pretty infrequent so that it doesn't interrupt or impact the performance of your other animations.
I've been busy for a few days trying to figure out how to handle touch in my Cocos2d project. The situation is a bit different as normal. I have a few different game layers that have items on it that I need to control with touch:
ControlLayer: Holds the game controls
(movement, action button). This layer is on top.
GameplayLayer: Holds the game objects
(CCSprites). This layer is directly beneath the ControlLayer.
Now my touches work fine in the ControlLayer, I can move my playable character around and make him jump and do other silly stuff. Yet I cannot grasp how to implement the touches to some of my CCSprites.
The information I've gathered so far makes me think I need get all my touch input from the control layer. Then I somehow need to 'cascade' the touch information to the GameplayLayer so I can handle the input there. Another option would be for me to get the CGRect information from my sprites by somehow creating an array with pointers to the objects that should be touchable. I should be able to use that information in the ControlLayer to check for each item in that list if the item was touched.
What is the best option to do this, and how do I implement this? I'm kind of new to programming with cocoa and Objective C so I'm not really sure what the best option is for this language and how to access the sprites CGRect information ([mySpriteName boundingBox]) in another class then the layer it is rendered in.
At the moment the only way I'm sure to get it to work is create duplicate CGRects for each CCSprite position and so I can check them, but I know this is not the right way to do it.
What I have so far (to test) is this:
ControlLayer.m
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
CGRect rect = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
//Tried some stuff here to get see if I could get a sprite by tagname so I could use it's bounding box but that didn't work
// Check for touch with specific location
if (CGRectContainsPoint([tree boundingBox], location)) {
CCLOG(#"CGRect contains the location, touched!");
}
CCLOG(#"Layer touched at %#", NSStringFromCGPoint(location));
}
Thanks in advance for helping me!
The easiest and simplest way to solve your problem, IMO, is by using ccTouchBegan/Moved/Ended instead of ccTouchesBegan/Moved/Ended. Meaning, you are handling a single touch at a particular moment so you avoid getting confuses over multiple touches, plus the most important feature of ccTouchBegan is a CCLayer can 'consume' the touch and stop it from propagating to the next layers. More explanation after code samples below.
Here are steps to do it. Implement these sets of methods in all CCLayer subclasses that should handle touch events:
First, register with CCTouchDispatcher:
- (void)registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
Next, implement ccTouchBegan, example below is from a game I've created (some part omitted of course):
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
if (scene.state != lvlPlaying) {
// don't accept touch if not playing
return NO;
}
CGPoint location = [self convertTouchToNodeSpace:touch];
if (scene.mode == modePlaying && !firstTouch) {
if (CGRectContainsPoint(snb_putt.sprite.boundingBox, location)) {
touchOnPutt = touch.timestamp;
// do stuff
// return YES to consume the touch
return YES;
}
}
// default to not consume touch
return NO;
}
And finally implement ccTouchMoved and ccTouchEnded like the ccTouches* counterparts, except that they handle single touch instead of touches. The touch that is passed to these methods is restricted to the one that is consumed in ccTouchBegan so no need to do validation in these two methods.
Basically this is how it works. A touch event is passed by CCScene to each of its CCLayers one by one based on the z-ordering (i.e starts from the top layer to the bottom layer), until any of the layers consume the touch. So if a layer at the top (e.g. control layer) consume the touch, the touch won't be propagated to the next layer (e.g. object layer). This way each layer only has to worry about itself to decide whether to consume the touch or not. If it decides that the touch cannot be used, then it just has to not consume the touch (return NO from ccTouchBegan) and the touch will automatically propagate down the layers.
Hope this helps.