Thanks for taking the time to look at my noob question and any help will be greatly appreciated.
Firstly I am trying to make an object 'bounce' from one side of the screen to the other using bezier curves (don't want to use box2d).
The object has 4 states representing the 4 movements, when the object hits (reacts to) the floor it moves from the previous state to a new state, please see picture below...
**My problem is that I can not get it to transition from state 1 to state 4 using CGRect correctly. I can currently get the object to move states but then one of two things happen..
1) The below code will allow the object to change from state 1 to state 2 correctly but it will then not react with the floor (it just moves through it) at the end of state 2 so does not change to state 3.
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
CGRect characterBox = [character adjustedBoundingBox];
if (CGRectIntersectsRect(floorBoudingBox, ballBoundingBox)) {
if (characterState == kStateTravelling) {
[self changeState:kStateTravellingPoint2];
}
if (characterState == kStateTravellingPoint2) {
[self changeState:kStateTravellingPoint3];
}
}
2) The below code will stop the object when it first hits the floor but if I remove the floor for a second while the program is running the object will then move to state 3 and once again stop when it hits the floor, if I remove the floor again it will once again move to state 4, this is without adding the other states (as you will see below) so it is just repeating state 2 each time. This I am guessing is because it returns TRUE as soon as it hits the floor and while it is TRUE it will not action.
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
isfloorWithinBoundingBox =
CGRectIntersectsRect(floorBoudingBox, ballBoundingBox) ?
YES : NO;
if (isfloorWithinBoundingBox) {
[self changeState:kStateTravellingPoint2];
}
What I want it to do is move between the 4 states if the floor is active in a 'bounce' movement, so as soon as state 1 is complete it will hit the floor and move into state 2 until it completes the curve movement and hits the floor again and then moves into state 3 etc.
Right I have got the above to work but do not completely understand how I have done it!
I tried using screenSize to determine where the object was and base my CGRect from that but it did not work at first. I then kept the code but added 'CGSize screenSize = [CCDirector sharedDirector].winSize;' to the method and then to object performed just how I wanted.
I now however have the following errors when I use screenSize..
'Local declaration of 'screenSize' hide instance variable'
What I would like to know is why its not working using screenSize until I declare it in the method (as its already been declared in another class that is imported into this one).
Thanks again to anyone that has taken the time to read this.
my code is below...
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
CGPoint floorPosition = [floorCharacter position];
*Below is where I have added screenSize*
CGSize screenSize = [CCDirector sharedDirector].winSize;
isfloorWithinBoundingBox =
CGRectIntersectsRect(floorBoudingBox, ballBoundingBox) ?
YES : NO;
if ((characterState == kStateTravelling) && (isfloorWithinBoundingBox == YES)) {{
[self changeState:kStateTravellingPoint2];
}
return;
}
if ((characterState == kStateTravellingPoint2)&&(isfloorWithinBoundingBox == YES) && (floorPosition.x > screenSize.width*0.32f) && (floorPosition.x < screenSize.width*0.45f)) {{
[self changeState:kStateTravellingPoint3];
}
return;
}
if ((characterState == kStateTravellingPoint3)&&(isfloorWithinBoundingBox == YES) && (floorPosition.x > screenSize.width*0.45f) && (floorPosition.x < screenSize.width*0.55f)) {{
[self changeState:kStateTravellingPoint4];
}
return;
}
Related
I am working with the flappy bird demo trying different things just to get to "know each other".
Going through the demo, I've managed to change the direction of the game to vertical scroll moving upwards.
Having reversed the CGFloat to negative values makes my obstacles move upward but once they are out of bounds they do not re-spawn.
If I change the values for a downward scroll they re-spawn as per the update method.
Can someone explain to me what I'm doing wrong with the x to y conversion? Why is the bottom recognized and the top of my screen not?
Thanks in advance
#import "MainScene.h"
static const CGFloat scrollSpeed = -280.f; //upwards
static const CGFloat firstObstaclePosition = -568.f;
static const CGFloat distanceBetweenObstacles = 80;
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
NSMutableArray *_obstacles;
}
- (void)spawnNewObstacle {
CCNode *previousObstacle = [_obstacles lastObject];
CGFloat previousObstacleYPosition = previousObstacle.position.y;
if (!previousObstacle) {
// this is the first obstacle
previousObstacleYPosition = firstObstaclePosition;
}
CCNode *obstacle = [CCBReader load:#"Obstacle"];
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles);
[_physicsNode addChild:obstacle];
[_obstacles addObject:obstacle];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y + delta * scrollSpeed);//move on Y axis
_physicsNode.position = ccp(_physicsNode.position.x, _physicsNode.position.y - (scrollSpeed *delta));//scroll in Y axis
//spawn more
NSMutableArray *offScreenObstacles = nil;
for (CCNode *obstacle in _obstacles) {
CGPoint obstacleWorldPosition = [_physicsNode convertToWorldSpace:obstacle.position];
CGPoint obstacleScreenPosition = [self convertToNodeSpace:obstacleWorldPosition];
if (obstacleScreenPosition.y < -obstacle.contentSize.height) {
if (!offScreenObstacles) {
offScreenObstacles = [NSMutableArray array];
}
[offScreenObstacles addObject:obstacle];
}
}
for (CCNode *obstacleToRemove in offScreenObstacles) {
[obstacleToRemove removeFromParent];
[_obstacles removeObject:obstacleToRemove];
// for each removed obstacle, add a new one
[self spawnNewObstacle];
}
}
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_obstacles = [NSMutableArray array];
[self spawnNewObstacle];
[self spawnNewObstacle];
[self spawnNewObstacle];
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
}
#end
I've attached the _physicsNode screenshot from SB.
It looks like your obstacles will be spawning fine if they are a short, constant height, and the distance between them value is large enough. It may be better to incorporate the height of the obstacles to get a more meaningful value of the distance variable. Just a thought.
The line -
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles);
Could be -
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles + previousObstacle.contentSize.height);
As for the problem of the vertical scrolling working downwards and not upwards I believe it is due to this line:
if (obstacleScreenPosition.y < -obstacle.contentSize.height) {
Since this line is responsible for determining when an obstacle is off the screen it has an effect on the spawning of the next obstacle. It makes sense why this line works for downwards scrolling but needs to be changed for upwards scrolling.
Try:
if (obstacleScreenPosition.y > (_physicsNode.contentSize.height + obstacle.contentSize.height)) {
You may or may not need the size of the obstacle depending on where it is anchored.
I hope this works, Good luck.
Seems impossible but there has to be a solution.
I have the following classes:
#interface EnemiesEntities : CCSprite {
bool isFunctional;
CCSprite * laserBeam; // <----------- !!!!! That's where I want to check the collision.
CCSprite * leftRingEffect;
CCSprite * rightRingEffect;
}
#interface ShipEntity : CCSprite
{}
And I simply want to verify the collision between the ShipEntity and the laserBeam sprite (laserBeam is a member variable and child of EnemiesEntities class).
The method [laserBeam boundingBox] doesn't work as the boundingBox converts the coordinates relative to the parent node.
I tried thend adding to CCNode a method computing the boundingBox relative to the world but also this one did not work:
- (CGRect) worldBoundingBox
{
CGRect rect = CGRectMake(0, 0, contentSize_.width, contentSize_.height);
return CGRectApplyAffineTransform(rect, [self nodeToWorldTransform]);
}
I checked online and found only unuseful (to me) answers to the same question.
I then tried a different approach and tried to start from the boudningBox and change the position of the rectangle so obtained in respect to the parent position as following:
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
[laserBeam worldBoundingBox];
CGRect laserBoundingBox = [laserBeam boundingBox];
CGRect laserBox = CGRectMake(laserBeam.parent.position.x, laserBeam.parent.position.y, laserBoundingBox.size.width, laserBoundingBox.size.height);
CGRect hitBox = [self hitBox];
if(CGRectIntersectsRect([self boundingBox], laserBox))
{
laserBeam.showCollisionBox=TRUE;
return TRUE;
}
else {
return FALSE;
}
}
Unfortunately this does work only when the rotation of the parent sprite is set to 0.0 but when it actually changes then it doesn't work (is probably because the boundingBox is relative to the parent node and not world).
I am a bit lost and was wondering if any of you had better luck in solving this problem and which solution (code snippets please :)) you used.
EDIT in Response to #LearnCocos2D answer:
I followed the suggestion and added the following code which doesn't work properly (e.g. try with an EnemiesEntities object is rotated to -130.0f).
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CCLOG(#"rotation %f", laserBeam.rotation);
CGRect laserBoundingBox = [laserBeam boundingBox];
laserBoundingBox.origin = [self convertToWorldSpace:laserBeam.position];
CGRect shipBoundingBox = [self boundingBox]; //As we are in ShipEntity class
shipBoundingBox.origin = [self convertToWorldSpace:shipBoundingBox.origin];
//As this method is in the ShipEntity class there is no need to convert the origin to the world space. I added a breakpoint here and doing in this way the CGRect of both ShipEntity and gets misplaced.
if(CGRectIntersectsRect(shipBoundingBox, laserBoundingBox))
{
return TRUE;
}
else {
return FALSE;
}
}
The problem is in this line I think:
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
laserBeam isn't in laserBeam's space but laserBeams parent space. So the correct is:
CGPoint newPosition = [[laserBeam parent] convertToWorldSpace:laserBeam.position];
The whole code
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
CGRect laserBoundingBox = [laserBeam boundingBox];
laserBoundingBox.origin = newPosition;
CGRect hitBox = [self boundingBox];
hitbox.origin = [[self parent] convertToWorldSpace:hitbox.origin];
if(CGRectIntersectsRect(hitbox, laserBoundingBox))
{
laserBeam.showCollisionBox=TRUE;
return TRUE;
}
else {
return FALSE;
}
}
for both boundingboxes do:
bbox.origin = [self convertToWorldSpace:bbox.origin];
now you can compare the rects...
Update to update:
The boundingBox is an axis-aligned bounding box.
If the entity is rotated, the bounding box size increases to encompass all of the sprite's corners. Therefore collision (intersection) may be detected even relatively far away from the node when testing axis-aligned bounding boxes.
In ccConfig.h there's an option you can turn on to draw sprite bounding boxes, you should set this to 1 to see the bounding boxes: #define CC_SPRITE_DEBUG_DRAW 1
For oriented rectangles you need a different data structure and different intersection test, see for example this tutorial.
I'm developing a very simple game. Here's my enemy class named Machine's behavior code:
#import "Machine.h"
#implementation Machine
+(id)machineWithWorld:(b2World*)world position:(CGPoint)pos
{
return [[[self alloc] initWithWorld:world position:pos] autorelease];
}
-(id)initWithWorld:(b2World*)world position:(CGPoint)pos
{
if(self = [super initWithShape:[AppDelegate renameFrameForIpad:#"machine"] inWorld:world])
{
size = [CCDirector sharedDirector].winSize;
self.body->SetTransform([Helper toMeters:pos], 0.0);
self.body->SetType(b2_staticBody);
safetyCounter = 5;
[self schedule:#selector(machineSafetyCounter)];
movementWidthInMeters = (size.width-self.contentSize.width)/PTM_RATIO;
linearSpeed = 0.5;
[self schedule:#selector(startMoving) interval:1.5];
}
return self;
}
#pragma mark<Machine Behavior>
-(void)startMoving
{
[self unschedule:_cmd];
float distanceFromCenterInMeters = (size.width/2 - self.position.x)/PTM_RATIO;
float interval = ABS(distanceFromCenterInMeters/linearSpeed);
if(interval < 0.01f)
interval = 0.02f;
b2Vec2 motionDirection = (distanceFromCenterInMeters > 0.0f) ? b2Vec2(1.0, 0.0) : b2Vec2(-1.0, 0.0);
self.body->SetType(b2_kinematicBody);
self.body->SetLinearVelocity(linearSpeed*motionDirection);
[self schedule:#selector(startMotionFromBeginning) interval:interval-0.01];
CCLOG(#"startMoving distance-->%f, interval-->%f", distanceFromCenterInMeters, interval);
}
-(void)startMotionFromBeginning
{
[self unschedule:_cmd];
float interval = (movementWidthInMeters/2)/linearSpeed;
self.body->SetLinearVelocity(0.5*b2Vec2(1.0, 0.0));
[self schedule:#selector(moveRTL) interval:interval-0.01];
[self schedule:#selector(checkIfHelmetIsBelowMachine) interval:0.1];
CCLOG(#"startMotionFromBeginning interval-->%f", interval);
}
-(void)moveRTL
{
[self unschedule:_cmd];
float interval = movementWidthInMeters/linearSpeed;
self.body->SetLinearVelocity(0.5*b2Vec2(-1.0, 0.0));
[self schedule:#selector(moveLTR) interval:interval-0.01];
CCLOG(#"moveRTL interval-->%f", interval);
}
-(void)moveLTR
{
[self unschedule:_cmd];
float interval = movementWidthInMeters/linearSpeed;
self.body->SetLinearVelocity(0.5*b2Vec2(1.0, 0.0));
[self schedule:#selector(moveRTL) interval:interval-0.01];
CCLOG(#"moveLTR interval-->%f", interval);
}
-(void)checkIfHelmetIsBelowMachine
{
[self unschedule:_cmd];
Helmet* helmet = (Helmet*)[[[[[CCDirector sharedDirector] runningScene] children] objectAtIndex:0] getChildByTag:kTagHelmet];
float helmetPosX = helmet.position.x;
if((self.position.x > helmetPosX) && (self.position.x < helmetPosX+helmet.contentSize.width))
{
[self unscheduleAllSelectors];
[self schedule:#selector(machineSafetyCounter) interval:0.1];
[self schedule:#selector(startMovingDownwards) interval:0.0];
return;
}
[self schedule:_cmd interval:0.1];
}
-(void)startMovingDownwards
{
[self unschedule:_cmd];
self.body->SetLinearVelocity(0.25*b2Vec2(0.0, -1.0));
[self schedule:#selector(stopMovingDownwards) interval:1.0];
CCLOG(#"startMovingDownwards");
}
-(void)stopMovingDownwards
{
[self unschedule:_cmd];
self.body->SetLinearVelocity(b2Vec2(0.0, 0.0));
[self schedule:#selector(startMoving) interval:0.2];
CCLOG(#"stopMovingDownwards");
}
All I have done is following:
1) The body is static initially and is positioned at ccp(size.width*0.5, size.height*0.75).
2) After 1.5 seconds, It becomes kinematic and starts moving with a linear speed of 0.5 m/s.
3) It checks it's current distance (from screen width center keeping height same), evaluates the time needed to reach that spot, and then starts moving in that direction horizontally.
4) After reaching that spot, it starts it's signature motion, it starts moving from Left to right, if at any time helmet(another game object) passes underneath it, it starts moving down and stops after 1.0 seconds, then whole cycle repeats.
5) It moves LTR & RTL until it starts to move down when it finds the helmet underneath it.
Now the problem is, sometimes the behavior is exactly same as expected.
And many many times, it starts moving upwards and I have never set the y bit for motion vector in positive direction.
After initializing the body, you should not change its bodyType. When you first make the body, set it as kinematic so you don't run into problems later when trying to change it. Is the body attached to a CCSprite? If so, you should make the class a subclass of CCSprite so you can use [super initWithFile:(NSString *) world:(b2World *)] or some method of your choosing to facilitate your endeavors.
I have been trying to implement a standard pinch/zoom on a CCLayer in cocos2d (using the Kobold2D gesture recognisers) but with only partial success.
Below is my code which does make pinch/zoom work, so long as the pinch point doesn't move. However if I zoom in over one point on the layer and then lift off and move my fingers to zoom in further over another point, there is an instantaneous jump of the layer. It jumps to where the layer would have been if I'd been zooming in over the second point from the start, instead of simply zooming smoothly from where it was.
Can you see what I'm doing wrong or have I missed an existing simple pinch/zoom algorithm that does this job for CCLayers?
NB: I've left the default (YES) value for ignoreAnchorInPosition. Also, at the start self.scalePrePinch = 1.0f
-(void) update:(ccTime)delta
{
KKInput* input = [KKInput sharedInput];
if (input.gesturePinchBegan) {
CGSize scr = [[CCDirector sharedDirector] screenSize];
CGPoint pinchLocation = [self convertToNodeSpace:input.gesturePinchLocation];
CGPoint anchor = ccp(pinchLocation.x/scr.width, pinchLocation.y/scr.height);
CGFloat newScale = input.gesturePinchScale * self.scalePrePinch;
self.anchorPoint = ccp(self.anchorPoint.x + self.scale / newScale * (anchor.x - self.anchorPoint.x),
self.anchorPoint.y + self.scale / newScale * (anchor.y - self.anchorPoint.y));
self.scale = newScale;
}
else
self.scalePrePinch = self.scale;
}
As the title says, I'm stuck with jumping and moving at the same time,
this is how I'm moving along the 'x' axis
-(void)collisionCheckingAndMovementRight:(ccTime)dt{
CGPoint tileCoord = [self tileCoordForPosition:player.position];
int tileGid = [csLayer tileGIDAt:tileCoord]; //csLayer is the layer which was created in tiled for the collision
if (tileGid) {
NSDictionary *properties = [levelOne propertiesForGID:tileGid];
if (properties) {
NSString *collision = [properties valueForKey:#"Collidable"];
if (collision && [collision compare:#"True"] == NSOrderedSame){
//[[SimpleAudioEngine sharedEngine] playEffect:#"hit.caf"];
return;
}
}
}
player.position = ccp(player.position.x +100*dt, player.position.y);
i want to try and do a jump that goes with the movement i have at the moment for the x axis.... help is greatly appreciated
You want to look at the actions examples in cocos2d. the CCBezierTo action comes to mind for a good way of animating a jump.