Giter Site home page Giter Site logo

Comments (5)

matthewjwhite avatar matthewjwhite commented on July 23, 2024

A grid-based platform seems too simplistic, ie. a square grid representing the entire world where each region/map is a square within that grid.

One possibility I have been toying with is:

  • A set of regions, each defined in a configuration file. Within each region, relative data points could be set up for particular NPCs, rare items, etc.
  • Each time a user selects N/E/S/W, this would advance one position in the selected direction.
  • Each region would have certain exit points or connection portions. Effectively, this weaves together regions as more or less a graph, where regions be viewed as nodes which point to one another.
    • A region could even be placed in the middle of an existing region, to serve as a teleportation point.
  • I think it's suitable for each region to be a square.

For example, consider the WesternDungeon region/map:

  • Could be 500 units wide, 300 units tall.
  • At point (20, 20) there could be a rare item, and at (70, 30) an NPC to purchase spellbooks.
  • Alongside the eastern edge, (500, 20) to (500, 50) there could be a connecting point to another region, and at (70, 60) a teleportation point to another world.

With the above example, some regions could even be tunnels to other regions, as opposed to primary regions themselves.

As a side note, it seems too restrictive to place monsters at specific points in regions. I feel that this should be a randomly generated aspect of the game.

To maintain the user's current position, all that would need to be restored is the current region and relative position in that region.


Example configuration file format:

---
# westernDungeon.yaml
name: westernDungeon
width: 500
height: 300
keyPoints:
  - type: npc
    x: 70
    y: 30
  - type: item
    x: 20
    y: 20
exitRanges:
  - dest: easternDungeon # Eastern edge of Western Dungeon.
    xLow: 500
    xHigh: 500
    yLow: 20
    yHigh: 50
  - dest: anotherWorld # Teleportation point.
    xLow: 70
    xHigh: 70
    yLow: 60
    yHigh: 60
monsters:
  - hellHounds
  - massiveBugs

TL;DR:

  • Each region defines it's relative boundaries, and what it contains.
  • Each region defines what it connects to.
  • Each region is defined by a configuration file.

The configuration file approach lends to the maximum configurability of this game. In other words, a server administrator should be able to tweak the game as they wish by simply adjusting configuration files.

from crystal-skull.

matthewjwhite avatar matthewjwhite commented on July 23, 2024

Switching Directions

After giving it further thought, I think the idea of only having regions aware of their neighboring exit points/maps, as opposed to having a single, main grid that controls everything, is too complex.

Some of the major pitfalls:

  • Each map, being self-aware, would have to keep information about its neighboring maps, meaning you'd have maps referencing each other, which screams anti-pattern.
    • One of the main goals of the above design was to just store the relative data points of a character (rather than the main grid point), to allow for easy world expansion. We can still do that.
  • If you cross an exit point, how do you know where you are in the other map?
    • If you have maps with the same height all lined up next to each other, you could probably make some assumptions, but that is unacceptable.

Overall, having a single controller map is going to make things easier.

New Proposal

The new proposal is:

  • Define regions separately.
    • Keeps track of relative points for NPCs, monsters, item drops, teleportation points, etc., similar to the above.
  • Have a controller map, which places the regions relative to each other.
    • The goal of storing the relative point is still to allow for easy world expansion, and even expansion of the map itself. In other words, you could move a map to a totally different place in the grid, or blow it up, and users would still be in that same spot.
      • It would be important to never reduce the dimensions of a map.
  • Do some validation on load to confirm no regions are overlapping. Unconnected regions are OK, ex. islands.

It's important to note that the idea of a controller map only matters when you're moving, to determine if you're entering a neighboring region and where you end up in that neighboring region. So, when moving, we need to invoke a relative -> absolute converter, to determine if we're: 1) breaching a boundary, and 2) where in the new map we are.

I think there are basically 3 pieces to implement here:

  1. Location conversion algorithm, invoked when moving.
  2. Individual map format.
  3. Controller map format.
  4. Additions to game loop.

