Methods of level loading in Panda3d (Solved)


#1

(This was originally the fallow up post from “Methods of loading in panda3d?”, separated into it’s own thread as per requested, again, sorry for any troubles I,m causing)


Hey, I just wanted to continue this thread with one more question based on loading, what would be the proper way to have multiple sections of content, so far I have been working with a demo-like style starting with Roaming Ralph, but I never tried multiple characters (before this thread) multiple levels (scenes) and areas, I stuck with one of everything and it worked.

But now I want to expand beyond just a demo-like structure, games need multiple levels/scenes not just one, so I come to you in this fallow up asking is there a proper way to do this? my idea was have python scripts each belonging it’s own egg file containing the level/world/scene itself.

The scripts themselves would contain everything about the egg file such as the opposing characters, NPCs, event logic, sound logic pertaining to the egg file, backgrounds for the egg file and so on, I would then append that logic from the necessary script into the main script to load in the new logic, then flush it out when done, does this sound good or is there a better way? thanks for the response.

Edit: as of today, I tired using python scripts but I got a error telling me that I could not load in modules at the module level, so I guess modules (scripts) can only be loaded outside of a function? I haven’t tried within the class yet, but I assume that classes don’t update like functions would through the task manger, right?


#2

I am against egg storage levels. I created a text file describing the scaling, position, address, rotation. My example:

[Info]
Name = Start
[Objectes]
0 = data / objects / Lev_0.egg 0,0,0 0.1
1 = data / objects / Lev_1.egg 0.10.0 0.1
2 = data / objects / Lev_2.egg 0.20.0 0.1
3 = data / objects / Lev_3.egg 0.30.0 0.1
4 = data / objects / Lev_4.egg 0,40,0 0.1
5 = data / objects / Lev_5.egg 0.50.0 0.1
6 = data / objects / Lev_6.egg 0.60.0 0.1
7 = data / objects / Lev_7.egg 0.70.0 0.1
8 = data / objects / Lev_8.egg 0.80.0 0.1
9 = data / objects / Lev_9.egg 0,90,0 0.1

I also created a bootloader based on the data from these lines, I liked using the INI format.


#3

My approach is to have a class for each level containing the custom behaviours for that level, each in its own file, subclassing a base Level class. Then, I can just import them and instantiate them in the central game class that controls the loading of levels.


#4

What I’ve done for my main project is to have a “Level” class, which handles the common logic and data for levels, and then have each instance of that class load a custom script-file that holds a “LevelScript” class (or more accurately, an individual sub-class of the general “LevelScript” class) containing custom functions used in that level.

Similarly, I generally have a “GameObject” class, and perhaps sub-classes if the design of the game calls for them, which have optional access to script-functions for individual-level logic.

However, each game is different: a simpler project might be better served by a simpler approach.

As to handling multiple levels, I usually have a file describing the level: the file-name of the model that it uses for its geometry, information about game-objects, and so on. This can include information on which level or levels follow from it.

[quote=“jnpickee, post:1, topic:24271”]
Edit: as of today, I tired using python scripts but I got a error telling me that I could not load in modules at the module level, so I guess modules (scripts) can only be loaded outside of a function?[/quote]
Unless I’m misreading you, I think that I’ve done something like this, and had it work. Perhaps there’s an issue with how you’re approaching it?

Classes should work perfectly well with the task manager, if I’m not much mistaken. Indeed, I make heavy use of classes, and still use tasks!


#5

“serega-kkz”: is that code for txt files? is that possible to create a txt file containing special code to read a level information?

“rdb”: so almost a copy of the main script minus the universal functions? that would be great, but how would I go about appending the other scripts to the main script?

“Thaumaturge”: well to be fair, I didn’t mention I had the “import function” inside a function, and the reason for that is I needed the repeating nature of the task manager, so it is why I asked if classes can be repeated like functions through the task manager, but if you and “rdb” are suggesting the use multiple classes, that may be the way to go.

I wanted to say thanks to “serega-kkz”, “rdb”, and “Thaumaturge” for the help, I,ll be trying these suggestions now, thank you.


#6

Text file stores information of any level. This is something like a database. You can store whatever you want, but it’s worth noting that you first ensure the logical use of data in your classes.
Can you tell us more about the special code?


#7

I have no special code, sorry, I attempted to understand your first post and was asking if you were suggesting the use of a normal txt file containing code readable only by the script, which I assumed your first post said.


