Best way to organize my project? [SOLVED]

Hello. I’m starting on a new project, and I was wondering what the best way to organize my game is. My last game ended up getting kind of messy, so I want to do it the right way this time. Sorry if this sort of question been asked before, I probably just wasn’t searching the right terms. Most code I see online just shows a few lines to do a specific thing, but doesn’t show how to fit it all together. So I have a few miscellaneous questions…

  1. Firstly, I’ve been trying to get the game to split into multiple files, but I’m ending up with things like circular imports. How do people usually organize their classes and files to avoid this kind of thing? I’ve looked at some examples, but they seem to do everything in one file and from one “World” class. I’ve been making a new class for everything in my game (like the character, blocks, enemies, etc), inheriting from DirectObject. Should I be doing this differently?

Also, should I be using import [name] or from [name] import *? I read that I should stay away from “from [name] import *”, but if I just use import, I’m ending up with things like Block.Block(), which seems redundant.

  1. I used “DirectStart” in my last game, but was looking at the manual, and it says that manually making a “ShowBase” class is a cleaner method. So I modified my “World” object to inherit from “ShowBase,” and it seems to be working, but now I’m a bit confused, because the manual then goes on to show code like this:
self.environ = self.loader.loadModel("models/environment")
self.environ.reparentTo(self.render)

But before, when I was using DirectStart, I could just do “loader.loadModel” to load a model. Now do all my objects need to reference my “World” object and do things like “self.world.loader.loadModel” instead?

  1. For movement, I’ve just been doing something like “self.model.setX(self.model.getX()+0.1)” every frame. This works fine if the framerate is fixed, but in my last project, the framerate would slow down at some points. Then I ended up multiplying all my movement numbers by the framerate, which mostly worked, but was a bit finicky. Is a better way of doing this that I’m unaware of?

  2. What’s the best way to check collisions for things that are block-shaped? I wrote a very basic function to check if two block-shaped things are colliding, which seems to work alright, but should I be somehow using panda3d for collision detection? And if I do use my function, what’s the best way to integrate it into the game? Right now, I just have my character object borrowing a list of all the objects in the level, looping through that, and then checking each one for a collision. But I’m not sure if that’s the right way to do it?

I know this is a long post, but if anyone could clarify some of these things for me, it would be very much appreciated. I just want to make sure I’m doing these things correctly before I get too far. Thanks for your time! :slight_smile:

  1. I use a file called main.py to serve as a starting point for the program; it mainly just contains the menu. It loads another file called Environment.py using “from Environment import *”, which contains the classes for players, objects, etc. Environment loads a scenerio-specific file that has the exact setup/placement for objects in the current scenerio.

  2. I can’t say, I just use DirectStart.

  3. Multiplying every movement by the framerate is probably the best option. You can use globalClock.getDt() to get the time since the last frame. Multiplying every movement by this will result in framerate-independent movement.

  4. Again, I can’t say. I use panda’s collision system, but I use mostly spheres, not blocks.

Hopefully this isn’t too commercial a reply, but I’d recommend picking up the Panda3D beginners guide I wrote for Packt Publishing. It will give you a good idea of how to organize things and cover the basics like using tasks, using the panda3D collision system, intervals, etc. Google “Packt Publishing” to find their website and search for “Panda3D” on it if you’re interested.

As for your specific questions, I can offer some advice. These aren’t all black/white right/wrong topics, different people like to do things their own way, so keep that in mind.

  1. When I was in school, I was taught to divide my program along two different criteria.

The first criteria is by object type. All of the code for a given object or type of object should be in the same place, separate from code for other objects. For example, if you have a bullet object, it’s code should be in a Bullet class. This might include code to move the bullet, code to determine if the bullet has hit something, etc. The bullet should not contain code that causes damage to a character, that should be in the character’s code. Instead the bullet would simply notify the character that it has struck the character, and tell the character how much damage it does. Does that make sense?

