How to add Highlighted Button in SpriteKit? - ios

I know how to add Button in SpriteKit.
I have done with SKSpriteNode.
However i don't know how to add highlight button like UIButton.
I mean when i pressing SKSpriteNode(Button), i want to show highlighted image like UIButton from UIKit.
How can i do that?
Here is my codes that for Button with SKSpriteNode.
self.playButton = [SKSpriteNode spriteNodeWithImageNamed:#"playButton.png"];
self.playButton.position = CGPointMake(self.frame.origin.x + 400, 100);
self.playButton.name = #"playButton";
self.playButton.zPosition = 2;
[self addChild:self.playButton];

I Tried and got what you are exactly looking for,
BOOL isMySpriteNodeTouched = NO;
mySprite = [[SKSpriteNode alloc]initWithImageNamed:#"ball"];
mySprite.position = CGPointMake(self.size.width/2.0f, self.size.height/2.0f);
mySprite.name = #"mySprite";
[self addChild:mySprite];
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
CGPoint touchPoint = [touch locationInNode:self];
SKNode *localNode = [[SKNode alloc]init];
localNode = [self nodeAtPoint:touchPoint];
if ([localNode.name isEqualToString:mySprite.name])
{
mySprite.texture = [SKTexture textureWithImage:[UIImage imageNamed:#"highlightedBall"]];
isMySpriteNodeTouched = YES;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(isMySpriteNodeTouched)
{
isMySpriteNodeTouched = !isMySpriteNodeTouched;
mysprite.texture = [SKTexture textureWithImage:[UIImage imageNamed:#"ball"]];
}
}

One solution is to create two SKSpriteNodes, one for the active state and one for the default state, and add both of them to your view.
In your touchesMoved and touchesEnded methods, hide and unhide the active and default buttons accordingly.
Here's a tutorial in Swift.

You can create a UIButton in SK using the view property in the method -(void)didMoveToView:(SKView *)view.
All the UIButton features, including highlight will be accessible.

Related

SKNode Subclass Not Moving Children

I have a sprite that can't exist in my game without a pairing SKFieldNode so my solution was to create a subclass of SKSpriteNode and create a property for the SKFieldNode but it didn't work because the SKSpriteNode was acting weird (I don't remember exactly what happened). So my next approach was to change the subclass to SKNode and then I would make the SKSpriteNode and the SKFieldNode a property of this new SKNode. But then it turns out that touchesMoved will only move one of the properties (whichever is on top) which turns out to always be the SKSpriteNode.
What's the best approach to this problem, and how can I fix it so that I can have an SKFieldNode for every SKSpriteNode while still making sure that actions and methods still work properly.
Current code of SKNode subclass:
#interface Whirlpool : SKNode
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff;
#property (nonatomic, strong) SKFieldNode *gravityField;
#end
#import "Whirlpool.h"
#import "Categories.h"
#implementation Whirlpool
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff {
if (self = [super init]) {
// whirlpool sprite
SKSpriteNode *whirlpoolSprite = [[SKSpriteNode alloc] initWithImageNamed:#"whirlpool"];
whirlpoolSprite.size = CGSizeMake(100, 100);
whirlpoolSprite.position = pos;
//removing physicsBody and associated attributes for now so that the boat does not collide with the whirlpool
//whirlpoolSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:whirlpoolSprite.size.width / 2];
//whirlpoolSprite.physicsBody.dynamic = NO;
whirlpoolSprite.zPosition = 1;
whirlpoolSprite.name = #"whirlpool";
[whirlpoolSprite runAction:[SKAction repeatActionForever:[self sharedRotateAction]]];
// whirlpool gravity field
_gravityField = [SKFieldNode radialGravityField];
_gravityField.position = pos;
_gravityField.strength = strength;
_gravityField.falloff = falloff;
_gravityField.region = [[SKRegion alloc] initWithRadius:region];
_gravityField.physicsBody.categoryBitMask = gravityFieldCategory;
_gravityField.zPosition = 1;
[self addChild:whirlpoolSprite];
[self addChild:_gravityField];
}
return self;
}
- (SKAction *)sharedRotateAction {
static SKAction *rotateWhirlpool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
rotateWhirlpool = [SKAction rotateByAngle:-M_PI * 2 duration:4.0];
});
return rotateWhirlpool;
}
#end
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// if the finger touches the boat or the whirlpool, update its location
if ([node.name isEqualToString:#"boat"]) {
node.position = CGPointMake(location.x, node.position.y);
} else if ([node.name isEqualToString:#"whirlpool"]) {
node.position = location;
}
}
}
I believe your issues comes down to that fact that "whirlpool" is a child of your SKNode subclass. So when you are identifying that you are indeed touching a "whirlpool" you are moving it within its parent (the SKNode subclass) and the SKFieldNode and parent stay put. This little adjustment to your original code should work...if I understand the problem correctly.
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// if the finger touches the boat or the whirlpool, update its location
if ([node.name isEqualToString:#"boat"]) {
node.position = CGPointMake(location.x, node.position.y);
} else if ([node.name isEqualToString:#"whirlpool"]) {
//move the SKNode subclass the whirlpool is a child of
node.parent.position = location;
}
}
}
Hopefully that helps.
Yikes, the problem here is the way you are grabbing nodes, you may be grabbing the wrong nodes due to all the children. Instead take this approach:
We already know that you are subclassing your sprites, so in your subclasses, add the following code:
//Whirlpool
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
self.position = location;
}
}
Then:
//Boat
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
self.position.x = location.x;
}
}
Then for your Scene, do this:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
//At this point it should call the children on touch events
[super.touchesMoved: touches withEvent:event];
}

Nevigating One ViewController to another on Spritekit

I have a SpriteKit project with two view controllers. One is default GameViewController and another I added a TableViewController.
I want to switch between GameViewController to TableViewController.It did not switch the view controller.
In GameScene.m
GameViewController *vc =(GameViewController*)self.view.window.rootViewController;
[vc moveToFriendsViewController];
NSLog(#"vc called from gamescene");
In GameViewController.h
#protocol ViewControllerDelegate <NSObject>
-(void)moveToFriendsViewController;
#end
#interface GameViewController : UIViewController<ViewControllerDelegate>
#end
In GameViewController.m
-(void)moveToFriendsViewController{
FriendsTableViewController *vc =[[FriendsTableViewController alloc] init];
// do any setup you need for myNewVC
[self.navigationController pushViewController:vc animated:YES];
NSLog(#"vc called from viewcontroller");
}
I created a sample project for you to understand what I meant by using a scrolling node. It is very generic in nature and you can tweak, modify and add your own values, code, etc...
I store the user's y position in the touchesBegan method. I then check for any changes in y during the touchesMoved method and move the menuNode accordingly. However, there are other ways of doing this. You could for example just add a "up" and "down" button and move the menu based on which one is touched. Different approach but same result.
To see if a menu item was selected, I compare the user's y position touch from the touchesBegan method to the y position in the touchesEnded method. If there is no change, the user did not swipe up or down and I NSLog the selected node. You can add a tolerance of a couple points here in case the user moves the touch just a little bit.
Again, it's generic code and there are many ways to do what you want but this should give you a couple of ideas to work with.
#import "GameScene.h"
#implementation GameScene {
// declare ivars
SKSpriteNode *menuNode;
float yTouch;
}
-(void)didMoveToView:(SKView *)view {
// add menu background
menuNode = [SKSpriteNode spriteNodeWithColor:[SKColor darkGrayColor] size:CGSizeMake(200, 1000)];
menuNode.name = #"menuNode";
menuNode.position = CGPointMake(100, 800);
menuNode.zPosition = 10;
[self addChild:menuNode];
float yPos = -450;
for (int i = 0; i < 23; i++) {
SKLabelNode *menuItem = [SKLabelNode labelNodeWithFontNamed:#"HelveticaNeue"];
menuItem.name = [NSString stringWithFormat:#"menuItem-%i",i];
menuItem.text = [NSString stringWithFormat:#"menuItem-%i",i];
menuItem.fontSize = 20;
menuItem.fontColor = [SKColor redColor];
menuItem.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
menuItem.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
menuItem.position = CGPointMake(0, yPos);
menuItem.zPosition = 25;
[menuNode addChild:menuItem];
yPos += 40;
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
// get starting y position of touch
yTouch = touchLocation.y;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
// check for changes in touched y position and menuNode limits
if((touchLocation.y > yTouch) && (menuNode.position.y < 800)) {
menuNode.position = CGPointMake(menuNode.position.x, menuNode.position.y+15);
}
if((touchLocation.y < yTouch) && (menuNode.position.y > 200)) {
menuNode.position = CGPointMake(menuNode.position.x, menuNode.position.y-15);
}
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
SKNode *node = [self nodeAtPoint:touchLocation];
// if current touch position y is same as when touches began
if(touchLocation.y == yTouch) {
NSLog(#"%#",node);
}
}
-(void)update:(CFTimeInterval)currentTime {
}
#end
If you are using xibs, your method should work, but in Storyboard the below method will work fine.
-(void)moveToFriendsViewController{
FriendsTableViewController *vc =[[FriendsTableViewController alloc] init];
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"FriendsTableViewController"];
[self.navigationController pushViewController:vc animated:YES];
NSLog(#"vc called from viewcontroller");
}

Drag UIImageView images using UITouch Methods

I have a UITableViewCell with UIImageViews embedded. I understand it's not common practice but I'm using the UITouch methods to accomplish horizontal swipes. I retrieve images from a url/api . The issue is I wish to drag one image at a time from either left to right. The problem is, when i drag my finger on across the imageView, all loaded images slide through quickly. I want to focus on one image at a time during the drag. Hope I was clear.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
startLocation = [[touches anyObject] locationInView:self];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint point = [[touches anyObject] locationInView:self];
UITouch * touch = [touches anyObject];
self.viewOne.center = CGPointMake(self.viewOne.center.x+point.x - startLocation.x, self.viewOne.center.y);
if (startLocation.x < point.x) {
//Swipe Right
if ([self.photo.photoURLS isKindOfClass:[NSArray class]]) {
if (self.isSwiping == NO) {
self.isSwiping = YES;
self.currentImage--;
if (self.currentImage == -1) {
self.currentImage = 12;
}
self.viewBack.frame = CGRectMake(-self.frame.size.width,0.0f, self.frame.size.width, self.frame.size.height);
self.viewBack.hidden = NO;
self.loadingImage.hidden = NO;
NSURL *imageURL = [[NSURL alloc] initWithString:self.photo.photoURLS[self.currentImage]];
[self asyncLoadImageFromURL:imageURL andPhotoID:self.photo.id withCallback:^(UIImage *image, NSString *photoID) {
self.loadingImage.hidden = YES;
[imageCache setImage:image forKey:self.photo.photoURLS[self.currentImage]];
self.viewBack.image = image;
//new
self.viewOne.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height);
self.viewOne.image = self.viewBack.image;
self.isSwiping = NO;
self.pageControl.currentPage = self.currentImage;
self.viewBack.hidden = YES;
}];
}
}
}
} else {
//Swipe Left
}
}
It's not entirely clear what you're asking or what the issue is, but the following lines are suspect:
CGPoint point = [[touches anyObject] locationInView:self.vehicleImage];
self.viewOne.center = CGPointMake(self.viewOne.center.x+point.x -startLocation.x, self.viewOne.center.y);
if (startLocation.x < endLocation.x)
You are not setting endLocation until after you've finished touching the screen. During your touchesMoved responder, you'll be using bad endLocation data. You should be using the value of point in this function to determine motion.
It's also not really clear what is the relationship between self.vehicleImage and self.viewOne. If self.vehicleImage is a subview of self.viewOne (or otherwise moves during your frame changes), your locationInView will return seemingly inconsistent values, exaggerating the motion of your finger. Instead, you should be picking a view that will stay static during this process (perhaps your ViewController's self.view).

SpriteKit - Stop Clicks on Sprite From Acting Upon Children

I currently use the following to create a button with text using SpriteKit:
SKLabelNode *startButtonText = [SKLabelNode labelNodeWithFontNamed:#"Verdana-Bold"];
startButtonText.text = #"Start";
startButtonText.fontColor = [SKColor colorWithRed:1 green:1 blue:1 alpha:1];
startButtonText.fontSize = 24;
startButtonText.position = CGPointMake(self.size.width/2, self.size.height/2-10);
startButtonText.name=#"startButton";
SKShapeNode *startButton = [[SKShapeNode alloc] init];
startButton.path = [UIBezierPath bezierPathWithRect:CGRectMake(center.x-65.0 , center.y-20.0, 130.0, 40.0)].CGPath;
startButton.fillColor = [SKColor colorWithRed:0.188 green:0.196 blue:0.161 alpha:1];
startButton.strokeColor = nill;
startButtonText.name=#"startButton";
[startButton addChild:startButtonText];
[self addChild:startButton];
My goal is to make it so that when the screen is touched, the touch only registers startButton not start startButtonText via:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"%#",node.name);
}
In action script, you would simply use:
mouseChildren = false;
Is there anyway this is possible?
Give both buttons a different name. It looks like there's a copy & paste error in your code.
Then check in your touchesBegan which button has been pressed and react on both the same way:
if ([node.name isEqualToString:#"startButton"]||[node.name isEqualToString:#"startButtonText"]) {
...
}

iOS >> Drag and Drop Issue >> UIView Returns to Original Position

I have 4 UIImageViews set in IB. I also have a UILabel describing the status (as described in code below).
I use the touchesBegan, touchesMoved and touchesEnd methods to capture the object move as follow:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* aTouch = [touches anyObject];
UIView* aView = [aTouch view];
if (aView != self.view) {
[self.view bringSubviewToFront:aView];
self.statusLabel.text = #"You're Dragging...";
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* aTouch = [touches anyObject];
UIView* aView = [aTouch view];
if (aView != self.view) {
aView.center = [aTouch locationInView:self.view];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.statusLabel.text = #"At Ease";
}
The problem is that after I move (in the simulator) one of the UIImageViews they "jump" back to their original position as its set in IB instead of remaining where I dropped them.
Why does it happen?
How can I "capture" the UIImageView new position at "touchesEnd" and avoid this "jump"?
P.S. I noticed that if I don't update the label at "touchesEnd" the UIImageView remains at its last position until I click on another UIImageView; what's going on over here?
your code seems to be correct..you just try it after unchecking auto layout ...
Later i found author has posted the answer himself...so,sorry for this post..
I un-checked the "Auto Layout" checkbox in IB and it worked fine.
That can't be the "best practice" to do that... but it works.

Resources