(Pretend that it's moving in some direction you didn't click.)
I just gotta work out the details of the navigation function a bit. But hey, it works in a component based framework.
Let's see if we can't get some particles shooting out the back of there, then some asteroids floating around, some collision detection ... and the navigation working properly, of course.
I'm kinda fried on technical details, but at least I got the trig mostly figured out this evening with little trouble. It's much better than that one program I made with gravity inversions in certain quadrants of this space simulation.
Let's call it milestone 0.9
Due to popular demand, here's some code and a little explanation:
An example of the components in-use
Building the ship:
class Ship(Entity): def __init__(self): Entity.__init__(self) # TEST self.addState( 'position' ) self.getStateByName('position').coords = [100,100] self.getStateByName('position').rotation = 2.0 self.addStateObject( Sprite( 'ship64_redA' ) ) self.addStateObject( ShipHull() ) self.pushAction( 'move' )
Here're the states, which are stuck into things to contain info of various sorts. Entities are handled depending on which states they have and what's in said states.
class Position(State): ''' contains position coordinates, x/y velocity vectors, and rotation ''' name = 'position' def __init__(self, coords=[0.0,0.0],velocity=0.0,angle=0.0,rotation=0.0): self.coords = coords self.velocity = velocity self.rotation = rotation self.angle = angle # flags self.noMove = False#from ..gfx import imageCacheclass Sprite(State): ''' contains a graphic -- be it a texture? ''' name = 'sprite' def __init__(self, imageName = 'testFiller'): #self.image = imageCache.get( 'testFiller' ) # can't get image before imagecache has been init'ed self.imageName = imageName # handle by name? self.color = [1.0,1.0,1.0,1.0] class NavInfo(State): ''' contains navigation infomartion ''' name = 'navinfo' def __init__(self): # list of targets to 'hit', in order # last target on list is 'full stop'; # others are 'follow through' self.targets = [] from ..ship.components import componentCache class ShipHull(State): ''' contains various components and stuff ''' name = 'shiphull' def __init__(self): # TEST: use some defaults self.engine = componentCache.get( 'engTest' )
Here are the actions which can be run on entities so far. "Move" is basically a physics movement updater, "Navigate" is a bit more complex (and is somewhat broken). Actions get called from action stacks held in entities and these repeating actions re-add themselves to the stack, usually. It's downright procedural, actually.
class Move(Action): name = 'move' id = False def __call__(self,parent): if parent.hasState( 'position'): posObj = parent.getState('position') ## rotate ship by rotation posObj.angle += posObj.rotation ## convert from velocity & angle to x/y components # TEST: hope this is correct vectorX = posObj.velocity * math.cos( math.radians( posObj.angle ) ) vectorY = posObj.velocity * math.sin( math.radians( posObj.angle ) ) ## find new position from vectors posObj.coords = add( (vectorX, vectorY), posObj.coords ) ## write new info to parent parent.setStateObject( posObj ) if self.id: parent.pushActionByID( self.id ) else: parent.pushAction( 'move' ) class Navigate(Action): ''' use NavInfo to move parent toward navinfo.targets[1:] ''' name = 'navigate' id = False def __call__(self,parent): # TODO: make sure parent has everything it needs? # eg. position, navinfo, shiphull w/ engines, power, etc. #if parent.hasState( 'navinfo' ): # # do stuff #print 'navigate!' fullstop = False position = parent.getState( STATE_POSITION ) shipHull = parent.getState( STATE_SHIPHULL ) navInfo = parent.getState( STATE_NAVINFO ) delta = timer.getDelta() ## first: Angle # aim self at target as much as possible # 1: get angle between self and target angleDif = angleBetween( position.coords, navInfo.targets[0] ) # 2: if not same, set angle toward target as much as possible if not angleDif == 0.0: turnRate = shipHull.engine['turnRate'] * delta # if dif < turnRate, set angle to target if angleDif <= turnRate: position.angle = angleDif position.rotation = 0.0 elif angleDif < 0.0: # right position.rotation = -turnRate else: # angleDif > 0 # left position.rotation = turnRate ## then: Position distToTarget = distanceBetween( position.coords, navInfo.targets[0] ) # if at target, stop (unless there are more targets!) if distToTarget < 1.0: position.coords = navInfo.targets[0] navInfo.targets = navInfo[:1] if len( navInfo.targets ) == 0: # full stop self.position.velocity = 0.0 fullstop = True else: # go to next target ... when this gets called next pass maxSpeed = shipHull.engine['maxSpeed'] thrust = shipHull.engine['thrust'] # if within decel range of target, do 'manuever speed' if distToTarget < maxSpeed / thrust: maxSpeed = maxSpeed * 0.5 else: # else speed up to maxvelocity! if position.velocity < maxSpeed: position.velocity += thrust if position.velocity > maxSpeed: position.velocity = maxSpeed ## write back parent.setStateObject(navInfo) parent.setStateObject(position) # end if not fullstop: if self.id: parent.pushActionByID( self.id ) return else: parent.pushAction( 'navigate' ) return
And finally, the generic entity class. Basically it holds and handles States, and has an action queue that gets actions pushed to it which get executed upon update():
class Entity: def __init__(self): self.name = False # add this later # self.id = #?? #self.possibleActions = {} # store by id ## remove this dict? self.states = {} # actual state objects self.actionQueue = [] self.children = [] # for sub-entities ## *** Adders def addState(self,stateName): ''' add new state by name w/ default settings ''' #if not self.states.has_key( stateName ): state = stateCache.getObject( stateName ) self.states[ stateCache.getID( stateName ) ] = state def addStateObject(self,stateObject): ''' add new state by passing in the object itself ''' self.states[ stateCache.getID( stateObject.name ) ] = stateObject def setStateObject(self,stateObject): ''' set current state to given state ''' self.states[ stateObject.id ] = stateObject ## *** Removers def removeState(self,stateName): if self.hasState(stateName): del self.states[stateName] ## *** Has boolean checks def hasState(self,nameOrID): stateID = self.stateNameToID( nameOrID ) if self.states.has_key( stateID ): return True else: return False ## *** Getters def getStateByName(self,stateName): stateID = stateCache.getID(stateName) if self.hasState( stateID ): return self.states[ stateID ] else: return False def getStateByID(self,stateID): #print stateID if self.hasState( stateID ): return self.states[ stateID ] else: return False def getState(self,nameOrID): return self.getStateByID( self.stateNameToID( nameOrID ) ) ## *** Name/ID utils def actionNameToID(self,nameOrID): if type( nameOrID ) == IntType: return nameOrID elif type( nameOrID ) == StringType: return actionCache.getID( nameOrID ) else: return False def stateNameToID(self,nameOrID): if type( nameOrID ) == IntType: return nameOrID elif type( nameOrID ) == StringType: return stateCache.getID( nameOrID ) else: return False # *** Action/Update handling def pushAction(self,nameOrID): ''' push action onto self.actionQueue ''' return self.pushActionByID( self.actionNameToID(nameOrID) ) def pushActionByID(self,actionID): ''' push action onto self.actionQueue by ID ''' #print 'push:', actionID self.actionQueue.append( actionID ) ## forget hasAction ? #if self.hasAction( actionID ): # self.actionQueue.append( actionID ) # return True #else: # return False def update(self): #print self.actionQueue # separate current queue from new queue tempQueue = copy( self.actionQueue ) self.actionQueue = [] while( len(tempQueue) > 0 ): actionID = tempQueue.pop(0) #print actionID actionCache.getByID( actionID )( self ) #self.actions[ action ](self) # pass self into function as parent for child in self.children: child.update()