Show Us Your Progress!

Do you have a Panda3D project that you’re working on? One that perhaps isn’t ready for its own thread, or the Showcase sub-forum? But one that you’d still like to share with the community?

Well, this is the thread in which to share it, and in which to share updates as you go along!

So come, show us your projects, and your progress in them!

In short, this is a “share and comment” thread for projects and project updates within the community!

~~ ~~ ~~ ~~ ~~

To start things off, I shared my main project–Moons in Crystal–in another thread, so let me instead show something else that I’ve been tinkering with on-and-off: a little pixel-art mini-RPG!

(The UI is in a bit of an in-between state, as I work and rework elements. So please excuse the lack of art there… ^^;

Likewise, the rest of the art is very much not final.)

6 Likes

Specify the current month and year in the topic name.

I was thinking that the thread would last beyond just one month, would be instead a running thread going forward.

Another month is a different topic, and it is always more convenient for an interested user to view fewer messages than the archive of messages accumulated over the years.

I mean, they can always just jump to the end and scroll back a bit.

But I’ll think about it, nevertheless.

1 Like

I have been working on this for a while now it barely counts as a game but just something I’ve been messing around with

6 Likes

A good start, I do think! And I like that cel-shading effect that you have applied! :slight_smile:

2 Likes

I've been working on a project for a year now, on a game based on the Backrooms.
For now, I'm progressing slowly because I'm a high school student.
Sorry for the low-quality video.

7 Likes

That looks like a solid start! (And very shiny!)

2 Likes

I’m trying to make a goldbox style rpg, not much is done besides a few ui pages but I’m learning a lot. The discord people have been very helpful. I came across DirectGuiExtension the other day and it has taken a lot of pain out of lining up buttons. I figured I would get a basic skeleton of the ui going, then the dice roll characer stuff in a little text console, then start putting visuals in

3 Likes

Oooh, a Goldbox-style RPG is an exciting project! And I’m glad that you’re taking it a step at a time! :slight_smile:

1 Like

My example is not part of a project, but rather a technical study on integrating the Dear ImGui into Panda3D rendering. Since there is success, I am sharing the result. I’m using C++ at the moment.

I think in the near future I will try using interrogate to build the pyd library, and also try to integrate it on the Ubuntu platform.

Test build for Windows:

5 Likes

That looks like it could be very useful! Especially since it’s implemented on the C++ side–I imagine that would potentially make it available for both C++ and Python users! :slight_smile:

1 Like

A tiny update on what I was working on testing enemy reactions to the player.

4 Likes

So here is the source code for my game. You can try building it to test it or look at the content to learn.

3 Likes

I’ve made Sea map navigator
It’s a simple ‘click on the map where you think the boat is’ game.

It’s actually three Panda3d scenes. I left the standard scene, render and camera untouched and added a leftRender and leftCamera and a rightRender and rightCamera. The new cameras their display region is half the screen so the world on the left and the map on the right can never overlap regardless of how big they become.

Initially, I wasted some time changing the display region of the standard camera but it is much easier to just leave the default render empty if you dont need it and add two new renders (they’re just empty nodepaths with a camera set to render it and all it’s children). Later in the project I then needed a full screen render scene for converting the mouse screen position to scene positions which the standard render is perfect for.

Also worked on a play in browser turn based version of Clean up in sector 3 but it doesnt play smoothly at the moment (too much mouse moving, need to add hotkeys to the action bar or move the action bar much closer to the tiles you want to click on) and has a bug with tiles becoming unselectable that can make it unplayable.

2 Likes

I gave it a quick try, and that sea-map puzzle is a cool little game! :slight_smile:

Does it have an end-point, or is it just indefinitely recurring?

Either way, I could see it making for a nice minigame in a larger project, perhaps… maybe a treasure-hunt, with the player being tasked to find certain objects (boats, chests, ruins, etc.) by old illustrations…

1 Like

It’s just what it is now, there’s no progress or change.

It was supposed to have a larger map and more actual navigating, like try to get from this port to another port three islands over. With marking your position being more like a flag in minesweeper, something to help you think. And the direction you decide to sail towards each day the actual impactful decision.

