今天我们学习一个Cocosd2d-iphone游戏。中间介绍了texturepacker 和physics editor的使用(需要MAC操作系统),我们一起做一个实战级别的游戏。
其中还介绍了 图片资源文件加密, 碰撞检测,动画效果等知识,比较有趣。
感觉代码太枯燥的,敬请期待视频上传
高清avi视频和项目源代码已经提供下载:
百度网盘:http://pan.baidu.com/s/1ELk78 里面的进阶篇。
// // Floor.h // MonkeyJump // // Created by Mac on 14-3-10. // Copyright (c) 2014年 codeandweb.de. All rights reserved. // #import "GB2Sprite.h" @interface Floor : GB2Sprite // 既有cocos2d CCSprite 又包含 b2Body +(Floor*)floorSprite; @end // // Floor.m // MonkeyJump // // Created by Mac on 14-3-10. // Copyright (c) 2014年 codeandweb.de. All rights reserved. // #import "Floor.h" @implementation Floor +(Floor*)floorSprite{ // GB2Sprite return [[[self alloc] initWithStaticBody:@"grassfront" spriteFrameName:@"floor/grassfront.png"] autorelease]; } @end // // GameLayer.h // MonkeyJump // // Created by Andreas Löw on 10.11.11. // Copyright codeandweb.de 2011. All rights reserved. // // When you import this file, you import all the cocos2d classes #import "cocos2d.h" @class Object; @class Monkey; @class HeadUpDisplay; // GameLayer @interface GameLayer : CCLayer { float _timeLeftBeforeNextObject; Object* _nextObject; Monkey* _monkey; CCSpriteBatchNode* _objectLayer ; CCSprite *__backGrass; float _heapHeight; //堆积物体的最高高度 } @property (assign,nonatomic) float highestObjectY; @property(nonatomic) float heapHeight; // 不需要写synthesis // returns a CCScene that contains the GameLayer as the only child +(CCScene *) scene; @end // // GameLayer.mm // MonkeyJump // // Created by Andreas Löw on 10.11.11. // Copyright codeandweb.de 2011. All rights reserved. // // Import the interfaces #import "GameLayer.h" #import "GB2DebugDrawLayer.h" #import "GB2Sprite.h" #import "Floor.h" #import "Object.h" #import "Monkey.h" #import "HeadUpDisplay.h" #import "SimpleAudioEngine.h" #import "ZipUtils.h" #import "cocos2d/Support/ZipUtils.h" // HelloWorldLayer implementation @implementation GameLayer { CCSprite *__background; Floor *_floorObj; HeadUpDisplay *_hud; int _tickAfterMonkeyDead; float _timeGap; CCLayerColor *_indicator; } +(CCScene *) scene { // 'scene' is an autorelease object. CCScene *scene = [CCScene node]; // 'layer' is an autorelease object. GameLayer *layer = [GameLayer node]; // add layer as a child to scene [scene addChild: layer]; // return the scene return scene; } // on "init" you need to initialize your instance -(id) init { caw_setkey_part(0, 0xb789a2ef); caw_setkey_part(1, 0xa185b1df); caw_setkey_part(2, 0x5e648b88); caw_setkey_part(3, 0x9cf4f44e); CGSize screenSize = [[CCDirector sharedDirector] winSize]; float temp; if (screenSize.width > screenSize.height){ temp = screenSize.width; screenSize.width = screenSize.height; screenSize.height = temp; } if( (self=[super init])) { // 加载我们的spritesheet pvr.czz [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"tp_objects.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"tp.plist"]; // 加载box2d 物体属性 [[GB2ShapeCache sharedShapeCache] addShapesWithFile:@"pe_monkey.plist"]; // 最底层 __background = [CCSprite spriteWithSpriteFrameName:@"jungle.png"]; __background.anchorPoint = ccp(0,0); __background.position = ccp(0,0); [self addChild:__background z:0]; // 背景的草 __backGrass = [CCSprite spriteWithSpriteFrameName:@"floor/grassbehind.png"]; __backGrass.anchorPoint = ccp(0, 0); __backGrass.position = ccp(0, 0); [self addChild:__backGrass z:2]; // 所有展示的 box2d世界中的物体 _objectLayer = [CCSpriteBatchNode batchNodeWithFile:@"tp_objects.pvr.ccz"]; [self addChild:_objectLayer z:10]; _floorObj = [Floor floorSprite]; CCSprite* floor = (CCSprite *) [_floorObj ccNode]; [_objectLayer addChild:floor]; [self scheduleUpdate]; _timeLeftBeforeNextObject = 2; _nextObject = nil; GB2Node* leftWall = [[[GB2Node alloc] initWithStaticBody:nil node:nil] autorelease]; [leftWall addEdgeFrom:b2Vec2FromCC(0, 0) to:b2Vec2FromCC(0, 10000)]; GB2Node*rightWall = [[[GB2Node alloc] initWithStaticBody:nil node:nil] autorelease]; [rightWall addEdgeFrom:b2Vec2FromCC(screenSize.height, 0) to:b2Vec2FromCC(screenSize.height, 10000)]; // i really not like //调试box2d // CCNode* debugLayer = [[[GB2DebugDrawLayer alloc] init] autorelease]; // [self addChild:debugLayer z:40]; _monkey = [[[Monkey alloc] initWithParentlayer:self] autorelease]; [_monkey setPhysicsPosition:b2Vec2FromCC(240, (120 + 32) / 2)]; [_objectLayer addChild:[_monkey ccNode] z:40]; _hud = [[HeadUpDisplay alloc] init]; [self addChild:_hud z:20]; [self setIsTouchEnabled:YES]; _heapHeight = floor.position.y + floor.contentSize.height; _indicator = [[[CCLayerColor alloc] initWithColor:ccc4(255, 0, 0, 128) width:70 height:20] autorelease]; [self addChild:_indicator z:30]; _indicator.visible = NO; [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"tafi-maradi-loop.caf" loop:YES]; } return self; } // 接受用户事件 触发猴子的运动 -(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch* touch = (UITouch*) [touches anyObject]; CGPoint uiPos = [touch locationInView:[touch view]]; CGPoint pos = [[CCDirector sharedDirector] convertToGL:uiPos]; float mX = [_monkey ccNode].position.x; if (pos.x < mX){ _monkey.isMovingLeft = YES; } else { _monkey.isMovingLeft = NO; } // 触屏的上半部分 触摸 让猴子起跳 if (pos.y > 160.0){ [_monkey jump]; } } -(void)update:(float) dt{ //CCLOG(@"update method"); _timeGap = 3.0f; [_hud notifiedHealth:_monkey.health]; [_hud notifieadScore:_monkey.score]; if (_monkey.health < 0){ _tickAfterMonkeyDead++; if (_tickAfterMonkeyDead > 90){ [[GB2Engine sharedInstance] deleteAllObjects]; // box2d 清空 [[CCDirector sharedDirector] replaceScene:[GameLayer scene]]; // cocos2d 图形 return; } } CGSize screenSize = [[CCDirector sharedDirector] winSize]; _timeLeftBeforeNextObject -= dt; float monkeyY = [_monkey ccNode].position.y; if (_timeLeftBeforeNextObject > 0.5 * _timeGap && nil != _nextObject){ _indicator.position = ccp([_nextObject ccNode].position.x, 300); _indicator.visible = YES; } else{ _indicator.visible = NO; } if (_timeLeftBeforeNextObject <= 0){ _nextObject = [Object randomObject]; int offsetX = rand() % 400 + 40; int offsetY = 400 + _heapHeight; [_nextObject setPhysicsPosition:b2Vec2FromCC(offsetX, offsetY)]; [_objectLayer addChild:[_nextObject ccNode]]; _timeGap *= 0.8; _timeLeftBeforeNextObject = _timeGap; if (_timeLeftBeforeNextObject < 0.5){ _timeLeftBeforeNextObject = 0.5; } //CCLOG(@"time gap is %f", _timeLeftBeforeNextObject); } // 调整摄像头位置 [self adjustViewPoint:screenSize monkeyY:monkeyY]; // 堆积物体的顶部 遍历box2d世界中的所有东西 [[GB2Engine sharedInstance] iterateObjectsWithBlock:^(GB2Node *node) { // 是Object吗 if ([node isKindOfClass:([Object class])]){ Object *obj = (Object *)node; if (b2_staticBody != [obj getBodyType] //已经变成静态的 不再考虑 && _nextObject != obj && obj.numContacts > 1 //和多个物体又接触 才算在堆上 && [obj linearVelocity].LengthSquared() < 0.4 ){ // 只有obj是落在堆上的物体 才去更新_heapHeight float objY = [obj ccNode].position.y + [obj ccNode].contentSize.height; if (_heapHeight < objY){ _heapHeight = objY; if (_heapHeight > 120){ ; } //CCLOG(@"heap height is %f", _heapHeight); } // 如果物体在堆的下层,离顶部很远 使它变成静态物体 box2d性能提高 if (objY < _heapHeight - 320){ [obj setBodyType:b2_staticBody]; static int numObjectsFromDynamicToStatic = 0; numObjectsFromDynamicToStatic ++; // CCLOG(@"%x(%d) become from dynamic to static , totally %d objects changed", // obj, obj.numContacts, numObjectsFromDynamicToStatic); } } } }]; } - (void)adjustViewPoint:(CGSize)screenSize monkeyY:(float)monkeyY { float centerY = monkeyY - screenSize.width / 2; if (centerY < 0){ centerY = 0; } _objectLayer.position = ccp(0, -centerY); __backGrass.position = ccp(0, -0.8 * centerY); __background.position = ccp(0, -0.6 * centerY); } @end // // Created by Mac on 14-3-16. // Copyright (c) 2014 codeandweb.de. All rights reserved. // #import <Foundation/Foundation.h> #import <cocos2d.h> #import "CCSpriteBatchNode.h" @interface HeadUpDisplay : CCSpriteBatchNode { float _displayHealth; } @property(nonatomic) float displayHealth; -(id)init; - (void)notifieadScore:(float)theScore; -(void)notifiedHealth:(float) monkeyHealth; @end // // Created by Mac on 14-3-16. // Copyright (c) 2014 codeandweb.de. All rights reserved. // #import "HeadUpDisplay.h" #import "CCSprite.h" #import "CCAction.h" #import "CCActionInterval.h" #import "CCActionInstant.h" #import "CCSpriteFrame.h" #import "CCSpriteFrameCache.h" #define MAX_DISPLAY_TOKE_NUM 10 @implementation HeadUpDisplay { CCSprite *_displayToken [MAX_DISPLAY_TOKE_NUM]; CCSprite* _scoreDisplay[5]; @private float _scoreOnBoard; CCSpriteFrame *_digits [10]; } - (id)init { [super initWithFile:@"tp_objects.pvr.ccz" capacity:50]; if (self){ for (int i = 0; i < MAX_DISPLAY_TOKE_NUM; i ++){ _displayToken[i] = [CCSprite spriteWithSpriteFrameName:@"hud/banana.png"]; _displayToken[i].position = ccp(20 + i * 15, 280); [self addChild:_displayToken[i]]; _displayToken[i].visible = YES; } for (int i = 0; i < 5; i ++){ _scoreDisplay[i] = [CCSprite spriteWithSpriteFrameName:@"numbers/0.png"]; _scoreDisplay[i].position = ccp(380 + i * 15, 280); _scoreDisplay[i].scale = 0.7f; [self addChild:_scoreDisplay[i]]; _scoreDisplay[i].visible = NO; } for (int i = 0; i < 10; i ++){ NSString* name = [NSString stringWithFormat:@"numbers/%d.png", i]; _digits[i] = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:name]; } } _displayHealth = 100.0; // 100 ---- MAX_DISPLAY_TOKE_NUM _scoreOnBoard = 0; return self; } -(void) notifieadScore:(float)theScore{ if (_scoreOnBoard < theScore){ _scoreOnBoard += 1; if (_scoreOnBoard > theScore){ _scoreOnBoard = theScore; } } int scoreInt = (int)_scoreOnBoard; char buf[5 + 1] = {0}; snprintf(buf, 6, "%5d", scoreInt); for (int i = 0; i < 5; i ++){ if (' ' == buf[i]){ _scoreDisplay[i].visible = NO; continue; } int index = buf[i] - '0'; [_scoreDisplay[i] setDisplayFrame:_digits[index]]; _scoreDisplay[i].visible = YES; } } - (void)notifiedHealth:(float)monkeyHealth { if (monkeyHealth > _displayHealth){ _displayHealth += 13.0f; if (_displayHealth > monkeyHealth){ _displayHealth = monkeyHealth; } } else{ _displayHealth -= 13.0f; if (_displayHealth < monkeyHealth){ _displayHealth = monkeyHealth; } } int displayNum = (int)((_displayHealth / 100) * MAX_DISPLAY_TOKE_NUM); int i; for (i =0; i < displayNum; i ++){ //_displayToken[i].visible = YES; CCSprite *token = _displayToken[i]; if (NO == token.visible){ token.visible = YES; token.opacity = 0.0f; token.scale = 0.3; // 多个动作 合并 CCSpawn *action = [CCSpawn actions: [CCScaleTo actionWithDuration:0.3 scale:1.0f], [CCFadeIn actionWithDuration:0.3f], nil] ; [token runAction:action]; } } for (; i < MAX_DISPLAY_TOKE_NUM; i ++){ CCSprite *token = _displayToken[i]; if (YES == token.visible && [token numberOfRunningActions] <= 0){ CCSpawn *disappear = [CCSpawn actions: [CCScaleTo actionWithDuration:0.3 scale:0.3], [CCFadeOut actionWithDuration:0.3], nil]; // visiable == YES; CCHide* hide = [CCHide action]; CCSequence *all = [CCSequence actions:disappear, hide, nil]; [token runAction:all]; } } } @end // // Monkey.h // MonkeyJump // // Created by Mac on 14-3-11. // Copyright (c) 2014年 codeandweb.de. All rights reserved. // #import "GB2Sprite.h" @class GameLayer; @class GB2Contact; @interface Monkey : GB2Sprite { bool _isMovingLeft; int _numContactWithFloor; int _numContactWithHead; int _numContactWithLeft; int _numContactWithRight; ccTime _timePassed; int _animatePhase; int _trappedCount; // 记录猴子被困住的时间 -- 跳到堆积物体的顶部以上 float _health; } @property (assign, nonatomic) bool isMovingLeft; @property (assign, nonatomic) int numbContactWithFloor; @property(nonatomic) float health; @property(nonatomic) float score; -(id)initWithParentlayer:(GameLayer*) theParentLayer; -(void)jump; - (void)beginContactWithFloor:(GB2Contact *)contact; - (void)endContactWithFloor:(GB2Contact *)contact; - (void)beginContactWithObject:(GB2Contact *)contact; - (void)endContactWithObject:(GB2Contact *)contact; @end // // Monkey.m // MonkeyJump // // Created by Mac on 14-3-11. // Copyright (c) 2014年 codeandweb.de. All rights reserved. // xcode -> appcode #import "Monkey.h" #import "SimpleAudioEngine.h" #import "GMath.h" #import "GB2Contact.h" #import "GameLayer.h" #import "Object.h" @implementation Monkey { GameLayer *__parentLayer; } @synthesize isMovingLeft = _isMovingLeft; @synthesize numbContactWithFloor = _numbContactWithFloor; -(id)initWithParentlayer:(GameLayer*) theParentLayer{ __parentLayer = theParentLayer; [super initWithDynamicBody:@"monkey" spriteFrameName:@"monkey/idle/2.png"]; if (self){ [self setFixedRotation:YES]; [self setBullet:YES]; self.isMovingLeft = NO; _numContactWithFloor = 0; _numContactWithHead = 0; _numContactWithLeft = 0; _numContactWithRight = 0; _timePassed = 0.3; _animatePhase = 1; _health = 100; } return self; } // 自己实现动画效果 周期性的改变自己展示的图形 -(void)updateCCFromPhysics{ [super updateCCFromPhysics]; //使物体 先服从box2d中原有的物理规律 if (_health < 0) { return; } float impulse = self.mass * 0.5; float velocityX = [self linearVelocity].x; float velocityY = [self linearVelocity].y; //CCLOG(@"v y is %f", velocityY); if (velocityY > 8){ //body->SetLinearDamping(0.6); self.linearVelocity = b2Vec2(velocityX, 0.6 * velocityY); } //使猴子水平移动 if (_isMovingLeft && velocityX > -1.0){ [self applyLinearImpulse:b2Vec2(-impulse, 0) point:self.worldCenter]; } else if (!_isMovingLeft && velocityX < 1.0){ [self applyLinearImpulse:b2Vec2(impulse, 0) point:self.worldCenter]; } // 设置猴子 在屏幕显示什么图片 实现动画效果 [self setMonkeyDisplayFrame]; // 计算猴子已经被困多长时间 if (_numContactWithHead > 0){ _trappedCount ++; if (_trappedCount > 60){ [self setPhysicsPosition:b2Vec2FromCC(ccNode.position.x, __parentLayer.heapHeight)]; } } else{ _trappedCount = 0; } float curRelevateScore = 1.0 * [self ccNode].position.y - 75; if (self.score < curRelevateScore){ self.score = curRelevateScore; } } // 设置猴子应该显示那一张图片 实现动画效果 - (void)setMonkeyDisplayFrame { _timePassed -= 1.0f / 60.0f; if (_timePassed <= 0){ _animatePhase++; if (_animatePhase >= 3){ _animatePhase = 1; } _timePassed = 0.3; } NSString* frameName; NSString* dir; float monkeyVelocityLengthSquare = [self linearVelocity].LengthSquared(); dir = _isMovingLeft ? @"left":@"right"; //CCLOG(@"v square is %f", monkeyVelocityLengthSquare); if ( monkeyVelocityLengthSquare < 0.3){ if (_numContactWithHead > 0){ frameName = @"arms_up"; } else{ frameName = @"idle/2"; } } else { if (_numContactWithFloor < 0){ frameName = [NSString stringWithFormat:@"jump/%@", dir]; } else{ if (_numContactWithLeft > 0 && _isMovingLeft){ frameName = [NSString stringWithFormat:@"push/%@_%d", dir, _animatePhase]; } else if (_numContactWithRight > 0 && !_isMovingLeft){ frameName = [NSString stringWithFormat:@"push/%@_%d", dir, _animatePhase]; } else{ frameName = [NSString stringWithFormat:@"walk/%@_%d", dir, _animatePhase]; } } } NSString* completeFrameName = [NSString stringWithFormat:@"monkey/%@.png", frameName]; [self setDisplayFrameNamed:completeFrameName]; } -(void)jump{ if (_health < 0) { return; } if (_numContactWithFloor <=0){ return; //离地 的时候不需要再施加冲量了 } float factor = 1.0f; if (_numContactWithHead > 0){ factor = 3.0f; } [self applyLinearImpulse:b2Vec2(0, self.mass * 6.0 * factor) point:self.worldCenter]; float _pan = ([self ccNode].position.x - 240.0) / 240.0; [[SimpleAudioEngine sharedEngine] playEffect:@"jump.caf" pitch:gFloatRand(0.8, 1.2) pan:_pan gain:1.0]; } -(void)beginContactWithFloor:(GB2Contact*) contact{ _numContactWithFloor ++; } -(void)endContactWithFloor:(GB2Contact*) contact{ _numContactWithFloor --; } -(void)beginContactWithObject:(GB2Contact*) contact{ GB2Node* otherNode = [contact otherObject] ; if ([otherNode isKindOfClass:[Object class]]){ // 如果是香蕉则补充生命值 Object *other = (Object *)otherNode; if ([other.objName isEqualToString:@"banana"]){ _health += 60; if (_health > 100){ _health = 100; } [other disappear]; [[SimpleAudioEngine sharedEngine] playEffect:@"gulp.caf"]; //return; } else if ([other.objName isEqualToString:@"bananabunch"]){ _health += 90; if (_health > 100){ _health = 100; } [other disappear]; [[SimpleAudioEngine sharedEngine] playEffect:@"gulp.caf"]; //return; } } void* str = contact.ownFixture->GetUserData(); NSString* fixId = (NSString*)str; if ([fixId isEqualToString:@"head"]){ _numContactWithHead ++; GB2Node* otherNode = [contact otherObject] ; if ([otherNode isKindOfClass:[Object class]]){ b2Vec2 otherV = [otherNode linearVelocity]; float otherVY = otherV.y; // 造成伤害 [self hurtHealth:otherNode otherVY:otherVY]; } } else if([fixId isEqualToString:@"push_left"]){ _numContactWithLeft ++; } else if ([fixId isEqualToString:@"push_right"]){ _numContactWithRight ++; } else{ _numContactWithFloor ++; } } - (void)hurtHealth:(GB2Node *)otherNode otherVY:(float)otherVY { if (otherVY < -0.3) // 另外一个物体是向下运动的 0 { _health += 0.5* otherVY * otherNode.mass; [[SimpleAudioEngine sharedEngine] playEffect:@"hurt.caf" pitch:1.0 pan:1.0 gain:2.0]; CCLOG(@"health is %f", _health); if (_health < 0){ [self setFixedRotation:NO]; [self setCollisionMaskBits:0x0001]; // 是猴子只和 地面发生碰撞 [self setDisplayFrameNamed:@"monkey/dead.png"]; } } } -(void)endContactWithObject:(GB2Contact*) contact{ void* str = contact.ownFixture->GetUserData(); NSString* fixId = (NSString*)str; if ([fixId isEqualToString:@"head"]){ _numContactWithHead --; } else if([fixId isEqualToString:@"push_left"]){ _numContactWithLeft --; } else if ([fixId isEqualToString:@"push_right"]){ _numContactWithRight --; } else{ _numContactWithFloor --; } } @end // // Object.h // MonkeyJump // // Created by Mac on 14-3-10. // Copyright (c) 2014年 codeandweb.de. All rights reserved. // #import "GB2Sprite.h" @interface Object : GB2Sprite { NSString* _objName; GB2Node* _contactOtherObjNames[50]; //和那些物体又接触 int _numContacts; } @property (assign, nonatomic) int numContacts; @property (retain, nonatomic) NSString *objName; -(id)initWithObjName:(NSString*) theObjName; +(id)randomObject; -(id)disappear; @end // // Object.m // MonkeyJump // // Created by Mac on 14-3-10. // Copyright (c) 2014年 codeandweb.de. All rights reserved. // #import "Object.h" #import "GMath.h" #import "GB2Contact.h" #import "SimpleAudioEngine.h" @implementation Object { bool _hasBeginDisappearAction; } @synthesize objName = _objName; @synthesize numContacts = _numContacts; -(id)initWithObjName:(NSString*) theObjName { NSString* pngName = [NSString stringWithFormat:@"objects/%@.png", theObjName]; // GB2Sprite [super initWithDynamicBody:theObjName spriteFrameName:pngName]; if (self){ _objName = theObjName; _numContacts = 0; _hasBeginDisappearAction = NO; } return self; } +(id)randomObject{ int type = rand() % 7; NSString* myObjName = @"backpack.png"; switch (type) { case 0: myObjName = @"backpack"; break; case 1: myObjName = @"statue"; break; case 2: myObjName = @"banana"; break; case 3: myObjName = @"bananabunch"; break; case 4: myObjName = @"hat"; break; case 5: myObjName = @"pineapple"; break; case 6: myObjName = @"canteen"; break; default: break; } return [[[Object alloc] initWithObjName:myObjName] autorelease]; } - (id)disappear { // 消失动画 if (NO == _hasBeginDisappearAction) { CCSpawn *outAction = [CCSpawn actions: [CCScaleTo actionWithDuration:0.2 scale:0.0], [CCFadeOut actionWithDuration:0.2], nil]; CCCallFunc *deleteAction = [CCCallFunc actionWithTarget:self selector:@selector(deleteNow)]; [self runAction:[CCSequence actions:outAction, deleteAction, nil ]]; _hasBeginDisappearAction = YES; } return nil; } -(void)beginContactWithObject:(GB2Contact*)contact{ GB2Node *other_ = (contact.otherObject); _contactOtherObjNames[_numContacts] = other_; _numContacts ++; if (_numContacts >= 50){ ; } b2Vec2 v = [self linearVelocity]; if (v.LengthSquared() < 3){ return; } // 播放声音 NSString* cafName = [NSString stringWithFormat:@"%@.caf",_objName]; float pan = ([self ccNode].position.x - 240.0) / 240.0; // [[SimpleAudioEngine sharedEngine] playEffect:cafName pitch:gFloatRand(0.8, 1.2) pan:(pan) gain:1.0]; } -(void)endContactWithObject:(GB2Contact*) contact{ _numContacts --; if (_numContacts < 0){ ; } } -(void)beginContactWithFloor:(GB2Contact*) contact{ [self beginContactWithObject:contact]; //_numContacts ++; } -(void)endContactWithFloor:(GB2Contact*) contact{ _numContacts --; if (_numContacts < 0){ ; } } -(void) dealloc{ [_objName release]; [super dealloc]; } @end