#+DATE: <2013-03-15> #+OPTIONS: toc:nil #+TOC: headlines 0 #+PROPERTY: header-args :exports results :results value :wrap export html #+PROPERTY: header-args:python :session #+noindex: t #+TITLE: Map homunculus Published: https://simblob.blogspot.com/2014/05/map-homunculus.html #+begin_export html #+end_export #+begin_src python :exports none :results none # Construct a density map import random from collections import namedtuple SVG_WIDTH = 600 SVG_HEIGHT = 50 random.seed(1) # NOTE: label should be xml (it will go inside ) Group = namedtuple('Group', ['lo', 'hi', 'label', 'cssclass', 'count', 'times']) groups = [ Group(0.0, 0.2, "picnic basket", "in", 200, []), Group(0.2, 0.8, "the woods", "out", 70, []), Group(0.8, 1.0, "grandma", "in", 300, []), ] countsum = 0 for g in groups: countsum += g.count for i in range(g.count): t = random.uniform(g.lo, g.hi) g.times.append(t) g.times.sort() for i in range(g.count): t0 = g.lo + (i / g.count) * (g.hi - g.lo) t1 = g.times[i] t = 0.5 * t0 + 0.5 * t1 g.times[i] = t def drawbox(g, rescale_lo, rescale_hi): return "".format( rescale_lo * SVG_WIDTH, 0, (rescale_hi-rescale_lo) * SVG_WIDTH, SVG_HEIGHT*2/3, g.cssclass) def drawlabel(g, rescale_lo, rescale_hi): x = 0.5 * (rescale_lo + rescale_hi) x = 0.95 * x + 0.05 * 0.5 # shift towards center x = x * SVG_WIDTH return "{}".format(x, SVG_HEIGHT*0.9, g.label) def drawgroup(g, rescale_lo, rescale_hi): results = ["") return "".join(results) def drawdensity(g, rescale_lo, rescale_hi): results = [] divisions = int(40 * (rescale_hi - rescale_lo)) buckets = [0] * divisions print(divisions) for t in g.times: b = int((t - g.lo) / (g.hi - g.lo) * divisions) buckets[b] += 1 m = max(buckets) height = SVG_HEIGHT*2/3 for i in range(divisions): width = (rescale_hi - rescale_lo) / divisions x = rescale_lo + i * width results.append("".format( x * SVG_WIDTH, height * (1.0 - buckets[i]/m), width * SVG_WIDTH, height * buckets[i]/m, g.cssclass)) return "".join(results) #+end_src Suppose I'm telling a story about Red walking 10mi through the woods to get to grandma's house. Let's look at how much time is spent in each activity: #+begin_src python results = "
".format(SVG_WIDTH, SVG_HEIGHT) for g in groups: results += drawbox(g, g.lo, g.hi) results += drawlabel(g, g.lo, g.hi) results += "
" results #+end_src #+results: #+begin_export html
picnic basketthe woodsgrandma
#+end_export I don't write the same number of sentences in the story on each minute of the adventure. The sentences might be distributed like this: #+begin_src python results = "
".format(SVG_WIDTH, SVG_HEIGHT) for g in groups: results += drawbox(g, g.lo, g.hi) results += drawgroup(g, g.lo, g.hi) results += drawlabel(g, g.lo, g.hi) results += "
" results #+end_src #+results: #+BEGIN_export html
picnic basketthe woodsgrandma
#+END_export Let's look at /how interesting/ each part of the story is over time: #+begin_src python results = "
".format(SVG_WIDTH, SVG_HEIGHT) for g in groups: results += drawdensity(g, g.lo, g.hi) results += drawlabel(g, g.lo, g.hi) results += "
" results #+end_src #+results: #+BEGIN_export html
picnic basketthe woodsgrandma
#+END_export If the storyteller were forced to match the pacing of the story with the amount of time each actually took, we'd have long periods of boring story. Instead, the storyteller can *stretch out interesting times* and *compress boring times* to more evenly distribute interesting events: #+begin_src python results = "
".format(SVG_WIDTH, SVG_HEIGHT) startsum = 0 for g in groups: rescale_lo = startsum / countsum startsum += g.count rescale_hi = startsum / countsum results += drawdensity(g, rescale_lo, rescale_hi) results += drawlabel(g, rescale_lo, rescale_hi) results += "
" results #+end_src #+results: #+BEGIN_export html
picnic basketthe woodsgrandma
#+END_export Let's look at where the events occur on this distorted timeline: #+begin_src python results = "
".format(SVG_WIDTH, SVG_HEIGHT) startsum = 0 for g in groups: # I want to rescale from (lo, hi) to (startsum-g.count, startsum) / countsum rescale_lo = startsum / countsum startsum += g.count rescale_hi = startsum / countsum results += drawbox(g, rescale_lo, rescale_hi) results += drawgroup(g, rescale_lo, rescale_hi) results += drawlabel(g, rescale_lo, rescale_hi) results += "
" results #+end_src #+results: #+BEGIN_export html
picnic basketthe woodsgrandma
#+END_export They're much more evenly distributed. Think about every story you have read and every movie you have watched. Most of them will stretch and shrink time by omitting or elaborating various details. They also use flashbacks and replays to further disconnect the time you experience from the time in the story. [[file:time-map.svg]] We want to do this in many types of games. In a game that directly tells a story, you can follow the technique used by storytellers. But what about more open ended games? *If you can't stretch and shrink time, try to stretch and shrink space.* Let's look at some games that stretch and shrink game maps. * DUMMY HEADING - need to adjust my export settings :PROPERTIES: :CUSTOM_ID: dummy-heading-need-to-adjust-my-export-settings :END: ** Ultima IV-VII :PROPERTIES: :CUSTOM_ID: ultima-iv-vii :END: Ultima 4 and 5 have a continental map that's almost all wilderness. In Ultima 6, the towns were integrated into the world map. Surrounding areas were shrunk to make room for the towns. In Ultima 7, the towns grew even larger and boring parts of the wilderness shrunk even more. Here's a rough sense of how that looked: #+begin_export html