3 Likes

Well, i think this can be a good thing to showcase, right !?

so i made Portals in Panda3D! ( not the game, the thing! :laughing: )

it does 2 of the most basic things that portals do :

  1. Render what is on the other side of the second portal
  2. Teleport the player on the other side of the second portal when he enters the first one. ( and vice-versa )

( Note: i have inetnsionally elongated all the variable names, to make it better to understand, and because i find short names to be hard to remember. )

from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import CardMaker, Texture, Camera, PerspectiveLens, TextNode, DirectionalLight, AmbientLight, TransformState, Vec3, Quat, Shader
from direct.task import Task
import sys
import random


def transform_node_through_portal_space(source_node_path, source_portal_node, destination_portal_node):
    relative_transform_to_source_portal = source_node_path.getTransform(source_portal_node)
    rotation_quaternion = Quat()
    rotation_quaternion.setFromAxisAngle(180, Vec3.up())
    rotation_transform_state = TransformState.makeQuat(rotation_quaternion)
    rotated_relative_transform = rotation_transform_state.compose(relative_transform_to_source_portal)
    destination_world_transform = destination_portal_node.getTransform(render).compose(rotated_relative_transform)
    return destination_world_transform

def clamp_value(value, minimum_value, maximum_value):
    return max(minimum_value, min(maximum_value, value))


class Portal:
    def __init__(self, portal_name, portal_position, portal_orientation):
        self.portal_name = portal_name

        self.portal_root_node = render.attachNewNode(portal_name)
        self.portal_root_node.setPosHpr(portal_position, portal_orientation)

        portal_screen_card_maker = CardMaker("portal_screen")
        portal_screen_card_maker.setFrame(-1, 1, -2, 2)
        self.portal_screen_node = self.portal_root_node.attachNewNode(portal_screen_card_maker.generate())
        self.portal_screen_node.setPos(0, 0.01, 0)

        self.portal_camera_node = render.attachNewNode(Camera(portal_name + "_camera"))
        portal_camera_lens = PerspectiveLens()
        portal_camera_lens.setFov(75)
        self.portal_camera_node.node().setLens(portal_camera_lens)

        portal_camera_marker = loader.loadModel("models/misc/sphere")
        portal_camera_marker.reparentTo(self.portal_camera_node)
        portal_camera_marker.setScale(0.1)
        portal_camera_marker.setColor(1, 0, 0, 1)
        portal_camera_marker.setLightOff()

        self.portal_render_texture = Texture()

        portal_texture_buffer = base.win.makeTextureBuffer(portal_name + "_buffer", 512, 512)
        portal_texture_buffer.setSort(-100)

        portal_display_region = portal_texture_buffer.makeDisplayRegion()
        portal_display_region.setCamera(self.portal_camera_node)

        self.portal_render_texture = portal_texture_buffer.getTexture()
        self.portal_screen_node.setTexture(self.portal_render_texture)

        portal_surface_shader = Shader.make(
            Shader.SL_GLSL,
            vertex="""
            #version 130
            in vec4 p3d_Vertex;
            uniform mat4 p3d_ModelViewProjectionMatrix;
            out vec4 clipPosition;
            void main() {
                clipPosition = p3d_ModelViewProjectionMatrix * p3d_Vertex;
                gl_Position = clipPosition;
            }
            """,
            fragment="""
            #version 130
            uniform sampler2D portalTexture;
            in vec4 clipPosition;
            out vec4 fragmentColor;
            void main() {
                vec2 normalizedDeviceCoordinates = clipPosition.xy / clipPosition.w;
                vec2 screenSpaceUV = normalizedDeviceCoordinates * 0.5 + 0.5;
                fragmentColor = texture(portalTexture, screenSpaceUV);
            }
            """
        )

        self.portal_screen_node.setShader(portal_surface_shader)
        self.portal_screen_node.setShaderInput("portalTexture", self.portal_render_texture)

        self.previous_side_of_plane = None

    def update_portal_camera_view(self, player_node_path, linked_portal):
        transformed_camera_state = self.compute_portal_transformed_node(player_node_path, linked_portal)
        self.portal_camera_node.setTransform(render, transformed_camera_state)

    def compute_portal_transformed_node(self, source_node_path, linked_portal):
        return transform_node_through_portal_space(
            source_node_path,
            self.portal_root_node,
            linked_portal.portal_root_node
        )

    def process_entity_teleportation(self, entity_node_path, linked_portal):
        world_position = entity_node_path.getPos(render)
        local_position = self.portal_root_node.getRelativePoint(render, world_position)

        portal_half_width = 1.0
        portal_half_height = 2.0

        is_in_front_of_portal = local_position.y > 0.0

        if self.previous_side_of_plane is None:
            self.previous_side_of_plane = is_in_front_of_portal
            return

        has_crossed_portal_plane = is_in_front_of_portal != self.previous_side_of_plane

        is_within_horizontal_bounds = -portal_half_width <= local_position.x <= portal_half_width
        is_within_vertical_bounds = -portal_half_height <= local_position.z <= portal_half_height
        is_within_portal_bounds = is_within_horizontal_bounds and is_within_vertical_bounds

        if has_crossed_portal_plane and is_within_portal_bounds:
            teleported_transform = self.compute_portal_transformed_node(entity_node_path, linked_portal)
            entity_node_path.setTransform(render, teleported_transform)
            self.previous_side_of_plane = None
            linked_portal.previous_side_of_plane = None
            return

        self.previous_side_of_plane = is_in_front_of_portal


