I'm developing a simple application that animates an image as the user moves a slider. This could easily be done with individual images, but for obvious reasons that method is inefficient.
Currently, I have the animation broken up into 14 sprite sheets with 16 images per sheet. I created a method that uses CGImageCreateWithImageInRect to find the current image dictated by the slider and update the image view with that image. This works, but not fluidly. I think I understand why, but I have no clue what to do otherwise. While I could use Cocos2d or OpenGL ES, I am stubborn and convinced that this is possible without them. I just want to know how.
Here's some example code:
- (void)setUp{
NSString *string;
NSString *bundleString = [[NSBundle mainBundle] bundlePath];
dsRedPathArray = [[NSMutableArray alloc] initWithCapacity:15];
for (int i = 0; i < 14; i++)
{
string = [bundleString stringByAppendingFormat:#"/dsRedAni_%d.png", i];
[dsRedPathArray addObject:string];
}
//initial image starts at (0, 1) of image dsRedAni_9
currentImage = [UIImage imageWithContentsOfFile:[dsRedPathArray objectAtIndex:9]];
currentRef = CGImageCreateWithImageInRect(currentImage.CGImage, CGRectMake(495, 0, kModelWidth, kModelHeight));
modelView.image = [UIImage imageWithCGImage:currentRef];
}
- (IBAction)sliderMoved:(UISlider*)sender
{
[self animateModel:sender.value];
}
- (void)animateModel:(int)index
{
index += 1;
imageIndex = (index / 16) + 9;
if (imageIndex > 13)
{
imageIndex = -14 + imageIndex;
}
currentX = kModelWidth * (index % 4);
currentY = kModelHeight * ((index / 4) % 4);
currentRect = CGRectMake(currentX, currentY, kModelWidth, kModelHeight);
currentImage = [UIImage imageWithContentsOfFile:[dsRedPathArray objectAtIndex: (imageIndex)]];
currentRef = CGImageCreateWithImageInRect(currentImage.CGImage, currentRect);
modelView.image = [UIImage imageWithCGImage:currentRef];
}
Thanks in advance for any help.
I finally found a rather quick and efficient, if unconventional, way of cycling through each section of my sprite sheets without any outside help from APIs. Rather than spending time and power cutting up the image into contexts or individual files, I found that it was more efficient for me to create a UIView in the size of the image I needed and then adding the entire sprite sheet to the view as a UIImageView.
With view.clipsToBounds set to YES, the view acts as a mask for my sprite sheet, limiting the visible portion to the size of each image I want to cycle through on the sheet. In order to give the effect of animation, I simply use imageview.center to move the sprite sheet around the view to the desired image coordinates. So, in a sprite-sheet with 16 images I only have to make one call to show the image, then move it around per each frame in the animation.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setUp];
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
}
return self;
}
- (void)setUp
{
//assign |lastImageIndex| a number not equal to |imageIndex|
lastImageIndex = -1;
modelImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1924, 1708)];
NSString *string;
NSString *bundleString = [[NSBundle mainBundle] bundlePath];
UIImage *image;
if (!imageArray)
{
imageArray = [[NSMutableArray alloc] init];
NSLog(#"Init egfp |imageArray|");
}
for (int i = 0; i < 10; i++)
{
string = [bundleString stringByAppendingFormat:#"/moleculeAni_%d.png", i];
image = [UIImage imageWithContentsOfFile:string];
[imageArray addObject:image];
if (i == 9)
{
NSLog(#"Filled egfp |imageCache|");
}
}
[self addSubview:modelImageView];
}
- (void)animateModel:(int)index
{
if (index != 1)
{
index -= 1;
}
imageIndex = (index / 16);
if (imageIndex < 9)
{
currentX = 962 - (481 * (index % 4));
currentY = 854 - (427 * ((index / 4) % 4));
}
else
{
currentX = 962 - (481 * (index % 4));
currentY = 427 - (427 * ((index / 4) % 4));
}
if (imageIndex != lastImageIndex)
{
if (imageIndex < 9 && onLastFrame)
{
modelImageView.frame = CGRectMake(0, 0, 1924, 1708);
onLastFrame = NO;
}
else if (imageIndex == 9 && !onLastFrame)
{
modelImageView.frame = CGRectMake(0, 0, 1924, 854);
onLastFrame = YES;
}
NSLog(#"Image: %d", imageIndex);
tempImage = [imageArray objectAtIndex:imageIndex];
modelImageView.image = tempImage;
}
modelImageView.center = CGPointMake(currentX, currentY);
lastImageIndex = imageIndex;
}
Most of the code here is spent determining where the imageview needs to move in order to display the correct image. This is taking the method I explained above and is spreading it across 10 sprite sheets each with 16 images spread evenly (except the last which has 8). The only slowdown came when the program was swapping between images every 16th frame. To get around this, my controller has the view quickly cycle through all the images beneath another view (like a curtain) so the user is only privy to a loading screen. Once the images have been cycled, the curtain view is removed and the animation (controlled by the user with a slider) moves like butter.
One way to do this would be to cut up the images with CGImageCreateWithImageInRect, like you started to above. Then, add them to an array:
myArray addObject:[UIImage imageWithCGImage:currentRef];
Next use a UIImageView with setAnimationImages, like this:
UIImageView *myAnimationImageView;
[myAnimationImageView setAnimationImages:myArray];
Then You can start the animation.
There is a good project doing this here: https://github.com/r3econ/UIImage-Sprite-Additions
Related
I am showing around 50 Images In a scroll view and I am facing the memory issue, every time the Controller loads the memory usage increases and it goes over 200MB in the Instruments App.
.h file of my custom class
#import
#class AnimalGrid;
#protocol AnimalGridViewDelegate <NSObject>
-(void)animalGrid:(AnimalGrid*)animalgridview tappedAtIndex:(int)index andName:(NSString*)animalName;
#end
#interface AnimalGrid : UIView
#property(nonatomic, strong) UIImage *animalImage;
#property(nonatomic, copy) NSString *animalName;
#property(nonatomic) int animalTag;
#property (nonatomic, assign) id <AnimalGridViewDelegate> Delegate;
-(id)initGridWithFrame:(CGRect)frame andAnimalImage:(UIImage*)image andAnimalName:(NSString*)animalname andTag:(int)tag;
-(void)setFont:(UIFont*)font;
#end
.m file of my Custom Class
#import "AnimalGrid.h"
#implementation AnimalGrid
{
UIImageView *_contentView;
UILabel *_accessoryView;
UIImage *displayImage;
NSString *displayText;
}
#synthesize Delegate = _Delegate;
-(id)initGridWithFrame:(CGRect)frame andAnimalImage:(UIImage*)image andAnimalName:(NSString*)animalname andTag:(int)tag
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.animalName = animalname;
self.animalTag = tag;
displayImage = image;
displayText = animalname;
CGFloat contentHeight = frame.size.height * 0.8;
if ( animalname == nil || animalname.length == 0)
contentHeight = frame.size.height;
CGFloat margin = 0;
if (displayImage.size.height < contentHeight)
{
margin = contentHeight - displayImage.size.height;
}
UIView *placeholderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, contentHeight)];
placeholderView.backgroundColor = [UIColor clearColor];
if (image.size.height > placeholderView.frame.size.height)
{
_contentView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, placeholderView.frame.size.height)];
}else if (image.size.height < placeholderView.frame.size.height){
CGFloat margin = placeholderView.frame.size.height - image.size.height;
_contentView = [[UIImageView alloc]initWithFrame:CGRectMake(0, margin, frame.size.width, placeholderView.frame.size.height - margin)];
}
_contentView.backgroundColor = [UIColor clearColor];
_accessoryView = [[UILabel alloc]initWithFrame:CGRectMake(0, frame.size.height * 0.7, frame.size.width, frame.size.height-frame.size.height * 0.7)];
_accessoryView.numberOfLines = 2;
_accessoryView.backgroundColor = [UIColor clearColor];
_accessoryView.font = [UIFont fontWithName:#"HFFAirApparent" size:23];
_accessoryView.textColor = [UIColor orangeColor];
[placeholderView addSubview:_contentView];
[self addSubview:placeholderView];
if ( animalname != nil || animalname.length > 0)
[self addSubview:_accessoryView];
if(image)
_contentView.image = image;
_contentView.contentMode = UIViewContentModeScaleAspectFit;
_accessoryView.text = animalname;
_accessoryView.numberOfLines = 0;
[_accessoryView sizeToFit];
CGFloat w = self.bounds.size.width/2;
_accessoryView.frame = CGRectMake(w - (_accessoryView.frame.size.width/2), _accessoryView.frame.origin.y, _accessoryView.frame.size.width, _accessoryView.frame.size.height);
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapedOnImage:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[_contentView addGestureRecognizer:singleTap];
_contentView.userInteractionEnabled = YES;
self.userInteractionEnabled = YES;
}
return self;
}
-(void)setFont:(UIFont*)font
{
_accessoryView.font = font;
}
-(void)tapedOnImage:(id)sender
{
if ([_Delegate respondsToSelector:#selector(animalGrid:tappedAtIndex:andName:)]) {
[_Delegate animalGrid:self tappedAtIndex:self.animalTag andName:self.animalName];
}
}
#end
And this is how I am creating the Object of my custom class and showing them over Scroll View, In my Controller file I am using following methods
-(void)setGridAnimals
{
#try {
[self creatingGridsWithCompletion:^(BOOL done, NSMutableArray *arr)
{
for (int i = 0; i < arr.count; i++)
{
UIView *gView = [arr objectAtIndex:i];
[gridScrollView addSubview:gView];
}
gridScrollView.scrollEnabled = YES;
gridScrollView.pagingEnabled = YES;
gridScrollView.bounces = YES;
gridScrollView.contentSize = CGSizeMake(gridScrollView.frame.size.width, gridScrollView.frame.size.height*arr.count);
[aView hideIndicatorView:YES];
}];
}
#catch (NSException *exception) {
NSLog(#" Exception is = %# ",exception.description);
}
#finally {
}
}
-(void)creatingGridsWithCompletion:(completionBlock)complete
{
#autoreleasepool {
NSMutableArray *pageArray = [NSMutableArray new];
int rowcount = 3;
int columncount = 5;
CGFloat width = gridScrollView.frame.size.width/columncount - 5;
CGFloat height = gridScrollView.frame.size.height/rowcount - 5;
int pagecount = gridAnimalArray.count/(rowcount*columncount);
if (gridAnimalArray.count%(rowcount*columncount))
{
pagecount += 1;
}
//pagecount = 1;
int x = 0;
for (int i = 0; i < pagecount; i++)
{
UIView *page = [[UIView alloc]initWithFrame:CGRectMake(0, x, gridScrollView.frame.size.width, gridScrollView.frame.size.height)];
for (int j = 0; j < rowcount; j++)
{
for (int k = 0; k < columncount; k++)
{
int tag = (i*(rowcount*columncount))+(j*columncount)+k+1;
if (tag > gridAnimalArray.count)
{
break;
}
YEAnimal *animal = [gridAnimalArray objectAtIndex:tag-1];
NSString *name = [[animal.GridName componentsSeparatedByString:#"."] objectAtIndex:0];
UIImage *gridImg;
if ([[NSFileManager defaultManager]fileExistsAtPath:[[NSBundle mainBundle] pathForResource:name ofType:#"png"]])
{
NSString * imgpath= [ [ NSBundle mainBundle] pathForResource:name ofType:#"png"];
gridImg=[UIImage imageWithContentsOfFile: imgpath];
}else if ([[NSFileManager defaultManager]fileExistsAtPath:[[NSBundle mainBundle] pathForResource:name ofType:#"jpg"]])
{
NSString * imgpath= [ [ NSBundle mainBundle] pathForResource:name ofType:#"jpg"];
gridImg=[UIImage imageWithContentsOfFile: imgpath];
}
AnimalGrid *grid = [[AnimalGrid alloc]initGridWithFrame:CGRectMake((k * width)+5, (j*height), width, height) andAnimalImage:gridImg andAnimalName:animal.SpeciesName andTag:tag];
grid.backgroundColor = [self getColor];
[grid setDelegate:self];
[page addSubview:grid];
}
}
[pageArray addObject:page];
x += gridScrollView.frame.size.height;
}
complete(YES,pageArray);
}
}
This is how the Grid Looks : http://s17.postimg.org/x9xdfl4j3/i_OS_Simulator_Screen_Shot_Mar_3_2015_1_54_45_P.png
So, far I came to know that I should use [UIImage imageWithContentsOfFile:] instead of [UIImage imageNamed:] to load the image, but still it does not help me.
Is there any way that I can free the Memory by releasing the ImageViews
Thanks.
There are several aproaches possible, depending on the layout of your view. How does it look like?
However, you can use a table to display the pictures in or use a collection. Actually a collection is made for that.
If you want to stick with the scroll view, then these 50 views are hardly visible at once. You could actually set their images just before the each view comes into sight and remove the images (set .image to nil) when the view moves off screen.
An App that I made recenly it is designed in a way that only one and exactly one of the images is visible full screen. When the user scrolls its neighbour image comes into view. (paging mechanism)
In my view the number of possible views is inpredictable. Although that is achievable with a collection too, I went for a scroll view.
In that view I designed the hosting scroll view in a way that it is just large enough to hold three views. The middle view of them is visible on screen. (Which exceptions applied when the very first or last - if any - view is displayed).
When the users scrolls just far enough for the neighbour view to be fully visible then I reset the scrollview so that its middle is visible again, move the view to the middle that just became visible, move the formerly visible view to the outer end and fill the other incoming end with a new view.
That allows for endless scrolling with not need to hold more than 4 images in memory at once. (4: the one that just moved off, the two that are shifted within the scroll view and the new one are allocated within the same method at a time.)
I'am searching for a (very) simple way to crossfade images within animationDuration time. I tried using NSTimer and some other stuff I found here, but no solution satisfy or helps me.
I create this stuff in a method (without comments) ...
- (UIImageView *)playSequence{
NSArray *structureArray = self.contenManager.ImageViewControllerSize;
NSMutableArray *structuresToPlay = [[NSMutableArray alloc] init];
// walk thru current collection
for(NSInteger i = 0; i < [self.currentCollection count]; i++)
{
NSInteger index = [[self.currentCollection objectAtIndex:i] integerValue];
[structuresToPlay addObject:[UIImage imageNamed:[structureArray objectAtIndex:index]]];
}
_playSequence = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
_playSequence.animationImages = structuresToPlay;
_playSequence.animationDuration = 5 * structuresToPlay.count;
[_playSequence startAnimating];
return _playSequence;
}
Here is the part where the method is called ...
- (void)justPlaySelection
{
UIView *justPlaySelection = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
// some background music code ...
// add play sequence
[justPlaySelection addSubview:self.playSequence];
// add close button
// { ... }
[self presentSemiView:justPlaySelection];
}
- (UIImageView *)playSequence
{
if (_playSequence == nil)
{
NSArray *structureArray = self.contenManager.ImageViewControllerSize;
NSMutableArray *structuresToPlay = [[NSMutableArray alloc] init];
// walk thru current collection
for(NSInteger i = 0; i < [self.currentCollection count]; i++)
{
NSInteger index = [[self.currentCollection objectAtIndex:i] integerValue];
[structuresToPlay addObject:[UIImage imageNamed:[structureArray objectAtIndex:index]]];
}
_playSequence = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
}
_playSequence.animationImages = structuresToPlay;
_playSequence.animationDuration = 5 * structuresToPlay.count;
[_playSequence startAnimating];
return _playSequence;
}
Here is another way to switch between to UIImageViews, it wouldn't be hard to add in the fade in and out.
- (void) performAnimationOfFrameAtIndex:(NSNumber*)indexNum
{
int index = indexNum.intValue;
BOOL completedSequece = index >= [self.frames count];
if (completedSequece) return;
UIImageView *imgIn;
UIImageView *imgOut;
if (index % 2 == 0) {
imgIn = self.animationImageViewOne;
imgOut = self.animationImageViewTwo;
}
else {
imgIn = self.animationImageViewTwo;
imgOut = self.animationImageViewOne;
}
imgIn.image = [self.frames objectAtIndex:index];
[self.view sendSubviewToBack:imgOut];
double speed = 0.1;
[self performSelector:#selector(performAnimationOfFrameAtIndex:) withObject:[NSNumber numberWithInt:index + 1] afterDelay:speed];
}
I created a project that does exactly what you describe (an effect like UIImageView animation sequence, but with a cross-fade between images.)
The project is on github: https://github.com/DuncanMC/Animate-Img
You need to have 2 image views.
The trick I use is to stack them on top of each other, and simply animate the top image view from alpha 1 -> 0, then switch images in the top image view and animate it from alpha 0 -> 1.0, so the new image is revealed.
The project demos a morph transition using only 5 frames. Each frame is marked with a number so you can see the frame changes. It has a switch which lets you turn cross-fading on or off.
Basically, I have an app where a user dodges bombs and collects coins. When a user collects a coin, another coin is spawned. I want each coin to not spawn on a bomb. (By the way these are all uiimageviews). In the code below, I have an array of my bombs called bombArray and the UIImageview called "one" is the coin UIImageView. I know the code below doesn't work, but what other method(s) could I use? Thanks, and here is the code:
UIImageView *one = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"goldCoin.png"]];
CGRect rectOne = CGRectMake(arc4random() % (900), arc4random() % (700), 40, 40);
[one setFrame:rectOne];
[self.view addSubview:one];
for (UIImageView* two in bombArray)
{
while (CGRectIntersectsRect(two.frame, one.frame))
{
one.center=CGPointMake(arc4random() % (900), arc4random() % (700));
}
}
*Note: I completely understand why the code above does not work, but I cannot find another solution to my problem.
First of all to make such a thing I'd use the new SpriteKit that gives you physics (and thus collision detection) out of the box.
But if you want to do this with standard UIViews what you can do for being absolutely sure that images don't collide between them is to divide the container in a grid and then loop through each cell of your grid and "flip a coin" to randomly put a coin or not in that cell. Then if you want to fill more space instead of dividing for rectangular cells you can divide for hexagonal cells, in this way you will cover more space.
So a fast and absolutely not too much checked version could be something like this:
int x = 0;
int y = 0;
int side = 40;
int it = 0;
do {
do {
CGRect imageFrame = CGRectMake(x, y, side, side);
UIImageView* imageView = [[UIImageView alloc] initWithFrame:imageFrame];
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.borderWidth = 1.0;
imageView.layer.cornerRadius = side / 2;
imageView.layer.masksToBounds = YES;
[self.containerView addSubview:imageView];
BOOL flip = arc4random() & 0x1;
if (flip) {
[imageView setImage:[UIImage imageNamed:#"coin_image"]];
}
} while ((x+=side) < (self.containerView.frame.size.width - side / 2));
it++;
if (it % 2 != 0) {
x = side / 2;
} else {
x = 0;
}
} while ((y+=(side * 0.85)) < (self.containerView.frame.size.height - side) );
Mind that this is NOT efficient, it is executed in the main thread and doesn't take in account possible (actually probable) memory constraints.
I have a series of images, I want them to repeat. However, I want the first half to play in, for example 2 seconds, then the second half to play in 5 seconds.
It is quite easy to play a series of images, using the following
- (void) completeCycles:(int) cycles inhalePercentage:(int) inhalePercent exhalePercentage:(int) exhalePercent totalCycleTime:(int) time
{
NSMutableArray *textures = [NSMutableArray array];
for (int i = 0; i < kNumberBreathingImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"breath%d.png", i];
for (int i = 0; i < inhalePercent; i++) {
[textures addObject:[UIImage imageNamed:textureName]];
}
}
for (int i = kNumberBreathingImages - 1; i > 0; i--) {
NSString *textureName = [NSString stringWithFormat:#"breath%d.png", i];
for (int i = 0; i < exhalePercent; i++) {
[textures addObject:[UIImage imageNamed:textureName]];
}
}
self.animationImageView.animationImages = textures;
self.animationImageView.animationDuration = time;
self.animationImageView.animationRepeatCount = cycles;
[self.animationImageView startAnimating];
}
However, I want the first half of the animation to take x time and the second half to take y time.
I don't think the above will allow me to do this, just wondering what a better approach would be.
Pretty sure will need to use
UIView animateWithDuration:animations: completion:
Thanks for any assistance.
This code creates this effect,
The animation system on UIImageView is very limited. I would suggest that you make your own implementation with say 2 image view.
You would than change the image in one imageview and the fade in and out using UIView animateWithDuration
I have written a method for you. Please note: I have not tested it.
It assumes you have your frames in a array called 'frames' and have two UIIMageView placed on top of each other called 'imgv1' and 'imgv2'
-(void)performAnimationOfFrameAtIndex:(NSNumber*)indexNum
{
int index = [indexNum intValue];
if(index >= frames.count)
{
return;
}
UIImage *img = [frames objectAtIndex:index];
UIImageView *imgIn;
UIImageView *imgOut;
if(index % 2 == 0)
{
imgIn = imgv1;
imgOut = imgv2;
}
else
{
imgIn = imgv2;
imgOut = imgv1;
}
imgIn.image = img;
[self.view sendSubviewToBack:imgIn];
[UIView animateWithDuration:0.1 animations:^
{
imgIn.alpha = 1;
imgOut.alpha = 0;
} completion:^(BOOL finished)
{
[self performSelectorOnMainThread:#selector(performAnimationOfFrameAtIndex:) withObject:[NSNumber numberWithInt:index+1] waitUntilDone:NO];
}];
}
Thanks for the idea,
I changed your code above to get the desired effect I wanted, now it works perfect. The alpha was causing a "strobe" effect even when the duration is 0 seconds.
Here is my final code
Enjoy the 100 :-)
- (void) performAnimationOfFrameAtIndex:(NSNumber*)indexNum
{
int index = indexNum.intValue;
if (index >= self.frames.count) return;
UIImage *img = self.frames[index];
UIImageView *imgIn;
UIImageView *imgOut;
float speed = (index <= self.frames.count / 2) ? 0.1 : 1; // first half : second half speed.
if (index % 2 == 0) {
imgIn = self.animationImageViewOne;
imgOut = self.animationImageViewTwo;
}
else {
imgIn = self.animationImageViewTwo;
imgOut = self.animationImageViewOne;
}
imgIn.image = img;
[self.view sendSubviewToBack:imgIn];
[self performSelector:#selector(performAnimationOfFrameAtIndex:) withObject:[NSNumber numberWithInt:index+1] afterDelay:speed];
}
I don't think the above will allow me to do this, just wondering what a better approach would be.
Actually, it will. You just need to repeat the images as you form the sequence. For the images in the first group, repeat each one twice before going on to the next one. For the images in the second group, repeat each one five times before going on to the next one. Now set the time for the total animation to 7 seconds. Since there are about the same number of images in each group originally, you can readily see that this means the first group will take about 2 seconds and the second group will take about 5 seconds.
In the iOS game flappy bird, there are pipes that generate after a certain distance and they generate at random heights
I am also trying to make flappy bird pipes (I called it a tree branch in my code instead of pipe). Except the pipes are moving vertically instead of horizontally because it is a vertical scrolling game (it scrolls like the game doodle jump)
This is a drawing of what I want it to be: https://docs.google.com/drawings/d/18bxsVsNOlScCvgi1mwuzD2At7R6xKM3QCh6BfAVMuMo/edit?usp=sharing
(The horizontal lines are the branches)
So this is what I have tried to do so far to make the vertical branches (or pipes)...
in my .h
CCSprite *branch;
NSMutableArray *_branches;
CCSprite *obstacle;
CCNode *previousBranch;
CGFloat previousBranchYPosition;
in my .m
#implementation HelloWorldLayer
static const CGFloat firstBranchPosition = 426.f;
static const CGFloat distanceBetweenBranches = 140.f;
#define ARC4RANDOM_MAX 0x100000000
static const CGFloat minimumXPositionRightBranch = 280.f;
static const CGFloat maximumXPositionLeftBranch = 50.f;
static const CGFloat pipeDistance = 100.f;
static const CGFloat maximumXPositionRightBranch = maximumXPositionLeftBranch - pipeDistance;
setBranchInitialPosition method
/* This is where I am setting the initial position of the branches.
So I am specifying the position of the first branch and the other branches after it so it gets placed every time a certain distance is passed. I have a left branch and a right branch*/
-(void) setBranchInitialPosition {
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat range = maximumXPositionRightBranch - minimumXPositionRightBranch;
_rightBranch.position = ccp(minimumXPositionRightBranch + (random * range), _rightBranch.position.y);
_leftBranch.position = ccp(_rightBranch.position.x + pipeDistance, _leftBranch.position.y);
}
spawnNewBranches method
// This is how I want the branches to spawn and I want to add them to an array full of branches
- (void)spawnNewBranches {
previousBranch = [_branches lastObject];
previousBranchYPosition = previousBranch.position.y;
if (!previousBranch) {
// this is the first obstacle
previousBranchYPosition = firstBranchPosition;
}
_rightBranch = [CCSprite spriteWithFile:#"branch.png"];
_leftBranch = [CCSprite spriteWithFile:#"branch.png"];
[_leftBranch addChild:_rightBranch];
[self setBranchInitialPosition];
obstacle = [CCSprite node];
[obstacle addChild:_leftBranch];
obstacle.position = ccp(160, previousBranchYPosition + distanceBetweenBranches);
[self addChild:obstacle];
[_branches addObject:obstacle];
}
scroll method
-(void) scroll:(ccTime)dt
{
// moves the bg
background.position = ccp(screenCenter.x, background.position.y + [[NSUserDefaults standardUserDefaults] integerForKey:#"scrollSpeed"]*dt);
bg2.position = ccp(screenCenter.x, background.position.y-background.contentSize.height);
// it adds the new bg's to the screen before the old bg's move off the screen
if (background.position.y >= screenSize.height*1.5)
{
background.position = ccp(screenCenter.x, (screenCenter.y)-(background.size.height/2));
} else if (bg2.position.y >= screenSize.height*1.5) {
bg2.position = ccp(screenCenter.x, (screenCenter.y)-(bg2.size.height/2));
}
// This is where I want them to appear every certain distance and also move with the brackground
obstacle.position = ccp(obstacle.position.x, obstacle.position.y*[[NSUserDefaults standardUserDefaults] integerForKey:#"scrollSpeed"]*dt);
NSMutableArray *offScreenObstacles = nil;
if (obstacle.position.y >= screenSize.height*1.5) {
[offScreenObstacles addObject:obstacle];
}
for (CCNode *obstacleToRemove in offScreenObstacles) {
[obstacleToRemove removeFromParent];
[_branches removeObject:obstacleToRemove];
// for each removed obstacle, add a new one
[self spawnNewBranches];
}
}
Right now, the branches are appearing, but they stay in the bottom left corner and they dont move or spawn at all. I want to make them move with the background and spawn after a certain distance while also being generated in random heights. I provided you with all my code, do you know how I can make this work? Thanks in advance!
You may want to try placement of the pipes based on a trigonometric curve like sine or cosine (https://en.wikipedia.org/wiki/Trigonometric_functions). It seems like you are placing the pipes within a fairly define random range though if you change this range to an offset from the plot of the trigonometric curve it would take into account the ability of the player to transition between the open gaps better. At least that's my feel. I think the code would be easier to follow as well as I'm a bit confused going through it. You can also easily vary the difficulty of the curve by changing the parameters such as increasing the amplitude or frequency.
I created a copy of Flappy Bird just for fun. I used this code to create the pipes:
-(void)createPipes{
//Create Random
int from = 65;
int max = [[UIScreen mainScreen] bounds].size.height - 124;
int delta = max - from - dy;
int y = from + arc4random() % (delta - from);
//Pipe Bottom
UIImageView *pipeBottom = [[UIImageView alloc] init];
[pipeBottom setContentMode:UIViewContentModeTop];
[pipeBottom setImage:[UIImage imageNamed:#"pipeBottom"]];
[pipeBottom setFrame:CGRectMake(320, y+dy, 60, max - y - dy)];
[pipeBottom setClipsToBounds:YES];
//Pipe Top
UIImageView *pipeTop = [[UIImageView alloc] init];
[pipeTop setFrame:CGRectMake(320, 0, 60, y)];
[pipeTop setContentMode:UIViewContentModeBottom];
[pipeTop setImage:[UIImage imageNamed:#"pipeTop"]];
[self.view insertSubview:pipeTop atIndex:1];
[self.view insertSubview:pipeBottom atIndex:1];
if (!self.pipes)
self.pipes = [[NSMutableArray alloc] init];
[self.pipes addObject:pipeBottom];
[self.pipes addObject:pipeTop];
}
and to move them:
-(void)moveArray:(NSMutableArray *)array{
float ds = dv * dt;
NSMutableArray *trash = [NSMutableArray array];
for (UIImageView *obj in array) {
CGRect frame = obj.frame;
frame.origin.x -= ds;
if (frame.origin.x < -frame.size.width) {
[obj removeFromSuperview];
[trash addObject:obj];
}else{
obj.frame = frame;
}
}
[array removeObjectsInArray:trash];
}
-(void)movePipes{
[self moveArray:self.pipes];
}
I call this function every 0.01 seconds, to run the game:
-(void)runGame{
_time += dt;
if (_time >= 180.0/dv) {
_time = 0;
[self createPipes];
}
[self movePipes];
[self moveEnemies];
[self moveYoshi];
[self moveBar];
[self verifyScore];
[self verifyCollision];
[self verifyState];
}
I defined dt = 0.01 and dv = 110.
You can see my parody in youtube: (http://www.youtube.com/watch?v=tTcYdpSIKJg)
I hope this help you.
Best, Rafael Castro.