I am trying to make an app where the user can press a button to increase the level of some skill. However, I am using the "touchesBegan" method, and if the user wants to upgrade the skill by 25 points, the user will have to click the label 25 times. Here is my code currently to detect if the user has touched the sprite.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if([node.name isEqualTo:#"red-upgrade"]){
if(_xp >= pow(_rpower+1, 2)){
_rpower ++;
_xp -= pow(_rpower, 2);
}
_rpowerlabel.text = [NSString stringWithFormat:#"Red Power: %i", _rpower];
}
}
Is there a way to implement a hold and press gesture on a sprite or a label? So the user can just press and hold on the upgrade button and it will increment many times instead of continuously clicking?
Any and all suggestions are appreciated!
Keep a member referencing the skill button in the enclosing class and then and start an action on the button to increment the needed value in the touch begin method:
if([node.name isEqualTo:#"red-upgrade"]){
[self.skillButton runAction:[SKAction repeatForever:[SKAction sequence:#[[SKAction waitForDuration:0.5],
[SKAction runBlock:^{
if(self.xp >= pow(self.rpower+1, 2)){
self.rpower ++;
self.xp -= pow(self.rpower, 2);
}
self.rpowerlabel.text = [NSString stringWithFormat:#"Red Power: %i", self.rpower]
}]]]]];
}
I used a small delay or else the value will be increased really fast (unless that what you want).
In the touchEnd method stop the node's action with [self.skillButton removeAllActions]; regardless where the location is (since then the user can slide his finger and the location will be different)
Not sure if the cleanest of solutions but that should work for you
Related
So I'm trying to implement a method into a game where if a player has their finger touching the screen at lets say 50 on the y axis or lower, an SKAction will run to move the character sprite down, and if their finger is touching above the 50, another action will run moving the sprite up. I don't know how you have it recognize a touch and hold though. Help please.
You will need to capture the touch event of the user in order to pull this off. I suggest checking out the excellent tutorial provided by RayWenderlich at: Animating Textures and Moving Them With Touch Events
If you don't have time for that this is what your code might look like:
First the knowledge that you will need is that the two most generic touch events are the:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
and the:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
Basically the touches began method is called when the user places their finger on the screen whereas the touches ended method will be triggered when the user takes their finger off the screen.
For this example I will use touchesEnded, but feel free to change it if you want, its as easy as switching the "Ended" with "Began".
All of the code I am about to show you will take place within the scenes implementation file:
If you just want the sprite to move up when above a certain point and down when below a certain point use the following:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//used to hold the location to travel
CGPoint moveLocation;
//check if the Y location is less than 50
if(location.y < 50)
{
//the the location to the bottom of the screen
moveLocation = CGPointMake(sprite.position.x, 0);
}
//and then if its more than 50
else
{
//set the locaiton to the top of the screen
moveLocation = CGPointMake(sprite.position.x, self.frame.size.height);
}
//move the sprite
//Check if their already moving
if(sprite actionForKey:#"moving")
{
//If they are stop them so they can move in the new direction
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:5.0f];
//remember to give the action a key so that we can check for it during the above if statement
[sprite runAction:moveAction withKey:#"moving"];
}
And there you have it. The only flaw is that it will always take 5 seconds to reach the location regardless of your distance from it, read on if you would like hints on how to correct that, or if you would like to see how to make the sprite travel to any touched location on the screen.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//move the sprite
//Check if their already moving
if(sprite actionForKey:#"moving")
{
//If they are stop them so they can move in the new direction
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:5.0f];
//remember to give the action a key so that we can check for it during the above if statement
[sprite runAction:moveAction withKey:#"moving"];
}
That code will make it so that for 5 seconds the sprite will move to the location touched on the screen. Just remember to replace the instances of "sprite" with the name of your SKSpriteNode variable. For more complex movement try the following:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//capture the location of the touch
CGPoint location = [[touches anyObject] locationInNode:self];
//set the velocity of the subject
float velocity = self.frame.size.width/3.0;
//The above will ensure that it will take the sprite 3 seconds to travel the distance of the screen
//Determine the difference between the point touched and the sprites position
CGPoint moveDifference = CGPointMake(location.x - sprite.position.x, location.y - sprite.position.y);
//Use pythagorean theorem to figure out the actual length to move
float distanceToMove = sqrtf(moveDifference.x * moveDifference.x + moveDifference.y*moveDifference.y);
//Use the distance to travel and the velocity to determine how long the sprite should move for
float moveDuration = distanceToMove / velocity;
//and finally move the sprite
if(sprite actionForKey:#"moving")
{
[sprite removeActionForKey:#"moving"];
}
SKAction *moveAction = [SKAction moveTo:location duration:moveDuration];
[sprite runAction:moveAction withKey:#"moving"];
}
That will set a velocity for the sprite, determine the length of the travel, and how long it will take to get to the new location.
If you want to involve textures I highly suggest reading the link I provided.
Upon request I would be glad to offer examples using forces to control movement speed as well.
I hope that helps, let me know if there are any problems I wasn't in a position where I could run the code but I am sure that it is fine.
I am trying to make a button in sprite kit using SKSpriteNode. I want the button image to change when it is pressed and revert back to old image as soon as the press ends. What i have done till now is following:-
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.startTouch = [[touches allObjects][0] locationInNode:self ];
for (UITouch *touch in touches){
CGPoint position = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:position];
if ([node.name isEqualToString:#"missileButton"]) {
TEMissileButtonNode *button = (TEMissileButtonNode*) node;
button.isPressed = YES;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches){
CGPoint position = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:position];
if ([node.name isEqualToString:#"missileButton"]) {
TEMissileButtonNode *button = (TEMissileButtonNode*) node;
button.isPressed = NO;
}
}
}
inside the update method i am calling this method to check if the touch has ended
-(void)changeMissileButton{
if (self.missileButton.isPressed) {
[self.missileButton addMoreMissileButtons];
[self.missileButton setTexture:[SKTexture textureWithImageNamed:#"missileButtonPressed"]];
}else{
[self.missileButton setTexture:[SKTexture textureWithImageNamed:#"missileButtonDeselected"]];
[self.missileButton hideMissileButtons];
}
}
The issue is that the touch doesn't get registered at times. Sometimes it works the way i want. When i touch it, its texture changes and when i remove my finger, the texture reverts back to old texture. But most of the times, the button doesn't react to my touch. Am i missing something?
Use touchesBegan, touchesMoved, touchesEnded.
touchesBegan - check if the button (SKSpriteNode) contains the touch. If so, change you button texture.
touchesMoved- if button does not contain touch, change texture back.
touchesEnded- if touch stayed within button during touchesMoved, change texture back.
Above is the logic for how to efficiently accomplish what you are trying to do. Yes, it is annoying Spritekit decided to abandon buttons.
i followed this wonderful guide about mario-style game:
http://www.raywenderlich.com/62053/sprite-kit-tutorial-make-platform-game-like-super-mario-brothers-part-2
however, i wanted to convert the movement controls to arrow keys, implemented by SKSpriteNodes with names that are detected by:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
// other left, up, down arrows with same code here
if ([node.name isEqualToString:#"rightArrow"]) {
self.player.moveRight = YES;
self.rightOriginalTouchLocation = location;
...
}
}
self.player.moveRight is a boolean value (much like moveForward in the guide), that tells the character at update to move.
it is terminated at:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
// other left, up, down arrows with same code here
if ([node.name isEqualToString:#"rightArrow"]) {
self.player.moveRight = NO;
}
...
}
however, i encounter the following problem - when i start the touch on the arrow, drag it outside the arrow, and then release the tap, it is not recognized as 'touch ended' for the arrow node (and it doesn't stop moving because of that).
i tried to solve it in many ways (even calculating touch move distance from original location and see if its too far, then cancel movement), but i always manage to reproduce the constant motion problem.
the issue lies with the fact that i can tap two arrows at the same time, so it is not enough to remember the last node tapped.
since i want to allow movement for different directions at the same time, i cant stop all movements in case one button is dismissed. i need to specifically know which button was released so i can stop that direction's movement only.
do you have any ideas for me? should i implement it in another way considering i want the arrow keys, or is there a method to detect which node is released even though it is not at its original location (of the tap)?
Thank you very much!
if anyone is interested about the issue - i had a problem where touching and moving from SKSpriteNode didnt call the touchesEnded for that SKSpriteNode (since the 'release' of the touch was not in the node).
i solved it by keeping the CGPoint of the touch at touchesBegan, and when touchesEnded called, i calculated distances to nearest key using simple distance function:
-(int)calculateDistanceWithPoints:(CGPoint)point1 andPoint:(CGPoint)point2 {
float d = sqrtf(pow((point1.x - point2.x), 2) + pow((point1.y - point2.y), 2));
return (int)d;
}
and then, in touchesEnded, i checked to which key the distance is minimal(i have 3 keys, so which key is most likely to be the key that was 'released') - and did the action required for that key release.
I'm building a game using Sprite Kit that needs quick precise movements following the user's touch. I need to detect if the user has touched on the "Player" in the view, and if they have, when they move, the player sprite needs to move with the touch accordingly.
I have this working now, however, it's not very precise... the more movement input (without lifting your finger), the more offset the sprite gets from the touch location.
Here's the code I'm using right now.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if (self.isFingerOnPlayer) {
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
// Calculate new position for player
int playerX = player.position.x + (touchLocation.x - previousLocation.x);
int playerY = player.position.y + (touchLocation.y - previousLocation.y);
// Limit x and y so that the player will not leave the screen any
playerX = MAX(playerX, player.size.width/2);
playerX = MIN(playerX, self.size.width - player.size.width/2);
playerY = MAX(playerY, (player.size.width/2)-3+inBoundsOffset);
playerY = MIN(playerY, (self.size.height - ((player.size.width/2)-3) - inBoundsOffset));
// Update position of player
player.position = CGPointMake(playerX, playerY);
}
}
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
SKPhysicsBody* body = [self.physicsWorld bodyAtPoint:touchLocation];
if (body && [body.node.name isEqualToString: #"player"]) {
NSLog(#"Began touch on player");
self.isFingerOnPlayer = YES;
}
}
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
self.isFingerOnPlayer = NO;
}
It detects the touch location, checks to make sure that you're touching the player sprite, if you are, then when you move, so does the sprite... but it can get off quite quickly if you're slinging your finger around (as playing this game will cause the player to do).
Can anybody suggest a more accurate way of accomplishing this, that will keep the sprite under the user's finger even when moving around a lot without lifting your finger?
You have to keep in mind that -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event ONLY gets called when the moving finger writes (sorry couldn't help the Inspector Clouseau reference).
In effect what happens is that the user can very quickly move his/her finger from one location to the next and as soon the the finger is lifted, your position updates stops.
What I suggest you do is create a CGPoint property and have -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event store the location in the CGPoint property.
Then in your -(void)update:(CFTimeInterval)currentTime you add the code that actually moves the player to the finger coordinates. This should make things a lot smoother.
I'm using Sprite Kit to create a game involving balloons. The balloon image is a long rectangle (dotted line) but I've defined a physics body around the balloon itself using a polygon (solid line). I'd like players to be able to "pop" the balloon. I'm using the following block of code to achieve this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
[node removeFromParent];
SKAction *pop = [SKAction playSoundFileNamed:#"pop.wav" waitForCompletion:NO];
[self runAction:pop];
}
}
Unfortunately, when there are several balloons in the bunch, this method can result in a single tap popping multiple balloons. This is because the sprite images overlap (even though the physics bodies do not). There is also the undesired effect that a tap on the string will have the same effect as a tap on the balloon.
I have access to the coordinates of the touch point, but is it even possible to detect whether a point lies within the area defined by the physics body (as opposed to the node)?
Use the physics world's bodyAtPoint: method:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:location];
bodyAtPoint is buggy but you have a working alternative.
You can create rays using baloon polygon path.
[self.physicsWorld enumerateBodiesAlongRayStart:rayStart end:rayEnd
usingBlock:^(SKPhysicsBody *body, CGPoint point,CGVector normal, BOOL *stop) {
if(body.categoryBitMask & baloonCategory){
SKPhysicsBody *contactBody=body;
CGPoint contactPoint=point;
*stop=YES;
}
}];
I looked but wasn't able to find a way of checking whether a touch is made within the physicsBody of a sprite. I would suggest separating the string and the ballon sprites.
You can try any the following:
Using separate sprite nodes for the balloon and the string and connecting them using a SKPhysicsJoint.
Subclass SKSpriteNode and add two sprites, i.e. the balloon and the string as children of the Sprite. Then u can check whether a touch made on the sprite is on the ballon or not.
Additionally for the multiple ballon pop problem, you can modify your code like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
//Instead of popping all nodes within the touch, pop only the first object.
if ([nodes count] > 0)
{
SKNode *node = [nodes firstObject];
[node removeFromParent];
SKAction *pop = [SKAction playSoundFileNamed:#"pop.wav" waitForCompletion:NO];
[self runAction:pop];
}
}