#8

In fact, the data in the file are not code. This is a data set for building a level, it contains transformations and other information. I think I will make a sample code.


#9

“serega-kkz”: instructions for code to play out like a sequence or set of events, right?


“rdb” and “Thaumaturge”: your method was working up to the point where I got the “AttributeError: Room’ object has no attribute ‘loader’”, “Room” being the level script and “loader” being load model function and I figured out why I got it.

But here is the issue, problem seems to stem on the fact that function has the attribute “self” in front of it, and from I can get is, the program is trying to access the self attribute of it’s own namespace rather then one from my main code, I would just get rid of it, but there are some dependencies on it, so I ask, is there way to pass the self attribute from the main code’s namespace to my level scripts’s?


#10

I’m not quite sure of what you’re trying to do that might be a problem for classes. Classes can be instantiated multiple times without trouble–something like this, given a class named “MyClass”:

def someFunction():
    myInstance1 = MyClass(<parameters, if any>)
    myInstance2 = MyClass(<parameters, if any>)

We then have two instances of the same class.

(They’ll be lost once the function exits, of course, but you already know about that. I’m just giving a very simple example of instantiating two instances of a class.)

You don’t need to, if I’m not much mistaken: “loader” is a global variable, and should be available without a reference like “self”.

However, in general, “self” is in essence just another parameter, like any other passed to a function or method. It doesn’t even have to be named “self”, if I’m not much mistaken. It just happens to be one that’s (usually) automatically filled in by Python for you, providing a reference to the class to which the method belongs.

As to getting access to the main object that you’re using, you can just pass in a reference to it like any other parameter.

For example, let’s say that your “main class” is called “Game”, and we’ll stick to the name of your “Room” class. Then we might have something like this:

class Game():
    def __init__(self, <other parameters, if called for):
        # Initialisation here

    def someFunctionThatAccessesRoom(self, <parameters, if called for>):
        # Perhaps this is your task, perhaps it's something else; it shouldn't matter
        # Since this method belongs to "Game", "self" here will refer to the
        # instance of "Game" to which it belongs
        someRoomObject.someRoomFunction(self)

class Room():
    def __init__(self, <parameters, if called for>):
        # Initialisation here

    def someRoomFunction(self, game):
        # Note that the game is the >second< parameter here. As always, the first
        # parameter to an instance function, here called "self", is automatically
        # filled in by Python with a reference to the instance--in this case a reference
        # to the instance of the "Room" class associated with this call. Any parameters
        # passed in manually, as the "Game" instance was above, follow after that.
        self.someVariable = game.someOtherVariable

All of that said, you may find this a little awkward at times. In that case, you may find it worthwhile to create a “common” object or file that can be included in any other, and give access to frequently-used objects, like the game, or the current level.


#11

My example of the control level, which I came to gradually. However, I note that this approach is classic. I use the task manager to make the download bar work. I store all the data in INI text format, simply using it in a non-standard way, using value separation. This allows you to put more information in one line. Or it also allows using named access to a string, this is useful for developing a third-party level editor.

Demo_Level_Load.zip (89.5 KB)


#12

“Thaumaturge”: what I mean is I’m under the impression that the class only runs once, but after having a look at the ShowBase section of the manual, I may be the one confused, since I learned that the “run” command is panda3d’s loop. but it does not explain some things I seen happen in the game if the class was repeated, I,m going to try your code example for a “update function” that I will make.

With the loader issue, I actually used the “from main import *” line I got from someone at “stackoverflow” and it worked for the global variables, but I concerned because there are others like the "self.taskMgr.add:, “self.accept(“fr-again-in”, self.function)”, “self.render”, “self.camera” and so on, your suggestion seems to focus on functions, but I also have code in the sub-class itself that needs the main class’s self.


“serega-kkz”: from your demo, yes I got the impression that you mean using a algorithm to read text from a txt file, sorry I worded it badly earlier, but that is what I was trying to say, but this demo is impressive, it has concepts that I was going to implement later, like the load bar and menu, not to mention, your text method, it’s good, and I may actually implement it in my save functions later.

I could also imagine rewriting my code to utilize algorithms, to use it like you suggested, but then I have look at my functions and plan them out carefully, as I add and customize them on the fly, it is something for me to think about, thank you for the demo, it is programming treasure trove, and something I will be looking more into.


#13