class PortalDemo(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.configure_lighting()
        self.build_environment()
        
        self.accept("escape", sys.exit)

        self.debug_display_text = OnscreenText(
            text="",
            pos=(-0.8, 0.8),
            scale=0.05,
            align=TextNode.ARight,
            fg=(1, 1, 1, 1),
            mayChange=True
        )

        self.portal_alpha = Portal("portal_alpha", (0, 5, 1.5), (0, 0, 0))
        self.portal_beta = Portal("portal_beta", (10, 0, 1.5), (180, 0, 0))

        self.player_root_node = self.render.attachNewNode("player_root")
        self.player_root_node.setPos(0, 0, 1.8)
        self.camera.reparentTo(self.player_root_node)

        self.disableMouse()
        self.win.setClearColor((0.1, 0.1, 0.3, 1))

        self.keyboard_state = {"w": 0, "s": 0, "a": 0, "d": 0}
        for key in self.keyboard_state:
            self.accept(key, self.set_keyboard_state, [key, 1])
            self.accept(key + "-up", self.set_keyboard_state, [key, 0])

        self.taskMgr.add(self.update, "update")

    def set_keyboard_state(self, key, value):
        self.keyboard_state[key] = value

    def configure_lighting(self):
        directional_light = DirectionalLight("directional_light")
        directional_light.setColor((1, 1, 0.9, 1))
        directional_light_node = self.render.attachNewNode(directional_light)
        directional_light_node.setHpr(45, -45, 0)
        self.render.setLight(directional_light_node)

        ambient_light = AmbientLight("ambient_light")
        ambient_light.setColor((0.3, 0.3, 0.4, 1))
        ambient_light_node = self.render.attachNewNode(ambient_light)
        self.render.setLight(ambient_light_node)

    def build_environment(self):
        ground_card_maker = CardMaker("ground_plane")
        ground_card_maker.setFrame(-50, 50, -50, 50)
        ground_node = self.render.attachNewNode(ground_card_maker.generate())
        ground_node.lookAt(0, 0, -1)
        ground_node.setZ(0)
        ground_node.setColor(1, 0.8, 0.5, 1)

        sky_sphere = self.loader.loadModel("models/misc/sphere")
        sky_sphere.reparentTo(self.render)
        sky_sphere.setScale(200)
        sky_sphere.setLightOff()
        sky_sphere.setShaderOff()
        sky_sphere.setBin("background", 0)
        sky_sphere.setDepthWrite(False)
        sky_sphere.setCompass()
        sky_sphere.setColor(0.5, 0.7, 1.0, 1)

        for index in range(20):
            cube_model = self.loader.loadModel("models/box")
            cube_model.reparentTo(self.render)
            cube_model.setScale(0.5, 0.5, 0.5)
            cube_model.setPos((index * 2) - 20, 10, 0.5)
            cube_model.setColor(random.random(), random.random(), random.random(), 1)

    def update(self, task):
        mouse_data = self.win.getPointer(0)
        mouse_x = mouse_data.getX()
        mouse_y = mouse_data.getY()

        if self.win.movePointer(0, self.win.getXSize() // 2, self.win.getYSize() // 2):
            delta_x = mouse_x - self.win.getXSize() // 2
            delta_y = mouse_y - self.win.getYSize() // 2
            self.player_root_node.setH(self.player_root_node.getH() - delta_x * 0.1)
            self.camera.setP(self.camera.getP() - delta_y * 0.1)
            self.camera.setP(clamp_value(self.camera.getP(), -80, 80))

        movement_speed = 5 * globalClock.getDt()
        if self.keyboard_state["w"]:
            self.player_root_node.setY(self.player_root_node, movement_speed)
        if self.keyboard_state["s"]:
            self.player_root_node.setY(self.player_root_node, -movement_speed)
        if self.keyboard_state["a"]:
            self.player_root_node.setX(self.player_root_node, -movement_speed)
        if self.keyboard_state["d"]:
            self.player_root_node.setX(self.player_root_node, movement_speed)

        self.portal_alpha.update_portal_camera_view(self.player_root_node, self.portal_beta)
        self.portal_beta.update_portal_camera_view(self.player_root_node, self.portal_alpha)

        self.portal_alpha.process_entity_teleportation(self.player_root_node, self.portal_beta)
        self.portal_beta.process_entity_teleportation(self.player_root_node, self.portal_alpha)

        player_position = self.player_root_node.getPos(self.render)
        player_camera_orientation = self.camera.getHpr(self.render)

        portal_alpha_camera_position = self.portal_alpha.portal_camera_node.getPos(self.render)
        portal_alpha_camera_orientation = self.portal_alpha.portal_camera_node.getHpr(self.render)

        portal_beta_camera_position = self.portal_beta.portal_camera_node.getPos(self.render)
        portal_beta_camera_orientation = self.portal_beta.portal_camera_node.getHpr(self.render)

        self.debug_display_text.setText(
            f"Player Position\n"
            f"X: {player_position.x:.2f}\n"
            f"Y: {player_position.y:.2f}\n"
            f"Z: {player_position.z:.2f}\n\n"
            f"Camera Orientation\n"
            f"H: {player_camera_orientation.x:.2f}\n"
            f"P: {player_camera_orientation.y:.2f}\n"
            f"R: {player_camera_orientation.z:.2f}\n\n"
            f"Portal Alpha Camera\n"
            f"X: {portal_alpha_camera_position.x:.2f}\n"
            f"Y: {portal_alpha_camera_position.y:.2f}\n"
            f"Z: {portal_alpha_camera_position.z:.2f}\n"
            f"H: {portal_alpha_camera_orientation.x:.2f}\n"
            f"P: {portal_alpha_camera_orientation.y:.2f}\n"
            f"R: {portal_alpha_camera_orientation.z:.2f}\n\n"
            f"Portal Beta Camera\n"
            f"X: {portal_beta_camera_position.x:.2f}\n"
            f"Y: {portal_beta_camera_position.y:.2f}\n"
            f"Z: {portal_beta_camera_position.z:.2f}\n"
            f"H: {portal_beta_camera_orientation.x:.2f}\n"
            f"P: {portal_beta_camera_orientation.y:.2f}\n"
            f"R: {portal_beta_camera_orientation.z:.2f}"
        )

        return Task.cont


app = PortalDemo()
app.run()


8 Likes

That’s pretty cool! Well done! :slight_smile:

Do you happen to have a video of it in action? I find with portals that showing them in motion can be particularly effective in conveying their features–and their coolness.

1 Like