My questions thread

(12)
If you’re getting a RequestDenied, it follows you must be using a defaultTransitions table to list the allowed transitions (unless you’re using your own custom filter func). Simply add a transition from ‘Jumping’ -> ‘Jumping’ to this table. Or, if you do have your own custom filter func, make it return None in response to the request for ‘Jumping’ -> ‘Jumping’.

(13)
I don’t know specifically where the slowdown is occurring; you might find it valuable to run your setup code in the Python profiler to look for obvious bottlenecks. My suspicion is that the cost is in the setup time for each of the DirectRadioButtons, though. In general, one of Python’s biggest weaknesses is the time it takes to call constructors on complex objects, and the DirectGui objects are fairly complex. To reduce this time, you might need to go with a more custom solution; rather than creating a bunch of DirectRadioButtons, just create standalone geometry and then listen for mouse clicks and handle them explicitly by looking at the (x, y) click position and figuring out which star system was clicked.

David

(12)
I kinda solved this by simply removing the FSM from the player, since it simply isn’t one.
I guess a solution is to simply use a “isEnabled” attribute that at class creation is 0, during Jumping is 1, and at the end of Jumping goes back to 0. I use that pattern now in other places.

(13)
You’re right, it is simply the setup time of the DirectRadioButtons. I brought the time down from 5-6 seconds to 3-4 seconds by using only DirectButtons. I guess I will have to come back to that later.
I know how to create a bunch of geoms via CardMaker for example. I know how to get the x,y coords of the mouse on click. But to be honest I don’t know how to connect the two.

(14)
I’ve got a problem it seems with class attributes.

I’ve got a HUD that is in it’s own class in a file, like:

class MCHUD(DirectObject):
	def __init__(self):
		pass
	
	# the build function only sets background images and empty text fields
	def buildHUD(self):
		self.hudpictures = []
		self.hudtexts = []
		
		self.targetBackgrd = OnscreenImage( bla )
		self.targetBackgrd.reparentTo(base.a2dTopRight)
		self.hudpictures.append(self.targetBackgrd)

		(...)

	def deleteHUD(self):
		for hudpicture in self.hudpictures:
			hudpicture.destroy()
		self.hudpictures = []
		for hudtext in self.hudtexts:
			hudtext.destroy()
		self.hudtexts = []

At start of the game or when I jump from system to system the Environment AKA World class gets (re-)created. This is where the HUD initially becomes visible:

from mchud import MCHUD	

class MCEnvironment(DirectObject): # AKA the World class
	def __init__(self, ..., ...):

		self.hud = MCHUD()
		self.hud.buildHUD()
		# now fill the empty fields with current data
		self.FreeCargoSpace = int(self.mcdbships.ShipList[self.PlayerShipIdentifier][8])
		self.hud.incargoText.setText('In Hold: '+str(sum(self.CargoContent.values()))+"t ("+str(self.CargoContent.keys())+")")
		self.hud.creditsText.setText('Credits: '+str(self.PlayerCredits))
		self.hud.freecargoText.setText('Free: '+str(self.FreeCargoSpace - sum(self.CargoContent.values())))
		self.hud.starsystemText.setText(self.StobIdentifier)
		self.hud.stellarnavGoal.setText("- none selected -")

Now upgrading the HUD content from the Environment works fine. But there are other elements in the game that also update the HUD. For example, the Starmap, where the HUD reflects some button clicks:

from mchud import MCHUD

class MCStarmap(DirectObject):
	def __init__(self, ...):

		self.hud = MCHUD()
		# on some button click
		self.hud.hudtexts[1].setText(chosenStob[0])
		self.hud.stellarnavGoal.setText(chosenStob[0])

Another entity manipulating the HUD is some ingame menus. I think I see what is going on. The HUD class get’s thrown into several namespaces over time.

The init of the HUD is a pass to avoid multiple builds of the HUD when the classes import the HUD class. If I put the setup code from def buildHUD(self) to the init I get three or four HUDs over each other, and only one of them gets changed with setText() commands.
What helped historically is to do something like:

class MCIngameMenu(DirectObject):
	def __init__(self, ..., ...):

		self.hud = MCHUD()
		self.hud.deleteHUD()
		self.hud.buildHUD()
		# now fill the empty fields with current data
		self.hud.incargoText.setText('In Hold: '+str(sum(self.CargoContent.values()))+"t ("+str(self.CargoContent.keys())+")")

