• entries
  • comments
  • views

Milestone 0.9

Sign in to follow this  


It moves, but not as we know it.

(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):
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 imageCache

class 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
# go to next target ... when this gets called next

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 speed up to maxvelocity!
if position.velocity < maxSpeed:
position.velocity += thrust
if position.velocity > maxSpeed:
position.velocity = maxSpeed
## write back

# end
if not fullstop:
if self.id:
parent.pushActionByID( self.id )
parent.pushAction( 'navigate' )

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
return False

## *** Getters
def getStateByName(self,stateName):
stateID = stateCache.getID(stateName)
if self.hasState( stateID ):
return self.states[ stateID ]
return False
def getStateByID(self,stateID):
#print stateID
if self.hasState( stateID ):
return self.states[ stateID ]
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 )
return False
def stateNameToID(self,nameOrID):
if type( nameOrID ) == IntType:
return nameOrID
elif type( nameOrID ) == StringType:
return stateCache.getID( nameOrID )
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
# 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:

Sign in to follow this  


Recommended Comments

I'm always interested to hear about component systems -- how is yours structured? It seems like it might be a good way to start increasing my reusability.

Share this comment

Link to comment
No obligatory dcosborn reference? What is the world coming to?

Incidentally, I tried imagining clicking and the damn thing kept going in the direction I expected.

Share this comment

Link to comment
Oort: An Intrastellar Romance
Nice name. You don't see too many Romance-genre games out there. [wink] Its looking good so far. Kind of like an "Attas: The Second Attempt", but it sounds like with a more finishable scope.

Share this comment

Link to comment
Posted code and such. I'm still working the system out, but I like how it's working so far. And yeah, it's a lot of trouble for getting one ship flying around, but I think it'll really start paying off when all sorts of entities with different attributes and behaviours are added to the world.

Will probably be some major refactorings involved, but 'eh. I'll keep everything posted.

Better start imaginin' a lot harder than that!
And dcosborn showed up, so it's not like the post is completely without him.

Well, it's more of a romance is the classical sense, like uh, a "grand story". Or maybe there can be other romance too, that'd be nice. I mean, I really just wanted a fun window title, but maybe you'll force my hand on this and it'll actually have to have romance. Hmm ...

But yeah, it is a lot like ATTAS 2, but smaller. Maybe if it takes off and we want to go ahead and build something on the scale of ATTAS out of Oort, you'll want to join in? The engine (and openGL parts especially) would no doubt benefit from your sk1llZ.

Well no point in planning that yet, we'll sort it out all in good time. And hey, there's always Procyon: Plan B from outer space!

Share this comment

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now