I need to make an end screen for my app after the collision detection. What is the easiest possible way to make the end screen with a button back to the main menu/to the game. Can I use a ViewController? I've read a lot of tutorials, videos, and also all of the posts on here:
This is my current code (not all of it just some important things):
#implementation MyScene
static const int squirrelHitCategory = 1;
static const int nutHitCategory = 2;
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
_squirrelSprite = [SKSpriteNode spriteNodeWithImageNamed:#"squirrel"];
_squirrelSprite.position = _firstPosition;
_atFirstPosition = YES;
_squirrelSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
_squirrelSprite.physicsBody.categoryBitMask = squirrelHitCategory;
_squirrelSprite.physicsBody.contactTestBitMask = nutHitCategory;
_squirrelSprite.physicsBody.collisionBitMask = nutHitCategory;
_squirrelSprite.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild:_squirrelSprite];
SKAction *wait = [SKAction waitForDuration:3.0];
SKSpriteNode *lightnut = [SKSpriteNode spriteNodeWithImageNamed:#"lightnut.png"];
lightnut.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(200,160)];
lightnut.physicsBody.categoryBitMask = nutHitCategory;
lightnut.physicsBody.contactTestBitMask = squirrelHitCategory;
lightnut.physicsBody.collisionBitMask = squirrelHitCategory;
lightnut.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild: lightnut];
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
// Check time since the last touch event
if (touch.timestamp-_lastTouch >= .9) {
// Allow touch
NSLog(#"greater than or equal to 3 seconds");
if (_atFirstPosition)
{
SKAction *moveNodeLeft = [SKAction moveByX:-207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeLeft withKey:#"moveleft"];
} else {
SKAction *moveNodeRight = [SKAction moveByX:207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeRight withKey:#"moveright"];
}
_atFirstPosition = !_atFirstPosition;
_squirrelSprite.xScale *= -1.0;
}
else {
// Ignore touch
NSLog(#"Seconds since last touch %g",touch.timestamp-_lastTouch);
}
// Store timestamp
_lastTouch = touch.timestamp;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"contact detected");
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
if(firstBody.categoryBitMask == squirrelHitCategory || secondBody.categoryBitMask == nutHitCategory)
{
}
}
I want my end screen to be simple, so the easiest way to make it is best. Thank you!
For my games I add a UIViewController property and assign it to the SKScene.
You can then add a view as a sub view. Example...
SKScene *scene = [SKScene sceneWithSize:self.view.frame.size];
scene.viewController = self;
Then in your scene...
[self.viewController addSubview:yourView];
Here is some of my own game's implementation to help get you started.
In your Storyboard, or XIB whichever you prefer you add a view with buttons as properties of the view. In my case I have a scoreLabel, a menuButton, and a restartButton as properties and can set them all as x.hidden = YES; as I wish by triggering them in the scene such as...
self.vc.menuButton.hidden = YES;
self.vc.restartButton.hidden = YES;
In your GameViewController (Which is a subclass of UIViewController)...
//header
#interface GameViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
#property (weak, nonatomic) IBOutlet UIView *hudView;
#property (weak, nonatomic) IBOutlet UIButton *menuButton;
#property (weak, nonatomic) IBOutlet UIButton *restartButton;
#property (nonatomic) MazeScene *maze;
#end
//class
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
// Debug Options
// skView.showsFPS = YES;
// skView.showsNodeCount = YES;
// skView.showsPhysics = YES;
// Create and configure the maze scene.
CGSize sceneSize = skView.bounds.size;
skView.ignoresSiblingOrder = YES;
MazeScene *maze = [[MazeScene alloc] initWithSize:sceneSize];
maze.scaleMode = SKSceneScaleModeAspectFill;
maze.vc = self;
[skView presentScene:maze];
_maze = maze;
}
In your SKScene...
//header
#interface MazeScene : SKScene <SKPhysicsContactDelegate>
#property (nonatomic, weak) GameViewController *vc;
#end
//class
-(id) initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
//(GameViewController*) is a static cast of a subclass of UIViewController.
[self.view addSubview:((GameViewController*)self.vc).hudView];
// [self restartGame] is a specific method for my game, don't worry about it
[self restartGame];
}
return self;
}
Related
I'm making my second game on XCode and there seems to be something wrong with the code. It's a space shooter game where the playership follows your finger and you tap to release the missile. The problem is... when I press 'start game', everything is hidden and will not popup. Here is my viewcontroller.h and viewcontroller.m
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
int score;
int lives;
int enemyAttackOccurence;
int enemyPosition;
int randomSpeed;
float enemySpeed;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
#implementation PlayViewController
-(void)viewDidAppear:(BOOL)animated {
// Images that are to be hidden
playerShip.hidden = YES;
enemyShip.hidden = YES;
missile.hidden = YES;
earth.hidden = YES;
// Hidden Labels
scoreLabel.hidden = YES;
livesLabel.hidden = YES;
// Set score and lives remaining
score = 0;
lives = 0;
// Strings
scoreString = [NSString stringWithFormat:#"Score: 0"];
liveString = [NSString stringWithFormat:#"Lives: 0"];
// Initial Label Text
scoreLabel.text = scoreString;
livesLabel.text = liveString;
// Image starting positions
playerShip.center = CGPointMake(150, 658);
enemyShip.center = CGPointMake(175, 20);
missile.center = CGPointMake(playerShip.center.x, playerShip.center.y);
}
-(IBAction)startGame:(id)sender {
// Hide buttons
startButton.hidden = YES;
exitButton.hidden = YES;
// Images to show
playerShip.hidden = NO;
enemyShip.hidden = NO;
earth.hidden = NO;
// Labels
scoreLabel.hidden = NO;
livesLabel.hidden = NO;
[self positionEnemy];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];
playerShip.center = CGPointMake(point.x, playerShip.center.y);
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[missileMovementTimer invalidate];
missile.hidden = NO;
missile.center = CGPointMake(playerShip.center.x, playerShip.center.y);
missileMovementTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(missileMovement) userInfo:nil repeats:YES];
}
-(void)positionEnemy {
// Random enemy position
enemyPosition = arc4random() % 249;
enemyPosition = enemyPosition + 20;
// Enemy Image Location
enemyShip.center = CGPointMake(enemyPosition, -40);
// Set enemy speed
randomSpeed = arc4random() % 3;
switch (randomSpeed) {
case 0:
enemySpeed = 0.03;
break;
case 1:
enemySpeed = 0.02;
break;
case 2:
enemySpeed = 0.01;
default:
break;
}
enemyAttackOccurence = arc4random() % 5;
[self performSelector:#selector(enemyMovementTimerMethod) withObject:nil afterDelay:enemyAttackOccurence];
}
-(void)enemyMovementTimerMethod {
enemyMovementTimer = [NSTimer scheduledTimerWithTimeInterval:enemySpeed target:self selector:#selector(enemyMovement) userInfo:nil repeats:YES];
}
-(void)enemyMovement {
enemyShip.center = CGPointMake(enemyShip.center.x, enemyShip.center.y + 2);
if (CGRectIntersectsRect(enemyShip.frame, earth.frame)) {
lives = lives - 1;
liveString = [NSString stringWithFormat:#"Lives: %i", lives];
livesLabel.text = liveString;
// Stop Enemy Moving
[enemyMovementTimer invalidate];
if (lives > 0) {
[self positionEnemy];
}
if (lives == 0) {
[self gameOver];
}
}
}
-(void)missileMovement {
missile.hidden = NO;
missile.center = CGPointMake(missile.center.x, missile.center.y - 2);
if (CGRectIntersectsRect(missile.frame, enemyShip.frame)) {
score = score + 1;
scoreString = [NSString stringWithFormat:#"Score: %i", score];
scoreLabel.text = scoreString;
// Stop missile
[missileMovementTimer invalidate];
// Position missile to be at the playerShip's center
missile.center = CGPointMake(playerShip.center.x, playerShip.center.y);
missile.hidden = YES;
// Stop enemy movement
[enemyMovementTimer invalidate];
[self positionEnemy];
}
}
-(void)gameOver {
[enemyMovementTimer invalidate];
[missileMovementTimer invalidate];
[self performSelector:#selector(gameReplay) withObject:nil afterDelay:3];
}
-(void) gameReplay {
// Images that are to be hidden
playerShip.hidden = YES;
enemyShip.hidden = YES;
missile.hidden = YES;
earth.hidden = YES;
// Hidden Labels
scoreLabel.hidden = YES;
livesLabel.hidden = YES;
// Set score and lives remaining
score = 0;
lives = 0;
// Strings
scoreString = [NSString stringWithFormat:#"Score: 0"];
liveString = [NSString stringWithFormat:#"Lives: 0"];
// Initial Label Text
scoreLabel.text = scoreString;
livesLabel.text = liveString;
// Image starting positions
playerShip.center = CGPointMake(150, 658);
enemyShip.center = CGPointMake(175, 20);
missile.center = CGPointMake(playerShip.center.x, playerShip.center.y);
}
#end
ViewController.h (Just for backup)
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
IBOutlet UIButton *startGame;
}
#end
#interface PlayViewController : UIViewController {
IBOutlet UIImageView *playerShip;
IBOutlet UIImageView *enemyShip;
IBOutlet UIImageView *missile;
IBOutlet UIImageView *earth;
IBOutlet UILabel *livesLabel;
IBOutlet UILabel *scoreLabel;
IBOutlet UIButton *startButton;
IBOutlet UIButton *exitButton;
UITouch *touch;
NSString *liveString;
NSString *scoreString;
NSTimer *enemyMovementTimer;
NSTimer *missileMovementTimer;
}
-(IBAction)startGame:(id)sender;
#end
I am watching a tutorial for this game, the person who created doesn't reply. Please help -- I cannot be any more specific. It just must be a weird gap in the code. Thanks.
Also, you have an IBOutlet and IBAction set for your StartGame button. The IBOutlet you never seem to use. You could be confusing your compiler by having the same name for the UIButton's IBOutlet and IBAction. Remove the IBOutlet, or change the name properly and see if that changes anything.
I'd recommend messing with your lines of code where you are setting object.hidden = YES and object.hidden = NO and see what happens. Often times tampering and testing your code is a good way to see what is going on. Make sure images are set for your UIImageViews. I'm assuming they are set in your interface builder because i don't see where you set them in your code. If there is no image set for the UIImageViews they will be see-through unless given a specific color. If tampering with the code doesn't work it won't hurt to re-watch the tutorial and make sure you didn't mess anything up. Often times the tutorials we watch are out-dated and we are left to solve a small problem ourselves and this may or may not be one of those instances. Again though, test your code and see if things are actually being set to hidden or not when you press that button.
I have got a SKScene name “Scene” in which I instantiate 2 objects of my class “Button” subclass of SKNode.
In order to not multiply the number of lines of code in my SKScene, I wanted to implemente the touch methods directly in my class “Button”.
Here is the code :
Scene.h
#import <SpriteKit/SpriteKit.h>
#interface Scene : SKScene
#end
Scene.m
#import "Scene.h"
#import "Button.h"
#implementation Scene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
Button *button01 = [Button node];
[button01.number setString: #"1"];
button01.position = CGPointMake(CGRectGetMidX(self.frame) - 50, 160);
Button *button02 = [Button node];
[button02.number setString: #"2"];
button02.position = CGPointMake(CGRectGetMidX(self.frame) + 50, 160);
[self addChild:button01];
[self addChild:button02];
}
return self;
}
#end
Button.h
#import <SpriteKit/SpriteKit.h>
#interface Button : SKNode
#property (strong, nonatomic) NSMutableString *number;
#end
Button.m
#import "Button.h"
SKShapeNode *button;
#implementation Button
- (id)init
{
if (self = [super init])
{
self.userInteractionEnabled = YES;
_number = [[NSMutableString alloc] init];
[_number setString: #"0"];
button = [SKShapeNode node];
[button setPath:CGPathCreateWithRoundedRect(CGRectMake(-25, -25, 50, 50), 4, 4, nil)];
button.fillColor = [SKColor clearColor];
button.lineWidth = 1.0;
button.strokeColor = [SKColor whiteColor];
button.position = CGPointMake(0, 0);
SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
label.text = _number;
label.fontSize = 20;
label.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
label.position = CGPointMake(0, 0);
label.fontColor = [SKColor whiteColor];
[button addChild:label];
[self addChild:button];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if(CGRectContainsPoint(button.frame, location))
{
button.fillColor = [SKColor whiteColor];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
button.fillColor = [SKColor clearColor];
}
#end
The problem is when I touch inside the button01, it’s the button02 color’s which change. As if the action is only apply on the last instance of my class.
How can I fixe this.
Thanks in advance.
The issue is the following variable has global scope in your Button class:
SKShapeNode *button;
Since it is shared among the instances of Button, it contains a reference to the SKShapeNode of the last Button that was instantiated. In this case, button 2. You can resolve this issue by defining button as an instance variable:
#implementation Button
{
SKShapeNode *button;
}
...
For some reasons your Button has SKNode as a superclass. SKNode doesn't have a size property, which you can change, so it takes the whole size of it's parent node. Consider it as a layer of the scene. So wherever you touch, the latest SKScene's child (which is button02 in your case) will receive the touch.
Solution: Define Button as a subclass of SKShapeNode directly.
My app was working fine until recently, when the Sprite Scene began to crash after "return self;" it would display the error "-[BEGameScene setDelegate:]: unrecognized selector sent to instance 0x10040de10'" before "terminating with uncaught exception of type NSException" this bug seems to only happen on a device; the ios simulator works fine.
The weird thing is that the bug doesn't seem to be in my code. I took an older, still working version, duplicated it directly from finder and when I ran that it crashed too.
Could it be a problem with Xcode 6 (which I recently downloaded)?
Thanks in advance
Edit:
Sorry, here is the code
#import "BEGameScene.h"
#import "AppDelegate.h"
#import <UIKit/UIKit.h>
#import <SpriteKit/SpriteKit.h>
#interface BEMainGameViewController : UIViewController <BEGameSceneDelegate>
#property (nonatomic, assign) id <BEGameSceneDelegate> delegate;
#end
#import "BEMainGameViewController.h"
#import "BEGameScene.h"
#interface BEMainGameViewController ()
#property (strong, nonatomic) IBOutlet UIPanGestureRecognizer *control;
#end
#implementation BEMainGameViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView *skView = (SKView *)self.view;
skView.showsFPS = NO;
skView.showsNodeCount = NO;
// Create and configure the scene.
BEGameScene *scene = [BEGameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
scene.delegate = self;
// Present the scene.
[skView presentScene:scene];
[self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"Background 1.tiff"]]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)unwindFromGame {
[self performSegueWithIdentifier:#"unwindFromGameScene" sender:self];
}
#end
#import <SpriteKit/SpriteKit.h>
#protocol BEGameSceneDelegate <NSObject>
-(void)unwindFromGame;
#end
#interface BEGameScene : SKScene <UIGestureRecognizerDelegate>
#property (weak, nonatomic) id <BEGameSceneDelegate> delegate;
#end
#import "BEMainGameViewController.h"
#import "BEGameScene.h"
#import "AppDelegate.h"
#implementation BEGameScene {
NSArray *location;
int x_flower;
int x_Trap1;
int x_Trap2;
int randNum;
int speed;
int score;
int life;
BOOL rad;
BOOL collide;
NSInteger interval;
SKSpriteNode *sprite_bee;
SKSpriteNode *sprite_flower;
SKSpriteNode *sprite_flyTrap;
SKSpriteNode *sprite_flyTrap2;
SKSpriteNode *sprite_rad;
SKSpriteNode *sprite_radGlow;
NSTimer *timer;
SKLabelNode *youLoseLabel;
SKLabelNode *instruction;
NSString *scoreText;
NSString * honey;
}
-(id)initWithSize:(CGSize)size {
life = 1;
if (self = [super initWithSize:size]) {
/* Setup your scene here */
//self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKSpriteNode *bgImage = [SKSpriteNode spriteNodeWithImageNamed:#"BGImage"];
bgImage.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild:bgImage];
//Bee movement
SKTexture *texture1 = [SKTexture textureWithImageNamed:#"Bee up.tiff"];
SKTexture *texture2 = [SKTexture textureWithImageNamed:#"Bee down.tiff"];
SKTexture *texture3 = [SKTexture textureWithImageNamed:#"Flower.tiff"];
SKTexture *texture4 = [SKTexture textureWithImageNamed:#"Fly Trap.tiff"];
SKTexture *texture5 = [SKTexture textureWithImageNamed:#"radiation.png"];
SKTexture *texture6 = [SKTexture textureWithImageNamed:#"rad glow.png"];
sprite_bee = [SKSpriteNode spriteNodeWithTexture:texture1];
sprite_bee.position = CGPointMake(160, 100);
sprite_bee.zPosition = 100;
sprite_bee.size = CGSizeMake(110, 110);
SKAction *fly = [SKAction animateWithTextures:#[texture1,texture2] timePerFrame:.2];
[sprite_bee runAction:[SKAction repeatActionForever:fly]];
[self addChild:sprite_bee];
collide = NO;
//Plant creation
speed = 100;
score = 0;
x_flower = 40;
x_Trap1 = 160;
x_Trap2 = 280;
sprite_radGlow = [SKSpriteNode spriteNodeWithImageNamed:#"rad glow.png"];
sprite_radGlow.position = CGPointMake(100,3000);
sprite_radGlow.size = CGSizeMake(140, 140);
sprite_radGlow.zPosition = 50;
SKAction *glow = [SKAction sequence:#[
[SKAction fadeOutWithDuration:.5],
[SKAction fadeInWithDuration:.5],
]];
[sprite_radGlow runAction:[SKAction repeatActionForever:glow]];
[self addChild: sprite_radGlow];
sprite_flower = [SKSpriteNode spriteNodeWithTexture:texture3];
sprite_flower.position = CGPointMake(40, 300);
sprite_flower.size = CGSizeMake(100, 100);
[self addChild:sprite_flower];
sprite_flyTrap = [SKSpriteNode spriteNodeWithTexture:texture4];
sprite_flyTrap.position = CGPointMake(160, 300);
sprite_flyTrap.size = CGSizeMake(100, 100);
[self addChild:sprite_flyTrap];
sprite_flyTrap2 = [SKSpriteNode spriteNodeWithTexture:texture4];
sprite_flyTrap2.position = CGPointMake(280, 300);
sprite_flyTrap2.size = CGSizeMake(100, 100);
[self addChild:sprite_flyTrap2];
sprite_rad = [SKSpriteNode spriteNodeWithTexture:texture5];
sprite_rad.position = CGPointMake(160, 3000);
sprite_rad.size = CGSizeMake(500, 100);
[self addChild:sprite_rad];
}
return self;
}
#end
So I think your issue might be related to SKScene adding a delegate field in iOS 8.
I recommend renaming your internal delegate field to something else, like gameSceneDelgate and see if that fixes your issue.
I have two images: first image size is 50x85 and second image size is 50x113. When I touch the screen image goes up and second image is shown and when I release the screen image goes down and first image is shown. Now the problem is when I'm touching the screen collision happens earlier than when I'm not touching the screen.
How could I make that in second image about 30px at bottom won't collide, but image will still be there?
Edit: Here is some of the code:
-(void)AstronautMove
{
[self Collision];
Astronaut.center = CGPointMake(Astronaut.center.x, Astronaut.center.y + Y);
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
Y = -10;
x.image = [UIImage imageNamed:#"y.png"];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
Y = 17;
x.image = [UIImage imageNamed:#"x.png"];
}
-(void)Collision
{
if (CGRectIntersectsRect(Astronaut.frame, Landingarea.frame)) {
[self EndGame];
AstronautFire.hidden = YES;
RedBorder.hidden = NO;
AstronautCrashed.hidden = NO;
}
if (CGRectIntersectsRect(AstronautFire.frame, Landingarea.frame)) {
[self EndGame];
AstronautFire.hidden = YES;
RedBorder.hidden = NO;
AstronautCrashed.hidden = NO;
}
}
I'm just moving the frame up and down. At the bottom is landing area. When Astronaut and landing area frames touch, collision happens. But when I'm moving Astronaut up bigger image is shown (50x113) and when I'm moving Astronaut down smaller image is shown (50x85). When I'm near landing area and I press to go up bigger image shows and collision happens.
From the code samples, it really is hard to follow what you're trying to do. Notably, it's unclear how (or even if) you're animating the moving of the various views.
Whether you're animating or not, it's not clear from your code sample as to where AstronautMove is called. Did you mean to call that from touchesBegan and touchesEnded? But on the basis of what's been shared thus far, I don't see you calling AstronautMove and therefore don't see how you could possibly detect collisions.
Personally, I would have thought that you'd want to animate the moving of the views (it will render a more realistic and engaging moving of the views). And then, while an animation is underway, keep checking for a collision. In terms of how to "keep checking", you can use either a NSTimer or, even better, us a CADisplayLink (a mechanism to call a method each and every time the screen updates).
For example, you could do something like:
#interface ViewController ()
#property (nonatomic, strong) UIView *view1;
#property (nonatomic, strong) UIView *view2;
#property (nonatomic, strong) CADisplayLink *displayLink;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
view1.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:view1];
self.view1 = view1;
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(50, 150, 50, 50)];
view2.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:view2];
self.view2 = view2;
}
#pragma mark - Handle touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self startAnimationTo:CGPointMake(50, 250) collisionChecking:YES];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self startAnimationTo:CGPointMake(50, 50) collisionChecking:NO];
}
#pragma mark - Handle starting and stopping of animation
- (void)startAnimationTo:(CGPoint)point collisionChecking:(BOOL)collisionChecking
{
if (collisionChecking)
[self startDisplayLink];
[UIView animateWithDuration:2.0 delay:0.0 options:0 animations:^{
CGRect frame = self.view1.frame;
frame.origin = point;
self.view1.frame = frame;
} completion:^(BOOL finished) {
// in case we never have collision, let's make sure we stop the display link
if (collisionChecking)
[self stopDisplayLink];
}];
}
- (void)stopAnimation
{
CALayer *layer = self.view1.layer.presentationLayer;
[self.view1.layer removeAllAnimations];
self.view1.frame = layer.frame;
[self stopDisplayLink];
}
#pragma mark - Handle the display link & check for collisions
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(checkForCollision:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)checkForCollision:(CADisplayLink *)displayLink
{
// Note, when checking for collision while an animation is underway, the `frame` of the
// view represents the final location of the view, not the current location. To get the
// current location, one has to grab the `presentationLayer`. So, we're going to get the
// `presentationLayer` of each of the views and use those to determine a collision.
CALayer *layer1 = self.view1.layer.presentationLayer;
CALayer *layer2 = self.view2.layer.presentationLayer;
if (CGRectIntersectsRect(layer1.frame, layer2.frame)) {
[self stopAnimation];
}
}
#end
Or, in iOS 7, you can use UIKit Dynamics to animate the movement of the views, identifying collisions programmatically using a UICollisionBehavior and assigning a collisionDelegate:
#interface ViewController () <UICollisionBehaviorDelegate>
#property (nonatomic, strong) UIView *view1;
#property (nonatomic, strong) UIView *view2;
#property (nonatomic, strong) UIDynamicAnimator *animator;
#property (nonatomic, strong) UISnapBehavior *snap;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
view1.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:view1];
self.view1 = view1;
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(50, 150, 50, 50)];
view2.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:view2];
self.view2 = view2;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
// create behavior that will detect collisions, calling the `UICollisionBehaviorDelegate`
// methods if and when collisions are detected
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:#[view1, view2]];
collision.translatesReferenceBoundsIntoBoundary = YES;
collision.collisionDelegate = self;
[self.animator addBehavior:collision];
// in this example, I'm going to fix `view2` where it is, so it doesn't move when
// `view1` collides with it
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.view2 attachedToAnchor:self.view2.center];
[self.animator addBehavior:attachment];
// finally, let's specify that neither view1 nor view2 should rotate when
// snapping, colliding, etc.
UIDynamicItemBehavior *noRotate = [[UIDynamicItemBehavior alloc] initWithItems:#[view1, view2]];
noRotate.allowsRotation = NO;
[self.animator addBehavior:noRotate];
}
- (void)addSnap:(CGPoint)point
{
[self removeSnap];
self.snap = [[UISnapBehavior alloc] initWithItem:self.view1 snapToPoint:point];
[self.animator addBehavior:self.snap];
[self.animator updateItemUsingCurrentState:self.view1];
}
- (void)removeSnap
{
if (self.snap) {
[self.animator removeBehavior:self.snap];
self.snap = nil;
}
}
#pragma mark - Handle touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self addSnap:CGPointMake(75, 225)];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self addSnap:CGPointMake(75, 75)];
}
#pragma mark - UICollisionBehaviorDelegate
// you can use these to be informed when there is a collision
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
NSLog(#"%s", __FUNCTION__);
[self removeSnap];
}
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item1 withItem:(id<UIDynamicItem>)item2 atPoint:(CGPoint)p
{
NSLog(#"%s", __FUNCTION__);
[self removeSnap];
}
#end
Or, for a game, SpriteKit might be even better. You can google "SpriteKit example" and you'll probably find plenty of good introductions to SpriteKit.
I know that neither of the above examples are quite what you're doing, but hopefully it illustrates a couple of techniques for identifying collisions, which you could adapt for your app.
I read two questions here about that, both with the same answer. The suggested solution was to create the UIScrollView object, and then add it to the parent view of the SKScene in the target scene's didMoveToView:, removing it in willMoveFromView:.
And that is the target scene code I implemented:
#interface MQSSMakerScene ()
#property BOOL contentCreated;
#property MQSSMakerWorkspaceLayer *workspaceLayer;
#property MQSSMakerDockLayer *dockLayer;
#property UIScrollView *scrollView;
#end
#implementation MQSSMakerScene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
CGSize layerSize = CGSizeMake(944, 140);
CGPoint layerPosition = CGPointMake(40, 768-(140+30));
CGRect viewFrame = CGRectMake(layerPosition.x, layerPosition.y, layerSize.width, layerSize.height);
_scrollView = [[UIScrollView alloc] initWithFrame:viewFrame];
_scrollView.contentSize = CGSizeMake(2000, 120);
_scrollView.scrollEnabled = YES;
_scrollView.showsHorizontalScrollIndicator = YES;
_scrollView.backgroundColor = [UIColor brownColor];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
if(!self.contentCreated) {
[self createSceneContents];
self.contentCreated = YES;
}
[self.view addSubview:_scrollView]; // --> WHERE IT'S BEING ADDED
}
- (void)willMoveFromView:(SKView *)view
{
[super willMoveFromView:view];
[_scrollView removeFromSuperview]; // --> WHERE IT'S BEING REMOVED
}
- (void)createSceneContents
{
self.backgroundColor = [UIColor colorWithHue:.237 saturation:.56 brightness:.46 alpha:1];
self.scaleMode = SKSceneScaleModeAspectFill;
[self addChild:[self newMakerLayout]];
}
- (SKNode *)newMakerLayout
{
SKNode *mainLayout = [[SKNode alloc] init];
self.workspaceLayer = [[MQSSMakerWorkspaceLayer alloc] init];
self.dockLayer = [[MQSSMakerDockLayer alloc] init];
[mainLayout addChild:self.workspaceLayer];
[mainLayout addChild:self.dockLayer];
MQSSStoryObject *ob1 = [[MQSSStoryObject alloc] initWithImageNamed:#"maker-1.png"];
[self.workspaceLayer addChild:ob1];
return mainLayout;
}
#end
Please note that the user should go to this scene from the home scene when clicking on a button. The problem now is that once I add the line:
[self.view addSubview:_scrollView];
to add the UIScrollView object to this scene, the application no more goes to this scene and instead add the UIScrollView to the home scene I am transitioning from. It also doesn't remove it when transitioning to another scene. Once I comment out this line, everything works just as expected, clearly without presenting the UIScrollView.
I am stuck. Any help or advice is highly appreciated. Thanks!
I finally figured out what the problem was for me. I was using the -(void)willLayoutSubviews method of the viewController to ensure I had the correct bounds, but I had forgotten to see if my SKScene was already presented. That resulted in a new welcomeScene being presented each time the view was laid out on top of the old one.
-(void)willLayoutSubviews
{
if(!self.didPresentScene)
{
// Present scene here
....
self.didPresentScene = YES;
}
}