If I don’t do something like that, I draw the HUD originally in Environment, then I do self.hud.incargoText.setText() in the Starmap and get “self.hud has no attribute incargoText”.

(14) Why are you creating a new instance of your HUD class every time you want to access it? Don’t you really want to have only one instance, and have all accessors share that same instance?

Each time you call:

self.hud = MCHUD()

that creates a brand new instance of MCHUD. So, do this only once. In other classes that want to access the hud, you can get a pointer to the instance you’ve already created, instead of creating a new one:

self.hud = world.hud

Now that you’re only creating one instance, you can put the initializers back into init(), where they belong.

David

Oh, I thought stuff like that would get me circular imports. But I guess it won’t now that I think about it … thanks!

EDIT … circular imports, like:

player.py

from environment import Environment
from map import Map
class Player() # entry into the game
    self.environment = Environment() # initial init of the environment
    self.map = Map()
    self.hud = self.environment.hud
    self.hud.element.setText()

environment.py

from ingamemenu import IngameMenu
from hud import HUD
class Environment() # called from Player
    self.ingamemenu = IngameMenu()
    self.hud = HUD()
    self.hud.element.setText()

ingamemenu.py

from environment import Environment
class IngameMenu() # called from Environment
    self.environment = Environment()
    self.hud = self.environment.hud
    self.hud.element.setText()

map.py

from environment import Environment
class Map() # called from Player
    self.environment = Environment()
    self.hud = self.environment.hud
    self.hud.element.setText()

EDIT 2:
On the IRC channel Hypnos suggested using a globals file, I assume like:

### player.py

from globals import Globals
self.globals = Globals()
self.hud = self.globals.hud

	self.hud.element.setText()


### environment.py

from globals import Globals
self.globals = Globals()
self.hud = self.globals.hud

	self.hud.element.setText()


### ingamemenu.py

from globals import Globals
self.globals = Globals()
self.hud = self.globals.hud

	self.hud.element.setText()

### map.py

from globals import Globals
self.globals = Globals()
self.hud = self.globals.hud

	self.hud.element.setText()

### globals.py

from hud import HUD
self.hud = HUD()


### hud.py

class HUD():
	def __init__():
		self.buildHUD()

But I often read globals are bad.

rather something like this: :slight_smile:

### player.py
import GLOBALS
### GLOBALS.py
# i'm empty

### player.py
class Player:
  def __init__(self):
    self.x = 1
GLOBALS.player = Player()

### main.py

import GLOBALS
import player

class Main():
  def __init__(self):
    print GLOBALS.player.x
    GLOBALS.test = player.Player()
m = Main()

(14)
Following Hypnos’ advice on the IRC channel this is pretty much solved via the globals thing, even though I don’t like it.

You don’t need to use a global module if you don’t like it. If you use the “import environment” syntax instead of “from environment import Environment”, you will be largely safe from circular imports.

BTW, it looks like you are creating multiple different instances of your Environment class in the pseudo-code above. I suspect you only want one Environment instance instead of several. Fixing this will also remove the need to import Environment everywhere, so this may also solve your circular-import problem.

David

(15)
Got a new problem with class instances and intervals it seems.

I’ve got a base class for spaceships now, it has some attributes (which are present), one of them being the model node “.ShipModel”. Attached to the model node are: nodes for exhaust graphics; nodes for running lights; nodes for rotating parts.The player ship is based on that as well as some NPC ships that I throw in every some seconds. The exhaust appears only when I hit the UP button, so the NPC ships don’t have that effect, they have the nodes though for later use. So currently on the NPC ships you got two effect: blinking lights and rotating parts, while on the player ship you got those two + the exhaust when UP is pressed.

Adding the player:

def addPlayerShip(self):
    self.PlayerShip = MCShipBase(self.PlayerShipIdentifier)

Adding NPC ships:

def addSceneryShipByJumpIn(self):
    self.addShipObject(MCShipBase("Commando Ship"))
    # this adds it to a dictionary

The ship base class, just the important parts:

class MCShipBase(object): # object
    def __init__(self, ShipIdentifier): # ShipIdentifier
        self.ship_exhaustlist = [] # holds the
        self.ship_runlightlist = [] # exhaust and runlight nodes

    def loadShipModel(self):
        self.ShipModel = loader.loadModel("../mcships/bla")
        self.ShipModel.reparentTo(render)
        return self.ShipModel

### rotating parts

    def buildShipModelRot(self):
        self.shiprotmodel = loader.loadModel("../mcships/")
        self.shiprotmodel.reparentTo(self.ShipModel)
        self.shiprotint = self.shiprotmodel.hprInterval(bla)
        self.shiprotint.loop()

### runlights

    def buildShipRunlights(self):
        for i in range(bla):
        self.ship_runlightlist.append(self.createShipRunlights(self.ShipIdentifier, i))

        self.ship_runlights_seq = Sequence(Func(self.showShipRunlights), Wait(0.05), Func(self.hideShipRunlights), Wait(bla), name="spaceship_runlight_seq")
        self.ship_runlights_seq.loop()

    def createShipRunlights(self, ShipIdentifier, indexno):
        runlgt = CardMaker("runlight")
        runlight = runlgt.generate()
        self.runlgtnode = self.ShipModel.attachNewNode(runlight)
        self.runlgtnode.reparentTo(self.ShipModel)

    def showShipRunlights(self):
        for runninglight in self.ship_runlightlist:
            runninglight.show()

The problem here is that as soon as I throw in a new instance of the class (here: a new NPC ship), the runlights disappear. If only the player is present and no NPC ships, all continues to work, the exhaust, the runlights and the rotating part (the latter two using intervals). As soon as the first NPC ship comes in, the rotating node on the player ship continues rotating, the exhaust appears when pressing UP, but the runlights disappear. On the NPC ship things rotate and blink … until the next NPC ship comes in, then the runlights go off while the rotation continues. So the runlight effect only appears on the most recent ship.

I thought this comes from losing the class attributes at the creation of a new instance, here: the runlights and/or the intervals. But checking with getChildren() the nodes are still there. I thought the intervals would go, but checking with isPlaying() it says all intervals are still playing.

A screenshot to illustrate things: s5.directupload.net/images/090902/44zact9z.jpg

In the middle the player ship. The satellite dish rotates. Every some seconds a blinking light shows (not in picture). As soon as the NPC ships come in (here: every 5 seconds a new one), the satellite dish keeps rotating, but the blinking lights appear only on the most recent ship, even though the rotation interval continues.

I am using a dictionary to store stuff, but I don’t think that has to do with anything, like: {0: instance adress, 1: instance adress, …}

Then the main loop looks like:

def updateShipModels(self, task):
    for ID in self.ShipObjects.keys():
        self.positionObject(self.ShipObjects[ID].ShipModel)
        return Task.cont

All effects use nodes and reparentTo(bla.ShipModel), all effects use intervals. One thing I notice is that for the rotating part I use the shortcut:

self.shiprotint = self.shiprotmodel.hprInterval( ... ).loop()

For the exhaust I use no interval (since they appear on key press), the runlight effect uses a function sequence. There are no console errors.

Question: what happens if you don’t assign a specific name to your intervals? In that case it will give each interval a unique name.

For legacy purposes, I think we still have the rule that each interval must have a unique name, and that when you start a new interval with the same name as another interval, the previous one is implicitly stopped. Don’t know for sure that’s true, but I seem to remember that; and for that reason we generally give all of our intervals unique names. And I don’t know why your intervals would still report isPlaying() when they have been overridden in this way, but it’s worth a try.

I wouldn’t recommend relying on this rule, by the way, because I’d like to be able to phase it out–it’s a terrible rule.

David

Wow you’re right, it works now, thanks!
What you’re saying sounds familar. In my environment I got blinking lights too, assigned to the space stations. There can be multiple of them, and that caused problems when deleting the environment elements. I had unique names assigned, but I did not derive the stations from a base class, so it caused no problems upon creation. But it did cause problems when I want to delete all environment elements, including those intervals.
The solution was to append all intervals in the environment to a list and then delete the elements of that list.

How is it with those intervals anyway … Does an interval assigned to a node finish when the node is removed? Sometimes it seems to me yes, sometimes not.

Intervals are never finished automatically before they reach their end naturally, even when you remove the node they are operating on. It’s always up to you to explicitly finish your interval if you don’t want it to play any more.

