I am attempting to make a side scrolling game using Apple's SpriteKit. When wanting to make a endless scrolling background I came across this answer.
After implementing the solution it does appear to work although it drops my FPS significantly. This is probably due to the fact that the images positions are being recalculated on every frame.
I feel like it would be much better if I could use one or more SKAction calls to take care of this animation for me but I'm not certain how to implement it.
Thoughts?
The code I have so far in my scene class (this only animates the background across the screen once though)
- (void)addBackgroundTileAtPoint:(CGPoint)point {
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
bg.anchorPoint = CGPointZero;
bg.position = point;
bg.name = #"background";
bg.zPosition = -99;
[self addChild:bg];
SKAction *sequence = [SKAction sequence:#[
[SKAction moveByX:-(bg.size.width * 2) y:0 duration:10],
[SKAction removeFromParent]
]];
[bg runAction: sequence];
}
I did a small component called SKScrollingNode for that particular need in my last open source project : SprityBird.
FPS was not an issue even with 3 or 4 layers (for parallax), but you may need to try it yourself.
To use it you just have to add it like any other node and giving it a scrollingSpeed likeso :
back = [SKScrollingNode scrollingNodeWithImageNamed:#"back" inContainerWidth:WIDTH(self)];
[back setScrollingSpeed:BACK_SCROLLING_SPEED];
[self addChild:back];
SKScrollingNode.h
#interface SKScrollingNode : SKSpriteNode
#property (nonatomic) CGFloat scrollingSpeed;
+ (id) scrollingNodeWithImageNamed:(NSString *)name inContainerWidth:(float) width;
- (void) update:(NSTimeInterval)currentTime;
#end
SKScrollingNode.m
#implementation SKScrollingNode
+ (id) scrollingNodeWithImageNamed:(NSString *)name inContainerWidth:(float) width
{
UIImage * image = [UIImage imageNamed:name];
SKScrollingNode * realNode = [SKScrollingNode spriteNodeWithColor:[UIColor clearColor] size:CGSizeMake(width, image.size.height)];
realNode.scrollingSpeed = 1;
float total = 0;
while(total<(width + image.size.width)){
SKSpriteNode * child = [SKSpriteNode spriteNodeWithImageNamed:name ];
[child setAnchorPoint:CGPointZero];
[child setPosition:CGPointMake(total, 0)];
[realNode addChild:child];
total+=child.size.width;
}
return realNode;
}
- (void) update:(NSTimeInterval)currentTime
{
[self.children enumerateObjectsUsingBlock:^(SKSpriteNode * child, NSUInteger idx, BOOL *stop) {
child.position = CGPointMake(child.position.x-self.scrollingSpeed, child.position.y);
if (child.position.x <= -child.size.width){
float delta = child.position.x+child.size.width;
child.position = CGPointMake(child.size.width*(self.children.count-1)+delta, child.position.y);
}
}];
}
#end
I've been working on a library for an infinite tile scroller, you can easily use it to create a scrolling background. Take a look at it:
RPTileScroller
I made an effort to make it the most efficient possible, but I am not a game developer. I tried it with my iPhone 5 with random colors tiles of 10x10 pixels, and is running on a solid 60 fps.
Related
I have some strange error happening with my game. How anyone can help me to get solve form this.
I have a runner game and hero is collecting coins while running ,when hero touches the coin,now I am showing a particle effect created with the help of particle maker.and it's working fine. But last day I purchased a software and it gives us really good particle effects but it can only export as pngs. so I made sprite sheet and instead of previous particle animation I put this on the contact listener code for showing this effect when hero touches the coin. Unfortunately it is not showing anything. Here is my previous code :
-(void) checkHeroAndCoins {
float fDelta = 0;
if(m_hero->m_bMagnet)
fDelta = iDevPixelX(30);
for(Coin *coin in m_drawLayer.children){
if([coin isKindOfClass:[Coin class]]){
CGRect rCoin = CGRectMake(coin.position.x - coin.contentSize.width * coin.scaleX / 2.0f - fDelta,
coin.position.y - coin.contentSize.height * coin.scaleY / 2.0f - fDelta,
coin.contentSize.width * coin.scaleX + fDelta * 2, coin.contentSize.height * coin.scaleY + fDelta * 2);
CGRect rHero = CGRectMake(m_hero.position.x - m_hero.contentSize.width * m_hero.scaleX / 2.0f, m_hero.position.y, m_hero.contentSize.width * m_hero.scaleX, m_hero.contentSize.height * m_hero.scaleY);
if(CGRectIntersectsRect(rCoin, rHero)){
coin.tag = DELETED_TAG;
g_nCoin++;
[[AppDelegate getDelegate] playSystemEffect:E_COIN];
// this below code is to show the particle effect and its working perfect
CCParticleSystem *effect = [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:#"arroweffect.plist"];
[self addChild:effect];
effect.position = ccp(self.contentSize.width * self.scaleX / 2, self.contentSize.height * self.scaleY / 2.0f);
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
effect.scale *= 2;
}
}
}
[[AppDelegate getDelegate] saveSetting];
}
But after I put sprite sheet as animation to show the effect , it is not working , I tried my best but not able to solve this. this is my code for the sprite sheet animation :
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile: #"coinsprite.plist"];
//load the sprite sheet into a CCSpriteBatchNode object. If you're adding a new sprite
//to your scene, and the image exists in this sprite sheet you should add the sprite
//as a child of the same CCSpriteBatchNode object otherwise you could get an error.
CCSpriteBatchNode *parrotSheet12 = [CCSpriteBatchNode batchNodeWithFile:#"coinsprite.png"];
//add the CCSpriteBatchNode to your scene
[self addChild:parrotSheet12];
//load each frame included in the sprite sheet into an array for use with the CCAnimation object below
NSMutableArray *flyAnimFrames12 = [NSMutableArray array];
for(int i = 1; i <=30; ++i) {
[flyAnimFrames12 addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"coins%04d.png", i]]];
}
//Create the animation from the frame flyAnimFrames array
CCAnimation *flyAnim12 = [CCAnimation animationWithFrames:flyAnimFrames12 delay:1.0f];
//create a sprite and set it to be the first image in the sprite sheet
CCSprite * dragont2 = [CCSprite spriteWithSpriteFrameName:#"coins0000.png"];
//set its position to be dead center, i.e. screen width and height divided by 2
[dragont2 setPosition:ccp(600,600)];
//[self moveRandom:theParrot];
//create a looping action using the animation created above. This just continuosly
//loops through each frame in the CCAnimation object
CCAction *flyAction12 = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:flyAnim12 restoreOriginalFrame:NO]];
//start the action
[dragont2 runAction:flyAction12];
//add the sprite to the CCSpriteBatchNode object
[parrotSheet12 addChild:dragont2];
problem got solved
-(void) coinanimationeffect:(CGPoint) ptPos {
m_gamecoinffect = [CCSprite spriteWithSpriteFrameName:#"coins0001.png"];
[self addChild:m_gamecoinffect];
CCAnimate *coineffect = [CCAnimate actionWithSpriteSequence:#"coins%04d.png" numFrames:30 delay:0.01f restoreOriginalFrame:NO];
[m_gamecoinffect runAction:[CCRepeatForever actionWithAction:coineffect]];
m_gamecoinffect.position = ptPos;
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
m_gamecoinffect.scale *= 2;
}
added this function
and call that function in
-(void) checkHeroAndCoins {
float fDelta = 0;
if(m_hero->m_bMagnet)
fDelta = iDevPixelX(30);
for(Coin *coin in m_drawLayer.children){
if([coin isKindOfClass:[Coin class]]){
CGRect rCoin = CGRectMake(coin.position.x - coin.contentSize.width * coin.scaleX / 2.0f - fDelta,
coin.position.y - coin.contentSize.height * coin.scaleY / 2.0f - fDelta,
coin.contentSize.width * coin.scaleX + fDelta * 2, coin.contentSize.height * coin.scaleY + fDelta * 2);
CGRect rHero = CGRectMake(m_hero.position.x - m_hero.contentSize.width * m_hero.scaleX / 2.0f, m_hero.position.y, m_hero.contentSize.width * m_hero.scaleX, m_hero.contentSize.height * m_hero.scaleY);
if(CGRectIntersectsRect(rCoin, rHero)){
coin.tag = DELETED_TAG;
g_nCoin++;
[[AppDelegate getDelegate] playSystemEffect:E_COIN];
[self coinanimationeffect:ccp(coin.position.x, coin.position.y)];
}
}
}
[[AppDelegate getDelegate] saveSetting];
}
I have a simple game with stones "raining" from top to bottom of the screen.
I am working with SpriteKit and I would like the stones to increase their speed by time like it would be level 1 level 2 level 3 and so on. Maximum speed should be easy to implement then.
I have a variable "speed" for the speed of the stones.
I think I could just use a timer and after 5s speed = 2, after 10s speed = 5, and so on, but I'm not sure how I can implement that.
EDIT:
I'm moving my stones with this:
-(void) moveStones{
SKAction *actionMove = [SKAction moveByX:0 y:-5 duration:0.25];
for(SKSpriteNode *stone in self.stones)
{
[stone runAction:actionMove];
}
}
And in my viewDidLoad I have the timer:
moveStones = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(moveStones) userInfo:nil repeats:YES];
If the timer does not respect the view, is there a way to move them without that timer?
If you are moving stones by physics, then after the stone is spawned you should apply an stronger impulse/force (based on current level) If you are moving them with actions, then change the speed of action, or lower the moveDuration. If you are moving stones by changing its position in update: method, then change the value which indicates how much points a sprite should move in every update call.
To implement time based actions like spawning stones or to decide when to change the speed of spawned nodes, you should use SKAction, or update: method and its passed parameter called currentTime. Using NSTimer for any of this in SpriteKit can lead to problems, because NSTimer don't respect view's , scene's or node's paused state, so its better to avoid using it.
Here is an example on how you can do what you want with SKAction. In this example you can see how to:
track the number of seconds passed
maintain the info about current level
spawn node with random delay
stop spawning nodes by using action key
and probably some more
GameScene.h
#import "GameScene.h"
#interface GameScene()
#property (nonatomic, assign) NSInteger counter;
#property (nonatomic, assign) NSInteger level;
#property (nonatomic, strong) SKLabelNode *debug;
#end
#implementation GameScene
// Here we set initial values of counter and level. Debug label is created here as well.
-(void)didMoveToView:(SKView *)view {
self.counter = 0;
self.level = 1;
self.backgroundColor = [SKColor grayColor];
self.debug = [SKLabelNode labelNodeWithFontNamed:#"ArialMT"];
self.debug.fontColor = [SKColor purpleColor];
self.debug.fontSize = 30.0f;
self.debug.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.debug.text = [NSString stringWithFormat:#"Counter : [ %ld ], Level [ %ld ]", (long)self.counter,(long)self.level];
[self addChild:self.debug];
}
//Method to start a timer. SKAction is used here to track a time passed and to maintain current level
-(void)startTimer{
__weak typeof (self) weakSelf = self; //make a weak reference to scene to avoid retain cycle
SKAction *block = [SKAction runBlock:^{
weakSelf.counter++;
//Maintaining level
if(weakSelf.counter < 5){
//level 1
weakSelf.level = 1;
}else if(weakSelf.counter >=5 && weakSelf.counter <10){
//level 2
weakSelf.level = 2;
}else{
//level 3
weakSelf.level = 3;
}
weakSelf.debug.text =
[NSString stringWithFormat:#"Counter : [ %ld ], Level [ %ld ]", (long)weakSelf.counter,(long)weakSelf.level];
}];
[self runAction:[SKAction repeatActionForever:[SKAction sequence:#[[SKAction waitForDuration:1], block]]] withKey:#"counting"];
}
//Method for stopping timer and reseting everything to default state.
-(void)stopTimer{
if([self actionForKey:#"counting"]){
[self removeActionForKey:#"counting"];
}
self.counter = 0.0f;
self.level = 1;
self.debug.text =
[NSString stringWithFormat:#"Counter : [ %ld ], Level [ %ld ]", (long)self.counter,(long)self.level];
}
//Get current speed based on time passed (based on counter variable)
-(CGFloat)getCurrentSpeed{
if(self.counter < 5){
//level 1
return 1.0f;
}else if(self.counter >=5 && self.counter <10){
//level 2
return 2.0f;
}else{
//level 3
return 3.0f;
}
}
//Method which stop generating stones, called in touchesBegan
-(void)stopGeneratingStones{
if([self actionForKey:#"spawning"]){
[self removeActionForKey:#"spawning"];
}
}
//You can use this useful method to generate random float between two numbers
- (CGFloat)randomFloatBetween:(CGFloat)smallNumber and:(CGFloat)bigNumber {
CGFloat diff = bigNumber - smallNumber;
return (((CGFloat) (arc4random() % ((unsigned)RAND_MAX + 1)) / RAND_MAX) * diff) + smallNumber;
}
//Method for generating stones, you run this method when you want to start spawning nodes (eg. didMoveToView or when some button is clicked)
-(void)generateStones{
SKAction *delay = [SKAction waitForDuration:2 withRange:0.5]; //randomizing delay time
__weak typeof (self) weakSelf = self; //make a weak reference to scene to avoid retain cycle
SKAction *block = [SKAction runBlock:^{
SKSpriteNode *stone = [weakSelf spawnStoneWithSpeed:[weakSelf getCurrentSpeed]];
stone.zPosition = 20;
[weakSelf addChild:stone];
}];
[self runAction:[SKAction repeatActionForever:[SKAction sequence:#[delay, block]]] withKey:#"spawning"];
}
//Returns stone with moving action added. Inside, you set standard things, like size, texture, physicsbody, name and position of a stone
-(SKSpriteNode*)spawnStoneWithSpeed:(CGFloat)stoneSpeed{
CGSize stoneSize = CGSizeMake(30,30); //you can randomize size here
CGPoint stonePosition = CGPointMake( [self randomFloatBetween:0.0f and:self.frame.size.width], CGRectGetMaxY(self.frame)); //you can randomize position here
SKSpriteNode *stone = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:stoneSize];
stone.name = #"stone"; //this helps if you want to enumerate all stones by name later on in your game
stone.position = stonePosition;
SKAction *move = [SKAction moveByX:0 y:-200 duration:3.25];
//one way to change speed
move.speed = stoneSpeed;
SKAction *moveAndRemove = [SKAction sequence:#[move, [SKAction removeFromParent]]];
[stone runAction:moveAndRemove withKey:#"moving"]; //access this key if you want to stop movement
return stone;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
//just a simple way to start and stop a game
if(![self actionForKey:#"counting"]){
[self startTimer];
[self generateStones];
}else{
[self stopTimer];
[self stopGeneratingStones];
}
}
#end
And here is the result:
Now its up to you how you will set the speed. You can play with that.
If you want to skip some parts, or you don't know where to look, the heart of everything is in method called generateStones That is how you can spawn stones with a time delay by using SKActions. The another important method is spawnStoneWithSpeed: where you can see how to manipulate with speed of an action.
Another way to affect on node's moving speed, as I said, is to change duration parameter of method moveByX: duration:
Hope this helps!
Im working on a little game in IOS which involves a scrolling tilemap.
I have gotten my background picture to scroll like so
- (void)moveBg
{
[self enumerateChildNodesWithName:#"scroll" usingBlock:
^(SKNode *node, BOOL *stop) {
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(-BG_POINTS_PER_SEC, 0.0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity, _dt);
bg.position = CGPointAdd(bg.position, amtToMove);
}];
}
however if i load my tilemap and name it "scroll" as i have below
- (TileMapLayer *)createLandScape
{
_tileMap = [JSTileMap mapNamed:#"level1.tmx"];
_tileMap.name=#"scroll";
return [[TmxTileMapLayer alloc]
initWithTmxLayer:[_tileMap layerNamed:#"Background"]];
}
I am lead to believe that tilemap scrolling is then different from background image scrolling. Id like if someone could help me or point me to the right direction ton accomplish ths.
Thanks!
Added a background of SKNode and added the tilemap as a child. Now when the scroll background code is called it scrolls the tilemmap along with is
SKSpriteNode * bg =
[SKSpriteNode spriteNodeWithImageNamed:#"bg"];
bg.anchorPoint = CGPointZero;
bg.position = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
[bg addChild:_tileMap];
Since the JSTileMap extends from SKNode, you should be able to apply actions as if you would any other node.
Anyway, you're casting your tilemap into an SKSpriteNode. Not only that, but you're also wrapping your tilemap to the TmxTileMapLayer class. Not sure why you're doing this, but the problem is that your JSTileMap is out of scope once you cast it.
You might want to try this:
- (void)moveBg
{
[self enumerateChildNodesWithName:#"scroll" usingBlock:
^(SKNode *node, BOOL *stop) {
JSTileMap * bg = (JSTileMap *) node;
CGPoint bgVelocity = CGPointMake(-BG_POINTS_PER_SEC, 0.0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity, _dt);
bg.position = CGPointAdd(bg.position, amtToMove);
}];
}
You might also be able to move your entire wrapper class by casting it as well (if TmxTileMapLayer extends from SKNode)
TmxTileMapLayer * bg = (TmxTileMapLayer *) node;
A different way of scrolling your tilemap is just as simple as:
_tiledMap = [JSTileMap mapNamed:#"level1.tmx"];
if (_tiledMap) {
[self addChild:_tiledMap];
}
_tiledMap.position = CGPointMake(ORIGINPOINT);
SKAction *scroll = [SKAction moveTo:CGPointMake(MOVETOPOINT) duration:SPEED];
[_tiledMap scroll];
And even better way (move a map layer instead of the entire tileMap):
_tiledMap = [JSTileMap mapNamed:#"level1.tmx"];
if (_tiledMap) {
[self addChild:_tiledMap];
}
_tiledMap.position = CGPointMake(ORIGINPOINT);
TMXLayer *someLayer = [_tiledMap layerNamed:#"someLayer"];
SKAction *scroll = [SKAction moveTo:CGPointMake(MOVETOPOINT) duration:SPEED];
[someLayer scroll];
I'm working on a sprite-kit game for IOS. In my game, I have pieces of debris spawn every 5 seconds, then move towards the player, like obstacles. I made a series of images for the debris I wanted to use, but can't seem to figure out how to randomly spawn them in the game?
This is my code:
-(void)spawnDebris {
SKSpriteNode * debris = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"debrisPiece1.png"] size:CGSizeMake(40, 40)];
debris.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
debris.physicsBody.allowsRotation = NO; //might say yes
debris.physicsBody.categoryBitMask = CollisionDebris;
debris.position = CGPointMake(50, 50);
[_debris addObject:debris];
[self addChild:debris];
//next Spawn:
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:5],
[SKAction performSelector:#selector(spawnDebris) onTarget:self],
]]];
}
Where the parent "_debris" is an NSMutableArray * _debris. How would I go about writing it so that I can have debrisPiece 2 or debrisPiece 3 (images of debris) spawn too? Do I make another array inside the spriteNodeWithTexture:#[]?? Is that even possible?
Thanks.
You can generate a random number using the arc4random() function, so you can do it like this:
NSString *base = #"debrisPiece";
uint32_t num = arc4random_uniform(number_of_debris_images) + 1; //Generate a random number
NSString *textureName = [base stringByAppendingFormat:#"%d.png", num];
SKSpriteNode * debris = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:textureName] size:CGSizeMake(40, 40)];
//...
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.