Ultima 4

Ultima 6

Ultima 7
#+end_export If you want to see the actual maps, there's a high resolution map of [[http://code.zoic.org/ultima/world/][Ultima 4]] from Nick Moore, and there are high resolution maps of [[https://ian-albert.com/games/ultima_6_maps/][Ultima 6]] and [[https://ian-albert.com/games/ultima_7_maps/][Ultima 7]] from Ian Albert. You can find some scans of the cloth maps that came with these games on [[http://www.dengler.net/xedragon/hrump/][Xe Dragon's site]]. #+begin_comment # Commands I ran to generate small versions of maps convert ultima_6_overworld_16384x16384.png -geometry 256x256 ~/Desktop/u6.png convert ultima_7_24576x24576.png -geometry 256x256 ~/Desktop/u7.png convert ultima\ 4\ map.png -geometry 256x256 ~/Desktop/u4.png # I then used those to draw a simplified version, using Pixelmator # I then adjusted colors with convert u7-rough.png -modulate 200,40 u7-rough2.png #+end_comment Let's look at the path from Trinsic to Britain. In Ultima 4, most of that time is in the wilderness, which I found relatively uninteresting in that game: #+begin_comment Ultima 4 - 1 is radius of Trinsic - 40 path - 1 Paws - 57 path - 1 to castle throne - SUM: 100 = (+ 1 40 1 57 1) #+end_comment #+begin_src python u4 = [ Group(0.00, 0.01, "Trinsic", "in", 1, []), Group(0.01, 0.41, "path", "out", 1, []), Group(0.41, 0.42, "Paws", "in", 1, []), Group(0.42, 0.99, "path", "out", 1, []), Group(0.99, 1.00, "Britain", "in", 1, []), ] results = "
".format(SVG_WIDTH, SVG_HEIGHT) for g in u4: results += drawbox(g, g.lo, g.hi) results += drawlabel(g, g.lo, g.hi) results += "
" results #+end_src #+results: #+BEGIN_export html
TrinsicpathPawspathBritain
#+END_export Ultima 4 and 5 solved this problem by having a separate map for towns. When you entered the town on the main map, you were taken to a more detailed map with the town. Ultima 6 and Ultima 7 solve the problem a different way, by changing the world map: #+begin_comment Ultima 6 - 5 is radius of Trinsic - 25 path - 19 Paws - 30 path - 27 to castle throne - SUM: 106 (+ 5 25 19 30 27) #+end_comment #+begin_src python u6 = [ Group(0, 5/106, "Trinsic", "in", 1, []), Group(5/106, 30/106, "path", "out", 1, []), Group(30/106, 49/106, "Paws", "in", 1, []), Group(49/106, 79/106, "path", "out", 1, []), Group(79/106, 1.0, "Britain", "in", 1, []), ] results = "
".format(SVG_WIDTH, SVG_HEIGHT) for g in u6: results += drawbox(g, g.lo, g.hi) results += drawlabel(g, g.lo, g.hi) results += "
" results #+end_src #+results: #+BEGIN_export html
TrinsicpathPawspathBritain
#+END_export #+begin_comment Ultima 7 - 10 is radius of Trinsic - 19 path - 33 Paws - 5 path - 39 to castle throne - SUM: 106 (+ 10 19 33 5 39) #+end_comment #+begin_src python u7 = [ Group(0, 10/106, "Trinsic", "in", 1, []), Group(10/106, 29/106, "path", "out", 1, []), Group(29/106, 62/106, "Paws", "in", 1, []), Group(62/106, 67/106, "path", "out", 1, []), Group(67/106, 1.0, "Britain", "in", 1, []), ] results = "
".format(SVG_WIDTH, SVG_HEIGHT) for g in u7: results += drawbox(g, g.lo, g.hi) results += drawlabel(g, g.lo, g.hi) results += "
" results #+end_src #+results: #+BEGIN_export html
TrinsicpathPawspathBritain
#+END_export By stretching and shrinking the map, the Ultima series made the walk from Trinsic to Britain more interesting, but less realistic. ** Skyrim and World of Warcraft :PROPERTIES: :CUSTOM_ID: skyrim-and-world-of-warcraft :END: Ultima not only shrinks the repetitive areas between cities but also repetitive areas within cities. Most buildings have something interesting inside; other buildings are omitted. The same is true in many other open-world games. Skyrim's towns have very few people. Wowwiki says World of Warcraft's Stormwind City has 200,000 residents. Walk around and you'll see fewer than 100 buildings and people. Both games also shrink wilderness areas relative to cities. Stormwind City is as large as Elwynn Forest. Towns are much smaller than realistic towns would be; wilderness areas are extremely small compared to realistic counterparts. ** Civilization and Age of Empires :PROPERTIES: :CUSTOM_ID: civilization-and-age-of-empires :END: We see this same pattern in conquest games, but it manifests differently. You're not an adventurer walking across a map but you instead control military units walking around. Repetitive elements include wilderness, natural resources such as trees, military units, and town buildings. Each of these is reduced in size or number. A large army may consist of tens of "soldiers". Civilization also has a slowing of time. At the beginning of the game, a turn might mean 50 years. At the end of the game, a turn is only 1 year. ** Transport Tycoon :PROPERTIES: :CUSTOM_ID: transport-tycoon :END: In a transportation game the repetitive elements will be vehicles. A freight train in real life might have hundreds of cars but in Transport Tycoon will have fewer than ten. This makes cars easier to manage. A freight track might be 100 trains long; in Transport Tycoon distances between resources are shrunk so the track to train length ratio is much smaller than in real life. This also alters the balance for gameplay. Relative to real life, trains meet each other much more often. Double tracks and complex junctions are needed far more often than single track, making the layouts far more interesting and fun. #+begin_comment SimCity reduces sizes of parking lots http://www.humantransit.org/2013/05/how-sim-city-greenwashes-parking.html #+end_comment ** To ponder :PROPERTIES: :CUSTOM_ID: to-ponder :END: Look at the player's experience in your game. Identify the repetitive elements, both things the player has to look at and the things the player has to do. Shrink repetitive elements in time and/or space. Expand interesting gameplay elements to occupy more of the time and space. These changes will reduce realism but increase fun. #+begin_export html Created May 2014 with Emacs Org-mode, from blog.org. #+end_export