David

I have situations where I have to quit intervals manually that where started with .loop(), otherwise I get a “lost outstanding events in interval” crash when I remove the node this interval applies to. .finish() helps here.
Some intervals I do not finish manually however do not crash the game when I do that.

(16)
I’d like to display a selection around objects, now I got a scaling problem. Looks like this:

When I just use node.setScale(target.getScale) sometimes the frame is too large, on other objects too small. I know people had similar problems before, I’ve seen those threads, but they did not really help. What I want is that the selection model is lets say 10% larger then the target. getTightBounds() does not help since there is nothing like node.setSize(), only .setScale().

Stuff like
node.setScale(target.getScale()*1.1)
or
node.setScale(target, target.getScale()*1.1)
does not work.

What do I have to do? Relate the output from getTightBounds() to the target scale? But how?

(17)
More complex problem.

In the starmap I got points in certain angles to each other, representing star systems. The ship jumps from point to point. The ship 1) turns into the right direction, 2) moves from x/y 1 to x/y 2, 3) moves from x/y 2 to x/y 3. x/y 1 is the starting point (player.getPos), x/y 2 is that point + a distance at a certain angle. x/y 3 is a point at more a distance at the same angle.
All those steps are there right now, using intervals, the ship moves from point to point, just with the wrong coordinates. My coord system is: horizontal -x to +x, vertical -y bottom +y top. Just like on the 2D starmap. I have to be able to relate the look of the 2D map to the 3D game, I got that now and don’t want to change it.

Get the right angle to turn to via atan2, player.setH = angle+90. I know about fitDestAngle2Src() but I don’t see where it would help here.

I thought the formula for points 1, 2 and 3 was:

x2 = playerstartx + 6*cos(entrydeg)
y2 = playerstarty + 6*sin(entrydeg)
x3 = x2 + 24*cos(entrydeg)
y3 = y2 + 24*sin(entrydeg)

When I jump in AI ships I use a similar formula, that’s why I don’t understand why things don’t work:

angle = randrange(-180,180,40) # also works with (0,360,40)
x1 = 24*cos(angle)
y1 = 24*sin(angle)
x2 = 12*cos(angle)
y2 = 12*sin(angle)

entryheading = rad2Deg(atan2(y1,x1))-90 # -90
ShipObject.ShipModel.setH(entryheading)

The planet is in the center, the ships come in from random directions around the planet. The ships jump in from a far away point to a closer point.

(17)
Solved. Got the basic math all wrong.

(18 )
Using MeshDrawer, crash is gone but I still can’t see any stars. The goal as a first step is to draw little white dots around the player. Doesnt need a texture, color would suffice. I tried varying the bin, Depth stuff etc, but to no avail.

def buildStars(self):
		stargenerator = MeshDrawer()
		stargenerator.setBudget(100)
		stargenerator.setPlateSize(1)
		stargeneratorNode = stargenerator.getRoot()
		stargeneratorNode.reparentTo(render)
		stargeneratorNode.setPos(0,0,0)
		stargeneratorNode.setBin("background", 11)
#		stargeneratorNode.setBin("starfield", 11)
#		stargeneratorNode.setBin("fixed", 0)
		stargeneratorNode.setDepthWrite(False)
	#	stargeneratorNode.setDepthTest(False)
		stargeneratorNode.setLightOff(True)
		stargeneratorNode.setTwoSided(True)
		stargeneratorNode.setTexture(loader.loadTexture("../mceffects/white4x4.png"))
		stargeneratorNode.setColor(1,1,1,1)		
		
		for i in range(50):
			randomwhite = sample((0.8,0.9,1.0),1)[0] # randrange(0.8,1.0,0.1)
			stardata = [self.randomStarVec(),0,1,Vec4(randomwhite,randomwhite,randomwhite,1)]
			# billboard: pos, frame, size, color
			self.StarList.append(stardata)
		print self.StarList
		stargenerator.begin(base.cam,render)
		for pos,frame,size,color in self.StarList:
			stargenerator.billboard(pos,frame,size,color)
#		stargenerator.end()

	def randomStarVec(self):
		return Vec3(randint(0,60)-30,randint(0,60)-30,sample((-16,-12,8),1)[0])

(16)
More or less solved, thanks to the help of ThomasEgi. The code:

min, max = self.ShipObjects[self.selectedTargetID].ShipModel.getTightBounds() # 1
size=max-min
sizelength = size.length()
relativescalex = sizelength * self.ShipObjects[self.selectedTargetID].ShipModel.getSx(render)
relativescaley = sizelength * self.ShipObjects[self.selectedTargetID].ShipModel.getSy(render)


self.targetselnode.reparentTo(self.ShipObjects[self.selectedTargetID].ShipModel) # 2
self.targetselnode.setScale(render, (relativescalex,relativescaley,1)) # 3

where self.targetselnode is the selection frame model. getSx and getSy are specific to my problem, since Z is always 1. Note that you have to keep to a specific order: 1) getTightBounds 2) reparent 3) setScale.
Otherwise the seletion frame will grow with repeated selections, we did not really find out why.
In the end it did not solve the problem since the ship models can be quite oddly shaped, like length being shorter than width. Then getTightBounds() does not really help. BoundingSphere is my next approach, but that’s a class and not a convenient no-brainer function call.

(18 ) , you need to call it every frame in order to make meshdrawer work. It makes no scene to draw billboard once because as soon as you move you cant see them any more. It appears like you cant use the base.cam on the first init of the game. If you want to generate a static mesh (i don’t recommend with meshdrawer) you would have to do it on frame 1 not frame 0.

This sample works i think, how you wanted it too.

import direct.directbase.DirectStart
from pandac.PandaModules import *
from random import *

class a:

    StarList = [] # <-- member var with camel case???

    def buildStars(self):
          stargenerator = MeshDrawer()
          stargenerator.setBudget(100)
          #stargenerator.setPlateSize(1) <-- not using this in 1.7.0
          stargeneratorNode = stargenerator.getRoot()
          stargeneratorNode.reparentTo(render)
          stargeneratorNode.setPos(0,0,0)
          #stargeneratorNode.setBin("background", 11)
    #      stargeneratorNode.setBin("starfield", 11)
    #      stargeneratorNode.setBin("fixed", 0)
#          stargeneratorNode.setDepthWrite(False)
       #   stargeneratorNode.setDepthTest(False)
 #         stargeneratorNode.setLightOff(True)
          stargeneratorNode.setTwoSided(True)
          #stargeneratorNode.setTexture(loader.loadTexture("../mceffects/white4x4.png"))  <-- not loading a white texture - whats the point?
          stargeneratorNode.setColor(1,1,1,1)      
          
          for i in range(50):
             randomwhite = sample((0.8,0.9,1.0),1)[0] # randrange(0.8,1.0,0.1)
             stardata = [self.randomStarVec(),0,1,Vec4(randomwhite,randomwhite,randomwhite,1)]
             # billboard: pos, frame, size, color
             self.StarList.append(stardata)
          print self.StarList
                   
          def drawtask(taks):
              """ this is called every frame to regen the mesh """
              # you must call begin
              stargenerator.begin(base.cam,render)
              # do drawing between two calls
              for pos,frame,size,color in self.StarList:
                 print pos,Vec4(0,0,1,1),10,Vec4(1,1,1,1)
                 stargenerator.billboard(pos,Vec4(0,0,1,1),size,Vec4(1,1,1,1))
              # you must call end   
              stargenerator.end()
              stargeneratorNode.analyze()
              stargeneratorNode.ls()
              return taks.cont
         
          # add the draw task to be drawn every frame
          taskMgr.add(drawtask, "draw task")
                              
         

    def randomStarVec(self):
      return Vec3(randint(0,60)-30,randint(0,60)-30,sample((-16,-12,8),1)[0])


loader.loadModel("teapot").reparentTo(render)
a().buildStars()

run()

(18 )
Alright it works now. Right now in this early stage the stars spawn in a fixed area around (0,0). The problem is that if the player ship moves out of a certain area all stars disappear. It happens when the invisible center node at (0,0), that the stars are reparented to, moves out of the FOV.

Like culling or whatever. How can I disable this?

What is the relation between setBudget() and the number of elements in the coordinates list (for i in range(x): bla)?
setBudget has an influence, i.e. 50 vs 100 vs 200 but not beyond the length of the coord list (coord list x2 = budget). Is that it?

Budget is number of triangles. For each quad you draw thats 2 triangles.

