#ifndef TANKS_H
#define TANKS_H
-#define TANKS_DEBUG_LOG 1
+
+#define TANKS_DEBUG_LOG 0
+
#endif
+#import "TanksMacros.h"
#import "render/QQSparrowExtensions.h"
#ifndef TANKS_MACROS_H
#define TANKS_MACROS_H
-
+#define qqBoolToString(__val) ((__val)?("YES"):("NO"))
#endif
\ No newline at end of file
+#import "game/QQGameTime.h"
#import "game/QQThing.h"
/**
@property (nonatomic, readwrite, getter=isActive) BOOL active;
// @property (nonatomic, readwrite, getter=isSleeping) BOOL sleeping;
+@property (nonatomic, readwrite, retain) NSArray* cooldowns;
-- (void) tick:(float)elapsed;
+- (void) tick;
- (void) act;
@end
\ No newline at end of file
#import "Sparrow.h"
+#import "qq/QQNotificationCenter.h"
#import "physics/QQWorld.h"
#import "physics/debug/QQPhysicsDebugView.h"
+#import "game/QQGameTime.h"
#import "game/actor/QQActor.h"
#import "game/map/QQLevel.h"
-@interface QQGame : SPStage {
-
+@interface QQGame : QQNotificationCenter <QQGameTime> {
@private
- QQWorld* _world;
-
- QQLevel* _level;
- SPSprite* _root;
+ float _now;
+ float _elapsed;
+ long _ticks;
+ long _realtime;
- NSMutableSet* _actors;
- NSMutableSet* _awake;
- NSMutableSet* _units;
- NSMutableSet* _bullets;
+ BOOL _running;
+ BOOL _debugDrawingEnabled;
+ QQWorld* _world;
QQPhysicsDebugView* _debugView;
- BOOL _debugDrawingEnabled;
- long _ticks;
- BOOL _running;
+ SPStage* _stage;
+ SPSprite* _root;
+ QQLevel* _level;
+
+ NSSet* _actors;
+ NSSet* _awake;
+ NSSet* _units;
+ NSSet* _bullets;
}
-@property (nonatomic, readonly) QQWorld* world;
-@property (nonatomic, readonly) QQLevel* level;
-@property (nonatomic, readonly) NSSet* actors;
+@property (nonatomic, readonly) id<QQGameTime> time;
+@property (nonatomic, readonly) float now;
+@property (nonatomic, readonly) float elapsed;
+@property (nonatomic, readonly) long ticks;
-@property (nonatomic, readonly, assign) long ticks;
@property (nonatomic, readonly, getter=isPaused) BOOL paused;
@property (nonatomic, readwrite, assign) BOOL debugDrawingEnabled;
-- (void) start;
-- (void) stop;
+@property (nonatomic, readonly) SPStage* stage;
+@property (nonatomic, readonly) SPDisplayObjectContainer* root;
+@property (nonatomic, readonly) QQLevel* level;
+
+@property (nonatomic, readonly) QQWorld* world;
+
+@property (nonatomic, readonly, retain) NSSet* actors;
-- (void) tick:(float)elapsed;
+- (void) tick;
- (QQGame*) addActor:(QQActor*)actor;
+// - (QQGame*) addActorAndShape:(QQActor*)actor;
- (QQGame*) removeActor:(QQActor*)actor;
+- (QQGame*) destroyActor:(QQActor*)actor;
+
+- (void) start;
+- (void) stop;
+ (QQGame*) current;
#import <Box2D/Box2D.h>
+#import "Tanks.h"
+#import "qq/NSSet+QQExtensions.h"
#import "game/QQGame.h"
#import "game/actor/QQActors.h"
#import "physics/event/QQContactNotification.h"
-
static QQGame* _CurrentGame = NULL;
@interface QQGame ()
+@property (nonatomic, readwrite, retain) NSSet* actors;
+
- (void) onEnterFrame:(SPEnterFrameEvent*)event;
- (void) onCollide:(QQContactNotification*)msg;
@end
@implementation QQGame
-
- (id) init {
if (_CurrentGame) {
[self release];
#ifdef DEBUG
- [NSException raise:@"TooManyGames" format:@"cannot instantiate more than one QQGame at a time!"];
+ [NSException raise:@"WTFTooManyGames" format:@"cannot instantiate more than one QQGame at a time!"];
#else
return _CurrentGame;
#endif
if ( (self = [super init]) ){
_CurrentGame = self;
+ _now = 0;
_ticks = 0;
+ _elapsed = 0;
_running = NO;
+ _stage = [[SPStage alloc] init];
+ _root = [[SPSprite sprite] retain];
+ [_stage addChild:_root];
+
_world = [[QQWorld alloc] init];
- [_world addObserver:self selector:@selector(onCollide:) name:QQ_EVENT_CONTACT_BEGIN];
- [_world addObserver:self selector:@selector(onCollide:) name:QQ_EVENT_CONTACT_END];
+ _world.proxiedNotifier = self;
+ [_world addObserver:self selector:@selector(onCollide:) name:QQ_EVENT_CONTACT_BEGIN object:nil];
+ [_world addObserver:self selector:@selector(onCollide:) name:QQ_EVENT_CONTACT_END object:nil];
- _root = [SPSprite sprite];
- [self addChild:_root];
_level = [[QQLevel alloc] init];
[_root addChild:_level];
_debugView = [[QQPhysicsDebugView alloc] initWithWorld:_world];
[_root addChild:_debugView];
self.debugDrawingEnabled = YES;
- _actors = [[NSMutableSet alloc] initWithCapacity:10];
- _awake = [[NSMutableSet alloc] initWithCapacity:10];
- _units = [[NSMutableSet alloc] initWithCapacity:10];
- _bullets = [[NSMutableSet alloc] initWithCapacity:10];
+ _actors = [[NSSet alloc] init];
+ _awake = [[NSSet alloc] init];
+ _units = [[NSSet alloc] init];
+ _bullets = [[NSSet alloc] init];
CGSize frame = [UIScreen mainScreen].applicationFrame.size;
float wMax = frame.width / _world.scale;
float hMax = frame.height / _world.scale;
[[[QQTank alloc] initAtX:wMax/6 y:hMax/6 width:50 height:50 color:0xFF0071] autorelease];
[[[QQUnit alloc] initAtX:wMax/3 y:hMax/2 width:50 height:50 color:0x4596FF] autorelease];
+
+ [_stage addEventListener:@selector(logEvent:) atObject:self forType:SP_EVENT_TYPE_ANY];
}
return self;
}
- (void) dealloc {
- [self removeEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
- [self removeAllChildren];
+ [_stage removeEventListener:@selector(logEvent:) atObject:self forType:SP_EVENT_TYPE_ANY];
+ [_stage removeEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
- NSMutableSet* sets[] = {_actors, _awake, _units, _bullets};
- for (int i = 0; i < 4; i++) {
- NSMutableSet* set = sets[i];
- [set removeAllObjects];
- [set release];
- }
+ [_actors release];
+ [_awake release];
+ [_units release];
+ [_bullets release];
+ [_stage release];
+ [_root release];
[_level release];
+
[_debugView release];
- [_root release];
[_world release];
_CurrentGame = NULL;
/// properties
-@synthesize world = _world;
-@synthesize level = _level;
+@synthesize now = _now;
+@synthesize elapsed = _elapsed;
+@synthesize ticks = _ticks;
-@synthesize actors = _actors;
+- (id<QQGameTime>) time { return self; }
-@synthesize ticks = _ticks;
@synthesize paused = _running;
+@synthesize stage = _stage;
+@synthesize root = _root;
+@synthesize level = _level;
+
+@synthesize world = _world;
+@synthesize actors = _actors;
+
@synthesize debugDrawingEnabled = _debugDrawingEnabled;
- (void) setDebugDrawingEnabled:(BOOL)enable {
/// methods
- (QQGame*) addActor:(QQActor*)actor {
- [_actors addObject:actor];
- [_awake addObject:actor];
- if ([actor isKindOfClass:[QQUnit class]])
- [_units addObject:actor];
- if ([actor isKindOfClass:[QQBullet class]])
- [_bullets addObject:actor];
+ self.actors = [self.actors setByAddingObject:actor];
+// if (actor.isActive)
+// _awake = [_awake setByAddingObject:actor];
+// if ([actor isKindOfClass:[QQUnit class]])
+// _units = [_units setByAddingObject:actor];
+// if ([actor isKindOfClass:[QQBullet class]])
+// _bullets = [_bullets setByAddingObject:actor];
return self;
}
- (QQGame*) removeActor:(QQActor*)actor {
- [_actors removeObject:actor];
- [_awake removeObject:actor];
- [_units removeObject:actor];
- [_bullets removeObject:actor];
+ self.actors = [self.actors setByRemovingObject:actor];
+// if (actor.isActive)
+// _awake = [_awake setByRemovingObject:actor];
+// if ([actor isKindOfClass:[QQUnit class]])
+// _units = [_units setByRemovingObject:actor];
+// if ([actor isKindOfClass:[QQBullet class]])
+// _bullets = [_bullets setByRemovingObject:actor];
return self;
}
+- (QQGame*) destroyActor:(QQActor*)actor {
+ [_world destroy:actor];
+ return [self removeActor:actor];
+}
-- (void) tick:(float)elapsed {
- _ticks++;
-
+
+- (void) tick {
#if TANKS_DEBUG_LOG
if ((_ticks % 100) == 0) {
- NSLog(@"[%ld] Time passed since last 100 frames: %f", _ticks, elapsed);
+ NSLog(@"[%ld] Time passed since last 100 frames: %f", _ticks, _elapsed);
for (QQActor* actor in self.actors) {
b2Vec2 v = actor.body->GetLinearVelocity();
NSLog(@"[%@ impulse:(%f,%f)]", actor, v.x,v.y);
}
#endif
- for (QQActor* actor in _awake) [actor tick:elapsed];
+ for (QQActor* actor in self.actors)
+ [actor tick];
[self.world step];
- for (QQActor* actor in self.actors) [actor draw];
+ for (QQActor* actor in self.actors)
+ if (!actor.dead) [actor draw];
}
- (void) start {
if (!_running) {
_running = YES;
- [self addEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
+ [_stage addEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
}
}
- (void) stop {
if (_running) {
- [self removeEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
+ [_stage removeEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
_running = NO;
}
}
-/// event handlers
+/// event handlers
- (void) onEnterFrame:(SPEnterFrameEvent*)event {
- if (_running) [self tick:event.passedTime];
+ if (_running) {
+ _ticks++;
+ _elapsed = event.passedTime;
+ _now += _elapsed;
+ [self tick];
+ }
}
- (void) onCollide:(QQContactNotification*)msg {
NSLog(@"%@", msg);
}
+- (void) logEvent:(SPEvent*)evt {
+ if ([evt.type isEqualToString:SP_EVENT_TYPE_ENTER_FRAME]) return;
+ NSLog(@"%@", evt);
+}
+
+- (NSString*) description {
+ return [NSString stringWithFormat:@"<%@: %x> {running=%s, #actors=%i, time={now=%f, elapsed=%f, ticks=%l}}",
+ [self class], (long)self, qqBoolToString(self.isPaused), [self.actors count],
+ self.now, self.elapsed, self.ticks];
+}
+ (QQGame*) current {
--- /dev/null
+@protocol QQGameTime
+
+@property (nonatomic, readonly) float now;
+@property (nonatomic, readonly) float elapsed;
+@property (nonatomic, readonly) long ticks;
+
+@end
#include <Box2D/Box2D.h>
+@class QQContactNotification;
+
+
/**
* Anything that has a physics representation.
*/
*/
- (void) setupPhysics;
+- (void) applyLinearImpulseX:(float)xNs y:(float)yNs;
+
+
+@optional
+- (void) onCollideBegin:(QQContactNotification*)msg;
+- (void) onCollideEnd:(QQContactNotification*)msg;
+
@end
\ No newline at end of file
+#import "qq/QQObservable.h"
+
@class QQGame;
@class QQWorld;
/**
* Anything in the game-world.
*/
-@protocol QQThing
+@protocol QQThing <QQObservableNotifier>
@property (nonatomic, readonly) QQGame* game;
@property (nonatomic, readonly) QQWorld* world;
+#import "game/QQGameTime.h"
/**
* Advances the timer by {elapsed} milliseconds, updating ready state if necessary.
* @return Ready state.
*/
-- (BOOL) tick:(float)elapsed;
+- (BOOL) tick:(id<QQGameTime>)time;
+ (QQCooldown*) cooldownWithDuration:(float)duration;
* Advances the timer by {elapsed} milliseconds, updating ready state if necessary.
* @return Ready state.
*/
-- (BOOL) tick:(float)elapsed {
+- (BOOL) tick:(id<QQGameTime>)time {
if (self.ready)
return YES;
- self.elapsed += elapsed;
+ self.elapsed += time.elapsed;
return self.ready;
}
#include <Box2D/Box2D.h>
+
+#import "qq/QQNotificationProxy.h"
#import "game/QQThing.h"
#import "game/QQActive.h"
#import "game/QQPhysical.h"
@class QQGame;
-@interface QQActor : NSObject <QQThing, QQActive, QQPhysical, QQDisplayable> {
+@interface QQActor : QQNotificationProxy <QQThing, QQActive, QQPhysical, QQDisplayable> {
@protected
b2Body* _body;
@private
BOOL _active;
BOOL _dirty;
+ BOOL _dead;
id<QQActorDelegate> _delegate;
+ NSArray* _cooldowns;
}
@property (nonatomic, readonly) QQGame* game;
@property (nonatomic, readonly) QQWorld* world;
@property (nonatomic, readwrite, retain) id<QQActorDelegate> delegate;
+@property (nonatomic, readonly) BOOL dead;
- (id) initAtX:(float)x y:(float)y;
- (id) initType:(b2BodyType)type atX:(float)x y:(float)y;
+- (void) destroy;
+
+
/// internal methods
/** Updates the Shape using values from Box2D. */
/// properties
-@synthesize active = _active;
-@synthesize dirty = _dirty;
-@synthesize delegate = _delegate;
-
@dynamic shape;
+@synthesize active = _active;
+@synthesize dirty = _dirty;
+@synthesize dead = _dead;
+@synthesize delegate = _delegate;
+@synthesize cooldowns = _cooldowns;
+
- (QQGame*) game { return QQGame.current; }
- (QQWorld*) world { return self.game.world; }
- (b2Body*) body { return _body; }
- (b2Fixture*) fixture { return nil; }
-- (float) rotation { return self.body->GetAngle(); }
-- (void) setRotation:(float)r { self.body->SetTransform(self.body->GetPosition(), r); }
+- (id) implicitNotificationSender { return self; }
+- (void) setImplicitNotificationSender:(id)sender {}
+
+- (id<QQObservableNotifier>) proxiedNotifier { return self.game; }
+- (void) setProxiedNotifier:(id<QQObservableNotifier>)notifier {}
+
+
+- (float) x { return self.body->GetPosition().x; }
+- (void) setX:(float)x { [self setPositionX:x y:self.y]; }
+
+- (float) y { return self.body->GetPosition().y; }
+- (void) setY:(float)y { [self setPositionX:self.x y:y]; }
- (CGPoint) position {
b2Vec2 pos = self.body->GetPosition();
self.body->SetTransform(b2Vec2(x,y), self.body->GetAngle());
}
+- (float) rotation { return self.body->GetAngle(); }
+- (void) setRotation:(float)r { self.body->SetTransform(self.body->GetPosition(), r); }
+
+
- (void) updateShapeX:(float)x y:(float)y {
float px = self.world.scale;
[self.shape setCenterX:x*px y:y*px];
}
-- (float) x { return self.body->GetPosition().x; }
-- (void) setX:(float)x { [self setPositionX:x y:self.y]; }
-
-- (float) y { return self.body->GetPosition().y; }
-- (void) setY:(float)y { [self setPositionX:self.x y:y]; }
-
/// initializers
- (id) initType:(b2BodyType)type atX:(float)x y:(float)y {
if ((self = [super init])) {
+ _active = YES;
+ _dead = NO;
+ self.proxiedNotifier = self.game;
+ self.cooldowns = [NSArray array];
[self.game addActor:self];
b2BodyDef bd;
bd.position = b2Vec2(x, y);
bd.userData = self;
_body = self.world.world->CreateBody(&bd);
+
+ if ([self respondsToSelector:@selector(onCollideBegin:)])
+ [self addObserver:self selector:@selector(onCollideBegin:) name:QQ_EVENT_CONTACT_BEGIN];
}
return self;
}
+- (void) dealloc {
+ self.delegate = nil;
+ [_cooldowns release];
+ [super dealloc];
+}
+
+
/// methods
-- (void) tick:(float)elapsed {
- [self act];
+- (void) destroy {
+ NSLog(@"retainCount=%i", [self retainCount]);
+ if (!self.dead) {
+ _dead = YES;
+ self.shape = nil;
+ [self.game destroyActor:self];
+ NSLog(@"%@ \v\tretainCount=%i \v\tgame=%@", self, [self retainCount], self.game);
+ }
+}
+
+- (void) tick {
+ if (!self.dead) {
+ [self.cooldowns makeObjectsPerformSelector:@selector(tick:)withObject:self.game.time];
+ if (self.isActive) [self act];
+ }
}
- (void) act {}
- (void) setupPhysics {}
+
+- (void) applyLinearImpulseX:(float)xNs y:(float)yNs {
+ self.body->ApplyLinearImpulse(b2Vec2(xNs, yNs), self.body->GetPosition());
+}
+
+
- (NSString*) description {
b2Transform trans = self.body->GetTransform();
b2Vec2 pos = trans.position;
-#import "QQActor.h"
-#import "QQActorDelegate.h"
-#import "QQUnit.h"
-#import "bullet/QQBullet.h"
-#import "unit/QQTank.h"
+#import "game/actor/QQActor.h"
+#import "game/actor/QQActorDelegate.h"
+#import "game/actor/QQUnit.h"
+#import "game/actor/bullet/QQBullet.h"
+#import "game/actor/unit/QQTank.h"
}
- (void) dealloc {
- [self.game removeChild:_shape];
+ [self.game.level removeChild:_shape];
[_shape release];
[super dealloc];
}
[_shape release];
_shape = [newShape retain];
- [self updateShape];
- [self.game.level addChild:_shape];
+ if (_shape) {
+ [self updateShape];
+ [self.game.level addChild:_shape];
+ }
}
}
- (id) init:(QQUnit*)owner x:(float)x y:(float)y;
-- (void) applyLinearImpulseX:(float)xNs y:(float)yNs;
@end
#import "SparrowExtras.h"
#import "QQBullet.h"
+#import "physics/event/QQContactNotification.h"
return self;
}
-- (void) applyLinearImpulseX:(float)xNs y:(float)yNs {