The second criteria is by purpose. This one says that code which doesn’t have to do with objects, but has a unified purpose, should be in the same place. One example of this philosophy is creating a single class, called InputManager or some such, that monitors all of the possible forms of input the game might get from the user and converts it into meaningful data that other classes can access and reference. If you wanted to have a single key that is used for up on the menus and also for moving the character forward, you could have an up/forward boolean variable in the InputManager that is set to true when the key is press and false when it isn’t, and both the menu and the character look to that variable. That way if you need to change which key on the keyboard performs those actions, you only need to change it one place, instead of changing it in both the character and the menu.

Another example of organizing code by purpose would be making a single class, called MenuMother or something, that contains all of the functions that you use to generate all the menus in the game. There’s a bunch of advantages to this, such as making it easier to re-use code, always knowing where to look when debugging your menus, etc.

As for having circular imports, that actually isn’t a problem. See this quote from when I asked a similar question after just getting into Panda3D.

What this means is that when you import something once it’s placed in memory, and when you import it a second time the code that’s already in memory is returned for you, the code doesn’t get reloaded and duplicated in memory. So don’t worry about it.

As for using from file import class being a bad thing, I’m not sure where you heard that. I used from file import class for everything, and I recommend you do the same.

  1. I don’t know about inheriting from showbase being a cleaner method for starting Panda3D, but I do know that rdb and drwr, two members of this forum who are involved with the ongoing development of Panda3D, often speak in terms of “when you import DirectStart”. I don’t want to put words in their mouths, so take this with a grain of salt, but that implies to me that using import direct.directbase.DirectStart is totally acceptable. I know that I do things that way.

The reason loader.loadModel is working differently depending on whether you inherit from showbase or import DirectStart is a matter of variable scope, I think. I could be wrong about this, but I’m 90% sure. When you import DirectStart you can use loader.loadModel because the import makes a global variable called “loader” that points to the loader and is accessible anywhere in your program. When you inherit from showbase you have to use self.loader.loadModel or world.loader.loadModel because the global variable “loader” hasn’t been created, and you need to access the loader directly.

Come to think of it, that might be what is meant by a “cleaner” method. It’s missing some extra conveniences. My advice is to import DirectStart, the inheriting method may be cleaner, but I doubt the difference is worth the loss of convenience.

  1. Whenever you are going to do something that you want to be based on the passage of time instead of the frame rate, I’d recommend using code similar to the following:
def myControlTask(self, task):

	dt = globalClock.getDt()
	if( dt > .20):
		return task.cont
	# Gets the amount of time that has passed since the last frame from the global 
	# clock. If the value is too large, there has been a hiccup and the frame will
	# be skipped to prevent things from jumping around suddenly.
	
	walkSpd = 1 #speed in units/second
		
	self.model.setY(self.model, walkSpd * dt)
	# Sets the model to a new Y position that is relative to the old position. The 
	# distance to move the character is calculated by multiplying the character's
	# speed by the the amount of time that has passed.
	
	
	# If a given object or character does many different things each frame that all 
	# rely on time, it's a good idea to place them in their own functions and call 
	# them from this task, passing dt to each of them, like so:
	self.regenerateHealth(dt)
	self.rechargeMana(dt)
	self.checkForIdle(dt)
		
	return task.cont

Alternatively you could use Intervals, but because of their dependance on set destinations or set durations their usefulness can be a bit limited. They’re better for scripted stuff, like an in-engine cinematic or a moving platform that just floats from one spot to another and back every 3 seconds or whatever.

  1. Panda3D’s collision system is very effective, and I recommend you use it. If you do, you have, basically, 2 options on how to create collision objects for your blocks.

You could just export collision objects from a modeler like Maya into the egg format. If you’re dealing with cubes that would give you 6 collision polygons for each cube. You could also create these collision cubes in code using the eggData interface.

