How can I create an accurate parallax scene? - ios

I created an extension of the UIView class in order to implement the parallax effect throughout my entire project. I would advice you to do the same, it's so much easier.
#pragma mark - Handling Effects
- (void)setMotion:(CGFloat)motion
{
if (motion == 0) {
for (UIMotionEffect *motionEffect in self.motionEffects) {
[self removeMotionEffect:motionEffect];
}
}
else {
UIInterpolatingMotionEffect *horizontalEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
horizontalEffect.minimumRelativeValue = #(motion);
horizontalEffect.maximumRelativeValue = #(-motion);
UIInterpolatingMotionEffect *verticalEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
verticalEffect.minimumRelativeValue = #(motion);
verticalEffect.maximumRelativeValue = #(-motion);
[self addMotionEffect:horizontalEffect];
[self addMotionEffect:verticalEffect];
}
}
- (CGFloat)motion
{
if (self.motionEffects.count == 0) {
return 0;
}
else {
UIInterpolatingMotionEffect *horizontalEffect = (UIInterpolatingMotionEffect *)[self.motionEffects objectAtIndex:0];
return [horizontalEffect.minimumRelativeValue floatValue];
}
}
Example:
view1.motion = 10;
view2.motion = 15;
view3.motion = 20;
The problem is that the effect is not quite accurate. Yes, it looks like parallax but I don't get the strong feeling that I'm emerged in a 3D scenery. The views slide a bit more to left or a bit more to the right.
Is there anyway, I can calculate the parallax motion (.motion) of a UIView based on it's size?
I'm guessing angular sizes, and view points but I have no idea how those are measured...

This equation describes the size of objects relative to the distance they are placed at:
angularSize = realSize / distance;
By implementing your own initialization method:
- (instancetype)initWithRealSize:(CGSize)realSize;
You can change the size of the view by adjusting it's distance.
The motion value can be calculated as follows:
horizontalMotion = angularSize.width / 10.0;
verticalMotion = angularSize.height / 10.0;

Related

How can I modify Grant Pauls's Shimmer control so the mask responds to accelerometer input?