(19)
Got a recursion problem it seems.
In my world.py:

self.addShipObject(MCShipBase(random, 1, randomangle, 1))
# loops

In ship.py where the MCShipBase is:

class MCShipBase(object): # object
	def __init__(self, param, param, param, AIType):
		self.attribute = 30
		self.model = NodePath
		# cheap adapter
		if AIType == 1:
			self.ai = FSM1()
		elif AIType == 2:
			self.ai = FSM2()
		
class FSM1(FSM.FSM, MCShipBase):
	def __init__(self, ShipIdentifier, AddMode, EntryFromAngle, AIType):
		FSM.FSM.__init__(self, "WimpyTraderFSM")
		MCShipBase.__init__(self, ShipIdentifier, AddMode, EntryFromAngle, AIType)
		self.defaultTransitions = {
			'ApproachPlanet' : [ 'StayAtPlanet', 'LeavePlanet' ],
			'StayAtPlanet' : [ 'LeavePlanet' ],
			}
			
		print self.attribute # should be 30
		
		# FSM logic, manipulates self.model / NodePath

The FSM needs access to the Base attributes and maybe methods, so it calls it’s init. This is the manual by the book, so it should work. I tried to do a cheap adapter with the if AIType stuff, I’m not really aiming at something more complicated. I see that in world.py I could also instanciated the FSM, so it automatically gets the attributes from the Base class - but then where would I chose which FSM to use (the adapter, different ship types have different FSMs)? I use a similar construction in some place else, just without the inheritence of the Base in the FSM, so I wonder why it doesnt work.

(16)
Solved finally. Code:

self.targetselnode = loader.loadModel("../bla")

min, max = self.ShipObjects[self.selectedTargetID].ShipModel.getTightBounds() # 1
size=max-min
sizelength = size.length() * 0.4 # experiment here, 0.5 is correct
self.targetselnode.reparentTo(some.target.node) # 2
self.targetselnode.setScale(render, (sizelength,sizelength,1)) # make relative to render

Only a small change, just dont the scale to the scale of the model (which are useless in my case since the models had different sizes in the modelling app).

(19)
Solved by not using class inheritence. Until now I would only inherit to gain the attributes from another class - I did that now by passing a class reference (self) to the subclass.

(20)
Why does this not work?
pastebin.com/92fJgjcZ
Same code:

turnaroundleftseq = Sequence( ... Func(taskMgr.add, self.counteraccelToSpeed, "counteracceltask", extraArgs=[0.2], appendTask=True, uponDeath=lambda task: self.turnAlmostAround())).start()

def counteraccelToSpeed(self, targetspeed, task):
	if mcglobals.environment.getVelocity(self.ShipBase.ShipModel).lengthSquared() >= targetspeed:
		self.ShipBase.setKey("accelerate", 1)
		return task.cont
	self.ShipBase.setKey("accelerate", 0)
	return task.done

>>> TypeError: counteraccelToSpeed() takes exactly 3 arguments (2 given)

Same pattern works in a dozen other places.

Testing with *targetspeed, **task and print I get:

targetspeed (PythonTask counteracceltask,)
task {}

return task.done
AttributeError: ‘dict’ object has no attribute ‘done’

So that’s it but I don’t see why.

(21)
Not really a problem, just a note after hours of frustration. The FSMs got three attributes you can work with:
.newState
.state and
.oldState
Requesting one that doesnt exist gives you a hard crash. But I find it kinda hard to guess what is currently there and which is not. For example, I had a function inside an enterState function, the former then calling .state -> crash, even though the state should already be there, since we entered it.

Oh and the filter stuff does totally not work for me, after intensive study of SampleFSM.py, the manual and some forum threads. I was quite enthusiastic about this, sounded like what I needed.

For example:

def filterStayAtPlanet(self, request):
	if request == "waitatplanet":
		return "StayAtPlanet"
	else:
		print "waitatplanet denied"
		return None

I could do myfsm.request(“waitatplanet”) and I never got a single state change, both from the inside and the outside. Just do .request(“StayAtPlanet”) and of course it’s there.

By the way, in the SampleFSM.py -> ToonEyes sample you can see two filters defining a ‘unblick’ request (filterOpen and filterClosed). How, with the request method .request(“unblink”), does the FSM know which state to query!?