Ah, I see. Yeah, “run” in this context is just a method of the ShowBase class, which is called to start Panda’s main loop, I believe.

Well, show us an example of your code, and tell us what odd behaviour you’re seeing, and we may be able to tell you what the problem is. I will say that I make extensive use of classes, and they can work, I do believe.

Ah, yeah… I’m not sure that I’m a fan of that idea. That will import everything from the “main” module. If you really want to have some globally-accessible elements, I’d suggest separating them out into a “Common” file, and importing “*” from that.

Indeed, the “self” variable is local to a given method; it’s not globally available. Indeed, it’s not all that special–it’s really just a parameter to the method, albeit one that Python fills in for you (generally).

In this specific case, much of what you’re looking to have access to is already made globally available by Panda without the use of “self”.
To start with, you can generally access “render” just like that. As to the others, if you’re using the “run” method of “ShowBase” to start your game, then you should have access to a global variable (provided by Panda, that is) named “base”, which allows access to the methods of ShowBase–i.e. you should be able to write “base.cam”, “base.accept”, etc.

I think that I see what you mean. But remember: “self” is just a local variable that stores a reference to the class to which the function or method in question belongs. That’s all. What you want is access to a reference to the main class.

Now, if the main class is calling some method or function of the other class, then it can just pass in a reference to itself, as I showed above.

However, if there’s no such clear connection, then it might be worth storing a reference to your main class in a global variable, and using that. (I know that I’ve done so.)

(Come to think of it, if your “main” class is a sub-class of ShowBase, and you’re using its “run” method, then you may already have global access to it via Panda’s automatically-provided global variable “base”.)

However, I advise that you use such global variables sparingly: having lots of global variables could become a pain to manage, I fear. Where reasonable, I recommend passing references in as parameters.

Again, perhaps it would help if you showed us some of your code–that might make it easier for us to give more-specific advice,


#14

He may have a problem with the code examples. The fact is, if your class is used:

from direct.showbase.ShowBase import ShowBase

Obliges to use self.

import direct.directbase.DirectStart

Obliges to use base

Examples of using code in DirectStart or using ShowBase. Leads to a dead end when you need to combine several code demonstrations with different implementations. Using global variables is not necessary with ShowBase. Although DirectStart imposes the use of global.

I think DirectStart should be excluded from the textbooks.


#15

I’m pretty sure that you can use “base” if your main class is a sub-class of “ShowBase”–in fact, a quick check indicates that I’m doing so myself in at least two projects.

Yeah, the presence of two approaches in the samples could well lead to some confusion, I fear.:/

For the main class, you’re probably correct–but it can still be useful elsewhere. For example, I sometimes use it as a quick way to gain access to the current level.

You could probably get away with not using globals by passing in references to the game just about everywhere–but that would likely become rather tiresome!

I think that the current recommended method is sub-classing ShowBase. If so, then it would probably help indeed for the various resources to stick consistently to that one method.


#16

I just wanted to say in my post that newcomers are constantly stumbling over this.
Although I like to use import direct.directbase.DirectStart :slight_smile:


#17

Fair enough, on both counts!

Myself, I don’t think that I use “DirectStart” at all these days–a quick search suggests that the only place that I have it is in old projects, made before I moved over to sub-classing from “ShowBase”. :slight_smile:


#18

For the record, importing DirectStart is exactly equivalent to:

from direct.showbase.ShowBase import ShowBase
ShowBase()

The ShowBase constructor writes base to the built-in scope.

Far be it from me to criticise anyone for their approach to programming, but I do personally avoid the use of globals as much as possible; I think it’s perfectly possible to avoid them by structuring your classes well. For example, you can pass in a reference to a World class to your WorldObject class, so that they can do operations with respect to the world they are in, parent themselves to the World’s root, etc.


#19

I prefer to use functions, and it is not clear to me why it is necessary to create classes in Panda3D, at a time when this is not necessary. And the transfer of constantly referencing, sooner or later leads to a tangled tangle of threads. Sometimes it is easier to create a new code than to understand :slight_smile:

Using the built-in scope reduces many access problems and reduces the number of lines of code.


#20

It’s possible, and I do prefer doing so in general, but it can become unwieldy at times, in my experience. That was why I started using a “Common” object that provides sort-of global access to a few commonly-used things.

I think that it really is a matter of what you prefer, and what works better for you. For myself, I find that classes fit well with the way that I think, and so make it easier for me to keep everything organised and (relatively) neat.