Indeed, you can disable the shadow caster in the flashlight loop to remove the ridiculous performance penalty.
I think this can be improved over time. I like the gun, it doesn’t look bad. The rest can be replaced and corrected. And sooner or later there will be a good example.
- Changed ‘beep’ DirectObject to self.accept()
- Removed redundant calls to camLens
- Added basic time normalization to NPC handler movement increment applications with globalClock
- Corrected the walking animation playing the wrong animation segment
- Shadow casting is disabled on the flashlight by default because of the performance penalty
I have uploaded the initial case changes. There are a few examples that were not easy to switch over.
Update, these are not actually outstanding issues, they are non-issues, but I’m keeping it here for clarity:
Oustanding issues switching from camelCase:
lerp_pos_hpr_interval is not defined
mouse_watcher_node is not defined
lerp_func is not defined
global_clock is not defined
cam_lens is not defined
bullet_character_controller_node is not defined
Partial list of changes to snake_case:
The entire program has had a minor update. Now there are randomly colored collision cubes which surround the NPC and are capable of interacting with the NPC and player.
The player’s gun no longer immediately clips through walls as I have enlarged the character controller cylinder shape to envelop the gun model position (in lieu of an actual depth test).
Some of these are class names (e.g.
LerpFunc), while the others are instance variables (of
ShowBase), which do not have alternative snake_case names, so it’s fine . Thanks for the updates!
- Added Backface Culling to the handgun model
- minor syntactic updates
- Fixed player character’s walking animation not playing
- minor performance improvement
- Reduced hitch time when objects are cleaned up during success condition
- Eliminated most cases of the NPC clipping through the floor during success condition
- Added cursor-hidden #t to load_prc_file_data
- Changed LerpFunc input to t = t * 3 from t = t * 1 which appears to prevent most if not all cases of the handgun interval animation not playing upon success condition
Additional minor update:
- Added the animation sequence to just after the NPC cleanup routine to cover the additional cases where the animation was not playing on cleanup. I’d like to keep the option for an interval animation open as it allows for some randomization and fine control of the animations.
- Switched gun animation to an Armature Action instead of Interval lists. The handgun is now an Actor. I could not figure out how to get 100% animation coverage under all conditions (especially on cold start) with the Interval format so I gave up on that.
- Triangulated the handgun mesh faces for rigging purposes.
- I think Intervals are great, but for some reason they’re one of the first things to fail under cold start conditions. To solve all possible cases of cold start hitching, I think you’d need to load all the possibilities beforehand and hope that they remain in memory, and I do not have a clean solution for that. Armature Actions do seem to be more reliable, however, on startup.
Link for convenience: GitHub - rayanalysis/Panda3D-Arena-FPS-Sample-Program: This is a modern Panda3D sample program with an Arena first person shooter environment and many basic features. This program would be useful to experienced Python programmers jumping into Panda3D with the intention of building a first person shooter.
If this is meant to be a sample, I do wonder if the code might become more manageable to someone studying it if it were broken up into logical parts. As-is you’re not using any OOP concepts, for example (other than the fact that you have a class inheriting ShowBase, which is actually unnecessary in your case as you might as well just put everything at module scope as-is). Perhaps that is fine for such a relatively simple sample, though like this it will quickly become unmanageable to scale up to a more complex game while keeping the code readable and maintainable.
I suppose it really depends on what you want to achieve with this sample—if all you want to do is show the mechanics of how to implement a physics-based FPS, then this is probably the most compact way to do so.
Thanks for the reply! I didn’t spend any time to make this a maintainable sample, but I’m not against it. This was written mostly to prove a point, that Panda supports first person shooters “out of the box”.
I was also trying to follow the “classic Panda3D sample program” format, which maybe we should be clearer to everybody is not the only way to instantiate the engine or work with it.
So here’s my general concept of what a maintainable version would look like:
- the flashlight gets a class
- Bullet collision generator gets a class
- the first person camera/handgun setup routine gets a class
- I’m not sure what else
- the ray testing parts could be made part of a class but it’s already so compact I sort of wonder what the point of that would be
- the NPC controller is intentionally bad, I wouldn’t want that to be held to a standard
I have been “maintaining” a few of these code objects for several years. If there are specific suggestions about how to structure this to meet more standardized guidelines, I will certainly consider it.
This is, arguably, another reason to update the samples!
My rule of thumb for “should something be a class” (where “something” is some entity with its own lifetime and logic) is to ask a question like “Does this entity manage state that has a unique lifetime?”
Or, perhaps more simply, “can I imagine there being more than one of this entity?”. This usually leads me to separate out eg. a class for the World/Level from the main engine/game class, since the World may be torn down and recreated, or replaced with a different level that has different world logic. Or, the World logic and state and doesn’t even need to come into the picture until the user has navigated through menus and only then needs to be instantiated. And there might be multiple NPCs that all manage their own state and logic, and putting them in a class makes it easy to handle their lifetime and destruction cleanly.
I would not personally use classes to encapsulate, say, a one-time process (which could go into a function just fine), since it doesn’t have to manage the lifetime of some state.
I appreciate very much the theoretical approach of your logic. Are you a mathematician by chance?
Unfortunately, I cannot distinguish easily between such world states and my own concepts, which are usually unbounded in some sense. The collision generator I have created could be used an infinite number of times in it’s current, “functional” declaration. I have no personal need for it to exist in a class.
I am not a mathematician, but I do appreciate elegance in all things, including code architecture design. To me, readable code is ideally an easily-followable narrative of verbs being called on nouns.
I agree that a collision generator does not need to be in a class, since it is not a long-lived entity, but a one-time process. A function is the best fit for that.
- Radically improved shadow casting performance (I would appreciate a few testers)
- Shadow casting is enabled by default
- Removed simplepbr.init() requirement (gltf patch loader is still required)
- Antialiasing and setup is now being handled in the main loop
- The “Actor shader” is now a scene shader being applied to the whole scene
- Added a sky color
- Minor shader syntax improvements
- Doubled the number of physics cubes
- Changed near value of the flashlight lens to 0.5 from 0.05 to avoid shadow weirdness with the player character mesh
This looks like a neat update!
On my machine, with the window maximised, I’m seeing a frame-rate of about 60 with the light off, and a frame-rate of about 45 with it on.
(I haven’t been able to try the game at other sizes, as it grabs the cursor so completely that I can’t seem to extricate the cursor to do anything else, resizing of the window included.)
My system specs:
Dell Inspiron 15 3000 Series
CPU: Core i7 2.4GHz (4-core)
Graphics: GeForce 840M, 2GB
Thanks for the report, it is appreciated.
I am curious what would happen if you set the shadow caster of the flashlight to (True, 128, 128) on your system at line 252.
And I do have cursor-hidden #t set in the config. This is mostly to avoid how unsightly it is to see a relative mouse twitch around the target dot.