Alternatively, you could use 9 collision spheres to fill up the volume of the cube. One big sphere in the middle, and 1 smaller sphere in each of the eight corners.

Both of these methods have their own advantages and disadvantages.

The collision polygons are going to give the most accurate representation of the cube. On the other hand, collision polygons are the most expensive type of collision solid in terms of calculation time. Also, collision polygons aren’t suited for being the objects that bump into stuff, they’re much better suited for being the objects that get bumped into. (Meaning, they make good “into” objects but poor “from” objects, in Panda3D terms.)

The collision spheres are a little more complicated to set up and they won’t represent the shape as accurately, but they make good “into” AND good “from” objects. They are also the cheapest collision solid in terms of calculation time. I have heard, though I that Panda3D can perform 3-4 collision checks between spheres in the time it takes to perform a single collision check with a collision polygon. If that is accurate, that means you could check collision with 18-24 spheres in the time necessary to check the 6 polygons that would make up the collision polygon cube. Following that reasoning, the 9 collision spheres would take half the time, or less, as compared to the 6 collision polygons.

To sum up, I’d recommend using the 9 spheres to represent the volume of the block.

As for how to use the collision detection system in Panda3D, you could try my book, or look at the collision detection section of the panda3D manual, or look at the scores of different posts on collision detection on these forums. That’s a pretty popular topic around here.

Hopefully this helps, I know I got pretty long in the tooth.

I feel the need to point out that the Packt books are exclusively targeted to Windows users. Since this doesn’t seem to be specified clearly anywhere, this is essential to point out as people developing on non-Windows platforms will find little use in the book.

I think the code and concepts should translate fine, unless there’s something I don’t know about using python on other platforms. I haven’t done any coding on other platforms. I don’t recall using any of the os-specific python libraries.

Anyway, my book is far from the only option. I’m sure there are lots of titles on general game design concepts, programming concepts, etc, that would be good too. Might even be able to get some from a library without spending any money.

Wow, thanks! I wasn’t expecting such an extensive reply, haha… Hopefully you won’t mind if I ask for clarification on a few things. :smiley:

So would I pass a reference to my InputManager around to every object I create that needs input, right?

Also, what about when a key is first pressed? For example, when I press the spacebar, I want my character to jump, but only when I first press it, not every frame that I have it held down. I’ve been using “self.accept(‘space’,self.jump),” but how would I translate that to storing data in my InputManager object that could be understood by my character object?

I’m not quite sure what you mean here. Would other classes inherit from MenuMother (hence the name “Mother”)? For levels, I’ve been using a class called “Levels” that has an “endLevel” function and then functions for creating each level. Is that okay?

  1. For the DirectStart/ShowBase thing, I noticed that it now works if I just leave off the “self.world.” and just use “loader.loadModel”, which for some reason wasn’t working before. I assume I should just leave it how it is since it’s working now?

  2. So I should call dt = globalClock.getDt() at the beginning of each class’s task function, and then multiply movement by that? Or do I just retrieve it once and then pass dt to the other task functions?

Also, when I was multiplying movement by dt, I noticed that my character could jump to significantly different heights at different framerates. Should I be doing something different for acceleration, or does it just mean I have an error somewhere?

  1. I was planning on making a simple puzzle game, with the terrain designed out of blocks, so I’m not sure if the 9 spheres thing would work. If the ground is made of spheres, wouldn’t the character bob up and down while walking around?

Maybe I should scrap the levels designed from blocks idea, and just make the entire levels in a 3d modeler and then use collision polygons?

Exactly.

The easiest way would be to have a boolean variable in the character, say, “jumping”. Only allow the character to jump when the “jumping” variable is False. When you start a jump, set “jumping” to True, and then set it back to false when the key is released, or when the character lands again, etc.

That’s the idea, yeah. To expand on the MenuMother example, if might have functions like:

def createStartMenu(self):
  #code to create menu here

def createOptionsMenu(self):
  #code to create menu here

def createShopMenu(self):
  #code to create menu here

