This is Actor.m in view mode; [Download] [Up]
#import "Actor.h" #import "ActorMgr.h" #import "DisplayManager.h" #import "CacheManager.h" #import "collisions.h" #import <math.h> extern BOOL coalesce(NXRect *p1, NXRect *p2); float gx, gy; @implementation Actor // Hack Alert! I need 1 class variable for each subclass of Actor. // Since objc doesn't support class variables, I just appropriate the // use of the version to store a List... This is really gross and has no // place in production code; it could break archiving (which I don't do...) // If you just _had_ to do this, you could make the version point to an // allocated struct that contained the version and class variables, and // override +setVersion to access it (but you didn't hear it from me!) + initialize { [self setVersion: (int)[[List alloc] init]]; // Ack! return self; } // Each Actor subclass keeps a list of its instances; instances are never freed // since they come and go frequently and allocating them is too expensive. // Instead, the actor manager walks this list and reuses available actors. // Plus, since they're never killed, they can wait tables on the side. + instanceList { return (id)[self version]; // Ack! } - init { [super init]; actorType = (int)[self class]; [[[self class] instanceList] addObjectIfAbsent:self]; return self; } #define S_DEBUG 0 // note that this method is _not_ the object's designated initializer; // this method may be called more than once (by activate) - reinitWithImage:(const char *)imageName frameSize:(NXSize *) size numFrames:(int)frames shape: (COLLISION_SHAPE)shape alliance: (ALLIANCE)al radius: (float) r buffered: (BOOL) b x: (float)xp y: (float)yp theta: (float) thta vel: (float) v interval: (unsigned) time distToCorner: (NXSize *)d2c { image = [self findImageNamed:imageName]; frame = 0; interval = time; changeTime = timeInMS+time; numFrames = frames; frameSize = *size; theta = thta; vel = v; xv = vel * -sin(theta); yv = vel * cos(theta); collisionShape = shape; alliance = al; radius = r; buffered = b; drawRect.size = *size; collisionRect.size = *size; distToCorner = *d2c; [self moveTo:xp :yp]; complexShapePtr = NULL; eraseRect.size.width = 0; return self; } // The ActorManager means objects don't necessarily get created and freed; // instead they are created once and then activated and retired. - activate:sender :(int)tag { employed = YES; scoreTaker = sender; return self; } - retire { // [self erase]; if (buffered && (eraseRect.size.width > 0)) [cacheMgr displayRect:&eraseRect]; employed = NO; return self; } - erase { id mgr = (buffered ? cacheMgr : displayMgr); [mgr erase:&eraseRect]; if (!buffered) eraseRect.size.width = 0; return self; } - positionChanged { if (timeInMS > changeTime) { changeTime = timeInMS + interval; if (++frame >= numFrames) frame = 0; } return self; } - calcDxDy:(NXPoint *)dp { dp->x = timeScale * xv; dp->y = timeScale * yv; return self; } - calcDrawRect { drawRect.origin.x = floor(x - gx - distToCorner.width + xOffset); drawRect.origin.y = floor(y - gy - distToCorner.height + yOffset); return self; } - moveBy:(float)dx :(float)dy { x += dx; collisionRect.origin.x += dx; y += dy; collisionRect.origin.y += dy; // calculate offset into view [self calcDrawRect]; return self; } - moveTo:(float)newx :(float)newy { x = newx; y = newy; collisionRect.origin.x = x - distToCorner.width; collisionRect.origin.y = y - distToCorner.height; [self calcDrawRect]; return self; } - setXvYv:(float)xvel :(float)yvel sync:(BOOL)sync { xv = xvel; yv = yvel; if (sync) { theta = atan2(yvel, xvel); vel = sqrt(xv*xv + yv*yv); } return self; } - setVel:(float)newVel theta:(float)newTheta sync:(BOOL)sync { vel = newVel; theta = newTheta; if (sync) { xv = vel * -sin(theta); yv = vel * cos(theta); } return self; } - setVel:(float)newVel { vel = newVel; return self; } - setTheta:(float)newTheta { theta = newTheta; return self; } - oneStep { NXPoint dXdY; if (intersectsRect(&screenRect, &eraseRect)) { [self erase]; } [self calcDxDy: &dXdY]; [self moveBy:dXdY.x :dXdY.y]; complexShapePtr = NULL; [self positionChanged]; return self; } - scheduleDrawing { id mgr = (buffered ? cacheMgr : displayMgr); if (employed && intersectsRect(&screenRect, &drawRect)) { [mgr draw:self]; if (buffered) { [self addFlushRects]; } // eraseRect = drawRect; } else if ((eraseRect.size.width > 0) && buffered) { // we have an erasure region to flush to the screen [cacheMgr displayRect:&eraseRect]; eraseRect.size.width = 0; } return self; } - draw { NXRect src; src.origin.x = (frame & 3) * frameSize.width; src.origin.y = (frame >> 2) * frameSize.height; src.size = drawRect.size = frameSize; [image composite:NX_SOVER fromRect:&src toPoint:&drawRect.origin]; eraseRect = drawRect; #if S_DEBUG { NXRect t = collisionRect; t.origin.x = floor(collisionRect.origin.x - gx + xOffset); t.origin.y = floor(collisionRect.origin.y - gy + yOffset); PSsetrgbcolor(1,0,0); NXFrameRect(&t); PSnewpath(); PSsetrgbcolor(0,1,0); PSarc(floor(x - gx + xOffset), floor(y - gy + yOffset), radius-1, 0.0, 360.0); PSclosepath(); PSstroke(); } #endif return self; } // an actor will be sent a tile message when it is time to draw something // in the virgin buffered background. This is good for static images so you // only draw them once. Most actors move and thus shouldn't draw anything here. // focus is locked on the virgin buffer when this is called - tile { return self; } // both actors that have collided will be sent a doYouHurt: message to determine if the // other will be sent a performCollisionWith: message. You could use this opportunity to // store the other's pre-collision vector, if you care about it. - (BOOL) doYouHurt:sender { return YES; } - performCollisionWith:(Actor *) dude { if (pointValue) [dude addToScore:pointValue for:self gen:0]; [actorMgr destroyActor:self]; return nil; } - (BOOL) wrapAtDistance:(float)distx :(float)disty { float dx = 0, dy = 0; BOOL didWrap = NO; // warp things around as necessary... if (x > gx + distx) { dx = -2*distx; didWrap = YES; } else if (x < gx - distx) { dx = 2*distx; didWrap = YES; } if (y > gy + disty) { dy = -2*disty; didWrap = YES; } else if (y < gy - disty) { dy = 2*disty; didWrap = YES; } if (didWrap) [self moveBy:dx :dy]; return didWrap; } - (BOOL) bounceAtDistance:(float)distx :(float)disty { float dx=0,dy=0; BOOL didBounce = NO; if (x > gx + distx) { dx = (gx + distx) - x; if (xv > 0) xv = 0 - xv; didBounce = YES; } else if (x < gx - distx) { dx = (gx - distx) - x; if (xv < 0) xv = 0 - xv; didBounce = YES; } if (y > gy + disty) { dy = (gy + disty) - y; if (yv > 0) yv = 0 - yv; didBounce = YES; } else if (y < gy - disty) { dy = (gy - disty) - y; if (yv < 0) yv = 0 - yv; didBounce = YES; } if (didBounce) [self moveBy:dx :dy]; return didBounce; } #define xabs(x) (x >= 0 ? x : 0-x) extern XXLine *gln2; // this is a really quick and dirty hack that hopefully is good // enough to implement a believable bounce off of a nearly // stationary rectangular object // (should be more accurate and probably part of a better mechanism // for bouncing 2 moving arbitrary shape Actors off each other) - (int) bounceOff:(Actor *)dude { XXLine vector[3], line[2], t; NXPoint pts[3]; float dx, dy; int i, ret=0; XXLine *horozLn = &line[0]; line[0].x1 = dude->collisionRect.origin.x; line[0].x2 = dude->collisionRect.origin.x + dude->collisionRect.size.width; line[1].y1 = dude->collisionRect.origin.y; line[1].y2 = dude->collisionRect.origin.y + dude->collisionRect.size.height; if (yv > 0) { line[0].y1 = line[0].y2 = dude->collisionRect.origin.y; } else { line[0].y1 = line[0].y2 = dude->collisionRect.origin.y + dude->collisionRect.size.height; } if (xv > 0) { line[1].x1 = line[1].x2 = dude->collisionRect.origin.x; } else { line[1].x1 = line[1].x2 = dude->collisionRect.origin.x + dude->collisionRect.size.width; } if (xabs(xv) > xabs(yv)) // then favor horozontal collisions { t=line[0]; line[0]=line[1]; line[1]=t; horozLn = &line[1]; } dx = xv * 1024.; dy = yv * 1024.; pts[0].x = x; pts[0].y = y; pts[1].x = x - distToCorner.width; pts[2].x = x + distToCorner.width; if ((xv*yv) > 0.0) { pts[1].y = y + distToCorner.height; pts[2].y = y - distToCorner.height; } else { pts[1].y = y - distToCorner.height; pts[2].y = y + distToCorner.height; } for (i=0; i<3; i++) { vector[i].x1 = pts[i].x - dx; vector[i].x2 = pts[i].x + dx; vector[i].y1 = pts[i].y - dy; vector[i].y2 = pts[i].y + dy; } if ((linesCollide(vector,3,NO, line, 2, NO)) && (gln2 == horozLn)) { if (yv > 0) // bounce off bottom { [self moveTo:x :horozLn->y1-distToCorner.height]; [self setXvYv:xv :-yv sync:NO]; ret = 1; } else // bounce off top { [self moveTo:x :horozLn->y1+distToCorner.height]; [self setXvYv:xv :-yv sync:NO]; ret = 2; } } else { if (xv > 0) // bounce off left { [self moveTo:horozLn->x1-distToCorner.width :y]; [self setXvYv:-xv :yv sync:NO]; ret = 3; } else // bounce off top { [self moveTo:horozLn->x2+distToCorner.width :y]; [self setXvYv:-xv :yv sync:NO]; ret = 4; } } return ret; } // override in subclasses to lazily construct coords for rotated rects // or rect lists - constructComplexShape { return self; } + findImageNamed:(const char *)name { NXImage *ret_image = [NXImage findImageNamed:name]; if (!ret_image) { char path[256]; if ([[NXBundle bundleForClass:[self class]] getPath:path forResource:name ofType:"tiff"]) { ret_image = [[NXImage allocFromZone:[self zone]] initFromFile:path]; [ret_image setName:name]; } } return ret_image; } - findImageNamed:(const char *)name { return [[self class] findImageNamed:name]; } + cacheImage:(const char *)name { NXImage *timage; timage = [self findImageNamed:name]; if ([timage lockFocus]) [timage unlockFocus]; return self; } - (int)addToScore:(int)val for:dude gen:(int)age { if (age < 2) return [scoreTaker addToScore:val for:self gen:age+1]; else return 0; } - addFlushRects { if (!coalesce(&eraseRect, &drawRect)) { [cacheMgr displayRect:&drawRect]; } if (eraseRect.size.width > 0) [cacheMgr displayRect:&eraseRect]; return self; } - (BOOL) isGroup { return NO; } @end @implementation Object (scoreKeepingMethods) - (int)setScore:(int)val for:dude { return 0; } - (int)addToScore:(int)val for:dude gen:(int)age { return 0; } - (int)score; { return 0; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.