@property (nonatomic, retain, readonly) SPDisplayObject* shape;
+/**
+ * Called to update appearance after game actions have occurred
+ * and physics step has been processed.
+ */
+- (void) draw;
+
@end
\ No newline at end of file
@interface QQGame : SPStage {
- long ticks;
+ long _ticks;
@private
- QQUnit* _unit;
QQWorld* _world;
+ NSMutableSet* _actors;
+ NSMutableSet* _awake;
+ NSMutableSet* _units;
+ NSMutableSet* _bullets;
}
@property (nonatomic, retain) QQWorld* world;
+@property (nonatomic, retain) NSSet* actors;
+@property (nonatomic, assign, readonly) long ticks;
+
+- (QQGame*) addActor:(QQActor*)actor;
+- (QQGame*) removeActor:(QQActor*)actor;
+
- (void) onEnterFrame:(SPEnterFrameEvent*)event;
@interface QQGame ()
-@property (nonatomic, retain) QQUnit* unit;
@end
@implementation QQGame
+
+@synthesize ticks = _ticks;
@synthesize world = _world;
-@synthesize unit = _unit;
+@synthesize actors = _actors;
- (id) init {
if ( (self = [super init]) ){
_currentGame = self;
- ticks = 0l;
-
+ _ticks = 0l;
+ _actors = [[NSMutableArray alloc] initWithCapacity:10];
_world = [[QQWorld alloc] init];
- _unit = [[QQUnit alloc] init];
+
+ [self addActor:[[[QQUnit alloc] init] autorelease]];
[self addEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
}
}
- (void) dealloc {
- [_unit release];
+ [_actors removeAllObjects];
+ [_actors release];
[_world release];
_currentGame = NULL;
[super dealloc];
}
+- (QQGame*) addActor:(QQActor*)actor {
+ [_actors addObject:actor];
+ return self;
+}
+
+- (QQGame*) removeActor:(QQActor*)actor {
+ [_actors removeObject:actor];
+ return self;
+}
+
+
- (void) onEnterFrame:(SPEnterFrameEvent*)event {
- ticks++;
- if ((ticks % 100) == 0)
- NSLog(@"[%ld] Time passed since last 100 frames: %f", ticks, event.passedTime);
+ _ticks++;
+ if ((_ticks % 100) == 0)
+ NSLog(@"[%ld] Time passed since last 100 frames: %f", _ticks, event.passedTime);
+
+ for (QQActor* actor in self.actors) [actor act]; // XXX: self.awakeAgents
[self.world step];
+ for (QQActor* actor in self.actors) [actor draw]; // XXX: self.drawableAgents
}
--- /dev/null
+#include <Box2D/Box2D.h>
+
+/**
+ * Anything that has a physics representation.
+ */
+@protocol QQPhysical
+
+// FIXME: don't expose these, instead aggregate properties from body & fixtures/shapes
+@property (nonatomic, readonly) b2Body* body;
+@property (nonatomic, readonly) b2Fixture* fixture;
+
+@end
\ No newline at end of file
+#include <Box2D/Box2D.h>
#import "game/QQActive.h"
+#import "game/QQPhysical.h"
#import "game/QQDisplayable.h"
#import "physics/QQWorld.h"
@class QQGame;
-@interface QQActor : NSObject <QQActive, QQDisplayable> {
+@interface QQActor : NSObject <QQActive, QQPhysical, QQDisplayable> {
+@protected
+ b2Body* _body;
+
@private
BOOL _active;
}
@property (nonatomic, readonly) QQGame* game;
@property (nonatomic, readonly) QQWorld* world;
+- (id) initAtX:(float)x y:(float)y;
+- (id) initType:(b2BodyType)type atX:(float)x y:(float)y;
@end
- (SPDisplayObject*) shape { return nil; }
+- (b2Body*) body { return _body; }
+- (b2Fixture*) fixture { return nil; }
+
+
+- (id) initAtX:(float)x y:(float)y {
+ return [self initType:b2_dynamicBody atX:x y:y];
+}
+
+- (id) initType:(b2BodyType)type atX:(float)x y:(float)y {
+ if ((self = [super init])) {
+ b2BodyDef bd;
+ bd.type = type;
+ bd.position = b2Vec2(x, y);
+ bd.userData = (void*)self;
+ _body = self.world.world->CreateBody(&bd);
+ }
+ return self;
+}
+
- (void) act {
// stub
}
+- (void) draw {
+ // stub
+}
+
@end
+#include <Box2D/Box2D.h>
#import "Sparrow.h"
#import "render/QQSparrowExtensions.h"
@private
SPDisplayObject* _shape;
- id <QQUnitDelegate> _delegate;
+ id<QQUnitDelegate> _delegate;
}
////////////////////////////////////////////////////////////////////////////////////
@property (nonatomic, retain, readwrite) SPDisplayObject* shape;
-@property (nonatomic, retain, readwrite) id <QQUnitDelegate> delegate;
+@property (nonatomic, retain, readwrite) id<QQUnitDelegate> delegate;
////////////////////////////////////////////////////////////////////////////////////
- (id) initWithFile:(NSString*)fileName atX:(float)x y:(float)y;
-- (id) initWithShape:(SPDisplayObject*)aShape;
+- (id) initWithShape:(SPDisplayObject*)shape atX:(float)x y:(float)y;
- (void) onTouch:(SPTouchEvent*)event;
+#include <Box2D/Box2D.h>
#import "Sparrow.h"
#import "QQUnit.h"
////////////////////////////////////////////////////////////////////////////////////
- (id) init {
- return [self initWithShape:[[SPQuad quadWithWidth:32 height:32 color:0xff0000] setPositionX:50 y:50]];
+ return [self initWithShape:[SPQuad quadWithWidth:50 height:50 color:0xff0000] atX:15 y:15];
}
////////////////////////////////////////////////////////////////////////////////////
- (id) initWithFile:(NSString*)fileName atX:(float)x y:(float)y {
- return [self initWithShape:[[[[SPImage alloc] initWithContentsOfFile:fileName] autorelease] setPositionX:x y:y]];
+ return [self initWithShape:
+ [[[SPImage alloc] autorelease] initWithContentsOfFile:fileName]
+ atX:x y:y];
}
////////////////////////////////////////////////////////////////////////////////////
-- (id) initWithShape:(SPDisplayObject*)aShape {
- if ((self = [super init])) {
- self.shape = aShape;
+- (id) initWithShape:(SPDisplayObject*)shape atX:(float)x y:(float)y {
+ if ((self = [super initAtX:x y:y])) {
+ float px = self.world.scale;
+ self.shape = [shape setPositionX:x*px y:y*px];
+
+ b2PolygonShape box;
+ box.SetAsBox(shape.width/px, shape.height/px);
+ self.body->CreateFixture(&box, 5.0f);
+
[self.game addEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];
}
return self;
SPTouch* touch = [[event touchesWithTarget:self.shape.parent] anyObject];
if (touch) {
SPPoint* touchPosition = [touch locationInSpace:self.shape.parent];
- self.shape.x = touchPosition.x - self.shape.width / 2.0f;
- self.shape.y = touchPosition.y - self.shape.height / 2.0f;
+ float x = self.shape.x = touchPosition.x - self.shape.width / 2.0f;
+ float y = self.shape.y = touchPosition.y - self.shape.height / 2.0f;
+
+ float px = self.world.scale;
+ self.body->SetTransform(b2Vec2(x/px, y/px), self.body->GetAngle());
}
}
+
@protocol QQUnitDelegate
--(void)updateWithTick:(float)time;
+
+- (void) updateWithTick:(float)time;
+
@end
--- /dev/null
+#include <Box2D/Box2D.h>
+
+/*
+b2BodyDef defaults:
+ userData = NULL;
+ type = b2_staticBody;
+
+ position.Set(0.0f, 0.0f);
+ angle = 0.0f;
+
+ linearVelocity.Set(0.0f, 0.0f);
+ angularVelocity = 0.0f;
+
+ linearDamping = 0.0f;
+ angularDamping = 0.0f;
+ inertiaScale = 1.0f;
+
+ allowSleep = true;
+ fixedRotation = false;
+ bullet = false;
+
+ awake = true;
+ active = true;
+*/
+
+@interface QQBody : NSObject {
+
+}
+
+@end
--- /dev/null
+#import "QQBody.h"
+
+// private interface
+@interface QQBody ()
+
+@end
+
+
+/*
+b2BodyDef defaults:
+ userData = NULL;
+ type = b2_staticBody;
+
+ position.Set(0.0f, 0.0f);
+ angle = 0.0f;
+
+ linearVelocity.Set(0.0f, 0.0f);
+ angularVelocity = 0.0f;
+
+ linearDamping = 0.0f;
+ angularDamping = 0.0f;
+ inertiaScale = 1.0f;
+
+ allowSleep = true;
+ fixedRotation = false;
+ bullet = false;
+
+ awake = true;
+ active = true;
+*/
+
+@implementation QQBody
+
+- (id) init {
+ if ((self = [super init])){
+
+ }
+ return self;
+}
+
+
+@end
+
\ No newline at end of file
@interface QQWorld : NSObject {
@private
- float _timestep;
- int _velocityIterations;
- int _positionIterations;
-
- b2World* _world;
QQGLESDebugDraw debugDraw;
+ b2World* _world;
+ NSMutableArray* _walls;
+
+ float _timestep;
+ int _velocityIterations;
+ int _positionIterations;
+ BOOL _drawDebugLayer;
+ float _scale;
}
@property (nonatomic, readonly) b2World* world;
+
@property (nonatomic, assign) float timestep;
-@property (nonatomic, assign) int velocityIterations;
-@property (nonatomic, assign) int positionIterations;
+@property (nonatomic, assign) int velocityIterations;
+@property (nonatomic, assign) int positionIterations;
+@property (nonatomic, assign) BOOL drawDebugLayer;
+@property (nonatomic, assign) float scale; // pixels per meter
- (QQWorld*) init;
-- (QQWorld*) initWithTimestep:(float)hz;
-- (QQWorld*) initWithTimestep:(float)hz gravityX:(float)x y:(float)y;
- (void) setGravityX:(float)x y:(float)y;
-#import "QQWorld.h"
+#import <UIKit/UIKit.h>
+#include <Box2D/Box2D.h>
+#import "QQWorld.h"
@implementation QQWorld
-@synthesize world = _world;
-@synthesize timestep = _timestep;
+@synthesize timestep = _timestep;
@synthesize velocityIterations = _velocityIterations;
@synthesize positionIterations = _positionIterations;
+@synthesize drawDebugLayer = _drawDebugLayer;
+@synthesize scale = _scale;
+@synthesize world = _world;
-- (QQWorld*) init {
- return [self initWithTimestep:60.0f];
-}
-
-- (QQWorld*) initWithTimestep:(float)hz {
- return [self initWithTimestep:hz gravityX:0.0f y:0.0f];
-}
-- (QQWorld*) initWithTimestep:(float)hz gravityX:(float)x y:(float)y {
+- (QQWorld*) init {
if ((self = [super init])) {
- _timestep = 1.0f/hz;
+ _timestep = 1.0f/60.0f;
_velocityIterations = 10;
_positionIterations = 10;
+ _drawDebugLayer = NO;
+ _scale = 5.0f;
- b2Vec2 gravity = b2Vec2(x,y);
- bool doSleep = true;
- _world = new b2World(gravity, doSleep);
+ _world = new b2World(b2Vec2(0.0f, 0.0f), true); // b2World(gravity, doSleep)
// _world->SetDebugDraw(&debugDraw);
+
+ _walls = [[NSMutableArray alloc] initWithCapacity:4];
+
+ CGSize screen = [UIScreen mainScreen].bounds.size;
+ float w = screen.width;
+ float h = screen.height;
+ b2Vec2 origin = b2Vec2(0.0f, 0.0f);
+ b2Vec2 horz = b2Vec2(w, 0.0f);
+ b2Vec2 vert = b2Vec2(0.0f, h);
+
+ b2Vec2 positions[] = {
+ b2Vec2(w*0.5f, h*0.0f),
+ b2Vec2(w*0.5f, h*1.0f),
+ b2Vec2(w*0.0f, h*0.5f),
+ b2Vec2(w*1.0f, h*0.5f)
+ };
+ b2Vec2 bounds[] = {horz, horz, vert, vert};
+
+ for (int i=0; i<4; ++i) {
+ b2BodyDef bd;
+ bd.position = positions[i];
+ b2Body* wall = _world->CreateBody(&bd);
+
+ b2PolygonShape shape;
+ shape.SetAsEdge(origin, bounds[i]);
+
+ wall->CreateFixture(&shape, 0.0f);
+ // [_walls addObject:wall]; // doh, b2Body so does not descend from NSObject, and so is not an (id)
+ }
+
}
return self;
}
- (void) dealloc {
- // TODO: track and release all known bodies etc
- delete _world;
+ // for (b2Body* wall in _walls) {
+ // _world->DestroyBody(wall);
+ // }
+ [_walls removeAllObjects];
+ [_walls release];
+ delete _world; // b2World destructor destroys all physics entities and releases all heap memory.
[super dealloc];
}
- (void) step {
_world->Step(self.timestep, self.velocityIterations, self.positionIterations);
- // _world->DrawDebugData();
+ #ifdef DEBUG
+ if (_drawDebugLayer) _world->DrawDebugData();
+ #endif
}
- (void) setGravityX:(float)x y:(float)y {