Location Conversion

I think it would be something like this;

  1. User starts at WestDungeon, (5, 5) (relative point).
  2. User engages game loop, selecting a direction to go in.
  3. See if you're going to breach the edge of the map: are either the next relative X or Y coordinates equal to the height or width of the current map? If not, no need to involve the controller map, simply advance within the relative dimensions of the current map. if so, continue.
  4. Determine if there is even something on the other end (next steps), and advance into that region, continue. Otherwise, reject the mover, since you're at the edge with no region to jump to.
  5. Example, current map width is 200 (relative X range of 0 to 199), height 50 (relative Y range of 0 to 49), xMin 0, yMin 0. Traveling east, next relative point is (200, 40). The relative height is equal to the max height, so breach. There is a neighboring map with xMin 200, yMin 20, with a width of 300 and height of 200.
  6. Get the controller location by adding the current map's xMin (from controller) and xNext, same for Y. Ex. xMin 0 + 200 = 200, yMin 0 + 40 = (200, 40).
  7. Find the matching map by iterating over all maps and finding the one with the greatest xMin and yMin less than or equal to the controller location.
  8. Translate to the relative point in the new map. Add the current map's xMin to the next relative X and subtract the new map's xMin, ex. 0 + 200 - 200 = 0. Same for Y, ex. 0 + 40 - 20 = 20.
  9. Save location.

Individual Maps

name: westernDungeon
width: 500
height: 300
keyPoints:
  - type: npc
    x: 70
    y: 30
  - type: item
    x: 20
    y: 20
monsters:
  - hellHounds
  - massiveBugs
name: easternDungeon
width: 200
height: 200
monsters:
  - hellHounds

Controller Map

- map: westernDungeon
  xMin: 0
  yMin: 0
- map: easternDungeon
  xMin: 500
  yMin: 30

Notice no xMax or yMax - these can be inferred from the respective maps' dimensions; unfortunately, this requires careful coordination between the map definitions and controller, to ensure the width and height match up with the xMin and yMin of neighboring maps. Another option would be to not store any dimensions in the respective map definitions, and store the height and width in the controller, so it's all in one place.

from crystal-skull.

matthewjwhite avatar matthewjwhite commented on July 23, 2024

A few things to note in beginning implementation:

  • An explicit controller map is unnecessary. The overall dimensions can simply be stored with the individual map definitions - the controller map is implicit in this regard.
  • When checking if you're at a boundary, you also need to check nextX/Y against 0, in addition to the height and width of the map in case you're going back or down instead of up or forward.

Other interesting things being added/caught:

  • Config Singleton, to avoid needing to create multiple Config objects (thus resulting in a reload of the same data - changes mid-execution aren't supported) when referencing from multiple code files.
  • There are many places where data from the config is being instantiated into an object. These objects should realistically be loaded once unless necessarily, and preferably all references to other objects should be checked to confirm those referenced objects exist (to avoid complications during runtime).
    • This combined with the Singleton makes it a no-brainer to refer to the actual object parsed from the config as opposed to just maintaining an ID and looking it up when needed. For example a User references a Map for its location - rather than the User object holding a map_name, just point to the Map parsed from the config (obviously we're not dealing with actual pointers directly, but Python won't recreate the object if we assign object to a User instance variable, it'll just assign the address).
  • Realizing this project is in desperate need of a YAML schema, so we can check for the existence of required fields at startup, created #18.

from crystal-skull.

matthewjwhite avatar matthewjwhite commented on July 23, 2024

WIP branch: https://github.com/matthewjwhite/crystal-skull/compare/map?expand=1.

from crystal-skull.

matthewjwhite avatar matthewjwhite commented on July 23, 2024

Went with a way simpler approach of finding the map you're moving into by checking for the map that has that point in its space (see Dim::contains). The other way was too complex and seemed to be fraught with unknown boundary cases.

from crystal-skull.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.