The idea is that you put all of your menu code there, and anything that needs to make a menu should be passed the MenuMother instance for the game. The MenuMother could monitor menus to see when they need to be removed, etc, as well. This way all your menu related code is in one place.

That’s up to you, really. I would recommend switching back to DirectStart just in case, but I’m paranoid about problems “going away on their own”. That usually means they’re preparing an ambush.

I usually have one task in each class that needs to do something over time, which I call the ControlTask. I don’t use any other tasks in the class. In each ControlTask I get dt from the globalClock and pass into the subfunctions that do everything I need done. Here’s an example of a control task from the game I’m working on:

	def cycleControl(self, task):
		if(self.cycle == None):
			return task.done
			# Ends the task if the cycle model is removed.
			
		dt = globalClock.getDt()
		if( dt > .20):
			return task.cont
		# Gets the amount of time that has passed since the last frame from the global clock.
		# If the value is too large, there has been a hiccup and the frame will be skipped.
		
		if(self.active == True and self.shutDown == False):
		# limits player control to only occur when self.active is true and
		# self.shutDown is false.
		
			if(self.inputManager.keyMap["up"] == True):
				self.adjustThrottle("up", dt)
			elif(self.inputManager.keyMap["down"] == True):
				self.adjustThrottle("down", dt)
			# Checks the InputManager for throttle control initiated by the user and performs
			# the adjustments.
		
			if(self.inputManager.keyMap["right"] == True):
				self.turn("r", dt)
				self.turning = "r"
			elif(self.inputManager.keyMap["left"] == True):
				self.turn("l", dt)
				self.turning = "l"
			else:
				self.turning = None
			# Checks the InputManager for turning initiated by the user and performs it.
			# Also updates the self.turning variable to reflect it.
			
			if(self.currentLap > -1):
			
				if(self.inputManager.keyMap["mouse1"] == True):
					self.LMG.fire()
					self.RMG.fire()
				# If the left mouse button is pressed, fire the machine guns.
				
				if(self.inputManager.keyMap["mouse3"] == True):
					self.cannon.fire()
				# If the right mouse button is pressed, fire the cannon.
				
				if(self.inputManager.keyMap["space"] == True):
					self.hunters.fire()
		
		aimPoint = self.inputManager.getMouseAim()
		if(aimPoint != None):
			self.turret.lookAt(render, aimPoint)
		
		self.speedCheck(dt)
		self.simDrift(dt)
		self.groundCheck(dt)
		self.move(dt)
		self.checkMarkers()
		self.processRecharge(dt)
		# Calls the methods that control cycle behavior frame-by-frame.
		
		self.bumpCTrav.traverse(render)
		# Checks for collisions caused by the cycle's shield.
		
		return task.cont

I’d need to see the code to know the exact problem, can you post it?

In that case your best it is probably to use a 3d modeler, yeah. Blender is free, powerful, and with the chicken plugin it exports directly to Panda’s egg format. If you’re planning on having large levels, I would recommend learning how to use the egg-octree utility that’s available on these forums. Run a search on it. I think Treeform is still hosting it on a site somewhere.

Basically the egg-octree utility reorganizes your collision surfaces to make them much more efficient.

Well, I got the jumping thing to work by trail and error:

if(self.inputmanager.jump and self.ontheground):
    self.verticalspeed = -12
        
self.verticalspeed += 30*dt

self.model.setZ(self.model.getZ() - self.verticalspeed*dt)

I was a bit confused about which numbers I was supposed to multiply by dt. I was thinking that I should only multiply one of them by dt, otherwise I’d accidentally compensate for the framerate twice. But I guess there’s two things that are changing here (the vertical speed and the character’s position) so I need to multiply both? And the “self.verticalspeed = -12” shouldn’t be multiplied by dt because it’s only happening once, not every frame? Hopefully I’m understanding this right?