How can I modify Grant Pauls's cool Shimmer control to respond to accelerometer input? I am a novice with iOS dev and have isolated parts of the code, but I'm not sure how to go about "hooking it up" so it responds to accelerometer input like the sketch below:
(I made the sketch before finding the Shimmer control, the "White Stripe" here would be the non-masked layer of the control)
EDIT: I'm not looking for completed code, just ideas on how to approach creating the code. CAAnimation and Core Motion are totally new to me. "Make x a delegate of y and look at line z of the posted code" for instance would qualify as an answer to me.
Here is the code that I believe would be involved in "hooking up" the accelerometer:
BOOL disableActions = [CATransaction disableActions];
if (!_shimmering) {
if (disableActions) {
// simply remove mask
[self _clearMask];
} else {
// end slide
CFTimeInterval slideEndTime = 0;
CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];
if (slideAnimation != nil) {
// determing total time sliding
CFTimeInterval now = CACurrentMediaTime();
CFTimeInterval slideTotalDuration = now - slideAnimation.beginTime;
// determine time offset into current slide
CFTimeInterval slideTimeOffset = fmod(slideTotalDuration, slideAnimation.duration);
// transition to non-repeating slide
CAAnimation *finishAnimation = shimmer_slide_finish(slideAnimation);
// adjust begin time to now - offset
finishAnimation.beginTime = now - slideTimeOffset;
// note slide end time and begin
slideEndTime = finishAnimation.beginTime + slideAnimation.duration;
[_maskLayer addAnimation:finishAnimation forKey:kFBShimmerSlideAnimationKey];
}
// fade in text at slideEndTime
CABasicAnimation *fadeInAnimation = shimmer_end_fade_animation(self, _maskLayer.fadeLayer, 1.0, _shimmeringEndFadeDuration);
fadeInAnimation.beginTime = slideEndTime;
[_maskLayer.fadeLayer addAnimation:fadeInAnimation forKey:kFBFadeAnimationKey];
// expose end time for synchronization
_shimmeringFadeTime = slideEndTime;
}
} else {
// fade out text, optionally animated
CABasicAnimation *fadeOutAnimation = nil;
if (_shimmeringBeginFadeDuration > 0.0 && !disableActions) {
fadeOutAnimation = shimmer_begin_fade_animation(self, _maskLayer.fadeLayer, 0.0, _shimmeringBeginFadeDuration);
[_maskLayer.fadeLayer addAnimation:fadeOutAnimation forKey:kFBFadeAnimationKey];
} else {
BOOL innerDisableActions = [CATransaction disableActions];
[CATransaction setDisableActions:YES];
_maskLayer.fadeLayer.opacity = 0.0;
[_maskLayer.fadeLayer removeAllAnimations];
[CATransaction setDisableActions:innerDisableActions];
}
// begin slide animation
CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];
// compute shimmer duration
CGFloat length = 0.0f;
if (_shimmeringDirection == FBShimmerDirectionDown ||
_shimmeringDirection == FBShimmerDirectionUp) {
length = CGRectGetHeight(_contentLayer.bounds);
} else {
length = CGRectGetWidth(_contentLayer.bounds);
}
CFTimeInterval animationDuration = (length / _shimmeringSpeed) + _shimmeringPauseDuration;
if (slideAnimation != nil) {
// ensure existing slide animation repeats
[_maskLayer addAnimation:shimmer_slide_repeat(slideAnimation, animationDuration, _shimmeringDirection) forKey:kFBShimmerSlideAnimationKey];
} else {
// add slide animation
slideAnimation = shimmer_slide_animation(self, animationDuration, _shimmeringDirection);
slideAnimation.fillMode = kCAFillModeForwards;
slideAnimation.removedOnCompletion = NO;
slideAnimation.beginTime = CACurrentMediaTime() + fadeOutAnimation.duration;
[_maskLayer addAnimation:slideAnimation forKey:kFBShimmerSlideAnimationKey];
}
}
}
When you -init your view, you will want to create a MotionManager like so:
self.motionManager = [[CMMotionManager alloc] init];
[self.motionManager startAccelerometerUpdates];
On -dealloc in that view you will also want to get rid of it:
[self.motionManager stopAccelerometerUpdates];
self.motionManager = nil;
Here is the part that will matter most for you, you will need to query the motion manager for updates as to device orientation. I have always done this in the context of a SpriteKit scene in the update method, but I am pretty sure you can add a motion manager delegate (or something) and accomplish the same thing. The important bits are as follows:
CMAccelerometerData* data = self.motionManager.accelerometerData;
CGFloat xacceleration = data.acceleration.x;
CGFloat yacceleration = data.acceleration.y;
Using that x/y acceleration you can map that to a point in your view and map the shimmer effect to that point. I hope that helps.

Collision detection between objects in Tiled and a Sprite's bounding box in cocos2d

