Converting to Turn-Based

Working off and on (more off than on recently) on a pet project for a while now. It’s a Squad-based RPG. I had implemented movement as point-and-click (starcraft style) and it’s working fine.

Now I’m looking to switch to a turn-based style (a la XCOM which was my initial inspiration). My “map” is a simple flat plane model. I’m looking for advice/insight on the best (and simplest?) ways to implement the following:

  1. Allow player to see a “grid” on the map of the spaces they can click on. They should be able to toggle this on or off. (recently found a “DirectGrid” class but have not found many examples of using it, is this a good way?)

  2. Characters are simply at a position now, but switching to turn-based will mean they occupy a specific square. Would it work to simply have an internal calc handle this? That is, if my maps are 100x100 , each square is 10x10, and I want to move 2 spaces up and 1 left - I simply do the math: -20 on 1 axis and -10 on another ? Or will I need to actually store cords like square (2,5) somewhere?

  3. If a character can move say 3 spaces total, any thoughts on ways to show this on the grid. i.e. which spaces are “legal” moves. Advice on ways to “highlight” the good squares visually is what I’m looking for.

Thx!

I don’t know enough about DIrectGrid to comment on it, I’m afraid, so I’ll leave that for someone else.

If you don’t end up using DirectGrid, then a grid-overlay might be achieved by use of a second texture depicting the outline of a cell, tiled over your terrain such that it matches your grid-size. With appropriate texture-stage blending, you should be able to fade it in and out as desired, I believe. I imagine that something similar would be fairly easily achieved in a shader, too.

Hmm… I think that this might depend somewhat on the mechanics of your game. If your game’s usual calculations–line-of-sight, damage, movement, etc.–are done in terms of cells, then it might be convenient to store positions in terms of cells–or even to have a cellular structure in which objects are stored, and which can be simply queried. (In the case of a square grid, such a cellular structure would simply be an array of arrays, I believe.) On the other hand, if your game primarily works in world-space, then it might make more sense to store your positions in terms of world-space.

That said, I’m not entirely sure of the wisdom of mixing world-space calculations and grid-based gameplay–that seems to me to incur a danger of apparent inconsistency, as calculations may end up using a greater degree of accuracy than the game is suggesting to the player, causing misses or hits due to position within a given cell. Conversely, if your gameplay is purely cellular, then finer calculations might be overkill, and may even run afoul of precision errors.

Perhaps a MeshDrawer object producing simple “tiles” on command, which are then overlaid onto the terrain (presumably with a slight z-bias)? Or a shader taking cell-coordinates, converting them to UV-coordinates, and then highlighting the regions within those coordinates?

Here is some code that allows you to create a simple grid; it also shows how you can use a DecalEffect to overlay tile-highlight geometry onto your map geometry:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase


class MyGame(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)
        
        self.disableMouse()
        self.camera.setPos(0., -200., 200.)
        self.camera.setP(-45.)
        
        self.map = self.__createMap(200.)
        self.map.reparentTo(self.render)
        self.map.setColor(0., .3, 0.)
        
        grid_size = 200.
        tile_size = 10.
        
        self.grid = self.__createGrid(grid_size, tile_size)
        self.grid.reparentTo(self.map)
        self.grid.setColor(1., 1., 1.)
        self.grid.setRenderModeThickness(2)
        self.grid.setZ(.1) # prevent z-fighting
        
        # example tile highlight
        tile_hilite = self.__createTileHilite(tile_size)
        tile_hilite.reparentTo(self.map)
        tile_hilite.setColor(1., 0., 0.)
        tile_offset = 0. if (grid_size // tile_size) % 2 else .5
        tile_x = - (3 + tile_offset) * tile_size
        tile_y = (2 + tile_offset) * tile_size
        tile_hilite.setPos(tile_x, tile_y, 0.)
        self.map.setEffect(DecalEffect.make()) # prevent z-fighting


    def __createGridLineX(self, length):
    
        """ Create a grid line in the X-direction with its origin at its center """

        vertex_format = GeomVertexFormat.getV3n3cpt2()
        vertex_data = GeomVertexData("gridline_data", vertex_format, Geom.UHStatic)

        pos_writer = GeomVertexWriter(vertex_data, "vertex")
        pos_writer.addData3f(-length * .5, 0., 0.)
        pos_writer.addData3f(length * .5, 0., 0.)

        lines = GeomLines(Geom.UHStatic)
        lines.addVertices(0, 1)
        geom = Geom(vertex_data)
        geom.addPrimitive(lines)
        geom_node = GeomNode("grid_line")
        geom_node.addGeom(geom)

        return NodePath(geom_node)


    def __createGrid(self, line_length, line_spacing):
    
        """ Create a square grid with its origin at its center """

        grid = NodePath("grid")

        lines_x = grid.attachNewNode("")
        lines_x.setY(-line_length * .5)

        line = self.__createGridLineX(line_length)
        line_count = int(line_length // line_spacing) + 1

        for i in range(line_count):
            line_copy = line.copyTo(lines_x)
            line_copy.setY(i * line_spacing)

        lines_x.flattenStrong()
        lines_y = lines_x.copyTo(grid)
        lines_y.setH(90.)

        grid.flattenStrong()

        return grid


    def __createMap(self, size):

        vertex_format = GeomVertexFormat.getV3n3cpt2()
        vertex_data = GeomVertexData("map_data", vertex_format, Geom.UHStatic)

        pos_writer = GeomVertexWriter(vertex_data, "vertex")
        offset =  size * .5
        pos_writer.addData3f(-offset, -offset, 0.)
        pos_writer.addData3f(offset, -offset, 0.)
        pos_writer.addData3f(offset, offset, 0.)
        pos_writer.addData3f(-offset, offset, 0.)

        tris = GeomTriangles(Geom.UHStatic)
        tris.addVertices(0, 1, 2)
        tris.addVertices(0, 2, 3)
        geom = Geom(vertex_data)
        geom.addPrimitive(tris)
        geom_node = GeomNode("map")
        geom_node.addGeom(geom)

        return NodePath(geom_node)


    def __createTileHilite(self, size):
    
        """ Create a square tile highlight with its origin at its center """

        vertex_format = GeomVertexFormat.getV3n3cpt2()
        vertex_data = GeomVertexData("tile_data", vertex_format, Geom.UHStatic)

        pos_writer = GeomVertexWriter(vertex_data, "vertex")
        offset =  size * .5
        pos_writer.addData3f(-offset, -offset, 0.)
        pos_writer.addData3f(offset, -offset, 0.)
        pos_writer.addData3f(offset, offset, 0.)
        pos_writer.addData3f(-offset, offset, 0.)

        tris = GeomTriangles(Geom.UHStatic)
        tris.addVertices(0, 1, 2)
        tris.addVertices(0, 2, 3)
        geom = Geom(vertex_data)
        geom.addPrimitive(tris)
        geom_node = GeomNode("tile")
        geom_node.addGeom(geom)

        return NodePath(geom_node)
        
        
game = MyGame()
game.run()

Admittedly, offsetting the grid’s Z value isn’t the greatest remedy against z-fighting here, but with GeomLines tricks like using a DecalEffect or setDepthOffset simply don’t work, while setBin, setDepthTest, setDepthWrite et al can be problematic too, so it’s all I could come up with.

Anyway, hope it helps.

Thanks for the responses.

re: 1 - Great code snippet, Epihaius! I’ll definitely study this a bit (don’t know much about the geom functions)

re: 2 and 3
I came across this game : divinityoriginalsin.com/
which combines real-time “exploration” with turn-based combat and is pretty much what I was thinking of for my game (though mine is not a Fantasy setting).

for the turn-based combat, it doesn’t seem to use grids. Instead, player uses point-and-click (which I already have implemented) and it just costs different amounts of “action points” to move different distances. To implement this, I think I would need:
a) when in “move” mode - a graphical “line” would extend from the character and follow my cursor , showing that I could move to that point.
b) if beyond the move limit for that character , I can change the line color or just make it disappear.

I assume I would use some kind of graphic primitive for the line? haven’t done lines before, so any advice / link appreciated.

Hmm… Wouldn’t it be simpler to use MeshDrawer, if perhaps a little less efficient?

Hmm… I’d suggest caution with this. I imagine that it could be quite frustrating to find that one has moved a character such that the enemy is just outside of the player-character’s attack range, but close enough to be able to attack freely on the next turn. This sort of information is, I feel, fairly easy to see in a grid, but may be tricky in a more freeform game.

The simplest solution might be to just use a card/quad, anchored to the player-character’s position, then rotate and scale it accordingly. If you want to apply a texture, you may want to scale the card’s texture-coordinates accordingly.

If you want something slightly fancier, I again suggest MeshDrawer, which should (fairly-)easily allow for such niceties as end-caps to your line that aren’t affected by the length of said line.

That said, I suspect that as a player I’d want to immediately see where I can and can’t go, without having to drag the line to its fullest extent. Thus, instead of a line, why not present a circle around the character depicting their movement range?

Yes, I think showing a circle around the area you can move to might make the most sense.

I think I have a flat circle model I can use (unless there’s a better way with graphic primitives)