Oh, cool, I didn’t know they could be reorganized to be faster. I’d better make a note to do that at some point. (If I’m understanding this correctly, this is a script that takes my .egg files and makes a new optimized version? So I don’t really need to do that until I have the final versions of my levels, right?)

Anyway, I think that covers everything. Thanks again for the help!

When you get dt from globalClock.getDt() the result is in seconds, and is usually a small decimal. You want to multiply the actual distance that the object is moving, in panda units/second, by the dt. So for example, if an object is in freefall under normal earth gravity, and your game is scaled so that 1 panda unit = 1 meter:

fallSpeed = 9.8 #Standard gravity is approx. 9.8 m/s

object.setZ(object, -fallSpeed * dt)

The above code would make the object fall in a fairly realistic fashion.

If you had a car going 200 k/h, you could use:

speed = 200 # speed in kilometers/hour
mps = 200 * 1000/3600 #convert kilometers/hour to meters/second
car.setY(car, mps * dt)

Does that help clarify? When you have speed in units/second, you just need to multiply the number of seconds that have passed to get the distance traveled. That’s what dt is for. The same goes for things like regenerating health. If a character gets back 10 hit points/second, you just need to do 10*dt to get the number of hit points they have regained since last frame.

As for the egg-octree, the easiest way to explain it is that it takes your collision polygons and groups them in a tree structure. Instead of having to check every single polygon every time, it checks a few large groups and finds where a collision is, if any. Then it checks the groups inside that group, and then the groups inside that group, and so on. There’s a better explanation in the forum threads about the script itself, with pictures to help understand it.

Wouldn’t that make the character fall at a constant rate instead of accelerating downward, though? Unless there’s something in the code that I’m not noticing?

Anyway, the code I posted for jumping/falling in my last post seems to work, so I’ll just leave that for now and hopefully it won’t cause any problems.

Thanks!

You’re right, that would result in a constant rate of falling. For most game purposes, a constant fall rate is fine. Recall, also, that there is such a thing as terminal velocity: the velocity at which the medium being passed through exerts enough force upon the moving object that the moving object no longer accelerates.

If you want to have fall speed accelerate over time, you just need to think in terms of the units involved. Distance is in meters (for example), thus speed is in meters per second (for example, and and thus acceleration would be in meters per second per second.

In order to convert acceleration into speed, you need to multiply by time. This is because the “per second” is a mathematical division, and division is cancelled out by multiplication. Here’s an example:

accel = 2 m/s/s
speed = 5 m/s
dt = .01 sec
change in speed = accel * dt = .2 m/s
speed = speed + change in speed = 5.2 m/s

object.setY(object, 5.2 * dt)

I did a lot of this sort of unit conversion while I was in the US Navy’s nuclear power program. It’s easy if you just think of the units as another component in the algebra. If you have meters over seconds, and just want meters, you have to multiply by seconds.

x         x
- = 5 --> - * y = 5 * y --> x = 5y
y         y

meters     meters
------ --> ------ * sec --> meters
 sec        sec

If something like this is what you’re after, you need to figure out two values that will control how the falling looks/feels. The acceleration rate, and the terminal velocity. You can probably figure these out with trial and error. I’d recommend figuring out terminal velocity first. You’d employ that with the following:

speed = speed + change in speed = 5.2 m/s
if(speed > terminal velocity):
  speed = terminal velocity
object.setY(object, speed * dt)

You’re already doing the acceleration and speed over time correctly. You just need a terminal velocity, or your fall speed could increase to an outrageous value over a long fall. Depending on your game, this might not be a problem.

Oh, okay, that makes a lot more sense now. I knew units worked like that, I just didn’t make the connection between “x+=10dt" and "distance=ratetime”

(The reason I wanted an accelerating fall speed was because when jumping, the character would have to first move up, then slow down, and then fall back downward. I don’t think a constant fall rate would work for that?)

And yeah, a terminal velocity is probably a good idea, I should go add that now.

thanks!