I am trying to make a platform game for the iphone, using cocos2d and Tiled (for the maps).
All of the tutorials i've seen on the net are using Layers in Tiled to do collision detection.
I want to use objects to do that...not Layers.
With objects you can create custom shapes that can give a better 'reality' into the game.
To give an example of what i mean :
I've drawn the ground as a background and created an object layer on top.
I want to detect player collision with that, instead of the background tile.
Now using the most famous tutorial out there : http://www.raywenderlich.com/15230/how-to-make-a-platform-game-like-super-mario-brothers-part-1
I am trying to rewrite checkForAndResolveCollisions method to check collisions for the objects instead.
The problem is that in Tiled the coordinates system is different than cocos2d. Tiled starts from top left corner, cocos2d from bottom left corner....and not only that...I noticed that the width and height of the object properties in Tiled (probably) dont correspond to the same in iphone devices.
The above rectangle has properties:
its w/h is 480/128 in tiled (for retina devices) which means its probably huge inside the map if i keep them like this. My guess is i have to divide this by 2.
So far i got this:
-(void)checkForAndResolveObjCollisions:(Player *)p {
CCTiledMapObjectGroup *objectGroup = [map objectGroupNamed:#"Collision"];
NSArray* tiles = [objectGroup objects];
CGFloat x, y, wobj, hobj;
for (NSDictionary *dic in tiles) {
CGRect pRect = [p collisionBoundingBox]; //3
x = [[dic valueForKey:#"x"] floatValue];
y = [[dic valueForKey:#"y"] floatValue];
wobj = [[dic valueForKey:#"width"] floatValue];
hobj = [[dic valueForKey:#"height"] floatValue];
CGPoint position = CGPointMake(x, y);
CGPoint objPos = [self tileForPosition:position];
CGRect tileRect = CGRectMake(objPos.x, objPos.y, wobj/2, hobj/2);
if (CGRectIntersectsRect(pRect, tileRect)) {
CCLOG(#"INTERSECT");
CGRect intersection = CGRectIntersection(pRect, tileRect);
NSUInteger tileIndx = [tiles indexOfAccessibilityElement:dic];
if (tileIndx == 0) {
//tile is directly below player
p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + intersection.size.height);
p.velocity = ccp(p.velocity.x, 0.0);
p.onGround = YES;
} else if (tileIndx == 1) {
//tile is directly above player
p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y - intersection.size.height);
p.velocity = ccp(p.velocity.x, 0.0);
} else if (tileIndx == 2) {
//tile is left of player
p.desiredPosition = ccp(p.desiredPosition.x + intersection.size.width, p.desiredPosition.y);
} else if (tileIndx == 3) {
//tile is right of player
p.desiredPosition = ccp(p.desiredPosition.x - intersection.size.width, p.desiredPosition.y);
} else {
if (intersection.size.width > intersection.size.height) {
//tile is diagonal, but resolving collision vertially
p.velocity = ccp(p.velocity.x, 0.0);
float resolutionHeight;
if (tileIndx > 5) {
resolutionHeight = -intersection.size.height;
p.onGround = YES;
} else {
resolutionHeight = intersection.size.height;
}
p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + resolutionHeight );
} else {
float resolutionWidth;
if (tileIndx == 6 || tileIndx == 4) {
resolutionWidth = intersection.size.width;
} else {
resolutionWidth = -intersection.size.width;
}
p.desiredPosition = ccp(p.desiredPosition.x + resolutionWidth , p.desiredPosition.y);
}
}
}
// }
}
p.position = p.desiredPosition; //8
}
- (CGPoint)tileForPosition:(CGPoint)p
{
NSInteger x = (NSInteger)(p.x / map.tileSize.width);
NSInteger y = (NSInteger)(((map.mapSize.height * map.tileSize.width) - p.y) / map.tileSize.width);
return ccp(x, y);
}
I am getting the object x,y,w,h and try to convert them to cocos2d dimensions and sizes.
The above translates to this:
Dimens: 480.000000, 128.000000
Coord: 0.000000, 40.000000
Basically its a mess. And its not working .....at all. The player just falls right through.
I am surprised noone has done collision detection based on objects before...unless i am wrong.
Does anyone know if this can be done or how it can be done ?
Kinda what he does here : https://www.youtube.com/watch?feature=player_detailpage&v=2_KB4tOTH6w#t=30
Sorry for the long post.
Thanks for any answers in advance.

Why Do Objects Overlap with CGRectIntersectsRect?

I am creating a puzzle game for which you have to move an object around obstacles in order to reach your target. However, for some reason objects are overlapping when I use CGRectIntersectsRect. I want the objects to STOP when they touch edges with each other, NOT when they're overlapping each other. Current code is as follows:
-(void)objectObstacleCollision {
if (CGRectIntersectsRect(object.frame, obstacle1.frame)) {
xMotion = 0;
yMotion = 0;
if (objectMovingUp == YES) {
objectCrashedUp = YES;
objectMovingUp = NO;
if (objectCrashedUp == YES && objectMovingUp == NO) {
up.hidden = YES;
down.hidden = NO;
right.hidden = NO;
left.hidden = NO;
}
}
This is causing objects to overlap upon impact which causes problems when trying to move the object in a different direction. After many different attempts, for the life of me, I cannot get the object to stop when it touches edges with obstacles. How can I get this to happen?
If two rects share an edge, they don't intersect, they touch. For example, this code:
CGRect rect1 = CGRectMake(0, 0, 100, 100);
CGRect rect2 = CGRectMake(0, 100, 100, 100);
if (CGRectIntersectsRect(rect1, rect2)) {
NSLog(#"The intersection rect is %#", NSStringFromCGRect(CGRectIntersection(rect1, rect2)));
} else {
NSLog(#"The rects don't intersect.");
}
will output "The rects don't intersect."
There's no built-in CGRect function to determine if two rects are touching, but you could write one that iterates through the 4 possibilities.

Collision detection with sprites from an array in Cocos2D

i'm pretty new to Objective-C and Cocos2D so go easy, I know this is basic stuff.
I'm using an array to randomly place 4 gold coin sprites on the screen, and i'm using another sprite (a dragon) to fly around and collect the coins. Obviously, I want the coin to disappear and another to randomly appear (like collecting apples in Snake). My problem is that I don't know how to reference the individual sprites. I've added some handling to the update method but a) I don't think its appropriate and b) it doesn't do anything. Please help. I think I need to utilise:
[self removeChild:sprite cleanup:YES];
But I'm not sure how. This is how i'm populating the screen with rings::
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
CCSprite *_goldRing;
NSMutableArray *_goldRings;
}
-(void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_goldRing.physicsBody.collisionType = #"Level";
_goldRing.physicsBody.sensor = TRUE;
_physicsNode.collisionDelegate = self;
_hero.physicsBody.collisionType = #"hero";
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
_meteorites = [NSMutableArray array];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
}
-(void)spawnNewGoldRing {
CGFloat randomX = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat randomY = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat rangeX = 200;
CGFloat rangeY = 300;
CCNode *goldRing = [CCBReader load:#"goldRing"];
goldRing.position = ccp(randomX * rangeX, (randomY * rangeY)+100);
[_physicsNode addChild:goldRing];
[_goldRings addObject:goldRing];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y);
if(_hero.position.x <= _hero.contentSize.width/2 * -1.5) {
_hero.position = ccp(_ground.contentSize.width + _hero.contentSize.width/2, _hero.position.y);
NSMutableArray *collectedGoldRings = nil;
for (CCNode *goldRing in _goldRings) {
CGPoint goldRingWorldPosition = [_physicsNode convertToWorldSpace:goldRing.position];
CGPoint goldRingScreenPosition = [self convertToNodeSpace:goldRingWorldPosition];
if (goldRingScreenPosition.x < -goldRing.contentSize.width) {
if (!collectedGoldRings) {
collectedGoldRings = [NSMutableArray array];
}
[collectedGoldRings addObject:goldRing];
}
}
for (CCNode *goldRingToRemove in collectedGoldRings) {
[goldRingToRemove removeFromParent];
[_goldRings removeObject:goldRingToRemove];
// for each removed goldRing, add a new one
[self spawnNewGoldRing];
}
}
}
Would it be too much to ask how to keep count of these too to display a score?
Thanks so much for any help.
EDIT*
NSLog on the array of gold rings
"<goldRing = 0x9b756b0 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
)
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Gold Ring Loaded
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Array:(
"<goldRing = 0x9b77e50 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
As I see, you are using spritebuilder, so you are using the new version of Cocos2D.
In this version you can track a collision between two types of nodes. In your case, the coins and the hero.
This is as simple as:
1) Set your coin.physicsBody.collisionType = #"coin";
2) Set your hero.physicsBody.collisionType = #"hero";
3) Implement this method:
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair hero:(CCNode *)nodeA coin:(CCNode *)nodeB{
//this method will be automatically called when the hero hits a coin.
//now call your method to remove the coin and call the method to add another coin.
}
To keep a count of the score, just make an int variable, int score;. In your didLoadFromCCB method, initialize it to 0, score=0; and inside this collision method, just do score++; and maybe do a NSLog(#"Your score:%i",score);
Hope this helps.
for things like that, you do not need to use physics. When your hero will touch a coin, just update the user score (it could be an integer) and the coin position.
in your update method do something like
for(CCSprite *gold in _goldRings){
if(CGRectIntersectsRect(_hero.boundingBox,gold.boundingBox)){
//touched a coin
//do whatever u want
//do not remove the coin from the array, just change it's position!
_userPoints += 1;
gold.position=ccp(arc4random()%200 , arc4random()%300);
break;
}
}

CCLayer scaling and touch implementation?

I've made a CCLayer that holds CCSprite and two CCLabelBMFont's. My goal is to create a customized "button" which will scale down when pressed. I've ran into problems with touch and scaling of this layer.
First is the touch, I can't touch the layer bounding box accurately even if I convert the touch like this:
CGPoint currentTouchLocation = [self convertTouchToNodeSpace:touch];
Touch is handled like this:
// Touching shop item?
if(CGRectContainsPoint([self boundingBox], currentTouchLocation)) {
NSLog(#"Pressing item");
mShopItemPushed = true;
return true;
}
return false;
Seems like there is no realistic size boundingBox for a CCLayer with it's contents by default so I figure I need to overwrite one based on the CCLayer contents? Any ideas how I can do this correctly?
Second problem is the scaling of this CCLayer based "button". If I get a touch handling to work somehow, scaling the layer down by half causes the scaled layer to move off tens of pixels from the original position. There are no anchors set but still moves the layer quite a bit to the side and up when scaling. How can I prevent this behavior?
Here is some code of the CCLayer based button:
+(id) shopItem:(NSString*)fileName : (CGPoint)position : (NSString*)itemName : (int)itemPrice
{
return [[[self alloc] initWithShopItemData:fileName:position:itemName:itemPrice] autorelease];
}
-(id) initWithShopItemData:(NSString*)fileName : (CGPoint)position : (NSString*)itemName : (int)itemPrice
{
self = [super init];
[self setPosition:position];
mShopItemPushed = false;
mPicture = [CCSprite spriteWithSpriteFrameName:fileName];
[mPicture setPosition:CGPointMake(position.x - (3.0f * [DeviceSpecific cellSize]), position.y)];
[self addChild:mPicture z:1];
// Make price string
NSString* price = [NSString stringWithFormat:#"%d", itemPrice];
mItemPrice = [CCLabelBMFont labelWithString:price fntFile:[DeviceSpecific scoreAndCoinFont]];
[mItemPrice setScale:0.5f];
[mItemPrice setAnchorPoint:CGPointMake(1.0f, 0.5f)];
[mItemPrice setPosition:CGPointMake(position.x + (3.5f * [DeviceSpecific cellSize]), position.y)];
[self addChild:mItemPrice z:1];
mItemName = [CCLabelBMFont labelWithString:itemName fntFile:[DeviceSpecific scoreAndCoinFont]];
[mItemName setScale:0.5f];
[mItemName setAnchorPoint:CGPointMake(0.0f, 0.5f)];
[mItemName setPosition:CGPointMake(mPicture.position.x + [DeviceSpecific cellSize], mPicture.position.y)];
[self addChild:mItemName z:1];
self.isTouchEnabled = YES;
return self;
}
The [DeviceSpecific cellSize] is just a measuring unit to keep the distances correct on different devices.
I solved this by overwriting the boundingBox -function with a rect based on the outer limits of the items in this layer. Scaling problem remained so I just made another indicator for received touches.

Resources