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.
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.
How would I delay the 'schedule' and then it unschedules. So that there is a delay in the schedule then it reschedules and changes the interval to a smaller increment?
This the code
- (void)onEnter
{
[super onEnter];
[self schedule:#selector(addMonster:) interval:1.0];
[self schedule:#selector(addBomb:) interval:4.0];
[self schedule:#selector(addLife:) interval:45.0];
}
in your init
[self scheduleUpdate];
In the update call the different selectors when the time condition is met. In order to create the time condition we will need a few global variables.
ccTime addMonsterAfterDuration = 1;
ccTime addMonsterDurationDecrementValue = 0.1;
ccTime timeSpentSinceLastMonsterAdded = 0;
ccTime addMonsterMinDuration =0.2;
-(void) update:(ccTime)delta{
timeSpentSinceLastMonsterAdded += delta;
if(timeSpentSinceLastMonsterAdded == addMonsterAfterDuration){
[self addMonster];
timeSpentSinceLastMonsterAdded = 0;
addMonsterAfterDuration -= addMonsterDurationDecrementValue;
if(addMonsterAfterDuration < addMonsterMinDuration){
addMonsterAfterDuration = addMonsterMinDuration;
}
}
}
The above example is only for addMonsters. I believe you can do the same for other objects in your game.
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;
}
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;
}
I haven't seen easy examples about rotaion around a specified point. I tried something like this but it doesn't work:
//CCNode *node is declared
//In a function of a subclass of CCSprite
- (void)moveWithCicrlce
{
anchorNode = [CCNode node];
anchorNode.position = ccpSub(self.position, circleCenter);
anchorNode.anchorPoint = circleCenter;
[anchorNode runAction:[CCRotateBy actionWithDuration:1 angle:90]];
[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:[CCCallFunc actionWithTarget:self selector:#selector(rotate)], [CCDelayTime actionWithDuration:0.1], nil]]];
}
- (void)rotate
{
self.position = ccpAdd(anchorNode.position, anchorNode.anchorPoint);
}
Here's how you can rotate a node (sprite etc) around a certain point P (50,50) with a radius (distance from P) of 100:
CCNode* center = [CCNode node];
center.position = CGPointMake(50, 50);
[self addChild:center];
// node to be rotated is added to center node
CCSprite* rotateMe = [CCSprite spriteWithFile:#"image.png"];
[center addChild:rotateMe];
// offset rotateMe from center by 100 points to the right
rotateMe.position = CGPointMake(100, 0);
// perform rotation of rotateMe around center by rotating center
id rotate = [CCRotateBy actionWithDuration:10 rotation:360];
[center runAction:rotate];
My approximate solution:
#interface Bomb : NSObject {
CCSprite *center;
}
...
#end
and some methods:
- (void)explode
{
BombBullet *bullet = [BombBullet spriteWithFile:#"explosion03.png"];
[[[CCDirector sharedDirector] runningScene] addChild:bullet];
center = [CCSprite spriteWithTexture:bullet.texture];
center.position = explosionPoint;
center.anchorPoint = ccp(-0.5, -0.5);
center.visible = NO;
[[[CCDirector sharedDirector] runningScene] addChild:center];
[center runAction:[CCRotateBy actionWithDuration:1 angle:360]];
CCCallFunc *updateAction = [CCCallFuncN actionWithTarget:self selector:#selector(update:)];
[bullet runAction:[CCRepeatForever actionWithAction:[CCSequence actions:updateAction, [CCDelayTime actionWithDuration:0.01], nil]]];
}
- (void)update:(id)sender
{
BombBullet *bombBullet = (BombBullet *)sender;
bombBullet.rotation = center.rotation;
bombBullet.position = ccpAdd(center.position, center.anchorPointInPoints);
bombBullet.position = ccpAdd(bombBullet.position, ccp(-bombBullet.contentSize.width / 2, -bombBullet.contentSize.height / 2));
bombBullet.position = ccpRotateByAngle(bombBullet.position, center.position, bombBullet.rotation);
}
of course I should add sprite deleting.