[{"data":1,"prerenderedAt":11127},["ShallowReactive",2],{"blog-dilation-in-pil":3,"blog-surround":390},{"id":4,"title":5,"body":6,"date":380,"description":12,"extension":381,"meta":382,"navigation":51,"path":383,"seo":384,"stem":385,"tags":386,"__hash__":389},"blog\u002Fblog\u002Fdilation-in-pil.md","Dilation in PIL",{"type":7,"value":8,"toc":378},"minimark",[9,13,24,27,354,364,367,374],[10,11,12],"p",{},"Recently I wanted to try and figure out how dilation works and if I could replicate it inside of python using nothing but the PIL library. It's a pretty straightforward bit of code.",[10,14,15,16,23],{},"I found a tutorial online by Ryan Brucks on how to do ",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Fshaderbits.com\u002Fblog\u002Fuv-dilation",[21],"nofollow","Dilation Inside of Unreal"," and applied that to Python.",[10,25,26],{},"First lets check out the code",[28,29,35],"pre",{"className":30,"code":31,"filename":32,"language":33,"meta":34,"style":34},"language-py shiki shiki-themes github-light github-dark","from PIL import Image\n\nOPERATIONS = (\n    lambda idx, width: idx - 1,  # left pixel\n    lambda idx, width: idx - width,  # top pixel\n    lambda idx, width: idx + 1,  # right pixel\n    lambda idx, width: idx + width,  # bottom pixel\n    lambda idx, width: idx - width - 1,  # top left pixel\n    lambda idx, width: idx - width + 1,  # top right pixel\n    lambda idx, width: idx + width - 1,  # botom right pixel\n    lambda idx, width: idx + width + 1,  # bottom left pixel\n)\n\n\ndef dilate_texture(img: Image, iterations: int = 1):\n    \"\"\"\n    This function goes through each pixel and samples it's 8 neighbors and finds a non 0 neighbor to\n    replace the current pixels color with\n    :param img: Image to dilate\n    :param iterations: How many times to run the dilate filter\n    :return: Image the dilated image\n    \"\"\"\n    width = img.width\n    original_alpha = img.split()[-1]\n    for i in range(iterations):\n        pixels = img.getdata()  # create the pixel map\n        data = list()\n        for idx, pixel in enumerate(pixels):  # for every pixel:\n            # add our data in\n            data.append(pixel)\n            alpha = pixel[-1]\n            # if alpha is 0 we want to search for a neighbor with alpha\n            if alpha == 0:\n                for op in OPERATIONS:\n                    try:\n                        # find neighbor pixels\n                        r, g, b, a = pixels[op(idx, width)]\n                        # find the first non 0 alpha neighbor\n                        if a != 0:\n                            # replace the color we set earlier with neighbors\n                            data[idx] = (r, g, b, a)\n                            # bail out and move on to next pixel if we find a neighbor\n                            break\n                    except IndexError:\n                        pass\n        img.putdata(data)\n    img.putalpha(original_alpha)\n    return img\n\n\nimg = Image.open(\"dilate_test_undilated.png\")\nimg = dilate_texture(img, 10)\nimg.save(\"dilate_test_dilated.tga\")\n","dilate.py","py","",[36,37,38,46,53,59,65,71,77,83,89,95,101,107,113,118,123,129,135,141,147,153,159,165,170,176,182,188,194,200,206,212,218,224,230,236,242,248,254,260,266,272,278,284,290,296,302,308,314,320,326,331,336,342,348],"code",{"__ignoreMap":34},[39,40,43],"span",{"class":41,"line":42},"line",1,[39,44,45],{},"from PIL import Image\n",[39,47,49],{"class":41,"line":48},2,[39,50,52],{"emptyLinePlaceholder":51},true,"\n",[39,54,56],{"class":41,"line":55},3,[39,57,58],{},"OPERATIONS = (\n",[39,60,62],{"class":41,"line":61},4,[39,63,64],{},"    lambda idx, width: idx - 1,  # left pixel\n",[39,66,68],{"class":41,"line":67},5,[39,69,70],{},"    lambda idx, width: idx - width,  # top pixel\n",[39,72,74],{"class":41,"line":73},6,[39,75,76],{},"    lambda idx, width: idx + 1,  # right pixel\n",[39,78,80],{"class":41,"line":79},7,[39,81,82],{},"    lambda idx, width: idx + width,  # bottom pixel\n",[39,84,86],{"class":41,"line":85},8,[39,87,88],{},"    lambda idx, width: idx - width - 1,  # top left pixel\n",[39,90,92],{"class":41,"line":91},9,[39,93,94],{},"    lambda idx, width: idx - width + 1,  # top right pixel\n",[39,96,98],{"class":41,"line":97},10,[39,99,100],{},"    lambda idx, width: idx + width - 1,  # botom right pixel\n",[39,102,104],{"class":41,"line":103},11,[39,105,106],{},"    lambda idx, width: idx + width + 1,  # bottom left pixel\n",[39,108,110],{"class":41,"line":109},12,[39,111,112],{},")\n",[39,114,116],{"class":41,"line":115},13,[39,117,52],{"emptyLinePlaceholder":51},[39,119,121],{"class":41,"line":120},14,[39,122,52],{"emptyLinePlaceholder":51},[39,124,126],{"class":41,"line":125},15,[39,127,128],{},"def dilate_texture(img: Image, iterations: int = 1):\n",[39,130,132],{"class":41,"line":131},16,[39,133,134],{},"    \"\"\"\n",[39,136,138],{"class":41,"line":137},17,[39,139,140],{},"    This function goes through each pixel and samples it's 8 neighbors and finds a non 0 neighbor to\n",[39,142,144],{"class":41,"line":143},18,[39,145,146],{},"    replace the current pixels color with\n",[39,148,150],{"class":41,"line":149},19,[39,151,152],{},"    :param img: Image to dilate\n",[39,154,156],{"class":41,"line":155},20,[39,157,158],{},"    :param iterations: How many times to run the dilate filter\n",[39,160,162],{"class":41,"line":161},21,[39,163,164],{},"    :return: Image the dilated image\n",[39,166,168],{"class":41,"line":167},22,[39,169,134],{},[39,171,173],{"class":41,"line":172},23,[39,174,175],{},"    width = img.width\n",[39,177,179],{"class":41,"line":178},24,[39,180,181],{},"    original_alpha = img.split()[-1]\n",[39,183,185],{"class":41,"line":184},25,[39,186,187],{},"    for i in range(iterations):\n",[39,189,191],{"class":41,"line":190},26,[39,192,193],{},"        pixels = img.getdata()  # create the pixel map\n",[39,195,197],{"class":41,"line":196},27,[39,198,199],{},"        data = list()\n",[39,201,203],{"class":41,"line":202},28,[39,204,205],{},"        for idx, pixel in enumerate(pixels):  # for every pixel:\n",[39,207,209],{"class":41,"line":208},29,[39,210,211],{},"            # add our data in\n",[39,213,215],{"class":41,"line":214},30,[39,216,217],{},"            data.append(pixel)\n",[39,219,221],{"class":41,"line":220},31,[39,222,223],{},"            alpha = pixel[-1]\n",[39,225,227],{"class":41,"line":226},32,[39,228,229],{},"            # if alpha is 0 we want to search for a neighbor with alpha\n",[39,231,233],{"class":41,"line":232},33,[39,234,235],{},"            if alpha == 0:\n",[39,237,239],{"class":41,"line":238},34,[39,240,241],{},"                for op in OPERATIONS:\n",[39,243,245],{"class":41,"line":244},35,[39,246,247],{},"                    try:\n",[39,249,251],{"class":41,"line":250},36,[39,252,253],{},"                        # find neighbor pixels\n",[39,255,257],{"class":41,"line":256},37,[39,258,259],{},"                        r, g, b, a = pixels[op(idx, width)]\n",[39,261,263],{"class":41,"line":262},38,[39,264,265],{},"                        # find the first non 0 alpha neighbor\n",[39,267,269],{"class":41,"line":268},39,[39,270,271],{},"                        if a != 0:\n",[39,273,275],{"class":41,"line":274},40,[39,276,277],{},"                            # replace the color we set earlier with neighbors\n",[39,279,281],{"class":41,"line":280},41,[39,282,283],{},"                            data[idx] = (r, g, b, a)\n",[39,285,287],{"class":41,"line":286},42,[39,288,289],{},"                            # bail out and move on to next pixel if we find a neighbor\n",[39,291,293],{"class":41,"line":292},43,[39,294,295],{},"                            break\n",[39,297,299],{"class":41,"line":298},44,[39,300,301],{},"                    except IndexError:\n",[39,303,305],{"class":41,"line":304},45,[39,306,307],{},"                        pass\n",[39,309,311],{"class":41,"line":310},46,[39,312,313],{},"        img.putdata(data)\n",[39,315,317],{"class":41,"line":316},47,[39,318,319],{},"    img.putalpha(original_alpha)\n",[39,321,323],{"class":41,"line":322},48,[39,324,325],{},"    return img\n",[39,327,329],{"class":41,"line":328},49,[39,330,52],{"emptyLinePlaceholder":51},[39,332,334],{"class":41,"line":333},50,[39,335,52],{"emptyLinePlaceholder":51},[39,337,339],{"class":41,"line":338},51,[39,340,341],{},"img = Image.open(\"dilate_test_undilated.png\")\n",[39,343,345],{"class":41,"line":344},52,[39,346,347],{},"img = dilate_texture(img, 10)\n",[39,349,351],{"class":41,"line":350},53,[39,352,353],{},"img.save(\"dilate_test_dilated.tga\")\n",[10,355,356,357,361],{},"The undilated image on the left and the image dilated with one iteration on the right.\n",[358,359],"img",{"src":360},"http:\u002F\u002Fplacehold.it\u002F200x200?text=original",[358,362],{"src":363},"http:\u002F\u002Fplacehold.it\u002F200x200?text=dilated",[10,365,366],{},"It should be noted that this is certainly not fast by any means. A roughly 128x128 image dilated 64 times takes around 33 seconds, but you know, it's python what can you do.",[10,368,369,370],{},"If you have ideas to make it faster please let me know. I ended up making this in c++ as well and it's much faster! ",[17,371,373],{"href":372},"\u002Fblog\u002Fdilation-in-c++","Link here.",[375,376,377],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":48,"depth":48,"links":379},[],"11\u002F02\u002F2021","md",{},"\u002Fblog\u002Fdilation-in-pil",{"title":5,"description":12},"blog\u002Fdilation-in-pil",[36,387,388],"learning","python","GTwTs6hPb0u4jpgkJaQsSc4HTRntU1i45-CErykAVt4",[391,671,1069,1740,2633,2880,3187,3546,4253,5413,6689,6718,6742,7028,7600,7750,10960],{"id":392,"title":393,"body":394,"date":662,"description":663,"extension":381,"meta":664,"navigation":51,"path":665,"seo":666,"stem":667,"tags":668,"__hash__":670},"blog\u002Fblog\u002Fbarycentric-coordinates.md","Barycentric Coordinates",{"type":7,"value":395,"toc":659},[396,399,406,411,423,426,433,436,442,445,454,457,477,480,483,495,498,517,520,530,533,598,601,606,609,656],[10,397,398],{},"Barycentric coordinates are a way to describe a point in reference to the vertices of a triangle. They have a few\ninteresting properties and uses within game development so let's check them out. At the end of this article I hope\nyou can completely understand what barycentric coordinates are, how to calculate them and, what kinds of values you\ncan derive using barycentric coordinates.",[400,401,403],"info-box",{"type":402},"info",[10,404,405],{},"Most of the examples in this article are interactive. Try clicking and dragging on the vertices of the triangles to get a feel for what is going on behind the math!",[407,408,410],"h2",{"id":409},"how-to-calculate-barycentric-coordinates","How to calculate Barycentric Coordinates",[10,412,413,414,417,418,422],{},"In order to calculate barycentric coordinates you need 3 vertices ",[36,415,416],{},"[v1, v2, v3]"," of the ",[419,420,421],"em",{},"Vector"," type",[424,425],"barycentric-slide-1",{},[10,427,428,429,432],{},"We connect these 3 vertices with 3 edge vectors ",[36,430,431],{},"[e1, e2, e3]"," It is important to remember that winding\norder matters here as it determines the orientation of the triangle in space.",[434,435],"barycentric-slide-2",{},[10,437,438,439],{},"These vertices and edge vectors create a triangle ",[36,440,441],{},"T",[443,444],"barycentric-slide-3",{},[10,446,447,448,450,451,453],{},"To find the barycentric coordinates, we need a ",[419,449,421],{}," point ",[36,452,10],{},", for which we want to get the barycentric coordinates.",[455,456],"barycentric-slide-4",{},[10,458,459,460,462,463,465,466,469,470,473,474,476],{},"If we imagine the point ",[36,461,10],{}," is somewhere in the triangle and draw lines from the point ",[36,464,10],{}," to each\nvertex ",[36,467,468],{},"v1, v2, v3"," we can now see how the triangle has been split into 3 sub-triangles ",[36,471,472],{},"T1, T2, T3","\nwithin the main triangle ",[36,475,441],{},".",[478,479],"barycentric-slide-5",{},[10,481,482],{},"We have colored each sub-triangle a different color.",[10,484,485,486,488,489,491,492,494],{},"As we move this point around within the triangle observe how the area of each triangle shrinks and grows depending on\nwhere the point ",[36,487,10],{}," is located. Notice as well if we move the point ",[36,490,10],{}," to the very same positions as any of the\nvertices ",[36,493,468],{}," how it is possible for a triangle to have 0 area. This observation of the areas of\neach triangle is an important one to keep in mind.",[496,497],"barycentric-slide-6",{},[10,499,500,501,503,504,506,507,509,510,512,513,516],{},"If we were to calculate the area of the main triangle ",[36,502,441],{}," and then each sub-triangle ",[36,505,472],{}," and\ndivide each ",[36,508,472],{}," value by the main triangle ",[36,511,441],{}," area this would give us a value in the ",[36,514,515],{},"[0-1]"," range.\nThis value represents the ratio of that sub-triangles area to the area of the main triangle.\nThese are what your barycentric coordinates represent. This is the major gotcha!",[518,519],"barycentric-slide-7",{},[10,521,522,523,526,527,529],{},"Barycentric coordinates are expressed as a vector3 ",[36,524,525],{},"(u, v, w)",", each component being the ratio of a sub-triangle's area to ",[36,528,441],{},"’s area.",[10,531,532],{},"Once you have the barycentric coordinates there are some fun properties you can take advantage of.",[534,535,536],"ul",{},[537,538,539,540],"li",{},"All the components added together always equal 1\n",[534,541,542,548,560,575],{},[537,543,544,547],{},[36,545,546],{},"u + v + w === 1"," is always true for any point expressed in barycentric coordinates.",[537,549,550,551,553,554,556,557,476],{},"The point ",[36,552,10],{}," lies within the triangle ",[36,555,441],{}," when all components are non-negative: ",[36,558,559],{},"u >= 0 && v >= 0 && w >= 0",[537,561,562,563,565,566,572,573,476],{},"If any component is negative, the point ",[36,564,10],{}," is ",[419,567,568],{},[569,570,571],"strong",{},"not"," within the triangle ",[36,574,441],{},[537,576,577,578,580,581,583,584],{},"If the point ",[36,579,10],{}," is on any of the vertices of the triangle ",[36,582,441],{}," exact position it will look like\n",[534,585,586],{},[537,587,588,591,592,591,595],{},[36,589,590],{},"(1, 0, 0)"," or ",[36,593,594],{},"(0, 1, 0)",[36,596,597],{},"(0, 0, 1)",[10,599,600],{},"We can reconstruct the Cartesian position of a point from its barycentric coordinates using a weighted sum of the vertices:",[10,602,603],{},[36,604,605],{},"p′= u∗v1 + v∗v2 + w∗v3",[10,607,608],{},"In code that looks like:",[28,610,614],{"className":611,"code":612,"language":613,"meta":34,"style":34},"language-c shiki shiki-themes github-light github-dark","vector pPrime = u*v1 + v*v2 + w*v3;\n","c",[36,615,616],{"__ignoreMap":34},[39,617,618,622,626,629,632,635,638,641,643,646,648,651,653],{"class":41,"line":42},[39,619,621],{"class":620},"sVt8B","vector pPrime ",[39,623,625],{"class":624},"szBVR","=",[39,627,628],{"class":620}," u",[39,630,631],{"class":624},"*",[39,633,634],{"class":620},"v1 ",[39,636,637],{"class":624},"+",[39,639,640],{"class":620}," v",[39,642,631],{"class":624},[39,644,645],{"class":620},"v2 ",[39,647,637],{"class":624},[39,649,650],{"class":620}," w",[39,652,631],{"class":624},[39,654,655],{"class":620},"v3;\n",[375,657,658],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":48,"depth":48,"links":660},[661],{"id":409,"depth":48,"text":410},"04\u002F18\u002F2022","Barycentric coordinates are a way to describe a point in reference to a triangle. They have a few interesting properties and uses within game development so let's check them out. At the end of this article I hope you can completely understand what barycentric coordinates are, how to calculate them and, what kinds of values you can derive using barycentric coordinates.",{},"\u002Fblog\u002Fbarycentric-coordinates",{"title":393,"description":663},"blog\u002Fbarycentric-coordinates",[669,387],"math","pTwxNvmX_zxWHX3zDRaWh_1c1P3MGf-iw6bJrJ9Acng",{"id":672,"title":673,"body":674,"date":1060,"description":1061,"extension":381,"meta":1062,"navigation":51,"path":1063,"seo":1064,"stem":1065,"tags":1066,"__hash__":1068},"blog\u002Fblog\u002Fcryengine-python-script.md","CryEngine Python Scripts",{"type":7,"value":675,"toc":1035},[676,679,682,698,703,706,714,722,730,733,741,744,752,755,757,769,772,774,782,785,793,795,803,806,809,817,819,827,830,838,840,848,851,859,861,869,872,875,883,885,893,896,899,907,909,917,920,923,930,932,940,943,951,953,961,964,972,974,982,985,993,995,1003,1006,1014,1016,1024,1027],[10,677,678],{},"I maintain a library of CRYENGINE python Editor scripts I use to do my job at Entrada Interactive working on Miscreated. It has a lot of very useful functions that I've automated or found really needed.",[10,680,681],{},"If you need help with a specific script don't hesitate to shoot me a line.",[407,683,685,686,691,692,697],{"id":684},"check-out-the-github-page-or-the-download","Check out the ",[17,687,690],{"href":688,"rel":689},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python",[21],"GitHub Page"," or the ",[17,693,696],{"href":694,"rel":695},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python\u002Freleases\u002Ftag\u002F1.0.0",[21],"download","!",[699,700,702],"h3",{"id":701},"all-of-these-scripts-can-be-run-by-going-to-view-open-view-pane-python-scripts-and-then-select-the-script-and-click-execute-or-double-click-the-script-some-scripts-may-have-dedicated-toolbars-which-you-can-find-by-right-clicking-on-the-toolbar-and-choosing-the-appropriate-toolbar-as-documented-below","All of these scripts can be run by going to View → Open View Pane → \"Python Scripts\" and then select the script and click execute (or double click the script). Some scripts may have dedicated toolbars which you can find by right clicking on the toolbar and Choosing the appropriate toolbar as documented below",[704,705],"hr",{},[407,707,709,713],{"id":708},"capturemovie-toolbar",[17,710],{"href":711,"rel":712},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#capturemovie-toolbar",[21],"CaptureMovie Toolbar",[10,715,716],{},[17,717,720],{"href":718,"rel":719},"https:\u002F\u002Fcamo.githubusercontent.com\u002F568d4e8c1df220adece90b449360b8523db5c6f7\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d31312d30375f36323331616535642d383637392d346136612d383439312d6533333366353838663930352e706e67",[21],[358,721],{"alt":34,"src":718},[699,723,725,729],{"id":724},"setup-movie",[17,726],{"href":727,"rel":728},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#setup-movie",[21],"Setup Movie",[10,731,732],{},"Asks the user a series of questions that configure some options for capturing a video in the editor. Opens the outputfolder so you can watch the frames as they come in",[699,734,736,740],{"id":735},"start-recording",[17,737],{"href":738,"rel":739},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#start-recording",[21],"Start Recording",[10,742,743],{},"Starts capturing frames and plays a trackview at the same time",[699,745,747,751],{"id":746},"stop-recording",[17,748],{"href":749,"rel":750},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#stop-recording",[21],"Stop Recording",[10,753,754],{},"Stops an playing trackview and stops any capturing of frames",[704,756],{},[407,758,760,764,768],{"id":759},"creating-item-icons-icon-toolbar",[17,761],{"href":762,"rel":763},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#creating-item-icons---icon-toolbar",[21],[17,765,767],{"href":688,"rel":766},[21],"Creating Item Icons"," - Icon Toolbar",[10,770,771],{},"Guide on how to create icons for item files available by clicking link above",[704,773],{},[407,775,777,781],{"id":776},"distribute_objectspy-distribute-toolbar",[17,778],{"href":779,"rel":780},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#distribute_objectspy---distribute-toolbar",[21],"distribute_objects.py - Distribute Toolbar",[10,783,784],{},"With one or m ore brushes selected choose a plane to distribute the objects randomly along that plane.",[10,786,787],{},[17,788,791],{"href":789,"rel":790},"https:\u002F\u002Fcamo.githubusercontent.com\u002F76c60c1916c3d670582d023e073dc90c981d33a4\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d31302d30335f66363865326161642d343764322d346636392d393464362d3864306239343336636437622e676966",[21],[358,792],{"alt":34,"src":789},[704,794],{},[407,796,798,802],{"id":797},"simulatebrushpy-transformation-toolbar",[17,799],{"href":800,"rel":801},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#simulatebrushpy---transformation-toolbar",[21],"simulatebrush.py - Transformation Toolbar",[10,804,805],{},"With a single brush selected run the script to convert it to a rigid body, and simulate the rigid body.",[10,807,808],{},"After the simulation run the script again to convert it back to a brush",[10,810,811],{},[17,812,815],{"href":813,"rel":814},"https:\u002F\u002Fcamo.githubusercontent.com\u002F39c19d477554cfdac062a99498a2138d6a109b60\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f63633465373062622d633739382d343133362d613965302d3562326366396233323332302e676966",[21],[358,816],{"alt":34,"src":813},[704,818],{},[407,820,822,826],{"id":821},"rotate_randomlypy-transformation-toolbar",[17,823],{"href":824,"rel":825},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#rotate_randomlypy---transformation-toolbar",[21],"rotate_randomly.py - Transformation Toolbar",[10,828,829],{},"Randomly rotate all selected items.",[10,831,832],{},[17,833,836],{"href":834,"rel":835},"https:\u002F\u002Fcamo.githubusercontent.com\u002Fff78a526b8c753e501128bd077afbe51bd60c008\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31315f35646435666536632d323065642d343832632d626134612d3031383435656538623633612e676966",[21],[358,837],{"alt":34,"src":834},[704,839],{},[407,841,843,847],{"id":842},"unrotatepy-transformation-toolbar",[17,844],{"href":845,"rel":846},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#unrotatepy---transformation-toolbar",[21],"unrotate.py - Transformation Toolbar",[10,849,850],{},"Set the rotation to (0,0,0) on all selected objects",[10,852,853],{},[17,854,857],{"href":855,"rel":856},"https:\u002F\u002Fcamo.githubusercontent.com\u002F9b34daa4474fc6fadcbd2a3ed6b65cb6debb6a11\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31315f32396136646362632d313263382d346633642d393835362d6363356432613934656666332e676966",[21],[358,858],{"alt":34,"src":855},[704,860],{},[407,862,864,868],{"id":863},"align_itemspy-alignment-toolbar",[17,865],{"href":866,"rel":867},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#align_itemspy---alignment-toolbar",[21],"align_items.py - Alignment Toolbar",[10,870,871],{},"Aligns selected objects to a specific axis",[10,873,874],{},"(Run using the buttons on the Alignment Toolbar)",[10,876,877],{},[17,878,881],{"href":879,"rel":880},"https:\u002F\u002Fcamo.githubusercontent.com\u002Fa1da87166b4324d0312e2590d6db26065f7c4148\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f37323936326638642d646530382d346533652d613431362d3039373432313735643238392e676966",[21],[358,882],{"alt":34,"src":879},[704,884],{},[407,886,888,892],{"id":887},"xform_copypy-transformation-toolber",[17,889],{"href":890,"rel":891},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#xform_copypy---transformation-toolber",[21],"xform_copy.py - Transformation Toolber",[10,894,895],{},"Copy a single items xforms to be used elsewhere",[10,897,898],{},"(Run using the Copy Xforms button on the Transformation Toolbar)",[10,900,901],{},[17,902,905],{"href":903,"rel":904},"https:\u002F\u002Fcamo.githubusercontent.com\u002F355ff321b30356bc806e61eea38be5d758726ef9\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f61643539336231612d623430302d343438362d616136362d3666346161616566613937342e676966",[21],[358,906],{"alt":34,"src":903},[704,908],{},[407,910,912,916],{"id":911},"xform_pastepy-transformation-toolbar",[17,913],{"href":914,"rel":915},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#xform_pastepy---transformation-toolbar",[21],"xform_paste.py - Transformation Toolbar",[10,918,919],{},"Pastes copied xforms to a single selected item",[10,921,922],{},"(Run using the Paste Xforms buttons on the Transformation Toolbar)",[10,924,925],{},[17,926,928],{"href":903,"rel":927},[21],[358,929],{"alt":34,"src":903},[704,931],{},[407,933,935,939],{"id":934},"grabentitiespy-eitools-toolbar",[17,936],{"href":937,"rel":938},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#grabentitiespy---eitools-toolbar",[21],"grabentities.py - EiTools Toolbar",[10,941,942],{},"Add one or more entities to the level by xml file",[10,944,945],{},[17,946,949],{"href":947,"rel":948},"https:\u002F\u002Fcamo.githubusercontent.com\u002F770fbc2173cb1251e2b8ea24d4232a20bcdb6167\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f31663537623863652d376336302d343637322d623362622d6632323831386237663632362e676966",[21],[358,950],{"alt":34,"src":947},[704,952],{},[407,954,956,960],{"id":955},"grab_cgfs_from_folderpy-eitools-toolbar",[17,957],{"href":958,"rel":959},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#grab_cgfs_from_folderpy---eitools-toolbar",[21],"grab_cgfs_from_folder.py - EiTools Toolbar",[10,962,963],{},"Add one or more cgf files to the level by cgf file",[10,965,966],{},[17,967,970],{"href":968,"rel":969},"https:\u002F\u002Fcamo.githubusercontent.com\u002F84a515488d9ee1f9c43779e52ded2490b9ef575d\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f33306433633138342d656365652d343263362d386331642d6662333234333730313162382e676966",[21],[358,971],{"alt":34,"src":968},[704,973],{},[407,975,977,981],{"id":976},"kill_editorpy-eitools-toolbar",[17,978],{"href":979,"rel":980},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#kill_editorpy---eitools-toolbar",[21],"kill_editor.py - EiTools Toolbar",[10,983,984],{},"Instantly kill the editor",[10,986,987],{},[17,988,991],{"href":989,"rel":990},"https:\u002F\u002Fcamo.githubusercontent.com\u002Fcf690ca6298f26ff22fdf6b5f0663b2753fe483f\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f31373865353636302d666636352d343163322d623763352d6163333635313532643066662e676966",[21],[358,992],{"alt":34,"src":989},[704,994],{},[407,996,998,1002],{"id":997},"open_in_expolrerpy-eitools-toolbar",[17,999],{"href":1000,"rel":1001},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#open_in_expolrerpy---eitools-toolbar",[21],"open_in_expolrer.py - EiTools Toolbar",[10,1004,1005],{},"View the currently selected brush's cgf in windows explorer",[10,1007,1008],{},[17,1009,1012],{"href":1010,"rel":1011},"https:\u002F\u002Fcamo.githubusercontent.com\u002Fda3127c1904a5b8f1cbe49935ba50e6b91574000\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f36393763323737322d666664382d343063312d396362642d6263303832653535313537372e676966",[21],[358,1013],{"alt":34,"src":1010},[704,1015],{},[407,1017,1019,1023],{"id":1018},"take_screenshotpy-eitools-toolbar",[17,1020],{"href":1021,"rel":1022},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002FCE_Python#take_screenshotpy---eitools-toolbar",[21],"take_screenshot.py - EiTools Toolbar",[10,1025,1026],{},"Take a screenshot and open the screenshots folder",[10,1028,1029],{},[17,1030,1033],{"href":1031,"rel":1032},"https:\u002F\u002Fcamo.githubusercontent.com\u002F4cb60111998e676f4fbb76c5f8f805ac2b6650c4\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031382d30362d31325f34663534336437622d323332312d346164382d396666662d3061613237383836373262612e676966",[21],[358,1034],{"alt":34,"src":1031},{"title":34,"searchDepth":48,"depth":48,"links":1036},[1037,1041,1046,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059],{"id":684,"depth":48,"text":1038,"children":1039},"Check out the GitHub Page or the download!",[1040],{"id":701,"depth":55,"text":702},{"id":708,"depth":48,"text":713,"children":1042},[1043,1044,1045],{"id":724,"depth":55,"text":729},{"id":735,"depth":55,"text":740},{"id":746,"depth":55,"text":751},{"id":759,"depth":48,"text":1047},"Creating Item Icons - Icon Toolbar",{"id":776,"depth":48,"text":781},{"id":797,"depth":48,"text":802},{"id":821,"depth":48,"text":826},{"id":842,"depth":48,"text":847},{"id":863,"depth":48,"text":868},{"id":887,"depth":48,"text":892},{"id":911,"depth":48,"text":916},{"id":934,"depth":48,"text":939},{"id":955,"depth":48,"text":960},{"id":976,"depth":48,"text":981},{"id":997,"depth":48,"text":1002},{"id":1018,"depth":48,"text":1023},"02\u002F01\u002F2021","A library of CRYENGINE python Editor scripts I use to do my job at Entrada Interactive working on Miscreated. It has a lot of very useful functions that I've automated or found really needed.",{},"\u002Fblog\u002Fcryengine-python-script",{"title":673,"description":1061},"blog\u002Fcryengine-python-script",[1067,388],"cryengine","p7rVD_0y3dKIii3AjmupPUxQS3BQYU4qjx2DYLIJni4",{"id":1070,"title":1071,"body":1072,"date":1729,"description":1730,"extension":381,"meta":1731,"navigation":51,"path":1732,"seo":1733,"stem":1734,"tags":1735,"__hash__":1739},"blog\u002Fblog\u002Fcyengine-lod-maxscript.md","CRYENGINE Lods Maxscript",{"type":7,"value":1073,"toc":1725},[1074,1077,1080,1088,1092,1109,1118,1126,1143,1151,1722],[10,1075,1076],{},"During my work on Miscreated it was a common task to have to create LOD's for most props\u002Fweapons\u002Fcharacters. So I found a way to automate this in 3dsMax using the standard toolset provided with Max.",[10,1078,1079],{},"I present to you an automated Cryengine LOD creation script in 3ds max using maxscript and ProOptimizer.",[10,1081,1082],{},[17,1083,1087],{"href":1084,"rel":1085,"title":1086},"https:\u002F\u002Fgist.github.com\u002Fcsprance\u002Fed21190a1b29f8ac71fd81a67209dc80",[21],"Gist Page on GitHub","GitHub Page Here",[407,1089,1091],{"id":1090},"installation","Installation",[534,1093,1094,1097,1100,1103,1106],{},[537,1095,1096],{},"Open up the MaxScript Editor from within 3ds max",[537,1098,1099],{},"Paste in the CreateLODS.ms code",[537,1101,1102],{},"Select all the code in the MaxScript Editor",[537,1104,1105],{},"Drag and Drop the code into the toolbar",[537,1107,1108],{},"Adjust button to needs",[10,1110,1111],{},[17,1112,1115],{"href":1113,"rel":1114},"https:\u002F\u002Fcamo.githubusercontent.com\u002F3ad6bbc740441e6f21f58823de8771bf694098e9\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031392d30312d30375f61353266363762382d313032662d343433632d616430652d3638326639303936363230342e676966",[21],[358,1116],{"alt":1117,"src":1113},"Install",[407,1119,1121,1125],{"id":1120},"usage",[17,1122],{"href":1123,"rel":1124},"https:\u002F\u002Fgist.github.com\u002Fcsprance\u002Fed21190a1b29f8ac71fd81a67209dc80#usage",[21],"Usage",[534,1127,1128,1131,1134,1137,1140],{},[537,1129,1130],{},"Select the mesh to create lods for",[537,1132,1133],{},"Open the LOD tool",[537,1135,1136],{},"Adjust the settings",[537,1138,1139],{},"Click execute",[537,1141,1142],{},"Tadah LODS!",[10,1144,1145],{},[17,1146,1149],{"href":1147,"rel":1148},"https:\u002F\u002Fcamo.githubusercontent.com\u002Fcb9594b57c2c8518b039c6c7c34d0d27c2eb29df\u002F68747470733a2f2f63737072616e63652e636f6d2f73686f74732f323031392d30312d30375f61333036343165372d623837302d343336622d613033382d6364613830373064313635382e676966",[21],[358,1150],{"alt":1125,"src":1147},[28,1152,1155],{"className":611,"code":1153,"filename":1154,"language":613,"meta":34,"style":34},"-- Define the UI Rollout\ntry (destroyDialog EILodMaker) catch()\nrollout EILodMaker \"Lod Maker\" width:150 height:250 (\n    label lbl_num_lods \"Number of Lods\"\n    spinner num_lods_spinner \"\"  across:2 offset:[20,0] width:124.05 usePercentageWidth:true percentageWidth:82.7 enabled:true type:#integer range:[0,6,2]\n    checkbox chk_collapse \"Collapse?\"  pos:[18,52] checked:false\n    group \"Advanced Options\" (\n        spinner spinner_lod1 \"LOD 1\"  range:[0,100,50]   type:#float\n        spinner spinner_lod2 \"LOD 2\"  range:[0,100,24.5] type:#float\n        spinner spinner_lod3 \"LOD 3\"  range:[0,100,11.4] type:#float\n        spinner spinner_lod4 \"LOD 4\"  range:[0,100,4.2]  type:#float\n        spinner spinner_lod5 \"LOD 5\"  range:[0,100,3]    type:#float\n        spinner spinner_lod6 \"LOD 6\"  range:[0,100,1]    type:#float\n    )\n    button btn_exec \"Execute\"  width:150\n    on btn_exec pressed do (\n        lodSizes = #(spinner_lod1.value, spinner_lod2.value, spinner_lod3.value, spinner_lod4.value, spinner_lod5.value, spinner_lod6.value)\n        for obj in (selection as array) do (\n            lodNodes = for idx in 1 to num_lods_spinner.value collect (copy obj)\n            idx = 1\n            for lodNode in lodNodes do (\n                select lodNode\n                modPanel.addModToSelection (ProOptimizer ())\n                lodNode.modifiers[#ProOptimizer].VertexPercent = lodSizes[idx]\n                lodNode.modifiers[#ProOptimizer].KeepUV = on\n                lodNode.modifiers[#ProOptimizer].Calculate = on\n                lodNode.name = \"$lod0\" + idx as string\n                lodNode.parent = obj\n                idx = idx + 1\n                deselect $\n            )\n        )\n    )\n)\ncreateDialog EILodMaker\n","CreateLODS.ms",[36,1156,1157,1165,1181,1206,1214,1280,1305,1315,1344,1370,1395,1421,1447,1472,1477,1491,1501,1511,1535,1564,1574,1586,1591,1608,1627,1641,1654,1670,1680,1694,1699,1704,1709,1713,1717],{"__ignoreMap":34},[39,1158,1159,1162],{"class":41,"line":42},[39,1160,1161],{"class":624},"--",[39,1163,1164],{"class":620}," Define the UI Rollout\n",[39,1166,1167,1171,1174,1178],{"class":41,"line":48},[39,1168,1170],{"class":1169},"sScJk","try",[39,1172,1173],{"class":620}," (destroyDialog ",[39,1175,1177],{"class":1176},"s4XuR","EILodMaker",[39,1179,1180],{"class":620},") catch()\n",[39,1182,1183,1186,1190,1193,1197,1200,1203],{"class":41,"line":55},[39,1184,1185],{"class":620},"rollout EILodMaker ",[39,1187,1189],{"class":1188},"sZZnC","\"Lod Maker\"",[39,1191,1192],{"class":620}," width:",[39,1194,1196],{"class":1195},"sj4cs","150",[39,1198,1199],{"class":620}," height:",[39,1201,1202],{"class":1195},"250",[39,1204,1205],{"class":620}," (\n",[39,1207,1208,1211],{"class":41,"line":61},[39,1209,1210],{"class":620},"    label lbl_num_lods ",[39,1212,1213],{"class":1188},"\"Number of Lods\"\n",[39,1215,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1263,1266,1268,1270,1273,1275,1277],{"class":41,"line":67},[39,1217,1218],{"class":620},"    spinner num_lods_spinner ",[39,1220,1221],{"class":1188},"\"\"",[39,1223,1224],{"class":620},"  across:",[39,1226,1227],{"class":1195},"2",[39,1229,1230],{"class":620}," offset:[",[39,1232,1233],{"class":1195},"20",[39,1235,1236],{"class":620},",",[39,1238,1239],{"class":1195},"0",[39,1241,1242],{"class":620},"] width:",[39,1244,1245],{"class":1195},"124.05",[39,1247,1248],{"class":620}," usePercentageWidth:",[39,1250,1251],{"class":1195},"true",[39,1253,1254],{"class":620}," percentageWidth:",[39,1256,1257],{"class":1195},"82.7",[39,1259,1260],{"class":620}," enabled:",[39,1262,1251],{"class":1195},[39,1264,1265],{"class":620}," type:#integer range:[",[39,1267,1239],{"class":1195},[39,1269,1236],{"class":620},[39,1271,1272],{"class":1195},"6",[39,1274,1236],{"class":620},[39,1276,1227],{"class":1195},[39,1278,1279],{"class":620},"]\n",[39,1281,1282,1285,1288,1291,1294,1296,1299,1302],{"class":41,"line":73},[39,1283,1284],{"class":620},"    checkbox chk_collapse ",[39,1286,1287],{"class":1188},"\"Collapse?\"",[39,1289,1290],{"class":620},"  pos:[",[39,1292,1293],{"class":1195},"18",[39,1295,1236],{"class":620},[39,1297,1298],{"class":1195},"52",[39,1300,1301],{"class":620},"] checked:",[39,1303,1304],{"class":1195},"false\n",[39,1306,1307,1310,1313],{"class":41,"line":79},[39,1308,1309],{"class":620},"    group ",[39,1311,1312],{"class":1188},"\"Advanced Options\"",[39,1314,1205],{"class":620},[39,1316,1317,1320,1323,1326,1328,1330,1333,1335,1338,1341],{"class":41,"line":85},[39,1318,1319],{"class":620},"        spinner spinner_lod1 ",[39,1321,1322],{"class":1188},"\"LOD 1\"",[39,1324,1325],{"class":620},"  range:[",[39,1327,1239],{"class":1195},[39,1329,1236],{"class":620},[39,1331,1332],{"class":1195},"100",[39,1334,1236],{"class":620},[39,1336,1337],{"class":1195},"50",[39,1339,1340],{"class":620},"]   type:#",[39,1342,1343],{"class":624},"float\n",[39,1345,1346,1349,1352,1354,1356,1358,1360,1362,1365,1368],{"class":41,"line":91},[39,1347,1348],{"class":620},"        spinner spinner_lod2 ",[39,1350,1351],{"class":1188},"\"LOD 2\"",[39,1353,1325],{"class":620},[39,1355,1239],{"class":1195},[39,1357,1236],{"class":620},[39,1359,1332],{"class":1195},[39,1361,1236],{"class":620},[39,1363,1364],{"class":1195},"24.5",[39,1366,1367],{"class":620},"] type:#",[39,1369,1343],{"class":624},[39,1371,1372,1375,1378,1380,1382,1384,1386,1388,1391,1393],{"class":41,"line":97},[39,1373,1374],{"class":620},"        spinner spinner_lod3 ",[39,1376,1377],{"class":1188},"\"LOD 3\"",[39,1379,1325],{"class":620},[39,1381,1239],{"class":1195},[39,1383,1236],{"class":620},[39,1385,1332],{"class":1195},[39,1387,1236],{"class":620},[39,1389,1390],{"class":1195},"11.4",[39,1392,1367],{"class":620},[39,1394,1343],{"class":624},[39,1396,1397,1400,1403,1405,1407,1409,1411,1413,1416,1419],{"class":41,"line":103},[39,1398,1399],{"class":620},"        spinner spinner_lod4 ",[39,1401,1402],{"class":1188},"\"LOD 4\"",[39,1404,1325],{"class":620},[39,1406,1239],{"class":1195},[39,1408,1236],{"class":620},[39,1410,1332],{"class":1195},[39,1412,1236],{"class":620},[39,1414,1415],{"class":1195},"4.2",[39,1417,1418],{"class":620},"]  type:#",[39,1420,1343],{"class":624},[39,1422,1423,1426,1429,1431,1433,1435,1437,1439,1442,1445],{"class":41,"line":109},[39,1424,1425],{"class":620},"        spinner spinner_lod5 ",[39,1427,1428],{"class":1188},"\"LOD 5\"",[39,1430,1325],{"class":620},[39,1432,1239],{"class":1195},[39,1434,1236],{"class":620},[39,1436,1332],{"class":1195},[39,1438,1236],{"class":620},[39,1440,1441],{"class":1195},"3",[39,1443,1444],{"class":620},"]    type:#",[39,1446,1343],{"class":624},[39,1448,1449,1452,1455,1457,1459,1461,1463,1465,1468,1470],{"class":41,"line":115},[39,1450,1451],{"class":620},"        spinner spinner_lod6 ",[39,1453,1454],{"class":1188},"\"LOD 6\"",[39,1456,1325],{"class":620},[39,1458,1239],{"class":1195},[39,1460,1236],{"class":620},[39,1462,1332],{"class":1195},[39,1464,1236],{"class":620},[39,1466,1467],{"class":1195},"1",[39,1469,1444],{"class":620},[39,1471,1343],{"class":624},[39,1473,1474],{"class":41,"line":120},[39,1475,1476],{"class":620},"    )\n",[39,1478,1479,1482,1485,1488],{"class":41,"line":125},[39,1480,1481],{"class":620},"    button btn_exec ",[39,1483,1484],{"class":1188},"\"Execute\"",[39,1486,1487],{"class":620},"  width:",[39,1489,1490],{"class":1195},"150\n",[39,1492,1493,1496,1499],{"class":41,"line":131},[39,1494,1495],{"class":620},"    on btn_exec pressed ",[39,1497,1498],{"class":624},"do",[39,1500,1205],{"class":620},[39,1502,1503,1506,1508],{"class":41,"line":137},[39,1504,1505],{"class":620},"        lodSizes ",[39,1507,625],{"class":624},[39,1509,1510],{"class":620}," #(spinner_lod1.value, spinner_lod2.value, spinner_lod3.value, spinner_lod4.value, spinner_lod5.value, spinner_lod6.value)\n",[39,1512,1513,1516,1519,1522,1525,1528,1531,1533],{"class":41,"line":143},[39,1514,1515],{"class":624},"        for",[39,1517,1518],{"class":620}," obj ",[39,1520,1521],{"class":1169},"in",[39,1523,1524],{"class":620}," (selection as ",[39,1526,1527],{"class":1176},"array",[39,1529,1530],{"class":620},") ",[39,1532,1498],{"class":624},[39,1534,1205],{"class":620},[39,1536,1537,1540,1542,1545,1548,1550,1553,1556,1559,1562],{"class":41,"line":149},[39,1538,1539],{"class":620},"            lodNodes ",[39,1541,625],{"class":624},[39,1543,1544],{"class":624}," for",[39,1546,1547],{"class":620}," idx in ",[39,1549,1467],{"class":1195},[39,1551,1552],{"class":620}," to num_lods_spinner.value ",[39,1554,1555],{"class":1169},"collect",[39,1557,1558],{"class":620}," (copy ",[39,1560,1561],{"class":1176},"obj",[39,1563,112],{"class":620},[39,1565,1566,1569,1571],{"class":41,"line":155},[39,1567,1568],{"class":620},"            idx ",[39,1570,625],{"class":624},[39,1572,1573],{"class":1195}," 1\n",[39,1575,1576,1579,1582,1584],{"class":41,"line":161},[39,1577,1578],{"class":624},"            for",[39,1580,1581],{"class":620}," lodNode in lodNodes ",[39,1583,1498],{"class":624},[39,1585,1205],{"class":620},[39,1587,1588],{"class":41,"line":167},[39,1589,1590],{"class":620},"                select lodNode\n",[39,1592,1593,1596,1599,1602,1605],{"class":41,"line":172},[39,1594,1595],{"class":620},"                modPanel.",[39,1597,1598],{"class":1169},"addModToSelection",[39,1600,1601],{"class":620}," (",[39,1603,1604],{"class":1169},"ProOptimizer",[39,1606,1607],{"class":620}," ())\n",[39,1609,1610,1613,1616,1619,1621,1624],{"class":41,"line":178},[39,1611,1612],{"class":620},"                lodNode.",[39,1614,1615],{"class":1176},"modifiers",[39,1617,1618],{"class":620},"[#ProOptimizer].VertexPercent ",[39,1620,625],{"class":624},[39,1622,1623],{"class":1176}," lodSizes",[39,1625,1626],{"class":620},"[idx]\n",[39,1628,1629,1631,1633,1636,1638],{"class":41,"line":184},[39,1630,1612],{"class":620},[39,1632,1615],{"class":1176},[39,1634,1635],{"class":620},"[#ProOptimizer].KeepUV ",[39,1637,625],{"class":624},[39,1639,1640],{"class":620}," on\n",[39,1642,1643,1645,1647,1650,1652],{"class":41,"line":190},[39,1644,1612],{"class":620},[39,1646,1615],{"class":1176},[39,1648,1649],{"class":620},"[#ProOptimizer].Calculate ",[39,1651,625],{"class":624},[39,1653,1640],{"class":620},[39,1655,1656,1659,1661,1664,1667],{"class":41,"line":196},[39,1657,1658],{"class":620},"                lodNode.name ",[39,1660,625],{"class":624},[39,1662,1663],{"class":1188}," \"$lod0\"",[39,1665,1666],{"class":624}," +",[39,1668,1669],{"class":620}," idx as string\n",[39,1671,1672,1675,1677],{"class":41,"line":202},[39,1673,1674],{"class":620},"                lodNode.parent ",[39,1676,625],{"class":624},[39,1678,1679],{"class":620}," obj\n",[39,1681,1682,1685,1687,1690,1692],{"class":41,"line":208},[39,1683,1684],{"class":620},"                idx ",[39,1686,625],{"class":624},[39,1688,1689],{"class":620}," idx ",[39,1691,637],{"class":624},[39,1693,1573],{"class":1195},[39,1695,1696],{"class":41,"line":214},[39,1697,1698],{"class":620},"                deselect $\n",[39,1700,1701],{"class":41,"line":220},[39,1702,1703],{"class":620},"            )\n",[39,1705,1706],{"class":41,"line":226},[39,1707,1708],{"class":620},"        )\n",[39,1710,1711],{"class":41,"line":232},[39,1712,1476],{"class":620},[39,1714,1715],{"class":41,"line":238},[39,1716,112],{"class":620},[39,1718,1719],{"class":41,"line":244},[39,1720,1721],{"class":620},"createDialog EILodMaker\n",[375,1723,1724],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":48,"depth":48,"links":1726},[1727,1728],{"id":1090,"depth":48,"text":1091},{"id":1120,"depth":48,"text":1125},"02\u002F11\u002F2021","Automated Cryengine LOD creation script in 3ds max using maxscript and ProOptimizer.",{},"\u002Fblog\u002Fcyengine-lod-maxscript",{"title":1071,"description":1730},"blog\u002Fcyengine-lod-maxscript",[1736,1737,1738],"max","maxscript","games","p89i_sSQev5svjDXYjCxDXFwERGvwrumnJy4C_efyPs",{"id":1741,"title":1742,"body":1743,"date":2625,"description":2626,"extension":381,"meta":2627,"navigation":51,"path":2628,"seo":2629,"stem":2630,"tags":2631,"__hash__":2632},"blog\u002Fblog\u002Fdilation-in-cpp.md","Dilation in C++",{"type":7,"value":1744,"toc":2623},[1745,1752,2611,2614,2617,2620],[10,1746,1747,1748,1751],{},"This is a follow-up to another ",[17,1749,1750],{"href":383},"blog I did just recently in which I made a dilate texture function inside of Python",".\nThe speed of the python dilate was really, really slow, so I decided to remake this in C++ and see what kind of speed I'd get. I am using C++ and OpenCV to do most of the work.",[28,1753,1758],{"className":1754,"code":1755,"filename":1756,"language":1757,"meta":34,"style":34},"language-cpp shiki shiki-themes github-light github-dark","\u002F\u002F Arg parsed variables\nconst string input_file = parser.get\u003Cstring>(\"i\");\nconst string output_file = parser.get\u003Cstring>(\"o\");\nconst int iterations = parser.get\u003Cint>(\"n\");\nconst string extension = get_extension(output_file);\n\n\u002F\u002F Read File (PNG, Tiff, JPG)\nMat_\u003CVec4b> img = imread(input_file, IMREAD_UNCHANGED);\n\u002F\u002F Clone it\nMat dst = img.clone();\n\u002F\u002F Split in to R,G,B,A\nMat rgbchannel[4];\nsplit(img, rgbchannel);\nMat og_alpha = rgbchannel[3];\n\n\u002F\u002F For each iteration\nfor (int iter = 0; iter \u003C iterations; iter++) {\n    \u002F\u002F For every pixel (row\u002Fcol)\n    for (int row = 0; row \u003C img.rows; row++) {\n        for (int col = 0; col \u003C img.cols; col++) {\n            \u002F\u002F Get Pixel at row col\n            auto pixel = img.at\u003CVec4b>(row, col);\n            \u002F\u002F if alpha of pixel is less then 255 we want to search for a neighbor with alpha\n            if (pixel[3] \u003C 255) {\n                for (auto & neighbor : neighbors) {\n                    \u002F\u002F make sure our index doesn't go out of bounds\n                    int n_row = row + neighbor[0];\n                    int n_col = col + neighbor[1];\n                    if (row_col_in_bounds(n_row, n_col, img)) {\n                        \u002F\u002F find neighbor pixels\n                        auto n_pixel = img.at\u003CVec4b>(n_row, n_col);\n                        \u002F\u002F find the first non 0 neighbor\n                        if (n_pixel[3] > 0) {\n                            \u002F\u002F Replace the color in the dest image with this color\n                            dst.at\u003CVec4b>(row, col) = Vec4b(n_pixel[0], n_pixel[1], n_pixel[2], 255);\n                            \u002F\u002F Bail out of the loop\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n    \u002F\u002F Set the img to be the dest so on the next iteration\n    \u002F\u002F the img being sampled is the previously dilated image\n    img = dst;\n}\n\n\u002F\u002F split the channels in to RGBA\nMat dest_rgb_channel[4];\nsplit(dst, dest_rgb_channel);\n\u002F\u002F Assign RGB channels to vars\nconst Mat red = dest_rgb_channel[0],\n    green = dest_rgb_channel[1],\n    blue = dest_rgb_channel[2];\n\n\u002F\u002F If extension is TGA (swap RGB -> BGR for tga)\nif (extension == \"tga\") {\n    dest_rgb_channel[0] = blue; \u002F\u002F Blue\n    dest_rgb_channel[1] = green; \u002F\u002F Green\n    dest_rgb_channel[2] = red; \u002F\u002F Red\n}\nelse {\n    dest_rgb_channel[0] = red; \u002F\u002F Red\n    dest_rgb_channel[1] = green; \u002F\u002F Green\n    dest_rgb_channel[2] = blue; \u002F\u002F Blue\n}\n\n\u002F\u002F merge the original alpha back in to the image\ndest_rgb_channel[3] = og_alpha;\n\n\u002F\u002F Merge all the channels back in to the dst image they came from\nmerge(dest_rgb_channel, 4, dst);\n\n","file.cpp","cpp",[36,1759,1760,1766,1797,1821,1845,1860,1864,1869,1892,1897,1913,1918,1929,1937,1951,1955,1960,1992,1997,2025,2052,2057,2079,2084,2104,2120,2125,2146,2165,2178,2183,2204,2209,2227,2232,2273,2278,2286,2291,2296,2301,2306,2311,2316,2321,2326,2336,2341,2345,2350,2359,2366,2371,2388,2402,2416,2421,2427,2444,2462,2479,2496,2501,2510,2525,2540,2555,2560,2565,2571,2586,2591,2597],{"__ignoreMap":34},[39,1761,1762],{"class":41,"line":42},[39,1763,1765],{"class":1764},"sJ8bj","\u002F\u002F Arg parsed variables\n",[39,1767,1768,1771,1774,1776,1779,1782,1785,1788,1791,1794],{"class":41,"line":48},[39,1769,1770],{"class":624},"const",[39,1772,1773],{"class":620}," string input_file ",[39,1775,625],{"class":624},[39,1777,1778],{"class":620}," parser.get",[39,1780,1781],{"class":624},"\u003C",[39,1783,1784],{"class":620},"string",[39,1786,1787],{"class":624},">",[39,1789,1790],{"class":620},"(",[39,1792,1793],{"class":1188},"\"i\"",[39,1795,1796],{"class":620},");\n",[39,1798,1799,1801,1804,1806,1808,1810,1812,1814,1816,1819],{"class":41,"line":55},[39,1800,1770],{"class":624},[39,1802,1803],{"class":620}," string output_file ",[39,1805,625],{"class":624},[39,1807,1778],{"class":620},[39,1809,1781],{"class":624},[39,1811,1784],{"class":620},[39,1813,1787],{"class":624},[39,1815,1790],{"class":620},[39,1817,1818],{"class":1188},"\"o\"",[39,1820,1796],{"class":620},[39,1822,1823,1825,1828,1831,1833,1835,1838,1840,1843],{"class":41,"line":61},[39,1824,1770],{"class":624},[39,1826,1827],{"class":624}," int",[39,1829,1830],{"class":620}," iterations ",[39,1832,625],{"class":624},[39,1834,1778],{"class":620},[39,1836,1837],{"class":624},"\u003Cint>",[39,1839,1790],{"class":620},[39,1841,1842],{"class":1188},"\"n\"",[39,1844,1796],{"class":620},[39,1846,1847,1849,1852,1854,1857],{"class":41,"line":67},[39,1848,1770],{"class":624},[39,1850,1851],{"class":620}," string extension ",[39,1853,625],{"class":624},[39,1855,1856],{"class":1169}," get_extension",[39,1858,1859],{"class":620},"(output_file);\n",[39,1861,1862],{"class":41,"line":73},[39,1863,52],{"emptyLinePlaceholder":51},[39,1865,1866],{"class":41,"line":79},[39,1867,1868],{"class":1764},"\u002F\u002F Read File (PNG, Tiff, JPG)\n",[39,1870,1871,1874,1876,1879,1881,1884,1886,1889],{"class":41,"line":85},[39,1872,1873],{"class":620},"Mat_",[39,1875,1781],{"class":624},[39,1877,1878],{"class":620},"Vec4b",[39,1880,1787],{"class":624},[39,1882,1883],{"class":620}," img ",[39,1885,625],{"class":624},[39,1887,1888],{"class":1169}," imread",[39,1890,1891],{"class":620},"(input_file, IMREAD_UNCHANGED);\n",[39,1893,1894],{"class":41,"line":91},[39,1895,1896],{"class":1764},"\u002F\u002F Clone it\n",[39,1898,1899,1902,1904,1907,1910],{"class":41,"line":97},[39,1900,1901],{"class":620},"Mat dst ",[39,1903,625],{"class":624},[39,1905,1906],{"class":620}," img.",[39,1908,1909],{"class":1169},"clone",[39,1911,1912],{"class":620},"();\n",[39,1914,1915],{"class":41,"line":103},[39,1916,1917],{"class":1764},"\u002F\u002F Split in to R,G,B,A\n",[39,1919,1920,1923,1926],{"class":41,"line":109},[39,1921,1922],{"class":620},"Mat rgbchannel[",[39,1924,1925],{"class":1195},"4",[39,1927,1928],{"class":620},"];\n",[39,1930,1931,1934],{"class":41,"line":115},[39,1932,1933],{"class":1169},"split",[39,1935,1936],{"class":620},"(img, rgbchannel);\n",[39,1938,1939,1942,1944,1947,1949],{"class":41,"line":120},[39,1940,1941],{"class":620},"Mat og_alpha ",[39,1943,625],{"class":624},[39,1945,1946],{"class":620}," rgbchannel[",[39,1948,1441],{"class":1195},[39,1950,1928],{"class":620},[39,1952,1953],{"class":41,"line":125},[39,1954,52],{"emptyLinePlaceholder":51},[39,1956,1957],{"class":41,"line":131},[39,1958,1959],{"class":1764},"\u002F\u002F For each iteration\n",[39,1961,1962,1965,1967,1970,1973,1975,1978,1981,1983,1986,1989],{"class":41,"line":137},[39,1963,1964],{"class":624},"for",[39,1966,1601],{"class":620},[39,1968,1969],{"class":624},"int",[39,1971,1972],{"class":620}," iter ",[39,1974,625],{"class":624},[39,1976,1977],{"class":1195}," 0",[39,1979,1980],{"class":620},"; iter ",[39,1982,1781],{"class":624},[39,1984,1985],{"class":620}," iterations; iter",[39,1987,1988],{"class":624},"++",[39,1990,1991],{"class":620},") {\n",[39,1993,1994],{"class":41,"line":143},[39,1995,1996],{"class":1764},"    \u002F\u002F For every pixel (row\u002Fcol)\n",[39,1998,1999,2002,2004,2006,2009,2011,2013,2016,2018,2021,2023],{"class":41,"line":149},[39,2000,2001],{"class":624},"    for",[39,2003,1601],{"class":620},[39,2005,1969],{"class":624},[39,2007,2008],{"class":620}," row ",[39,2010,625],{"class":624},[39,2012,1977],{"class":1195},[39,2014,2015],{"class":620},"; row ",[39,2017,1781],{"class":624},[39,2019,2020],{"class":620}," img.rows; row",[39,2022,1988],{"class":624},[39,2024,1991],{"class":620},[39,2026,2027,2029,2031,2033,2036,2038,2040,2043,2045,2048,2050],{"class":41,"line":155},[39,2028,1515],{"class":624},[39,2030,1601],{"class":620},[39,2032,1969],{"class":624},[39,2034,2035],{"class":620}," col ",[39,2037,625],{"class":624},[39,2039,1977],{"class":1195},[39,2041,2042],{"class":620},"; col ",[39,2044,1781],{"class":624},[39,2046,2047],{"class":620}," img.cols; col",[39,2049,1988],{"class":624},[39,2051,1991],{"class":620},[39,2053,2054],{"class":41,"line":161},[39,2055,2056],{"class":1764},"            \u002F\u002F Get Pixel at row col\n",[39,2058,2059,2062,2065,2067,2070,2072,2074,2076],{"class":41,"line":167},[39,2060,2061],{"class":624},"            auto",[39,2063,2064],{"class":620}," pixel ",[39,2066,625],{"class":624},[39,2068,2069],{"class":620}," img.at",[39,2071,1781],{"class":624},[39,2073,1878],{"class":620},[39,2075,1787],{"class":624},[39,2077,2078],{"class":620},"(row, col);\n",[39,2080,2081],{"class":41,"line":172},[39,2082,2083],{"class":1764},"            \u002F\u002F if alpha of pixel is less then 255 we want to search for a neighbor with alpha\n",[39,2085,2086,2089,2092,2094,2097,2099,2102],{"class":41,"line":178},[39,2087,2088],{"class":624},"            if",[39,2090,2091],{"class":620}," (pixel[",[39,2093,1441],{"class":1195},[39,2095,2096],{"class":620},"] ",[39,2098,1781],{"class":624},[39,2100,2101],{"class":1195}," 255",[39,2103,1991],{"class":620},[39,2105,2106,2109,2111,2114,2117],{"class":41,"line":184},[39,2107,2108],{"class":624},"                for",[39,2110,1601],{"class":620},[39,2112,2113],{"class":624},"auto",[39,2115,2116],{"class":624}," &",[39,2118,2119],{"class":620}," neighbor : neighbors) {\n",[39,2121,2122],{"class":41,"line":190},[39,2123,2124],{"class":1764},"                    \u002F\u002F make sure our index doesn't go out of bounds\n",[39,2126,2127,2130,2133,2135,2137,2139,2142,2144],{"class":41,"line":196},[39,2128,2129],{"class":624},"                    int",[39,2131,2132],{"class":620}," n_row ",[39,2134,625],{"class":624},[39,2136,2008],{"class":620},[39,2138,637],{"class":624},[39,2140,2141],{"class":620}," neighbor[",[39,2143,1239],{"class":1195},[39,2145,1928],{"class":620},[39,2147,2148,2150,2153,2155,2157,2159,2161,2163],{"class":41,"line":202},[39,2149,2129],{"class":624},[39,2151,2152],{"class":620}," n_col ",[39,2154,625],{"class":624},[39,2156,2035],{"class":620},[39,2158,637],{"class":624},[39,2160,2141],{"class":620},[39,2162,1467],{"class":1195},[39,2164,1928],{"class":620},[39,2166,2167,2170,2172,2175],{"class":41,"line":208},[39,2168,2169],{"class":624},"                    if",[39,2171,1601],{"class":620},[39,2173,2174],{"class":1169},"row_col_in_bounds",[39,2176,2177],{"class":620},"(n_row, n_col, img)) {\n",[39,2179,2180],{"class":41,"line":214},[39,2181,2182],{"class":1764},"                        \u002F\u002F find neighbor pixels\n",[39,2184,2185,2188,2191,2193,2195,2197,2199,2201],{"class":41,"line":220},[39,2186,2187],{"class":624},"                        auto",[39,2189,2190],{"class":620}," n_pixel ",[39,2192,625],{"class":624},[39,2194,2069],{"class":620},[39,2196,1781],{"class":624},[39,2198,1878],{"class":620},[39,2200,1787],{"class":624},[39,2202,2203],{"class":620},"(n_row, n_col);\n",[39,2205,2206],{"class":41,"line":226},[39,2207,2208],{"class":1764},"                        \u002F\u002F find the first non 0 neighbor\n",[39,2210,2211,2214,2217,2219,2221,2223,2225],{"class":41,"line":232},[39,2212,2213],{"class":624},"                        if",[39,2215,2216],{"class":620}," (n_pixel[",[39,2218,1441],{"class":1195},[39,2220,2096],{"class":620},[39,2222,1787],{"class":624},[39,2224,1977],{"class":1195},[39,2226,1991],{"class":620},[39,2228,2229],{"class":41,"line":238},[39,2230,2231],{"class":1764},"                            \u002F\u002F Replace the color in the dest image with this color\n",[39,2233,2234,2237,2239,2241,2243,2246,2248,2251,2254,2256,2259,2261,2263,2265,2268,2271],{"class":41,"line":244},[39,2235,2236],{"class":620},"                            dst.at",[39,2238,1781],{"class":624},[39,2240,1878],{"class":620},[39,2242,1787],{"class":624},[39,2244,2245],{"class":620},"(row, col) ",[39,2247,625],{"class":624},[39,2249,2250],{"class":1169}," Vec4b",[39,2252,2253],{"class":620},"(n_pixel[",[39,2255,1239],{"class":1195},[39,2257,2258],{"class":620},"], n_pixel[",[39,2260,1467],{"class":1195},[39,2262,2258],{"class":620},[39,2264,1227],{"class":1195},[39,2266,2267],{"class":620},"], ",[39,2269,2270],{"class":1195},"255",[39,2272,1796],{"class":620},[39,2274,2275],{"class":41,"line":250},[39,2276,2277],{"class":1764},"                            \u002F\u002F Bail out of the loop\n",[39,2279,2280,2283],{"class":41,"line":256},[39,2281,2282],{"class":624},"                            break",[39,2284,2285],{"class":620},";\n",[39,2287,2288],{"class":41,"line":262},[39,2289,2290],{"class":620},"                        }\n",[39,2292,2293],{"class":41,"line":268},[39,2294,2295],{"class":620},"                    }\n",[39,2297,2298],{"class":41,"line":274},[39,2299,2300],{"class":620},"                }\n",[39,2302,2303],{"class":41,"line":280},[39,2304,2305],{"class":620},"            }\n",[39,2307,2308],{"class":41,"line":286},[39,2309,2310],{"class":620},"        }\n",[39,2312,2313],{"class":41,"line":292},[39,2314,2315],{"class":620},"    }\n",[39,2317,2318],{"class":41,"line":298},[39,2319,2320],{"class":1764},"    \u002F\u002F Set the img to be the dest so on the next iteration\n",[39,2322,2323],{"class":41,"line":304},[39,2324,2325],{"class":1764},"    \u002F\u002F the img being sampled is the previously dilated image\n",[39,2327,2328,2331,2333],{"class":41,"line":310},[39,2329,2330],{"class":620},"    img ",[39,2332,625],{"class":624},[39,2334,2335],{"class":620}," dst;\n",[39,2337,2338],{"class":41,"line":316},[39,2339,2340],{"class":620},"}\n",[39,2342,2343],{"class":41,"line":322},[39,2344,52],{"emptyLinePlaceholder":51},[39,2346,2347],{"class":41,"line":328},[39,2348,2349],{"class":1764},"\u002F\u002F split the channels in to RGBA\n",[39,2351,2352,2355,2357],{"class":41,"line":333},[39,2353,2354],{"class":620},"Mat dest_rgb_channel[",[39,2356,1925],{"class":1195},[39,2358,1928],{"class":620},[39,2360,2361,2363],{"class":41,"line":338},[39,2362,1933],{"class":1169},[39,2364,2365],{"class":620},"(dst, dest_rgb_channel);\n",[39,2367,2368],{"class":41,"line":344},[39,2369,2370],{"class":1764},"\u002F\u002F Assign RGB channels to vars\n",[39,2372,2373,2375,2378,2380,2383,2385],{"class":41,"line":350},[39,2374,1770],{"class":624},[39,2376,2377],{"class":620}," Mat red ",[39,2379,625],{"class":624},[39,2381,2382],{"class":620}," dest_rgb_channel[",[39,2384,1239],{"class":1195},[39,2386,2387],{"class":620},"],\n",[39,2389,2391,2394,2396,2398,2400],{"class":41,"line":2390},54,[39,2392,2393],{"class":620},"    green ",[39,2395,625],{"class":624},[39,2397,2382],{"class":620},[39,2399,1467],{"class":1195},[39,2401,2387],{"class":620},[39,2403,2405,2408,2410,2412,2414],{"class":41,"line":2404},55,[39,2406,2407],{"class":620},"    blue ",[39,2409,625],{"class":624},[39,2411,2382],{"class":620},[39,2413,1227],{"class":1195},[39,2415,1928],{"class":620},[39,2417,2419],{"class":41,"line":2418},56,[39,2420,52],{"emptyLinePlaceholder":51},[39,2422,2424],{"class":41,"line":2423},57,[39,2425,2426],{"class":1764},"\u002F\u002F If extension is TGA (swap RGB -> BGR for tga)\n",[39,2428,2430,2433,2436,2439,2442],{"class":41,"line":2429},58,[39,2431,2432],{"class":624},"if",[39,2434,2435],{"class":620}," (extension ",[39,2437,2438],{"class":624},"==",[39,2440,2441],{"class":1188}," \"tga\"",[39,2443,1991],{"class":620},[39,2445,2447,2450,2452,2454,2456,2459],{"class":41,"line":2446},59,[39,2448,2449],{"class":620},"    dest_rgb_channel[",[39,2451,1239],{"class":1195},[39,2453,2096],{"class":620},[39,2455,625],{"class":624},[39,2457,2458],{"class":620}," blue;",[39,2460,2461],{"class":1764}," \u002F\u002F Blue\n",[39,2463,2465,2467,2469,2471,2473,2476],{"class":41,"line":2464},60,[39,2466,2449],{"class":620},[39,2468,1467],{"class":1195},[39,2470,2096],{"class":620},[39,2472,625],{"class":624},[39,2474,2475],{"class":620}," green;",[39,2477,2478],{"class":1764}," \u002F\u002F Green\n",[39,2480,2482,2484,2486,2488,2490,2493],{"class":41,"line":2481},61,[39,2483,2449],{"class":620},[39,2485,1227],{"class":1195},[39,2487,2096],{"class":620},[39,2489,625],{"class":624},[39,2491,2492],{"class":620}," red;",[39,2494,2495],{"class":1764}," \u002F\u002F Red\n",[39,2497,2499],{"class":41,"line":2498},62,[39,2500,2340],{"class":620},[39,2502,2504,2507],{"class":41,"line":2503},63,[39,2505,2506],{"class":624},"else",[39,2508,2509],{"class":620}," {\n",[39,2511,2513,2515,2517,2519,2521,2523],{"class":41,"line":2512},64,[39,2514,2449],{"class":620},[39,2516,1239],{"class":1195},[39,2518,2096],{"class":620},[39,2520,625],{"class":624},[39,2522,2492],{"class":620},[39,2524,2495],{"class":1764},[39,2526,2528,2530,2532,2534,2536,2538],{"class":41,"line":2527},65,[39,2529,2449],{"class":620},[39,2531,1467],{"class":1195},[39,2533,2096],{"class":620},[39,2535,625],{"class":624},[39,2537,2475],{"class":620},[39,2539,2478],{"class":1764},[39,2541,2543,2545,2547,2549,2551,2553],{"class":41,"line":2542},66,[39,2544,2449],{"class":620},[39,2546,1227],{"class":1195},[39,2548,2096],{"class":620},[39,2550,625],{"class":624},[39,2552,2458],{"class":620},[39,2554,2461],{"class":1764},[39,2556,2558],{"class":41,"line":2557},67,[39,2559,2340],{"class":620},[39,2561,2563],{"class":41,"line":2562},68,[39,2564,52],{"emptyLinePlaceholder":51},[39,2566,2568],{"class":41,"line":2567},69,[39,2569,2570],{"class":1764},"\u002F\u002F merge the original alpha back in to the image\n",[39,2572,2574,2577,2579,2581,2583],{"class":41,"line":2573},70,[39,2575,2576],{"class":620},"dest_rgb_channel[",[39,2578,1441],{"class":1195},[39,2580,2096],{"class":620},[39,2582,625],{"class":624},[39,2584,2585],{"class":620}," og_alpha;\n",[39,2587,2589],{"class":41,"line":2588},71,[39,2590,52],{"emptyLinePlaceholder":51},[39,2592,2594],{"class":41,"line":2593},72,[39,2595,2596],{"class":1764},"\u002F\u002F Merge all the channels back in to the dst image they came from\n",[39,2598,2600,2603,2606,2608],{"class":41,"line":2599},73,[39,2601,2602],{"class":1169},"merge",[39,2604,2605],{"class":620},"(dest_rgb_channel, ",[39,2607,1925],{"class":1195},[39,2609,2610],{"class":620},", dst);\n",[10,2612,2613],{},"As I'm sure you can guess the performance on the C++ version is significantly faster. It takes about .2 seconds to do 512 iterations on a 2048x2048 image which is lightning fast compared to the python version that struggled to come in under a minute on 512x512 images doing only 32 iterations.",[10,2615,2616],{},"I plan to polish this one and release it on gumroad at some point soon.",[10,2618,2619],{},"That's it hope you enjoyed!",[375,2621,2622],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":48,"depth":48,"links":2624},[],"11\u002F12\u002F2021","Follow up to another blog post",{},"\u002Fblog\u002Fdilation-in-cpp",{"title":1742,"description":2626},"blog\u002Fdilation-in-cpp",[36,387,1757],"CNUksYFkQkgTZ_06zrv6bO_nbYGSP_omLC2EkBHzbe4",{"id":4,"title":5,"body":2634,"date":380,"description":12,"extension":381,"meta":2877,"navigation":51,"path":383,"seo":2878,"stem":385,"tags":2879,"__hash__":389},{"type":7,"value":2635,"toc":2875},[2636,2638,2643,2645,2861,2867,2869,2873],[10,2637,12],{},[10,2639,15,2640,23],{},[17,2641,22],{"href":19,"rel":2642},[21],[10,2644,26],{},[28,2646,2647],{"className":30,"code":31,"filename":32,"language":33,"meta":34,"style":34},[36,2648,2649,2653,2657,2661,2665,2669,2673,2677,2681,2685,2689,2693,2697,2701,2705,2709,2713,2717,2721,2725,2729,2733,2737,2741,2745,2749,2753,2757,2761,2765,2769,2773,2777,2781,2785,2789,2793,2797,2801,2805,2809,2813,2817,2821,2825,2829,2833,2837,2841,2845,2849,2853,2857],{"__ignoreMap":34},[39,2650,2651],{"class":41,"line":42},[39,2652,45],{},[39,2654,2655],{"class":41,"line":48},[39,2656,52],{"emptyLinePlaceholder":51},[39,2658,2659],{"class":41,"line":55},[39,2660,58],{},[39,2662,2663],{"class":41,"line":61},[39,2664,64],{},[39,2666,2667],{"class":41,"line":67},[39,2668,70],{},[39,2670,2671],{"class":41,"line":73},[39,2672,76],{},[39,2674,2675],{"class":41,"line":79},[39,2676,82],{},[39,2678,2679],{"class":41,"line":85},[39,2680,88],{},[39,2682,2683],{"class":41,"line":91},[39,2684,94],{},[39,2686,2687],{"class":41,"line":97},[39,2688,100],{},[39,2690,2691],{"class":41,"line":103},[39,2692,106],{},[39,2694,2695],{"class":41,"line":109},[39,2696,112],{},[39,2698,2699],{"class":41,"line":115},[39,2700,52],{"emptyLinePlaceholder":51},[39,2702,2703],{"class":41,"line":120},[39,2704,52],{"emptyLinePlaceholder":51},[39,2706,2707],{"class":41,"line":125},[39,2708,128],{},[39,2710,2711],{"class":41,"line":131},[39,2712,134],{},[39,2714,2715],{"class":41,"line":137},[39,2716,140],{},[39,2718,2719],{"class":41,"line":143},[39,2720,146],{},[39,2722,2723],{"class":41,"line":149},[39,2724,152],{},[39,2726,2727],{"class":41,"line":155},[39,2728,158],{},[39,2730,2731],{"class":41,"line":161},[39,2732,164],{},[39,2734,2735],{"class":41,"line":167},[39,2736,134],{},[39,2738,2739],{"class":41,"line":172},[39,2740,175],{},[39,2742,2743],{"class":41,"line":178},[39,2744,181],{},[39,2746,2747],{"class":41,"line":184},[39,2748,187],{},[39,2750,2751],{"class":41,"line":190},[39,2752,193],{},[39,2754,2755],{"class":41,"line":196},[39,2756,199],{},[39,2758,2759],{"class":41,"line":202},[39,2760,205],{},[39,2762,2763],{"class":41,"line":208},[39,2764,211],{},[39,2766,2767],{"class":41,"line":214},[39,2768,217],{},[39,2770,2771],{"class":41,"line":220},[39,2772,223],{},[39,2774,2775],{"class":41,"line":226},[39,2776,229],{},[39,2778,2779],{"class":41,"line":232},[39,2780,235],{},[39,2782,2783],{"class":41,"line":238},[39,2784,241],{},[39,2786,2787],{"class":41,"line":244},[39,2788,247],{},[39,2790,2791],{"class":41,"line":250},[39,2792,253],{},[39,2794,2795],{"class":41,"line":256},[39,2796,259],{},[39,2798,2799],{"class":41,"line":262},[39,2800,265],{},[39,2802,2803],{"class":41,"line":268},[39,2804,271],{},[39,2806,2807],{"class":41,"line":274},[39,2808,277],{},[39,2810,2811],{"class":41,"line":280},[39,2812,283],{},[39,2814,2815],{"class":41,"line":286},[39,2816,289],{},[39,2818,2819],{"class":41,"line":292},[39,2820,295],{},[39,2822,2823],{"class":41,"line":298},[39,2824,301],{},[39,2826,2827],{"class":41,"line":304},[39,2828,307],{},[39,2830,2831],{"class":41,"line":310},[39,2832,313],{},[39,2834,2835],{"class":41,"line":316},[39,2836,319],{},[39,2838,2839],{"class":41,"line":322},[39,2840,325],{},[39,2842,2843],{"class":41,"line":328},[39,2844,52],{"emptyLinePlaceholder":51},[39,2846,2847],{"class":41,"line":333},[39,2848,52],{"emptyLinePlaceholder":51},[39,2850,2851],{"class":41,"line":338},[39,2852,341],{},[39,2854,2855],{"class":41,"line":344},[39,2856,347],{},[39,2858,2859],{"class":41,"line":350},[39,2860,353],{},[10,2862,356,2863,2865],{},[358,2864],{"src":360},[358,2866],{"src":363},[10,2868,366],{},[10,2870,369,2871],{},[17,2872,373],{"href":372},[375,2874,377],{},{"title":34,"searchDepth":48,"depth":48,"links":2876},[],{},{"title":5,"description":12},[36,387,388],{"id":2881,"title":2882,"body":2883,"date":3179,"description":2887,"extension":381,"meta":3180,"navigation":51,"path":3181,"seo":3182,"stem":3183,"tags":3184,"__hash__":3186},"blog\u002Fblog\u002Fdynamic-descriptive-parms.md","Dynamic Descriptive Parms in Houdini",{"type":7,"value":2884,"toc":3170},[2885,2888,2892,2898,2901,2910,2924,2928,2943,2947,2950,2961,2964,2967,2973,2977,2980,2987,2990,3059,3065,3069,3076,3083,3086,3090,3093,3102,3109,3112,3115,3118,3159,3165,3168],[10,2886,2887],{},"Dynamic descriptive parms can be super helpful indicators of what an hda is doing without having to dive in to a node. Think about how handy the little blue text on the group node can be and try and think of ways to use that in your next hda. This guide will teach you what a descriptive parm is, how to use a descriptive parm, and how to create a dynamically updating descriptive parm using a little bit of python and a callback script. We'll learn about all of those things and how they work together.",[407,2889,2891],{"id":2890},"what-is-a-descriptive-parm","What is a Descriptive Parm?",[10,2893,2894],{},[358,2895],{"alt":2896,"src":2897},"Descriptive Parm Example","https:\u002F\u002Fcsprance.com\u002Fshots\u002Fdescriptive_parm_example.png",[10,2899,2900],{},"A Descriptive parm is a parameter you can define on a HDA in the Nodes tab of the HDA Type Properties window. The descriptive parm shows up on the node in the network view to the right and below the node name in blue text, just like the group node.",[10,2902,2903,2904,2909],{},"The houdini docs on Descriptive Parm ",[17,2905,2908],{"href":2906,"rel":2907},"https:\u002F\u002Fwww.sidefx.com\u002Fdocs\u002Fhoudini\u002Fref\u002Fwindows\u002Foptype.html#node",[21],"here"," describe the Descriptive Parm as follows:",[2911,2912,2913,2918,2921],"blockquote",{},[2914,2915,2917],"h4",{"id":2916},"descriptive-parm","Descriptive Parm",[10,2919,2920],{},"If this contains the internal name of a parameter on this asset, Houdini will display the value of the parameter as the descriptive text badge in the network editor.",[10,2922,2923],{},"This should be a parameter whose value tells the user at-a-glance the most important thing about the node’s current settings. For example, on a File node this is the file path.",[407,2925,2927],{"id":2926},"how-to-use-descriptive-parms","How to use Descriptive Parms.",[10,2929,2930,2931,2934,2935,2938,2939,2942],{},"To use a descriptive parm you can define which parm on the HDA should show up by opening the ",[569,2932,2933],{},"Type Properties"," window of the HDA, selecting the ",[569,2936,2937],{},"Node"," tab and typing the name of the parm in the descriptive parm input. ",[358,2940],{"alt":2917,"src":2941},"https:\u002F\u002Fcsprance.com\u002Fshots\u002Fdescriptive_parms.png"," This parm will now always show whatever it contains in the blue text. This is very hand by itself for lot's of things but sometimes we want multiple values to effect it or maybe generate some values based on some geometry data. This is when you want to use a little python magic.",[407,2944,2946],{"id":2945},"how-to-dynamically-update-a-descriptive-parm","How to Dynamically Update a Descriptive Parm",[10,2948,2949],{},"In order to create a dynamically updating descriptive parm we need to keep a few things in mind:",[534,2951,2952,2955,2958],{},[537,2953,2954],{},"We need a parm of the data type we want to use defined in the parameters section.",[537,2956,2957],{},"Sometimes it makes sense to have this parameter be invisible.",[537,2959,2960],{},"Sometimes maybe we want to generate a value and then allow the user to tweak it themselves after the fact.",[10,2962,2963],{},"To dynamically set a parm I created a string parm and marked it as invisible. It doesn't have to be marked as invisible but unless you want the user to be able to see it and edit, then this is the best way. This parm will serve as a string we can set and store on the HDA with python that will be used to construct the descriptive parm.",[10,2965,2966],{},"Once you create the invisible parm (as a string or whatever data type you need it as), don't forget to jump in to the node section and set the correct parm in the descriptive parm.",[10,2968,2969],{},[358,2970],{"alt":2971,"src":2972},"Invisible Parm","https:\u002F\u002Fcsprance.com\u002Fshots\u002Finvisible_parm.png",[699,2974,2976],{"id":2975},"scripts","Scripts",[10,2978,2979],{},"In order to update our invisible parm with the text we want to show we first need to create a Python Module and a function that can handle the updating of the HDA for us.",[10,2981,2982,2983],{},"Create a Python Module Script by Clicking on the Scripts tab and select the Python Module item from the Event Handle dropdown list. ",[358,2984],{"alt":2985,"src":2986},"Python Module","https:\u002F\u002Fcsprance.com\u002Fshots\u002Fpython_module.png",[10,2988,2989],{},"The code for the update function could look something like this:",[28,2991,2993],{"className":30,"code":2992,"language":33,"meta":34,"style":34},"def update_descriptive_parm(hda):\n    # Grab our values from our HDA parms\n    min_h = hda.parm('min_height').eval()\n    max_h = hda.parm('max_height').eval()\n    min_a = hda.parm('min_area').eval()\n    max_a = hda.parm('max_area').eval()\n    # Use those values to assemble the string we want to show in the descriptive parm\n    text = \"\"\"@height: {0}-{1}\n              @area: {2}-{3}\"\"\".format(\n        min_h, max_h, min_a, max_a\n    )\n    # Set that parm which will then show up on the node itself in the Network View\n    hda.parm('descriptive_parm').set(text)\n\n",[36,2994,2995,3000,3005,3010,3015,3020,3025,3030,3035,3040,3045,3049,3054],{"__ignoreMap":34},[39,2996,2997],{"class":41,"line":42},[39,2998,2999],{},"def update_descriptive_parm(hda):\n",[39,3001,3002],{"class":41,"line":48},[39,3003,3004],{},"    # Grab our values from our HDA parms\n",[39,3006,3007],{"class":41,"line":55},[39,3008,3009],{},"    min_h = hda.parm('min_height').eval()\n",[39,3011,3012],{"class":41,"line":61},[39,3013,3014],{},"    max_h = hda.parm('max_height').eval()\n",[39,3016,3017],{"class":41,"line":67},[39,3018,3019],{},"    min_a = hda.parm('min_area').eval()\n",[39,3021,3022],{"class":41,"line":73},[39,3023,3024],{},"    max_a = hda.parm('max_area').eval()\n",[39,3026,3027],{"class":41,"line":79},[39,3028,3029],{},"    # Use those values to assemble the string we want to show in the descriptive parm\n",[39,3031,3032],{"class":41,"line":85},[39,3033,3034],{},"    text = \"\"\"@height: {0}-{1}\n",[39,3036,3037],{"class":41,"line":91},[39,3038,3039],{},"              @area: {2}-{3}\"\"\".format(\n",[39,3041,3042],{"class":41,"line":97},[39,3043,3044],{},"        min_h, max_h, min_a, max_a\n",[39,3046,3047],{"class":41,"line":103},[39,3048,1476],{},[39,3050,3051],{"class":41,"line":109},[39,3052,3053],{},"    # Set that parm which will then show up on the node itself in the Network View\n",[39,3055,3056],{"class":41,"line":115},[39,3057,3058],{},"    hda.parm('descriptive_parm').set(text)\n",[10,3060,3061],{},[358,3062],{"alt":3063,"src":3064},"Python Module Code","https:\u002F\u002Fcsprance.com\u002Fshots\u002Fpython_module_code.png",[699,3066,3068],{"id":3067},"what-does-the-script-do","What does the script do?",[10,3070,3071,3072,3075],{},"Notice in the script we pass it in an HDA. This HDA will be the root HDA node that we will pass in from the callback script from ",[36,3073,3074],{},"kwargs['node']"," (More on that below).",[10,3077,3078,3079,3082],{},"We can then grab specific parms from the hda that we want to show in the descriptive parm. In this case we are grabbing each of the parms of min\u002Fmax height, min\u002Fmax area and then constructing a string from those parms and finally setting the invisible parm we created that is being referenced in the descriptive parm input under the HDA's Type Properties window in the Node Tab. I have named that invisible parm ",[36,3080,3081],{},"descriptive_parm"," to be explicit.",[10,3084,3085],{},"Now that we have the script written let's figure out how to run it. We do this using callback scripts.",[699,3087,3089],{"id":3088},"callback-scripts","Callback Scripts",[10,3091,3092],{},"Add a callback script to each parm you want to have update the descriptive parm.",[28,3094,3096],{"className":30,"code":3095,"language":33,"meta":34,"style":34},"kwargs['node'].hm().update_descriptive_parm(kwargs['node'])\n",[36,3097,3098],{"__ignoreMap":34},[39,3099,3100],{"class":41,"line":42},[39,3101,3095],{},[10,3103,3104,3105,3108],{},"Each time one of these parms is changed it will fire off the script we created in the Python Module section passing the current kwargs",[39,3106,3107],{},"'node'"," (Which is the HDA itself).",[10,3110,3111],{},"Notice as well that you can change the language to python or hscript. In this instance we want to use Python so make sure that is set correctly.",[358,3113],{"src":3114},"\u002Fimages\u002Fcallback_scripts.png",[10,3116,3117],{},"The callback script does a few things here.",[534,3119,3120,3136,3143,3149],{},[537,3121,3122,3125,3126,3128,3129,3132,3133],{},[36,3123,3124],{},"kwargs['node'].hm()"," Grabs the HDA node and returns us the ",[569,3127,2985],{}," defined inside of ",[419,3130,3131],{},"Scripts\u002FPython Module"," in the HDA's ",[569,3134,3135],{},"Type Properties Window",[537,3137,3138,3139,3142],{},"Calls the function ",[36,3140,3141],{},"update_descriptive_parm",". Remember when we defined this method earlier? This is how you call those functions.",[537,3144,3145,3146,3148],{},"Notice here how we pass in the HDA node to the function using ",[36,3147,3074],{}," again.",[537,3150,3151,3152,3155,3156,476],{},"There are other interesting things inside of ",[36,3153,3154],{},"kwargs[]"," that you may want to know about at some point check it out some time with ",[36,3157,3158],{},"dir()",[10,3160,3161,3162,3164],{},"This means each times this input is changed, it will go and update the ",[36,3163,3081],{}," parm and you will see that value reflected in the network view.",[10,3166,3167],{},"That's the whole process, you can get as creative as you need.",[375,3169,377],{},{"title":34,"searchDepth":48,"depth":48,"links":3171},[3172,3173,3174],{"id":2890,"depth":48,"text":2891},{"id":2926,"depth":48,"text":2927},{"id":2945,"depth":48,"text":2946,"children":3175},[3176,3177,3178],{"id":2975,"depth":55,"text":2976},{"id":3067,"depth":55,"text":3068},{"id":3088,"depth":55,"text":3089},"02\u002F12\u002F2021",{},"\u002Fblog\u002Fdynamic-descriptive-parms",{"title":2882,"description":2887},"blog\u002Fdynamic-descriptive-parms",[3185,387],"houdini","sESFM7MBbQyv_CzcRfg9bDssBJNm8GV5XXCviyTrBaQ",{"id":3188,"title":3189,"body":3190,"date":3535,"description":3536,"extension":381,"meta":3537,"navigation":51,"path":3538,"seo":3539,"stem":3540,"tags":3541,"__hash__":3545},"blog\u002Fblog\u002Fgecs.md","GECS: Godot Entity Component System",{"type":7,"value":3191,"toc":3523},[3192,3200,3205,3231,3233,3237,3240,3243,3245,3249,3252,3272,3275,3277,3281,3284,3309,3312,3314,3318,3321,3324,3326,3330,3361,3365,3372,3376,3383,3387,3394,3398,3404,3408,3417,3421,3424,3428,3431,3433,3437,3479,3481,3485,3488,3490,3492,3496,3512,3516],[2911,3193,3194],{},[10,3195,3196],{},[17,3197,3198],{"href":3198,"rel":3199},"https:\u002F\u002Fgithub.com\u002Fcsprance\u002Fgecs",[21],[10,3201,3202],{},[569,3203,3204],{},"This is Part 1 of the GECS series:",[3206,3207,3208,3213,3219,3225],"ol",{},[537,3209,3210,3212],{},[569,3211,3189],{}," (You are here)",[537,3214,3215],{},[17,3216,3218],{"href":3217},"\u002Fblog\u002Fgecs-entities","GECS: Entities and Components",[537,3220,3221],{},[17,3222,3224],{"href":3223},"\u002Fblog\u002Fgecs-systems-queries","GECS: Systems and Queries",[537,3226,3227],{},[17,3228,3230],{"href":3229},"\u002Fblog\u002Fgecs-relationships","GECS: Relationships",[704,3232],{},[407,3234,3236],{"id":3235},"gecs","GECS",[10,3238,3239],{},"A lightweight ECS framework for Godot 4.x providing a data-driven architecture:\nWhen developing games or complex applications in Godot, organizing logic can be a challenge. An Entity Component System (ECS) offers a flexible, data-oriented paradigm that simplifies combining entities and their behaviors. GECS (Godot Entity Component System) is a lightweight ECS framework for Godot designed to fit seamlessly into Godot 4.x. This post highlights what ECS is, why it matters, and how GECS applies these concepts in Godot.",[10,3241,3242],{},"GECS helps keep your logic modular by separating data (in components) from behaviors (in systems), reducing complexity.",[704,3244],{},[699,3246,3248],{"id":3247},"why-ecs","Why ECS?",[10,3250,3251],{},"Clearly divides data and logic:\nTraditional object-oriented approaches often bundle data and behavior together. Over time, that can become unwieldy or force complicated inheritance structures. ECS keeps data (components) separate from logic (systems). It revolves around three pillars:",[3206,3253,3254,3260,3266],{},[537,3255,3256,3259],{},[569,3257,3258],{},"Entities"," – Think of them as IDs or “slots” for your game objects.",[537,3261,3262,3265],{},[569,3263,3264],{},"Components"," – Pure data objects that define a piece of state (e.g., velocity, health).",[537,3267,3268,3271],{},[569,3269,3270],{},"Systems"," – Logic that processes all entities with specific components.",[10,3273,3274],{},"This pattern simplifies orginization and collaboration among team members and future refactoring. This structure makes it easier to add, remove, or modify behaviors, since everything is modular. Systems only act upon relevant components. Entities can freely change their makeup without breaking the overall design.",[704,3276],{},[699,3278,3280],{"id":3279},"what-is-gecs","What Is GECS?",[10,3282,3283],{},"An extension of the Godot workflow giving you:",[534,3285,3286,3289,3296,3303,3306],{},[537,3287,3288],{},"Integration with Godot nodes (you can treat Entities as scenes and Components as resources).",[537,3290,3291,3292,3295],{},"A ",[36,3293,3294],{},"World"," to manage all Entities and Systems.",[537,3297,3298,3299,3302],{},"An ",[36,3300,3301],{},"ECS"," singleton to coordinate queries and processing.",[537,3304,3305],{},"QueryBuilders for comprehensive entity queries, including property-based filtering.",[537,3307,3308],{},"Relationship management that lets you define complex associations among entities.",[10,3310,3311],{},"Entities become merely data containers, while systems operate on them based on component queries.",[704,3313],{},[699,3315,3317],{"id":3316},"relationship-driven-queries","Relationship-Driven Queries",[10,3319,3320],{},"Use relationships to link entities:\nGECS extends the ECS idea with relationship objects, letting you define links (e.g., “likes,” “belongs to,” “is attacking”) between two entities for easier dynamic querying.",[10,3322,3323],{},"For example, a “ParentOf” relationship can simplify hierarchical access in your game.",[704,3325],{},[699,3327,3329],{"id":3328},"core-concepts","Core Concepts",[3206,3331,3332,3338,3344,3350,3355],{},[537,3333,3334,3337],{},[569,3335,3336],{},"Entity"," – Node with multiple component resources",[537,3339,3340,3343],{},[569,3341,3342],{},"Component"," – Resource storing data",[537,3345,3346,3349],{},[569,3347,3348],{},"System"," – Node with queries to process matched entities",[537,3351,3352,3354],{},[569,3353,3294],{}," – Node owning entities and systems",[537,3356,3357,3360],{},[569,3358,3359],{},"ECS Singleton"," – Central manager to run queries or process frames",[2914,3362,3364],{"id":3363},"_1-entities","1. Entities",[10,3366,3367,3368,3371],{},"Entities are Godot nodes extending ",[36,3369,3370],{},"Entity.gd",". They hold multiple components and can emit signals when components or relationships are added or removed.",[2914,3373,3375],{"id":3374},"_2-components","2. Components",[10,3377,3378,3379,3382],{},"Components are data-only resources that hold properties. They do not contain game logic. For example, a ",[36,3380,3381],{},"Transform"," component might store a position, rotation, or scale.",[2914,3384,3386],{"id":3385},"_3-systems","3. Systems",[10,3388,3389,3390,3393],{},"Systems are nodes extending ",[36,3391,3392],{},"System.gd",". They define a query (e.g., “which entities do I care about?”) and a process function that operates on those entities.",[2914,3395,3397],{"id":3396},"_4-world","4. World",[10,3399,3400,3401,3403],{},"The central manager that holds all Entities and Systems. You typically place a ",[36,3402,3294],{}," node in a scene, and it automatically handles adding or removing your Entities and Systems.",[2914,3405,3407],{"id":3406},"_5-ecs-singleton","5. ECS Singleton",[10,3409,3410,3411,3413,3414,3416],{},"The ",[36,3412,3301],{}," autoload offers global access to the active ",[36,3415,3294],{},". It can be used to process each frame (or physics frame) and run queries.",[2914,3418,3420],{"id":3419},"_6-querybuilder","6. QueryBuilder",[10,3422,3423],{},"Queries can be built using a fluent API to find entities with required components or exclude those with certain components or relationships.",[2914,3425,3427],{"id":3426},"_7-relationships","7. Relationships",[10,3429,3430],{},"GECS extends the ECS idea with relationship objects, letting you define links (e.g., “likes,” “belongs to,” “is attacking”) between two entities for easier dynamic querying.",[704,3432],{},[699,3434,3436],{"id":3435},"example-workflow","Example Workflow",[3206,3438,3439,3445,3453,3462,3470],{},[537,3440,3441,3444],{},[569,3442,3443],{},"Create Entities"," and add components via the editor or code",[537,3446,3447,3450,3451],{},[569,3448,3449],{},"Add Entities"," to the ",[36,3452,3294],{},[537,3454,3455,3458,3459],{},[569,3456,3457],{},"Create Systems"," that define queries and implement ",[36,3460,3461],{},"process",[537,3463,3464,3467,3468],{},[569,3465,3466],{},"Add Systems"," to the same ",[36,3469,3294],{},[537,3471,3472,3478],{},[569,3473,3474,3475],{},"Call ",[36,3476,3477],{},"ECS.process(delta)"," each frame to update all systems",[704,3480],{},[699,3482,3484],{"id":3483},"next-steps","Next Steps",[10,3486,3487],{},"Explore how relationships can link multiple entities and how advanced queries filter entities by property data, giving you precise control over your game’s logic. This post serves as an overview of how ECS concepts map into Godot with GECS. Future posts will explore each feature—creating components, building queries, handling relationships, and more. Stay tuned!",[704,3489],{},[704,3491],{},[699,3493,3495],{"id":3494},"gecs-series","GECS Series",[534,3497,3498,3506],{},[537,3499,3500,3503,3504],{},[569,3501,3502],{},"Next",": ",[17,3505,3218],{"href":3217},[537,3507,3508],{},[17,3509,3511],{"href":3510},"\u002Fblog\u002Ftags\u002Fgecs","All posts in this series",[407,3513,3515],{"id":3514},"gecs-on-github","GECS on Github",[2911,3517,3518],{},[10,3519,3520],{},[17,3521,3198],{"href":3198,"rel":3522},[21],{"title":34,"searchDepth":48,"depth":48,"links":3524},[3525,3534],{"id":3235,"depth":48,"text":3236,"children":3526},[3527,3528,3529,3530,3531,3532,3533],{"id":3247,"depth":55,"text":3248},{"id":3279,"depth":55,"text":3280},{"id":3316,"depth":55,"text":3317},{"id":3328,"depth":55,"text":3329},{"id":3435,"depth":55,"text":3436},{"id":3483,"depth":55,"text":3484},{"id":3494,"depth":55,"text":3495},{"id":3514,"depth":48,"text":3515},"04\u002F06\u002F2025","When developing games or complex applications in Godot, organizing logic can be a challenge. An Entity Component System (ECS) offers a flexible, data-oriented paradigm that simplifies combining entities and their behaviors. GECS (Godot Entity Component System) is a lightweight ECS framework for Godot designed to fit seamlessly into Godot 4.x. This post highlights what ECS is, why it matters, and how GECS applies these concepts in Godot.",{},"\u002Fblog\u002Fgecs",{"title":3189,"description":3536},"blog\u002Fgecs",[3542,3543,3544,3235],"gamedev","ecs","godot","FsZTXslQZnGn2BdtN-bZr1bt5iegHn22yQXwPnEN4Qc",{"id":3547,"title":3218,"body":3548,"date":4246,"description":4247,"extension":381,"meta":4248,"navigation":51,"path":3217,"seo":4249,"stem":4250,"tags":4251,"__hash__":4252},"blog\u002Fblog\u002Fgecs-entities.md",{"type":7,"value":3549,"toc":4229},[3550,3557,3562,3580,3582,3586,3589,3601,3614,3617,3619,3622,3626,3629,3632,3713,3730,3733,3739,3742,3745,3752,3810,3832,3834,3838,3845,3874,3876,3880,3886,3924,3926,3930,3933,3935,3939,3942,3944,3947,3951,3954,3960,4052,4058,4060,4064,4071,4100,4102,4106,4112,4157,4160,4162,4166,4193,4195,4197,4216,4219,4226],[2911,3551,3552],{},[10,3553,3554],{},[17,3555,3198],{"href":3198,"rel":3556},[21],[10,3558,3559],{},[569,3560,3561],{},"This is Part 2 of the GECS series:",[3206,3563,3564,3568,3572,3576],{},[537,3565,3566],{},[17,3567,3189],{"href":3538},[537,3569,3570,3212],{},[569,3571,3218],{},[537,3573,3574],{},[17,3575,3224],{"href":3223},[537,3577,3578],{},[17,3579,3230],{"href":3229},[704,3581],{},[407,3583,3585],{"id":3584},"entities-and-components-in-gecs","Entities and Components in GECS",[10,3587,3588],{},"Welcome to our second post in the GECS series! This time, we’ll focus on two ECS essentials:",[534,3590,3591,3596],{},[537,3592,3593,3595],{},[569,3594,3258],{}," – The glue code\u002Fcontainers that hold data.",[537,3597,3598,3600],{},[569,3599,3264],{}," – Pure data objects that live on entities and define the 'thing'.",[10,3602,3603,3604,3606,3607,3610,3611,476],{},"In GECS (Godot Entity Component System), each entity is a regular Godot node that extends ",[36,3605,3370],{},", and each component is a ",[36,3608,3609],{},"Resource"," extending ",[36,3612,3613],{},"Component.gd",[10,3615,3616],{},"Leaning into the way Godot does things and wrapping the ECS around this is one of the strengths of GECS. You get the pros of Godot and the data organization and execution patterns of ECS.",[704,3618],{},[407,3620,3258],{"id":3621},"entities",[699,3623,3625],{"id":3624},"entity-basics","Entity Basics",[10,3627,3628],{},"Entities are the core data type you work with in GECS, you'll always be operating on Entities and their Component and Relationships. There are a few ways to create entities.",[10,3630,3631],{},"You can define it in code:",[28,3633,3637],{"className":3634,"code":3635,"language":3636,"meta":34,"style":34},"language-gdscript shiki shiki-themes github-light github-dark","var  e_my_entity = Entity.new() # look at me I'm an entity!\ne_my_entity.add_components([C_Transform.new(), C_Velocity.new(Vector3.UP)]) # I have the data to move now!\nECS.world.add_entity(e_my_entity) # I can move now! Systems will pick me up.\n","gdscript",[36,3638,3639,3663,3697],{"__ignoreMap":34},[39,3640,3641,3644,3647,3649,3652,3654,3657,3660],{"class":41,"line":42},[39,3642,3643],{"class":624},"var",[39,3645,3646],{"class":620},"  e_my_entity ",[39,3648,625],{"class":624},[39,3650,3651],{"class":1169}," Entity",[39,3653,476],{"class":620},[39,3655,3656],{"class":1169},"new",[39,3658,3659],{"class":620},"() ",[39,3661,3662],{"class":1764},"# look at me I'm an entity!\n",[39,3664,3665,3668,3671,3674,3676,3679,3681,3683,3686,3688,3691,3694],{"class":41,"line":48},[39,3666,3667],{"class":620},"e_my_entity.",[39,3669,3670],{"class":1169},"add_components",[39,3672,3673],{"class":620},"([C_Transform.",[39,3675,3656],{"class":1169},[39,3677,3678],{"class":620},"(), C_Velocity.",[39,3680,3656],{"class":1169},[39,3682,1790],{"class":620},[39,3684,3685],{"class":1169},"Vector3",[39,3687,476],{"class":620},[39,3689,3690],{"class":1195},"UP",[39,3692,3693],{"class":620},")]) ",[39,3695,3696],{"class":1764},"# I have the data to move now!\n",[39,3698,3699,3701,3704,3707,3710],{"class":41,"line":55},[39,3700,3301],{"class":1195},[39,3702,3703],{"class":620},".world.",[39,3705,3706],{"class":1169},"add_entity",[39,3708,3709],{"class":620},"(e_my_entity) ",[39,3711,3712],{"class":1764},"# I can move now! Systems will pick me up.\n",[10,3714,3715,3716,3718,3719,3722,3723,3725,3726,3729],{},"Since we're in Godot a common thing to do is to create a scene with a root node of type ",[36,3717,3336],{}," and save it as ",[36,3720,3721],{},"e_my_entity.tscn",". We call these types of ",[419,3724,3258],{},", ",[569,3727,3728],{},"Prefabs"," because they have a bunch of prefabricated nodes ready to go.",[10,3731,3732],{},"With prefabs, we can use any of the Godot Nodes as children of the entity and take advantage of the utility they offer in a compositional way. We can use all the nodes we want and just have a component that references the node be stored on the entity.",[10,3734,3735,3736,3738],{},"Any node extending ",[36,3737,3336],{}," can contain components. For example, you might have a player entity with health, position, and an inventory component. You can only have one instance of a component on an entity, but you can have multiple entities with the same component.",[10,3740,3741],{},"In Godot, from within the editor, there is an @export variable named component_resources. This allows you to define components that will be added to the entity whenever it is spawned. It also allows you to run tools from within components as Entity has the @tool annotation. You can easily see and modify the data from within Godot using the standard Godot resource editing tools. Especially with the introduction of the new tool button in 4.4. It unlocks a lot of power for tools on resources to get\u002Fset the data they need from the scene or to the scene.",[10,3743,3744],{},"Entities can also serve as glue code to connect different nodes in your scene to components or more commonly, help with initialization.",[10,3746,3747,3748,3751],{},"A common technique is in the ",[36,3749,3750],{},"on_ready()"," lifecycle function, sync the transform of the C_Transform component with the global_transform of the Node3D. This is useful when you want to move the entity around in the editor, and sync that position when the entity is ready.",[28,3753,3755],{"className":3634,"code":3754,"language":3636,"meta":34,"style":34},"# filepath: e_player.gd\nclass_name Player\nextends Entity\n\nfunc on_ready():\n    Utils.sync_transform(self)\n",[36,3756,3757,3762,3770,3778,3782,3793],{"__ignoreMap":34},[39,3758,3759],{"class":41,"line":42},[39,3760,3761],{"class":1764},"# filepath: e_player.gd\n",[39,3763,3764,3767],{"class":41,"line":48},[39,3765,3766],{"class":624},"class_name",[39,3768,3769],{"class":1169}," Player\n",[39,3771,3772,3775],{"class":41,"line":55},[39,3773,3774],{"class":624},"extends",[39,3776,3777],{"class":1169}," Entity\n",[39,3779,3780],{"class":41,"line":61},[39,3781,52],{"emptyLinePlaceholder":51},[39,3783,3784,3787,3790],{"class":41,"line":67},[39,3785,3786],{"class":624},"func",[39,3788,3789],{"class":1169}," on_ready",[39,3791,3792],{"class":620},"():\n",[39,3794,3795,3798,3800,3803,3805,3808],{"class":41,"line":73},[39,3796,3797],{"class":1169},"    Utils",[39,3799,476],{"class":620},[39,3801,3802],{"class":1169},"sync_transform",[39,3804,1790],{"class":620},[39,3806,3807],{"class":624},"self",[39,3809,112],{"class":620},[3206,3811,3812,3826],{},[537,3813,3814,3817,3818,3821,3822,3825],{},[569,3815,3816],{},"Attach Components"," – Add component resources to an entity’s ",[36,3819,3820],{},"component_resources"," array, or use ",[36,3823,3824],{},"add_component(...)"," in code.",[537,3827,3828,3831],{},[569,3829,3830],{},"Enable \u002F Disable"," – Entities can be enabled or disabled, determining whether systems will process them.",[704,3833],{},[699,3835,3837],{"id":3836},"naming-entities","Naming Entities",[10,3839,3840,3841,3844],{},"Entity class names should be in ClassCase, they should be named after the thing they represent. The file should be saved like ",[36,3842,3843],{},"e_entity_name.gd",".\nExamples:",[534,3846,3847,3856,3865],{},[537,3848,3849,3852,3853],{},[36,3850,3851],{},"Player"," (player character) - Saved as ",[36,3854,3855],{},"e_player.gd",[537,3857,3858,3861,3862],{},[36,3859,3860],{},"Enemy"," (enemy character) - Saved as ",[36,3863,3864],{},"e_enemy.gd",[537,3866,3867,3870,3871],{},[36,3868,3869],{},"Projectile"," (bullet or missile) - Saved as ",[36,3872,3873],{},"e_projectile.gd",[704,3875],{},[699,3877,3879],{"id":3878},"entity-lifecycle","Entity Lifecycle",[10,3881,3882,3883,3885],{},"Entities have a lifecycle managed by the ",[36,3884,3294],{}," node.",[3206,3887,3888,3895,3900,3906,3912,3918],{},[537,3889,3890,3891,3894],{},"When you add an entity to the world, it initializes and adds all the component_resources as components with the data defined in the editor. It then calls ",[569,3892,3893],{},"define_components()"," and adds those components to the entity. Allowing you to define components in two places code\u002Feditor.",[537,3896,3897,3899],{},[569,3898,3750],{}," – Called when the entity is ready. You can use this to set up initial states or sync transforms.",[537,3901,3902,3905],{},[569,3903,3904],{},"on_update(delta)"," – Called every frame in systems.",[537,3907,3908,3911],{},[569,3909,3910],{},"on_destroy()"," – Cleanup before removal.",[537,3913,3914,3917],{},[569,3915,3916],{},"on_disable()"," – When the entity is disabled.",[537,3919,3920,3923],{},[569,3921,3922],{},"on_enable()"," – When the entity is enabled.",[704,3925],{},[407,3927,3929],{"id":3928},"relationships-vs-components","Relationships vs Components",[10,3931,3932],{},"Components store data for a single entity, while relationships link entities together for cross-referencing. Use them to group or define bonds across entities without bloating component data.",[704,3934],{},[407,3936,3938],{"id":3937},"data-driven-architecture","Data-Driven Architecture",[10,3940,3941],{},"I encourage small, single-purpose components. Leverage queries to decouple logic from data. This ensures modularity and clarity, easing maintenance and scaling.",[704,3943],{},[407,3945,3264],{"id":3946},"components",[699,3948,3950],{"id":3949},"component-basics","Component Basics",[10,3952,3953],{},"Components only store data, they should never contain direct game logic. Although it is ok if you're providing functions that fetch or set properties, they should never contain logic that modifies the game state, only transforming its own data.",[10,3955,3956,3957,3959],{},"To create a new component, extend ",[36,3958,3342],{},":",[28,3961,3963],{"className":3634,"code":3962,"language":3636,"meta":34,"style":34},"# filepath: c_health.gd\n## Health Component.\n## Represents the health of an entity.\n## Stores both the total and current health values.\n## Used by systems to determine if an entity should be destroyed when health reaches zero.\n## Affected by the [Damage] Component.\nclass_name C_Health\nextends Component\n\n## How much total health this has\n@export var total := 1\n## The current health\n@export var current := 1\n",[36,3964,3965,3970,3975,3980,3985,3990,3995,4002,4009,4013,4018,4034,4039],{"__ignoreMap":34},[39,3966,3967],{"class":41,"line":42},[39,3968,3969],{"class":1764},"# filepath: c_health.gd\n",[39,3971,3972],{"class":41,"line":48},[39,3973,3974],{"class":1764},"## Health Component.\n",[39,3976,3977],{"class":41,"line":55},[39,3978,3979],{"class":1764},"## Represents the health of an entity.\n",[39,3981,3982],{"class":41,"line":61},[39,3983,3984],{"class":1764},"## Stores both the total and current health values.\n",[39,3986,3987],{"class":41,"line":67},[39,3988,3989],{"class":1764},"## Used by systems to determine if an entity should be destroyed when health reaches zero.\n",[39,3991,3992],{"class":41,"line":73},[39,3993,3994],{"class":1764},"## Affected by the [Damage] Component.\n",[39,3996,3997,3999],{"class":41,"line":79},[39,3998,3766],{"class":624},[39,4000,4001],{"class":1169}," C_Health\n",[39,4003,4004,4006],{"class":41,"line":85},[39,4005,3774],{"class":624},[39,4007,4008],{"class":1169}," Component\n",[39,4010,4011],{"class":41,"line":91},[39,4012,52],{"emptyLinePlaceholder":51},[39,4014,4015],{"class":41,"line":97},[39,4016,4017],{"class":1764},"## How much total health this has\n",[39,4019,4020,4023,4026,4029,4032],{"class":41,"line":103},[39,4021,4022],{"class":1169},"@export",[39,4024,4025],{"class":624}," var",[39,4027,4028],{"class":620}," total ",[39,4030,4031],{"class":624},":=",[39,4033,1573],{"class":1195},[39,4035,4036],{"class":41,"line":109},[39,4037,4038],{"class":1764},"## The current health\n",[39,4040,4041,4043,4045,4048,4050],{"class":41,"line":115},[39,4042,4022],{"class":1169},[39,4044,4025],{"class":624},[39,4046,4047],{"class":620}," current ",[39,4049,4031],{"class":624},[39,4051,1573],{"class":1195},[10,4053,4054,4055,4057],{},"Use ",[36,4056,4022],{}," to make properties visible in the Godot inspector, so you can set them in the editor. It also determines what should be serialized when importing\u002Fexporting ECS data.",[704,4059],{},[699,4061,4063],{"id":4062},"naming-components","Naming Components",[10,4065,4066,4067,4070],{},"Component class names should start with C_ and be in ClassCase. The file should be saved like ",[36,4068,4069],{},"c_component_name.gd",".\nIt helps to group components by their purpose in folders. Each component should reflect the data it carries. Examples:",[534,4072,4073,4082,4091],{},[537,4074,4075,4078,4079],{},[36,4076,4077],{},"C_Transform"," (holds position, rotation, scale) - Saved as ",[36,4080,4081],{},"c_transform.gd",[537,4083,4084,4087,4088],{},[36,4085,4086],{},"C_Velocity"," (speed vectors) - Saved as ",[36,4089,4090],{},"c_velocity.gd",[537,4092,4093,4096,4097],{},[36,4094,4095],{},"C_Health"," (current health, max health, etc.) - Saved as ",[36,4098,4099],{},"c_health.gd",[704,4101],{},[699,4103,4105],{"id":4104},"adding-components-to-an-entity","Adding Components to an Entity",[10,4107,4108,4109,4111],{},"You can add components in the editor by adding them in the entity’s Inspector under ",[36,4110,3820],{},", these will be added to the entity when the entity is added to the world. You can also add them dynamically in code:",[28,4113,4115],{"className":3634,"code":4114,"language":3636,"meta":34,"style":34},"var c_health := C_Health.new()\nc_health.max_health = 10\nentity.add_component(health_component)\n",[36,4116,4117,4136,4146],{"__ignoreMap":34},[39,4118,4119,4121,4124,4126,4129,4131,4133],{"class":41,"line":42},[39,4120,3643],{"class":624},[39,4122,4123],{"class":620}," c_health ",[39,4125,4031],{"class":624},[39,4127,4128],{"class":1169}," C_Health",[39,4130,476],{"class":620},[39,4132,3656],{"class":624},[39,4134,4135],{"class":620},"()\n",[39,4137,4138,4141,4143],{"class":41,"line":48},[39,4139,4140],{"class":620},"c_health.max_health ",[39,4142,625],{"class":624},[39,4144,4145],{"class":1195}," 10\n",[39,4147,4148,4151,4154],{"class":41,"line":55},[39,4149,4150],{"class":620},"entity.",[39,4152,4153],{"class":1169},"add_component",[39,4155,4156],{"class":620},"(health_component)\n",[10,4158,4159],{},"This approach keeps data modular, since your systems identify entities by the presence (or absence) of particular components.",[704,4161],{},[699,4163,4165],{"id":4164},"summary","Summary",[534,4167,4168,4173,4178,4184],{},[537,4169,4170,4172],{},[569,4171,3336],{}," = Node with data containers (components).",[537,4174,4175,4177],{},[569,4176,3342],{}," = Data resource with no logic.",[537,4179,4180,4183],{},[569,4181,4182],{},"Naming"," – Use meaningful component names for clear code.",[537,4185,4186,4189,4190,4192],{},[569,4187,4188],{},"Adding"," – Attach components via the inspector or ",[36,4191,3824],{}," methods.",[704,4194],{},[699,4196,3495],{"id":3494},[534,4198,4199,4206,4212],{},[537,4200,4201,3503,4204],{},[569,4202,4203],{},"Previous",[17,4205,3189],{"href":3538},[537,4207,4208,3503,4210],{},[569,4209,3502],{},[17,4211,3224],{"href":3223},[537,4213,4214],{},[17,4215,3511],{"href":3510},[4217,4218,3515],"h1",{"id":3514},[2911,4220,4221],{},[10,4222,4223],{},[17,4224,3198],{"href":3198,"rel":4225},[21],[375,4227,4228],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":48,"depth":48,"links":4230},[4231,4232,4237,4238,4239],{"id":3584,"depth":48,"text":3585},{"id":3621,"depth":48,"text":3258,"children":4233},[4234,4235,4236],{"id":3624,"depth":55,"text":3625},{"id":3836,"depth":55,"text":3837},{"id":3878,"depth":55,"text":3879},{"id":3928,"depth":48,"text":3929},{"id":3937,"depth":48,"text":3938},{"id":3946,"depth":48,"text":3264,"children":4240},[4241,4242,4243,4244,4245],{"id":3949,"depth":55,"text":3950},{"id":4062,"depth":55,"text":4063},{"id":4104,"depth":55,"text":4105},{"id":4164,"depth":55,"text":4165},{"id":3494,"depth":55,"text":3495},"04\u002F07\u002F2025","An overview of Entities and Components in Godot Entity Component System GECS.",{},{"title":3218,"description":4247},"blog\u002Fgecs-entities",[3542,3543,3544,3235],"1TDI7wZ5DJ61f3uSS9oyH47lXY2Ca_Qw1VNl6L2-oXA",{"id":4254,"title":3230,"body":4255,"date":5406,"description":5407,"extension":381,"meta":5408,"navigation":51,"path":3229,"seo":5409,"stem":5410,"tags":5411,"__hash__":5412},"blog\u002Fblog\u002Fgecs-relationships.md",{"type":7,"value":4256,"toc":5393},[4257,4264,4269,4287,4289,4293,4296,4299,4302,4306,4309,4312,4316,4376,4380,4383,4409,4432,4455,4529,4552,4794,4798,4801,4809,5070,5074,5077,5084,5087,5093,5097,5100,5133,5136,5365,5367,5369,5381,5383,5390],[2911,4258,4259],{},[10,4260,4261],{},[17,4262,3198],{"href":3198,"rel":4263},[21],[10,4265,4266],{},[569,4267,4268],{},"This is Part 4 of the GECS series:",[3206,4270,4271,4275,4279,4283],{},[537,4272,4273],{},[17,4274,3189],{"href":3538},[537,4276,4277],{},[17,4278,3218],{"href":3217},[537,4280,4281],{},[17,4282,3224],{"href":3223},[537,4284,4285,3212],{},[569,4286,3230],{},[704,4288],{},[407,4290,4292],{"id":4291},"relationships-link-entities-together","Relationships link entities together",[10,4294,4295],{},"Think of components as the data that makes up the state of an entity and relationships as the links that connect entity to entity(s)",[10,4297,4298],{},"Components are for storing data about the entity and relationships are for storing how entities interact or relate to other entities.",[10,4300,4301],{},"They can just be a direct link or they can carry some data about the relationship.",[407,4303,4305],{"id":4304},"what-are-relationships-in-gecs","What are relationships in GECS?",[10,4307,4308],{},"In GECS relationships consist of three parts, a source(Entity), target(Entity) and relation (Component).",[10,4310,4311],{},"Relationships allow you to easily associate things together and simplify querying for data by being able to use relationships as a way to search in addition to normal query methods.",[407,4313,4315],{"id":4314},"definitions","Definitions",[4317,4318,4319,4332],"table",{},[4320,4321,4322],"thead",{},[4323,4324,4325,4329],"tr",{},[4326,4327,4328],"th",{},"Name",[4326,4330,4331],{},"Description",[4333,4334,4335,4344,4352,4360,4368],"tbody",{},[4323,4336,4337,4341],{},[4338,4339,4340],"td",{},"Relationship",[4338,4342,4343],{},"A relationship that can be added and removed",[4323,4345,4346,4349],{},[4338,4347,4348],{},"Pair",[4338,4350,4351],{},"Relationship with two elements",[4323,4353,4354,4357],{},[4338,4355,4356],{},"Relation",[4338,4358,4359],{},"The first element of a pair",[4323,4361,4362,4365],{},[4338,4363,4364],{},"Target",[4338,4366,4367],{},"The second element of a pair",[4323,4369,4370,4373],{},[4338,4371,4372],{},"Source",[4338,4374,4375],{},"Entity which a relationship is added",[407,4377,4379],{"id":4378},"examples","Examples",[10,4381,4382],{},"Let's take a look at some classic Alice Bob and Heather examples that seem like they're straight out of your high-school math book.",[28,4384,4386],{"className":3634,"code":4385,"language":3636,"meta":34,"style":34},"# Create a new relationship (Shortened to Rel)\nRelationship.new(C_Relation.new(), e_target)\n",[36,4387,4388,4393],{"__ignoreMap":34},[39,4389,4390],{"class":41,"line":42},[39,4391,4392],{"class":1764},"# Create a new relationship (Shortened to Rel)\n",[39,4394,4395,4397,4399,4401,4404,4406],{"class":41,"line":48},[39,4396,4340],{"class":1169},[39,4398,476],{"class":620},[39,4400,3656],{"class":1169},[39,4402,4403],{"class":620},"(C_Relation.",[39,4405,3656],{"class":1169},[39,4407,4408],{"class":620},"(), e_target)\n",[28,4410,4412],{"className":3634,"code":4411,"language":3636,"meta":34,"style":34},"# c_likes.gd\nclass_name C_Likes\nextends Component\n",[36,4413,4414,4419,4426],{"__ignoreMap":34},[39,4415,4416],{"class":41,"line":42},[39,4417,4418],{"class":1764},"# c_likes.gd\n",[39,4420,4421,4423],{"class":41,"line":48},[39,4422,3766],{"class":624},[39,4424,4425],{"class":1169}," C_Likes\n",[39,4427,4428,4430],{"class":41,"line":55},[39,4429,3774],{"class":624},[39,4431,4008],{"class":1169},[28,4433,4435],{"className":3634,"code":4434,"language":3636,"meta":34,"style":34},"# c_loves.gd\nclass_name C_Loves\nextends Component\n",[36,4436,4437,4442,4449],{"__ignoreMap":34},[39,4438,4439],{"class":41,"line":42},[39,4440,4441],{"class":1764},"# c_loves.gd\n",[39,4443,4444,4446],{"class":41,"line":48},[39,4445,3766],{"class":624},[39,4447,4448],{"class":1169}," C_Loves\n",[39,4450,4451,4453],{"class":41,"line":55},[39,4452,3774],{"class":624},[39,4454,4008],{"class":1169},[28,4456,4458],{"className":3634,"code":4457,"language":3636,"meta":34,"style":34},"# c_eats.gd\nclass_name C_Eats\nextends Component\n\n@export var quantity :int = 1\n\nfunc _init(qty: int = quantity):\n    quantity = qty\n",[36,4459,4460,4465,4472,4478,4482,4498,4502,4519],{"__ignoreMap":34},[39,4461,4462],{"class":41,"line":42},[39,4463,4464],{"class":1764},"# c_eats.gd\n",[39,4466,4467,4469],{"class":41,"line":48},[39,4468,3766],{"class":624},[39,4470,4471],{"class":1169}," C_Eats\n",[39,4473,4474,4476],{"class":41,"line":55},[39,4475,3774],{"class":624},[39,4477,4008],{"class":1169},[39,4479,4480],{"class":41,"line":61},[39,4481,52],{"emptyLinePlaceholder":51},[39,4483,4484,4486,4488,4491,4493,4496],{"class":41,"line":67},[39,4485,4022],{"class":1169},[39,4487,4025],{"class":624},[39,4489,4490],{"class":620}," quantity :",[39,4492,1969],{"class":1169},[39,4494,4495],{"class":624}," =",[39,4497,1573],{"class":1195},[39,4499,4500],{"class":41,"line":73},[39,4501,52],{"emptyLinePlaceholder":51},[39,4503,4504,4506,4509,4512,4514,4516],{"class":41,"line":79},[39,4505,3786],{"class":624},[39,4507,4508],{"class":1169}," _init",[39,4510,4511],{"class":620},"(qty: ",[39,4513,1969],{"class":1169},[39,4515,4495],{"class":624},[39,4517,4518],{"class":620}," quantity):\n",[39,4520,4521,4524,4526],{"class":41,"line":85},[39,4522,4523],{"class":620},"    quantity ",[39,4525,625],{"class":624},[39,4527,4528],{"class":620}," qty\n",[28,4530,4532],{"className":3634,"code":4531,"language":3636,"meta":34,"style":34},"# e_food.gd\nclass_name Food\nextends Entity\n",[36,4533,4534,4539,4546],{"__ignoreMap":34},[39,4535,4536],{"class":41,"line":42},[39,4537,4538],{"class":1764},"# e_food.gd\n",[39,4540,4541,4543],{"class":41,"line":48},[39,4542,3766],{"class":624},[39,4544,4545],{"class":1169}," Food\n",[39,4547,4548,4550],{"class":41,"line":55},[39,4549,3774],{"class":624},[39,4551,3777],{"class":1169},[28,4553,4555],{"className":3634,"code":4554,"language":3636,"meta":34,"style":34},"# example.gd\n# Create our entities\nvar e_bob = Entity.new()\nvar e_alice = Entity.new()\nvar e_heather = Entity.new()\nvar e_apple = Food.new()\n\n# Create our relationships\n# bob likes alice\ne_bob.add_relationship(Relationship.new(C_Likes.new(), e_alice))\n# alice loves heather\ne_alice.add_relationship(Relationship.new(C_Loves.new(), e_heather))\n# heather likes food\ne_heather.add_relationship(Relationship.new(C_Likes.new(), Food))\n# heather eats 5 apples\ne_heather.add_relationship(Relationship.new(C_Eats.new(5), e_apple))\n# alice no longer loves heather\nalice.remove_relationship(Relationship.new(C_Loves.new(), e_heather))\n",[36,4556,4557,4562,4567,4584,4601,4618,4636,4640,4645,4650,4674,4679,4702,4707,4735,4740,4767,4772],{"__ignoreMap":34},[39,4558,4559],{"class":41,"line":42},[39,4560,4561],{"class":1764},"# example.gd\n",[39,4563,4564],{"class":41,"line":48},[39,4565,4566],{"class":1764},"# Create our entities\n",[39,4568,4569,4571,4574,4576,4578,4580,4582],{"class":41,"line":55},[39,4570,3643],{"class":624},[39,4572,4573],{"class":620}," e_bob ",[39,4575,625],{"class":624},[39,4577,3651],{"class":1169},[39,4579,476],{"class":620},[39,4581,3656],{"class":1169},[39,4583,4135],{"class":620},[39,4585,4586,4588,4591,4593,4595,4597,4599],{"class":41,"line":61},[39,4587,3643],{"class":624},[39,4589,4590],{"class":620}," e_alice ",[39,4592,625],{"class":624},[39,4594,3651],{"class":1169},[39,4596,476],{"class":620},[39,4598,3656],{"class":1169},[39,4600,4135],{"class":620},[39,4602,4603,4605,4608,4610,4612,4614,4616],{"class":41,"line":67},[39,4604,3643],{"class":624},[39,4606,4607],{"class":620}," e_heather ",[39,4609,625],{"class":624},[39,4611,3651],{"class":1169},[39,4613,476],{"class":620},[39,4615,3656],{"class":1169},[39,4617,4135],{"class":620},[39,4619,4620,4622,4625,4627,4630,4632,4634],{"class":41,"line":73},[39,4621,3643],{"class":624},[39,4623,4624],{"class":620}," e_apple ",[39,4626,625],{"class":624},[39,4628,4629],{"class":1169}," Food",[39,4631,476],{"class":620},[39,4633,3656],{"class":1169},[39,4635,4135],{"class":620},[39,4637,4638],{"class":41,"line":79},[39,4639,52],{"emptyLinePlaceholder":51},[39,4641,4642],{"class":41,"line":85},[39,4643,4644],{"class":1764},"# Create our relationships\n",[39,4646,4647],{"class":41,"line":91},[39,4648,4649],{"class":1764},"# bob likes alice\n",[39,4651,4652,4655,4658,4660,4662,4664,4666,4669,4671],{"class":41,"line":97},[39,4653,4654],{"class":620},"e_bob.",[39,4656,4657],{"class":1169},"add_relationship",[39,4659,1790],{"class":620},[39,4661,4340],{"class":1169},[39,4663,476],{"class":620},[39,4665,3656],{"class":1169},[39,4667,4668],{"class":620},"(C_Likes.",[39,4670,3656],{"class":1169},[39,4672,4673],{"class":620},"(), e_alice))\n",[39,4675,4676],{"class":41,"line":103},[39,4677,4678],{"class":1764},"# alice loves heather\n",[39,4680,4681,4684,4686,4688,4690,4692,4694,4697,4699],{"class":41,"line":109},[39,4682,4683],{"class":620},"e_alice.",[39,4685,4657],{"class":1169},[39,4687,1790],{"class":620},[39,4689,4340],{"class":1169},[39,4691,476],{"class":620},[39,4693,3656],{"class":1169},[39,4695,4696],{"class":620},"(C_Loves.",[39,4698,3656],{"class":1169},[39,4700,4701],{"class":620},"(), e_heather))\n",[39,4703,4704],{"class":41,"line":115},[39,4705,4706],{"class":1764},"# heather likes food\n",[39,4708,4709,4712,4714,4716,4718,4720,4722,4724,4726,4729,4732],{"class":41,"line":120},[39,4710,4711],{"class":620},"e_heather.",[39,4713,4657],{"class":1169},[39,4715,1790],{"class":620},[39,4717,4340],{"class":1169},[39,4719,476],{"class":620},[39,4721,3656],{"class":1169},[39,4723,4668],{"class":620},[39,4725,3656],{"class":1169},[39,4727,4728],{"class":620},"(), ",[39,4730,4731],{"class":1169},"Food",[39,4733,4734],{"class":620},"))\n",[39,4736,4737],{"class":41,"line":125},[39,4738,4739],{"class":1764},"# heather eats 5 apples\n",[39,4741,4742,4744,4746,4748,4750,4752,4754,4757,4759,4761,4764],{"class":41,"line":131},[39,4743,4711],{"class":620},[39,4745,4657],{"class":1169},[39,4747,1790],{"class":620},[39,4749,4340],{"class":1169},[39,4751,476],{"class":620},[39,4753,3656],{"class":1169},[39,4755,4756],{"class":620},"(C_Eats.",[39,4758,3656],{"class":1169},[39,4760,1790],{"class":620},[39,4762,4763],{"class":1195},"5",[39,4765,4766],{"class":620},"), e_apple))\n",[39,4768,4769],{"class":41,"line":137},[39,4770,4771],{"class":1764},"# alice no longer loves heather\n",[39,4773,4774,4777,4780,4782,4784,4786,4788,4790,4792],{"class":41,"line":143},[39,4775,4776],{"class":620},"alice.",[39,4778,4779],{"class":1169},"remove_relationship",[39,4781,1790],{"class":620},[39,4783,4340],{"class":1169},[39,4785,476],{"class":620},[39,4787,3656],{"class":1169},[39,4789,4696],{"class":620},[39,4791,3656],{"class":1169},[39,4793,4701],{"class":620},[699,4795,4797],{"id":4796},"relationship-queries","Relationship Queries",[10,4799,4800],{},"Now that we've created these entities and add the relationships we can query for these entities based on their relationships in the following ways",[534,4802,4803,4806],{},[537,4804,4805],{},"Relation: A component type, or an instanced component with data",[537,4807,4808],{},"Target: Either a reference to an entity, or the entity archetype.",[28,4810,4812],{"className":3634,"code":4811,"language":3636,"meta":34,"style":34},"# Any entity that likes alice\nECS.world.query.with_relationship([Relationship.new(C_Likes.new(), e_alice)])\n# Any entity with any relations toward heather\nECS.world.query.with_relationship([Relationship.new(ECS.wildcard, e_heather)])\n# Any entity with any relations toward heather that don't have any relationships with bob\nECS.world.query.with_relationship([Relationship.new(ECS.wildcard, e_heather)]).without_relationship([Relationship.new(C_Likes.new(), e_bob)])\n# Any entity that eats 5 apples\nECS.world.query.with_relationship([Relationship.new(C_Eats.new(5), e_apple)])\n# any entity that likes the food entity archetype\nECS.world.query.with_relationship([Relationship.new(C_Likes.new(), Food)])\n# Any entity that likes anything\nECS.world.query.with_relationship([Relationship.new(C_Likes.new(), ECS.wildcard)])\nECS.world.query.with_relationship([Relationship.new(C_Likes.new())])\n# Any entity with any relation to Enemy Entity archetype\nECS.world.query.with_relationship([Relationship.new(ECS.wildcard, Enemy)])\n",[36,4813,4814,4819,4845,4850,4873,4878,4919,4924,4951,4956,4983,4988,5015,5038,5043],{"__ignoreMap":34},[39,4815,4816],{"class":41,"line":42},[39,4817,4818],{"class":1764},"# Any entity that likes alice\n",[39,4820,4821,4823,4826,4829,4832,4834,4836,4838,4840,4842],{"class":41,"line":48},[39,4822,3301],{"class":1195},[39,4824,4825],{"class":620},".world.query.",[39,4827,4828],{"class":1169},"with_relationship",[39,4830,4831],{"class":620},"([",[39,4833,4340],{"class":1169},[39,4835,476],{"class":620},[39,4837,3656],{"class":1169},[39,4839,4668],{"class":620},[39,4841,3656],{"class":1169},[39,4843,4844],{"class":620},"(), e_alice)])\n",[39,4846,4847],{"class":41,"line":55},[39,4848,4849],{"class":1764},"# Any entity with any relations toward heather\n",[39,4851,4852,4854,4856,4858,4860,4862,4864,4866,4868,4870],{"class":41,"line":61},[39,4853,3301],{"class":1195},[39,4855,4825],{"class":620},[39,4857,4828],{"class":1169},[39,4859,4831],{"class":620},[39,4861,4340],{"class":1169},[39,4863,476],{"class":620},[39,4865,3656],{"class":1169},[39,4867,1790],{"class":620},[39,4869,3301],{"class":1195},[39,4871,4872],{"class":620},".wildcard, e_heather)])\n",[39,4874,4875],{"class":41,"line":67},[39,4876,4877],{"class":1764},"# Any entity with any relations toward heather that don't have any relationships with bob\n",[39,4879,4880,4882,4884,4886,4888,4890,4892,4894,4896,4898,4901,4904,4906,4908,4910,4912,4914,4916],{"class":41,"line":73},[39,4881,3301],{"class":1195},[39,4883,4825],{"class":620},[39,4885,4828],{"class":1169},[39,4887,4831],{"class":620},[39,4889,4340],{"class":1169},[39,4891,476],{"class":620},[39,4893,3656],{"class":1169},[39,4895,1790],{"class":620},[39,4897,3301],{"class":1195},[39,4899,4900],{"class":620},".wildcard, e_heather)]).",[39,4902,4903],{"class":1169},"without_relationship",[39,4905,4831],{"class":620},[39,4907,4340],{"class":1169},[39,4909,476],{"class":620},[39,4911,3656],{"class":1169},[39,4913,4668],{"class":620},[39,4915,3656],{"class":1169},[39,4917,4918],{"class":620},"(), e_bob)])\n",[39,4920,4921],{"class":41,"line":79},[39,4922,4923],{"class":1764},"# Any entity that eats 5 apples\n",[39,4925,4926,4928,4930,4932,4934,4936,4938,4940,4942,4944,4946,4948],{"class":41,"line":85},[39,4927,3301],{"class":1195},[39,4929,4825],{"class":620},[39,4931,4828],{"class":1169},[39,4933,4831],{"class":620},[39,4935,4340],{"class":1169},[39,4937,476],{"class":620},[39,4939,3656],{"class":1169},[39,4941,4756],{"class":620},[39,4943,3656],{"class":1169},[39,4945,1790],{"class":620},[39,4947,4763],{"class":1195},[39,4949,4950],{"class":620},"), e_apple)])\n",[39,4952,4953],{"class":41,"line":91},[39,4954,4955],{"class":1764},"# any entity that likes the food entity archetype\n",[39,4957,4958,4960,4962,4964,4966,4968,4970,4972,4974,4976,4978,4980],{"class":41,"line":97},[39,4959,3301],{"class":1195},[39,4961,4825],{"class":620},[39,4963,4828],{"class":1169},[39,4965,4831],{"class":620},[39,4967,4340],{"class":1169},[39,4969,476],{"class":620},[39,4971,3656],{"class":1169},[39,4973,4668],{"class":620},[39,4975,3656],{"class":1169},[39,4977,4728],{"class":620},[39,4979,4731],{"class":1169},[39,4981,4982],{"class":620},")])\n",[39,4984,4985],{"class":41,"line":103},[39,4986,4987],{"class":1764},"# Any entity that likes anything\n",[39,4989,4990,4992,4994,4996,4998,5000,5002,5004,5006,5008,5010,5012],{"class":41,"line":109},[39,4991,3301],{"class":1195},[39,4993,4825],{"class":620},[39,4995,4828],{"class":1169},[39,4997,4831],{"class":620},[39,4999,4340],{"class":1169},[39,5001,476],{"class":620},[39,5003,3656],{"class":1169},[39,5005,4668],{"class":620},[39,5007,3656],{"class":1169},[39,5009,4728],{"class":620},[39,5011,3301],{"class":1195},[39,5013,5014],{"class":620},".wildcard)])\n",[39,5016,5017,5019,5021,5023,5025,5027,5029,5031,5033,5035],{"class":41,"line":115},[39,5018,3301],{"class":1195},[39,5020,4825],{"class":620},[39,5022,4828],{"class":1169},[39,5024,4831],{"class":620},[39,5026,4340],{"class":1169},[39,5028,476],{"class":620},[39,5030,3656],{"class":1169},[39,5032,4668],{"class":620},[39,5034,3656],{"class":1169},[39,5036,5037],{"class":620},"())])\n",[39,5039,5040],{"class":41,"line":120},[39,5041,5042],{"class":1764},"# Any entity with any relation to Enemy Entity archetype\n",[39,5044,5045,5047,5049,5051,5053,5055,5057,5059,5061,5063,5066,5068],{"class":41,"line":125},[39,5046,3301],{"class":1195},[39,5048,4825],{"class":620},[39,5050,4828],{"class":1169},[39,5052,4831],{"class":620},[39,5054,4340],{"class":1169},[39,5056,476],{"class":620},[39,5058,3656],{"class":1169},[39,5060,1790],{"class":620},[39,5062,3301],{"class":1195},[39,5064,5065],{"class":620},".wildcard, ",[39,5067,3860],{"class":1169},[39,5069,4982],{"class":620},[699,5071,5073],{"id":5072},"relationship-wildcard","Relationship Wildcard",[10,5075,5076],{},"When querying for relationship pairs, it can be helpful to find all entities for a given relation or target. To accomplish this, we can use a wildcard constant.",[534,5078,5079],{},[537,5080,5081],{},[36,5082,5083],{},"ECS.wildcard = null",[10,5085,5086],{},"Using null will work as well but this constant keeps it semantic.",[10,5088,5089,5090],{},"Omitting the target in a a pair implicitly indicates ",[36,5091,5092],{},"ECS.WildCard",[407,5094,5096],{"id":5095},"naming-and-best-practice","Naming and Best Practice",[10,5098,5099],{},"Relationships should be named in snake case based on the Relationship that they provide. You can store relationships and reuse them rather then creating them each time for best performance.",[28,5101,5103],{"className":3634,"code":5102,"language":3636,"meta":34,"style":34},"# This stored relationship is cheaper to reuse then recreating it each time\nvar r_likes_apples = Relationship.new(C_Likes.new(), e_apple)\n",[36,5104,5105,5110],{"__ignoreMap":34},[39,5106,5107],{"class":41,"line":42},[39,5108,5109],{"class":1764},"# This stored relationship is cheaper to reuse then recreating it each time\n",[39,5111,5112,5114,5117,5119,5122,5124,5126,5128,5130],{"class":41,"line":48},[39,5113,3643],{"class":624},[39,5115,5116],{"class":620}," r_likes_apples ",[39,5118,625],{"class":624},[39,5120,5121],{"class":1169}," Relationship",[39,5123,476],{"class":620},[39,5125,3656],{"class":1169},[39,5127,4668],{"class":620},[39,5129,3656],{"class":1169},[39,5131,5132],{"class":620},"(), e_apple)\n",[10,5134,5135],{},"It can also be helpful to have a Relationships static class with functions to retrieve these relationships to encourage reuse like. Then you can have a big file filled with all your relationships on autocomplete.",[28,5137,5139],{"className":3634,"code":5138,"language":3636,"meta":34,"style":34},"class_name Relationships\n\nstatic func attacking_players():\n    return Relationship.new(C_IsAttacking.new(), Player)\n\nstatic func attacking_anything():\n    return Relationship.new(C_IsAttacking.new(), ECS.wildcard)\n\nstatic func range_attacking_players():\n    return Relationship.new(C_IsAttackingRanged.new(), Player)\n\nstatic func range_attacking_anything():\n    return Relationship.new(C_IsAttackingRanged.new(), ECS.wildcard)\n\nstatic func chasing_players():\n    return Relationship.new(C_IsChasing.new(), Player)\n\nstatic func chasing_anything():\n    return Relationship.new(C_IsChasing.new(), ECS.wildcard)\n",[36,5140,5141,5148,5152,5165,5187,5191,5202,5223,5227,5238,5259,5263,5274,5294,5298,5309,5330,5334,5345],{"__ignoreMap":34},[39,5142,5143,5145],{"class":41,"line":42},[39,5144,3766],{"class":624},[39,5146,5147],{"class":1169}," Relationships\n",[39,5149,5150],{"class":41,"line":48},[39,5151,52],{"emptyLinePlaceholder":51},[39,5153,5154,5157,5160,5163],{"class":41,"line":55},[39,5155,5156],{"class":624},"static",[39,5158,5159],{"class":624}," func",[39,5161,5162],{"class":1169}," attacking_players",[39,5164,3792],{"class":620},[39,5166,5167,5170,5172,5174,5176,5179,5181,5183,5185],{"class":41,"line":61},[39,5168,5169],{"class":624},"    return",[39,5171,5121],{"class":1169},[39,5173,476],{"class":620},[39,5175,3656],{"class":1169},[39,5177,5178],{"class":620},"(C_IsAttacking.",[39,5180,3656],{"class":1169},[39,5182,4728],{"class":620},[39,5184,3851],{"class":1169},[39,5186,112],{"class":620},[39,5188,5189],{"class":41,"line":67},[39,5190,52],{"emptyLinePlaceholder":51},[39,5192,5193,5195,5197,5200],{"class":41,"line":73},[39,5194,5156],{"class":624},[39,5196,5159],{"class":624},[39,5198,5199],{"class":1169}," attacking_anything",[39,5201,3792],{"class":620},[39,5203,5204,5206,5208,5210,5212,5214,5216,5218,5220],{"class":41,"line":79},[39,5205,5169],{"class":624},[39,5207,5121],{"class":1169},[39,5209,476],{"class":620},[39,5211,3656],{"class":1169},[39,5213,5178],{"class":620},[39,5215,3656],{"class":1169},[39,5217,4728],{"class":620},[39,5219,3301],{"class":1195},[39,5221,5222],{"class":620},".wildcard)\n",[39,5224,5225],{"class":41,"line":85},[39,5226,52],{"emptyLinePlaceholder":51},[39,5228,5229,5231,5233,5236],{"class":41,"line":91},[39,5230,5156],{"class":624},[39,5232,5159],{"class":624},[39,5234,5235],{"class":1169}," range_attacking_players",[39,5237,3792],{"class":620},[39,5239,5240,5242,5244,5246,5248,5251,5253,5255,5257],{"class":41,"line":97},[39,5241,5169],{"class":624},[39,5243,5121],{"class":1169},[39,5245,476],{"class":620},[39,5247,3656],{"class":1169},[39,5249,5250],{"class":620},"(C_IsAttackingRanged.",[39,5252,3656],{"class":1169},[39,5254,4728],{"class":620},[39,5256,3851],{"class":1169},[39,5258,112],{"class":620},[39,5260,5261],{"class":41,"line":103},[39,5262,52],{"emptyLinePlaceholder":51},[39,5264,5265,5267,5269,5272],{"class":41,"line":109},[39,5266,5156],{"class":624},[39,5268,5159],{"class":624},[39,5270,5271],{"class":1169}," range_attacking_anything",[39,5273,3792],{"class":620},[39,5275,5276,5278,5280,5282,5284,5286,5288,5290,5292],{"class":41,"line":115},[39,5277,5169],{"class":624},[39,5279,5121],{"class":1169},[39,5281,476],{"class":620},[39,5283,3656],{"class":1169},[39,5285,5250],{"class":620},[39,5287,3656],{"class":1169},[39,5289,4728],{"class":620},[39,5291,3301],{"class":1195},[39,5293,5222],{"class":620},[39,5295,5296],{"class":41,"line":120},[39,5297,52],{"emptyLinePlaceholder":51},[39,5299,5300,5302,5304,5307],{"class":41,"line":125},[39,5301,5156],{"class":624},[39,5303,5159],{"class":624},[39,5305,5306],{"class":1169}," chasing_players",[39,5308,3792],{"class":620},[39,5310,5311,5313,5315,5317,5319,5322,5324,5326,5328],{"class":41,"line":131},[39,5312,5169],{"class":624},[39,5314,5121],{"class":1169},[39,5316,476],{"class":620},[39,5318,3656],{"class":1169},[39,5320,5321],{"class":620},"(C_IsChasing.",[39,5323,3656],{"class":1169},[39,5325,4728],{"class":620},[39,5327,3851],{"class":1169},[39,5329,112],{"class":620},[39,5331,5332],{"class":41,"line":137},[39,5333,52],{"emptyLinePlaceholder":51},[39,5335,5336,5338,5340,5343],{"class":41,"line":143},[39,5337,5156],{"class":624},[39,5339,5159],{"class":624},[39,5341,5342],{"class":1169}," chasing_anything",[39,5344,3792],{"class":620},[39,5346,5347,5349,5351,5353,5355,5357,5359,5361,5363],{"class":41,"line":149},[39,5348,5169],{"class":624},[39,5350,5121],{"class":1169},[39,5352,476],{"class":620},[39,5354,3656],{"class":1169},[39,5356,5321],{"class":620},[39,5358,3656],{"class":1169},[39,5360,4728],{"class":620},[39,5362,3301],{"class":1195},[39,5364,5222],{"class":620},[704,5366],{},[699,5368,3495],{"id":3494},[534,5370,5371,5377],{},[537,5372,5373,3503,5375],{},[569,5374,4203],{},[17,5376,3224],{"href":3223},[537,5378,5379],{},[17,5380,3511],{"href":3510},[407,5382,3515],{"id":3514},[2911,5384,5385],{},[10,5386,5387],{},[17,5388,3198],{"href":3198,"rel":5389},[21],[375,5391,5392],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":34,"searchDepth":48,"depth":48,"links":5394},[5395,5396,5397,5398,5402,5405],{"id":4291,"depth":48,"text":4292},{"id":4304,"depth":48,"text":4305},{"id":4314,"depth":48,"text":4315},{"id":4378,"depth":48,"text":4379,"children":5399},[5400,5401],{"id":4796,"depth":55,"text":4797},{"id":5072,"depth":55,"text":5073},{"id":5095,"depth":48,"text":5096,"children":5403},[5404],{"id":3494,"depth":55,"text":3495},{"id":3514,"depth":48,"text":3515},"04\u002F09\u002F2025","An overview of Relationships in Godot Entity Component System GECS.",{},{"title":3230,"description":5407},"blog\u002Fgecs-relationships",[3542,3543,3544,3235],"MkfiqsCZyAVxbIDZpCVdRgVQVY4BsbAdH49MzFuMijc",{"id":5414,"title":3224,"body":5415,"date":6682,"description":6683,"extension":381,"meta":6684,"navigation":51,"path":3223,"seo":6685,"stem":6686,"tags":6687,"__hash__":6688},"blog\u002Fblog\u002Fgecs-systems-queries.md",{"type":7,"value":5416,"toc":6667},[5417,5424,5429,5447,5449,5452,5458,5461,5464,5467,5487,5490,5494,5508,5512,5515,5522,5529,5689,5693,5699,5702,5845,5849,5858,5862,5869,5880,5884,6377,6381,6384,6411,6415,6421,6439,6448,6452,6470,6474,6477,6558,6561,6593,6596,6616,6620,6633,6635,6637,6655,6657,6664],[2911,5418,5419],{},[10,5420,5421],{},[17,5422,3198],{"href":3198,"rel":5423},[21],[10,5425,5426],{},[569,5427,5428],{},"This is Part 3 of the GECS series:",[3206,5430,5431,5435,5439,5443],{},[537,5432,5433],{},[17,5434,3189],{"href":3538},[537,5436,5437],{},[17,5438,3218],{"href":3217},[537,5440,5441,3212],{},[569,5442,3224],{},[537,5444,5445],{},[17,5446,3230],{"href":3229},[704,5448],{},[407,5450,3270],{"id":5451},"systems",[10,5453,5454,5455,5457],{},"Systems in GECS are nodes extending ",[36,5456,3392],{},", containing game logic to process specific entities based on component queries.",[10,5459,5460],{},"Systems are the core units of work that make an ECS world come alive. They should be small and atomic (Only do one thing).",[10,5462,5463],{},"If you keep them small they're easier to reason about and easier to test.",[10,5465,5466],{},"Systems are made of two parts:",[534,5468,5469,5472],{},[537,5470,5471],{},"A Query - Defines what entities we're interested in running code on based on the components and relationships they have",[537,5473,5474,5475],{},"A Process - The function that runs on either all entities at once or a single entity. There are two different process functions you can override\n",[534,5476,5477,5482],{},[537,5478,5479],{},[36,5480,5481],{},"process(entity:  Entity, delta: float) -> void:",[537,5483,5484],{},[36,5485,5486],{},"process_all(entity:  Array[Entity], delta: float) -> void:",[10,5488,5489],{},"A good way to see what I mean by this is to take a look at some examples.",[699,5491,5493],{"id":5492},"writing-a-system","Writing a System",[10,5495,5496,5497,5500,5501,591,5504,5507],{},"Systems use a ",[36,5498,5499],{},"query()"," function to collect entities and either ",[36,5502,5503],{},"process(entity, delta)",[36,5505,5506],{},"process_all(entities, delta)"," to update them each frame.\nKeep logic small and focused in each system, ensuring easier reuse and maintenance.",[2914,5509,5511],{"id":5510},"transform-system","Transform System",[10,5513,5514],{},"We often want to process a bunch of entities all in one go because the code should apply to all of them and running a function for each one has some overhead if all we need to do is some simple data transformation and we have a lot of entities.",[10,5516,5517,5518,5521],{},"For that you can use the ",[36,5519,5520],{},"process_all(entities: Array, delta: float)"," to run a single process on all the entities at once.",[10,5523,5524,5525,5528],{},"This is usually a good thing to do for physics and simple transformation.\nYou can also get all the components from a list of entities using the ",[36,5526,5527],{},"ECS.get_components(entities: Array[Entities], ComponentType)"," to make it easier to grab all the components of the same type.",[28,5530,5532],{"className":3634,"code":5531,"language":3636,"meta":34,"style":34},"## TransformSystem.\n##\n## Synchronizes the `Transform` component with the entity's actual transform in the scene.\n## Updates the entity's position, rotation, and scale based on the `Transform` component.\n## Processes entities with the `Transform` component.\nclass_name TransformSystem\nextends System\n\nfunc query() -> QueryBuilder:\n    return q.with_all([C_Transform])\n\nfunc process_all(entities: Array, _delta):\n    var transforms = ECS.get_components(entities, C_Transform) as Array[C_Transform]\n    for i in range(entities.size()):\n        entities[i].global_transform = transforms[i].transform\n",[36,5533,5534,5539,5544,5549,5554,5559,5566,5573,5577,5595,5608,5612,5628,5658,5679],{"__ignoreMap":34},[39,5535,5536],{"class":41,"line":42},[39,5537,5538],{"class":1764},"## TransformSystem.\n",[39,5540,5541],{"class":41,"line":48},[39,5542,5543],{"class":1764},"##\n",[39,5545,5546],{"class":41,"line":55},[39,5547,5548],{"class":1764},"## Synchronizes the `Transform` component with the entity's actual transform in the scene.\n",[39,5550,5551],{"class":41,"line":61},[39,5552,5553],{"class":1764},"## Updates the entity's position, rotation, and scale based on the `Transform` component.\n",[39,5555,5556],{"class":41,"line":67},[39,5557,5558],{"class":1764},"## Processes entities with the `Transform` component.\n",[39,5560,5561,5563],{"class":41,"line":73},[39,5562,3766],{"class":624},[39,5564,5565],{"class":1169}," TransformSystem\n",[39,5567,5568,5570],{"class":41,"line":79},[39,5569,3774],{"class":624},[39,5571,5572],{"class":1169}," System\n",[39,5574,5575],{"class":41,"line":85},[39,5576,52],{"emptyLinePlaceholder":51},[39,5578,5579,5581,5584,5586,5589,5592],{"class":41,"line":91},[39,5580,3786],{"class":624},[39,5582,5583],{"class":1169}," query",[39,5585,3659],{"class":620},[39,5587,5588],{"class":624},"->",[39,5590,5591],{"class":1169}," QueryBuilder",[39,5593,5594],{"class":620},":\n",[39,5596,5597,5599,5602,5605],{"class":41,"line":97},[39,5598,5169],{"class":624},[39,5600,5601],{"class":620}," q.",[39,5603,5604],{"class":1169},"with_all",[39,5606,5607],{"class":620},"([C_Transform])\n",[39,5609,5610],{"class":41,"line":103},[39,5611,52],{"emptyLinePlaceholder":51},[39,5613,5614,5616,5619,5622,5625],{"class":41,"line":109},[39,5615,3786],{"class":624},[39,5617,5618],{"class":1169}," process_all",[39,5620,5621],{"class":620},"(entities: ",[39,5623,5624],{"class":1169},"Array",[39,5626,5627],{"class":620},", _delta):\n",[39,5629,5630,5633,5636,5638,5641,5643,5646,5649,5652,5655],{"class":41,"line":115},[39,5631,5632],{"class":624},"    var",[39,5634,5635],{"class":620}," transforms ",[39,5637,625],{"class":624},[39,5639,5640],{"class":1195}," ECS",[39,5642,476],{"class":620},[39,5644,5645],{"class":1169},"get_components",[39,5647,5648],{"class":620},"(entities, C_Transform) ",[39,5650,5651],{"class":624},"as",[39,5653,5654],{"class":1169}," Array",[39,5656,5657],{"class":620},"[C_Transform]\n",[39,5659,5660,5662,5665,5667,5670,5673,5676],{"class":41,"line":120},[39,5661,2001],{"class":624},[39,5663,5664],{"class":620}," i ",[39,5666,1521],{"class":624},[39,5668,5669],{"class":1169}," range",[39,5671,5672],{"class":620},"(entities.",[39,5674,5675],{"class":1169},"size",[39,5677,5678],{"class":620},"()):\n",[39,5680,5681,5684,5686],{"class":41,"line":125},[39,5682,5683],{"class":620},"        entities[i].global_transform ",[39,5685,625],{"class":624},[39,5687,5688],{"class":620}," transforms[i].transform\n",[2914,5690,5692],{"id":5691},"lifetime-system","Lifetime System",[10,5694,5695,5696,476],{},"Sometimes you just want to grab components off a single entity and process it and we don't need to worry about it being a lot of entities we can do a more simple version using the ",[36,5697,5698],{},"process(entity: Entity, delta: float)",[10,5700,5701],{},"This is great when we just need to do something that will run on a few entities and it's easier to have the iterator code handled for us.",[28,5703,5705],{"className":3634,"code":5704,"language":3636,"meta":34,"style":34},"## Removes any entity after its lifetime has expired\nclass_name LifetimeSystem\nextends System\n\nfunc query() -> QueryBuilder:\n    return q.with_all([C_Lifetime])\n\nfunc process(entity: Entity, delta: float):\n    var c_lifetime = entity.get_component(C_Lifetime) as C_Lifetime\n    c_lifetime.lifetime -= delta\n\n    if c_lifetime.lifetime \u003C= 0:\n        ECS.world.remove_entity(entity)\n",[36,5706,5707,5712,5719,5725,5729,5743,5754,5758,5779,5802,5813,5817,5832],{"__ignoreMap":34},[39,5708,5709],{"class":41,"line":42},[39,5710,5711],{"class":1764},"## Removes any entity after its lifetime has expired\n",[39,5713,5714,5716],{"class":41,"line":48},[39,5715,3766],{"class":624},[39,5717,5718],{"class":1169}," LifetimeSystem\n",[39,5720,5721,5723],{"class":41,"line":55},[39,5722,3774],{"class":624},[39,5724,5572],{"class":1169},[39,5726,5727],{"class":41,"line":61},[39,5728,52],{"emptyLinePlaceholder":51},[39,5730,5731,5733,5735,5737,5739,5741],{"class":41,"line":67},[39,5732,3786],{"class":624},[39,5734,5583],{"class":1169},[39,5736,3659],{"class":620},[39,5738,5588],{"class":624},[39,5740,5591],{"class":1169},[39,5742,5594],{"class":620},[39,5744,5745,5747,5749,5751],{"class":41,"line":73},[39,5746,5169],{"class":624},[39,5748,5601],{"class":620},[39,5750,5604],{"class":1169},[39,5752,5753],{"class":620},"([C_Lifetime])\n",[39,5755,5756],{"class":41,"line":79},[39,5757,52],{"emptyLinePlaceholder":51},[39,5759,5760,5762,5765,5768,5770,5773,5776],{"class":41,"line":85},[39,5761,3786],{"class":624},[39,5763,5764],{"class":1169}," process",[39,5766,5767],{"class":620},"(entity: ",[39,5769,3336],{"class":1169},[39,5771,5772],{"class":620},", delta: ",[39,5774,5775],{"class":1169},"float",[39,5777,5778],{"class":620},"):\n",[39,5780,5781,5783,5786,5788,5791,5794,5797,5799],{"class":41,"line":91},[39,5782,5632],{"class":624},[39,5784,5785],{"class":620}," c_lifetime ",[39,5787,625],{"class":624},[39,5789,5790],{"class":620}," entity.",[39,5792,5793],{"class":1169},"get_component",[39,5795,5796],{"class":620},"(C_Lifetime) ",[39,5798,5651],{"class":624},[39,5800,5801],{"class":620}," C_Lifetime\n",[39,5803,5804,5807,5810],{"class":41,"line":97},[39,5805,5806],{"class":620},"    c_lifetime.lifetime ",[39,5808,5809],{"class":624},"-=",[39,5811,5812],{"class":620}," delta\n",[39,5814,5815],{"class":41,"line":103},[39,5816,52],{"emptyLinePlaceholder":51},[39,5818,5819,5822,5825,5828,5830],{"class":41,"line":109},[39,5820,5821],{"class":624},"    if",[39,5823,5824],{"class":620}," c_lifetime.lifetime ",[39,5826,5827],{"class":624},"\u003C=",[39,5829,1977],{"class":1195},[39,5831,5594],{"class":620},[39,5833,5834,5837,5839,5842],{"class":41,"line":115},[39,5835,5836],{"class":1195},"        ECS",[39,5838,3703],{"class":620},[39,5840,5841],{"class":1169},"remove_entity",[39,5843,5844],{"class":620},"(entity)\n",[699,5846,5848],{"id":5847},"process_all-vs-process","process_all vs process",[10,5850,4054,5851,5854,5855,5857],{},[36,5852,5853],{},"process_all"," for batch operations, or ",[36,5856,3461],{}," if you need per-entity granularity. Striking a balance between the two makes your system code more efficient.",[699,5859,5861],{"id":5860},"sub-systems","Sub-Systems",[10,5863,5864,5865,5868],{},"Sometimes you want to create a system that runs multiple systems together and groups them into one file. For that we can override the ",[36,5866,5867],{},"sub_systems()"," function.",[10,5870,5871,5872,5875,5876,5879],{},"This function returns an array of ",[36,5873,5874],{},"ECS.world.query"," and the Callable in the form of ",[36,5877,5878],{},"my_process_function(entity: Entity, delta: float)"," that will run on each one of the entities that matches the query.",[2914,5881,5883],{"id":5882},"damage-system","Damage System",[28,5885,5887],{"className":3634,"code":5886,"language":3636,"meta":34,"style":34},"## DamageSystem.\n##\n## Processes entities that have taken damage.\n## Reduces the entity's health based on the `Damage` component.\n## Plays a sound effect when damage is taken.\n## Removes the `Damage` component after processing.\nclass_name DamageSystem\nextends System\n\nfunc sub_systems():\n    return [\n        # Handles damage on entities with health\n        [ECS.world.query.with_all([C_Health]).with_any([C_Damage, C_HeavyDamage]).with_none([C_Death, C_Invunerable, C_Breakable]), health_damage_subsys], \n        # Handles damage on breakable entities and heavy damage done to them\n        [ECS.world.query.with_all([C_Breakable, C_Health,C_HeavyDamage]).with_none([C_Death, C_Invunerable]), breakable_damage_subsys], \n    ]\n\nfunc breakable_damage_subsys(entity, delta):\n    var c_heavy_damage = entity.get_component(C_HeavyDamage) as C_HeavyDamage\n    var c_health = entity.get_component(C_Health) as C_Health\n\n    c_health.current -= c_heavy_damage.amount\n\n    if c_health.current \u003C= 0:\n        entity.add_component(C_Death.new())\n\nfunc health_damage_subsys(entity: Entity, _delta: float):\n    var c_damage = entity.get_component(C_Damage) as C_Damage\n    var c_heavy_damage = entity.get_component(C_Damage) as C_Damage\n    var c_health = entity.get_component(C_Health) as C_Health\n\n    var damages = [c_damage, c_heavy_damage].filter( func(damage): return damage != null )\n\n    for damage in damages:\n        # Damage the Health Component by the damage amount\n        c_health.current -= damage.amount\n        entity.remove_component(damage.get_script())\n\n    if c_health.current > 0:\n        Loggie.debug('Damaged', c_damage, c_health)\n        #SoundManager.play('fx', 'c_damage')\n\n    if c_health.current \u003C= 0:\n        entity.add_component(C_Death.new())\n    \n    if entity is Player:\n        GameState.health_changed.emit(c_health.current)\n",[36,5888,5889,5894,5898,5903,5908,5913,5918,5925,5931,5935,5944,5951,5956,5982,5987,6005,6010,6014,6024,6045,6064,6068,6078,6082,6095,6110,6114,6132,6153,6171,6189,6193,6231,6235,6246,6251,6261,6276,6280,6292,6310,6315,6319,6331,6343,6348,6363],{"__ignoreMap":34},[39,5890,5891],{"class":41,"line":42},[39,5892,5893],{"class":1764},"## DamageSystem.\n",[39,5895,5896],{"class":41,"line":48},[39,5897,5543],{"class":1764},[39,5899,5900],{"class":41,"line":55},[39,5901,5902],{"class":1764},"## Processes entities that have taken damage.\n",[39,5904,5905],{"class":41,"line":61},[39,5906,5907],{"class":1764},"## Reduces the entity's health based on the `Damage` component.\n",[39,5909,5910],{"class":41,"line":67},[39,5911,5912],{"class":1764},"## Plays a sound effect when damage is taken.\n",[39,5914,5915],{"class":41,"line":73},[39,5916,5917],{"class":1764},"## Removes the `Damage` component after processing.\n",[39,5919,5920,5922],{"class":41,"line":79},[39,5921,3766],{"class":624},[39,5923,5924],{"class":1169}," DamageSystem\n",[39,5926,5927,5929],{"class":41,"line":85},[39,5928,3774],{"class":624},[39,5930,5572],{"class":1169},[39,5932,5933],{"class":41,"line":91},[39,5934,52],{"emptyLinePlaceholder":51},[39,5936,5937,5939,5942],{"class":41,"line":97},[39,5938,3786],{"class":624},[39,5940,5941],{"class":1169}," sub_systems",[39,5943,3792],{"class":620},[39,5945,5946,5948],{"class":41,"line":103},[39,5947,5169],{"class":624},[39,5949,5950],{"class":620}," [\n",[39,5952,5953],{"class":41,"line":109},[39,5954,5955],{"class":1764},"        # Handles damage on entities with health\n",[39,5957,5958,5961,5963,5965,5967,5970,5973,5976,5979],{"class":41,"line":115},[39,5959,5960],{"class":620},"        [",[39,5962,3301],{"class":1195},[39,5964,4825],{"class":620},[39,5966,5604],{"class":1169},[39,5968,5969],{"class":620},"([C_Health]).",[39,5971,5972],{"class":1169},"with_any",[39,5974,5975],{"class":620},"([C_Damage, C_HeavyDamage]).",[39,5977,5978],{"class":1169},"with_none",[39,5980,5981],{"class":620},"([C_Death, C_Invunerable, C_Breakable]), health_damage_subsys], \n",[39,5983,5984],{"class":41,"line":120},[39,5985,5986],{"class":1764},"        # Handles damage on breakable entities and heavy damage done to them\n",[39,5988,5989,5991,5993,5995,5997,6000,6002],{"class":41,"line":125},[39,5990,5960],{"class":620},[39,5992,3301],{"class":1195},[39,5994,4825],{"class":620},[39,5996,5604],{"class":1169},[39,5998,5999],{"class":620},"([C_Breakable, C_Health,C_HeavyDamage]).",[39,6001,5978],{"class":1169},[39,6003,6004],{"class":620},"([C_Death, C_Invunerable]), breakable_damage_subsys], \n",[39,6006,6007],{"class":41,"line":131},[39,6008,6009],{"class":620},"    ]\n",[39,6011,6012],{"class":41,"line":137},[39,6013,52],{"emptyLinePlaceholder":51},[39,6015,6016,6018,6021],{"class":41,"line":143},[39,6017,3786],{"class":624},[39,6019,6020],{"class":1169}," breakable_damage_subsys",[39,6022,6023],{"class":620},"(entity, delta):\n",[39,6025,6026,6028,6031,6033,6035,6037,6040,6042],{"class":41,"line":149},[39,6027,5632],{"class":624},[39,6029,6030],{"class":620}," c_heavy_damage ",[39,6032,625],{"class":624},[39,6034,5790],{"class":620},[39,6036,5793],{"class":1169},[39,6038,6039],{"class":620},"(C_HeavyDamage) ",[39,6041,5651],{"class":624},[39,6043,6044],{"class":620}," C_HeavyDamage\n",[39,6046,6047,6049,6051,6053,6055,6057,6060,6062],{"class":41,"line":155},[39,6048,5632],{"class":624},[39,6050,4123],{"class":620},[39,6052,625],{"class":624},[39,6054,5790],{"class":620},[39,6056,5793],{"class":1169},[39,6058,6059],{"class":620},"(C_Health) ",[39,6061,5651],{"class":624},[39,6063,4001],{"class":620},[39,6065,6066],{"class":41,"line":161},[39,6067,52],{"emptyLinePlaceholder":51},[39,6069,6070,6073,6075],{"class":41,"line":167},[39,6071,6072],{"class":620},"    c_health.current ",[39,6074,5809],{"class":624},[39,6076,6077],{"class":620}," c_heavy_damage.amount\n",[39,6079,6080],{"class":41,"line":172},[39,6081,52],{"emptyLinePlaceholder":51},[39,6083,6084,6086,6089,6091,6093],{"class":41,"line":178},[39,6085,5821],{"class":624},[39,6087,6088],{"class":620}," c_health.current ",[39,6090,5827],{"class":624},[39,6092,1977],{"class":1195},[39,6094,5594],{"class":620},[39,6096,6097,6100,6102,6105,6107],{"class":41,"line":184},[39,6098,6099],{"class":620},"        entity.",[39,6101,4153],{"class":1169},[39,6103,6104],{"class":620},"(C_Death.",[39,6106,3656],{"class":1169},[39,6108,6109],{"class":620},"())\n",[39,6111,6112],{"class":41,"line":190},[39,6113,52],{"emptyLinePlaceholder":51},[39,6115,6116,6118,6121,6123,6125,6128,6130],{"class":41,"line":196},[39,6117,3786],{"class":624},[39,6119,6120],{"class":1169}," health_damage_subsys",[39,6122,5767],{"class":620},[39,6124,3336],{"class":1169},[39,6126,6127],{"class":620},", _delta: ",[39,6129,5775],{"class":1169},[39,6131,5778],{"class":620},[39,6133,6134,6136,6139,6141,6143,6145,6148,6150],{"class":41,"line":202},[39,6135,5632],{"class":624},[39,6137,6138],{"class":620}," c_damage ",[39,6140,625],{"class":624},[39,6142,5790],{"class":620},[39,6144,5793],{"class":1169},[39,6146,6147],{"class":620},"(C_Damage) ",[39,6149,5651],{"class":624},[39,6151,6152],{"class":620}," C_Damage\n",[39,6154,6155,6157,6159,6161,6163,6165,6167,6169],{"class":41,"line":208},[39,6156,5632],{"class":624},[39,6158,6030],{"class":620},[39,6160,625],{"class":624},[39,6162,5790],{"class":620},[39,6164,5793],{"class":1169},[39,6166,6147],{"class":620},[39,6168,5651],{"class":624},[39,6170,6152],{"class":620},[39,6172,6173,6175,6177,6179,6181,6183,6185,6187],{"class":41,"line":214},[39,6174,5632],{"class":624},[39,6176,4123],{"class":620},[39,6178,625],{"class":624},[39,6180,5790],{"class":620},[39,6182,5793],{"class":1169},[39,6184,6059],{"class":620},[39,6186,5651],{"class":624},[39,6188,4001],{"class":620},[39,6190,6191],{"class":41,"line":220},[39,6192,52],{"emptyLinePlaceholder":51},[39,6194,6195,6197,6200,6202,6205,6208,6211,6213,6216,6219,6222,6225,6228],{"class":41,"line":226},[39,6196,5632],{"class":624},[39,6198,6199],{"class":620}," damages ",[39,6201,625],{"class":624},[39,6203,6204],{"class":620}," [c_damage, c_heavy_damage].",[39,6206,6207],{"class":1169},"filter",[39,6209,6210],{"class":620},"( ",[39,6212,3786],{"class":624},[39,6214,6215],{"class":620},"(damage): ",[39,6217,6218],{"class":624},"return",[39,6220,6221],{"class":620}," damage ",[39,6223,6224],{"class":624},"!=",[39,6226,6227],{"class":1195}," null",[39,6229,6230],{"class":620}," )\n",[39,6232,6233],{"class":41,"line":232},[39,6234,52],{"emptyLinePlaceholder":51},[39,6236,6237,6239,6241,6243],{"class":41,"line":238},[39,6238,2001],{"class":624},[39,6240,6221],{"class":620},[39,6242,1521],{"class":624},[39,6244,6245],{"class":620}," damages:\n",[39,6247,6248],{"class":41,"line":244},[39,6249,6250],{"class":1764},"        # Damage the Health Component by the damage amount\n",[39,6252,6253,6256,6258],{"class":41,"line":250},[39,6254,6255],{"class":620},"        c_health.current ",[39,6257,5809],{"class":624},[39,6259,6260],{"class":620}," damage.amount\n",[39,6262,6263,6265,6268,6271,6274],{"class":41,"line":256},[39,6264,6099],{"class":620},[39,6266,6267],{"class":1169},"remove_component",[39,6269,6270],{"class":620},"(damage.",[39,6272,6273],{"class":1169},"get_script",[39,6275,6109],{"class":620},[39,6277,6278],{"class":41,"line":262},[39,6279,52],{"emptyLinePlaceholder":51},[39,6281,6282,6284,6286,6288,6290],{"class":41,"line":268},[39,6283,5821],{"class":624},[39,6285,6088],{"class":620},[39,6287,1787],{"class":624},[39,6289,1977],{"class":1195},[39,6291,5594],{"class":620},[39,6293,6294,6297,6299,6302,6304,6307],{"class":41,"line":274},[39,6295,6296],{"class":1169},"        Loggie",[39,6298,476],{"class":620},[39,6300,6301],{"class":1169},"debug",[39,6303,1790],{"class":620},[39,6305,6306],{"class":1188},"'Damaged'",[39,6308,6309],{"class":620},", c_damage, c_health)\n",[39,6311,6312],{"class":41,"line":280},[39,6313,6314],{"class":1764},"        #SoundManager.play('fx', 'c_damage')\n",[39,6316,6317],{"class":41,"line":286},[39,6318,52],{"emptyLinePlaceholder":51},[39,6320,6321,6323,6325,6327,6329],{"class":41,"line":292},[39,6322,5821],{"class":624},[39,6324,6088],{"class":620},[39,6326,5827],{"class":624},[39,6328,1977],{"class":1195},[39,6330,5594],{"class":620},[39,6332,6333,6335,6337,6339,6341],{"class":41,"line":298},[39,6334,6099],{"class":620},[39,6336,4153],{"class":1169},[39,6338,6104],{"class":620},[39,6340,3656],{"class":1169},[39,6342,6109],{"class":620},[39,6344,6345],{"class":41,"line":304},[39,6346,6347],{"class":620},"    \n",[39,6349,6350,6352,6355,6358,6361],{"class":41,"line":310},[39,6351,5821],{"class":624},[39,6353,6354],{"class":620}," entity ",[39,6356,6357],{"class":624},"is",[39,6359,6360],{"class":1169}," Player",[39,6362,5594],{"class":620},[39,6364,6365,6368,6371,6374],{"class":41,"line":316},[39,6366,6367],{"class":1169},"        GameState",[39,6369,6370],{"class":620},".health_changed.",[39,6372,6373],{"class":1169},"emit",[39,6375,6376],{"class":620},"(c_health.current)\n",[699,6378,6380],{"id":6379},"naming-systems","Naming Systems",[10,6382,6383],{},"Systems are named in ClassCase and should end with System and the files should be snake case and be prefixed with s_ for example:",[534,6385,6386,6395,6403],{},[537,6387,6388,6391,6392],{},[36,6389,6390],{},"TransformSystem"," - ",[36,6393,6394],{},"s_transform.gd",[537,6396,6397,6391,6400],{},[36,6398,6399],{},"PhysicsSystem",[36,6401,6402],{},"s_physics.gd",[537,6404,6405,6391,6408],{},[36,6406,6407],{},"CharacterBody3DSystem",[36,6409,6410],{},"s_character_body_3d.gd",[699,6412,6414],{"id":6413},"system-lifecycle-and-setup","System Lifecycle and Setup",[10,6416,6417,6418,6420],{},"Systems extend ",[36,6419,3392],{}," and follow a typical node lifecycle in Godot:",[534,6422,6423,6428,6434],{},[537,6424,6425,6427],{},[36,6426,3750],{},": Perform initial setup after all components are loaded.",[537,6429,6430,6433],{},[36,6431,6432],{},"process(delta)",": Called each frame for matching entities if overridden.",[537,6435,6436,6438],{},[36,6437,5506],{},": Batch processing approach for more complex logic.",[10,6440,6441,6442,591,6444,6447],{},"In your game loop, call ",[36,6443,3477],{},[36,6445,6446],{},"ECS.process(delta, \"group\")"," to update all systems or a specific group respectively.",[699,6449,6451],{"id":6450},"adding-systems-to-the-world","Adding Systems to the World",[10,6453,6454,6455,6457,6458,6461,6462,6464,6465,3725,6467,6469],{},"You can attach your systems to the ",[36,6456,3294],{}," node via the editor or dynamically in code:\n",[36,6459,6460],{},"ECS.world.add_system(MovementSystem.new())","\nThis ensures they stay synchronized when ",[36,6463,3477],{},"or ECS.process('group', delta) runs, leveraging queries (via ",[36,6466,5604],{},[36,6468,5978],{},", etc.) to handle entities matching component filters.",[407,6471,6473],{"id":6472},"queries","Queries",[10,6475,6476],{},"Queries are at the core of how GECS finds the entities it operates on. A query uses a builder style function to specify the kind of query you want to create for example:",[28,6478,6480],{"className":3634,"code":6479,"language":3636,"meta":34,"style":34},"ECS.world.query\n  .with_all([]) # Find entities that have all these components\n  .with_any([]) # Find entities that have any of these components\n  .with_none([]) # Exclude entities that have these components\n  .with_relationship([]) # Must have these relationships\n  .without_relationship([]) # must not  these relationships\n  .with_reverse_relationship([]) # must have these reverse relationships\n",[36,6481,6482,6489,6502,6513,6524,6535,6546],{"__ignoreMap":34},[39,6483,6484,6486],{"class":41,"line":42},[39,6485,3301],{"class":1195},[39,6487,6488],{"class":620},".world.query\n",[39,6490,6491,6494,6496,6499],{"class":41,"line":48},[39,6492,6493],{"class":620},"  .",[39,6495,5604],{"class":1169},[39,6497,6498],{"class":620},"([]) ",[39,6500,6501],{"class":1764},"# Find entities that have all these components\n",[39,6503,6504,6506,6508,6510],{"class":41,"line":55},[39,6505,6493],{"class":620},[39,6507,5972],{"class":1169},[39,6509,6498],{"class":620},[39,6511,6512],{"class":1764},"# Find entities that have any of these components\n",[39,6514,6515,6517,6519,6521],{"class":41,"line":61},[39,6516,6493],{"class":620},[39,6518,5978],{"class":1169},[39,6520,6498],{"class":620},[39,6522,6523],{"class":1764},"# Exclude entities that have these components\n",[39,6525,6526,6528,6530,6532],{"class":41,"line":67},[39,6527,6493],{"class":620},[39,6529,4828],{"class":1169},[39,6531,6498],{"class":620},[39,6533,6534],{"class":1764},"# Must have these relationships\n",[39,6536,6537,6539,6541,6543],{"class":41,"line":73},[39,6538,6493],{"class":620},[39,6540,4903],{"class":1169},[39,6542,6498],{"class":620},[39,6544,6545],{"class":1764},"# must not  these relationships\n",[39,6547,6548,6550,6553,6555],{"class":41,"line":79},[39,6549,6493],{"class":620},[39,6551,6552],{"class":1169},"with_reverse_relationship",[39,6554,6498],{"class":620},[39,6556,6557],{"class":1764},"# must have these reverse relationships\n",[10,6559,6560],{},"Each of the functions is described like:",[534,6562,6563,6568,6573,6578,6583,6588],{},[537,6564,6565,6567],{},[569,6566,5604],{},": Entities must have all of these components.",[537,6569,6570,6572],{},[569,6571,5972],{},": Entities must have at least one of these components.",[537,6574,6575,6577],{},[569,6576,5978],{},": Entities must not have any of these components.",[537,6579,6580,6582],{},[569,6581,4828],{},": Entities must have these relationships",[537,6584,6585,6587],{},[569,6586,4903],{},": Entities must not have these relationships",[537,6589,6590,6592],{},[569,6591,6552],{},": This finds the entities of reverse relationships (aka the target of the relationship, not the source)",[10,6594,6595],{},"Once you create a query you can do a few things with it.",[534,6597,6598,6604,6610],{},[537,6599,6600,6603],{},[36,6601,6602],{},"query.execute()"," - Execute the query and get the results of the query back which will be a list of entities in the world that match that query.",[537,6605,6606,6609],{},[36,6607,6608],{},"query.matches(entities: Array[Entity])"," - Sometimes you already have a list of entities and you want to further filter down this list of entities based on another query. You can use matches for this and it will return you back a list of entities that match the query.",[537,6611,6612,6615],{},[36,6613,6614],{},"combine(q: QueryBuilder)"," - Combine together a query with an existing one.",[699,6617,6619],{"id":6618},"putting-it-all-together","Putting It All Together",[10,6621,6622,6623,6625,6626,6629,6630,6632],{},"Add each system to the ",[36,6624,3294],{},", then call ",[36,6627,6628],{},"ECS.process(delta, group)"," to execute only that group’s systems or ",[36,6631,3477],{}," to run them all. Stay consistent in your naming and grouping conventions so you can quickly find or disable relevant systems when debugging.",[704,6634],{},[699,6636,3495],{"id":3494},[534,6638,6639,6645,6651],{},[537,6640,6641,3503,6643],{},[569,6642,4203],{},[17,6644,3218],{"href":3217},[537,6646,6647,3503,6649],{},[569,6648,3502],{},[17,6650,3230],{"href":3229},[537,6652,6653],{},[17,6654,3511],{"href":3510},[407,6656,3515],{"id":3514},[2911,6658,6659],{},[10,6660,6661],{},[17,6662,3198],{"href":3198,"rel":6663},[21],[375,6665,6666],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":34,"searchDepth":48,"depth":48,"links":6668},[6669,6677,6681],{"id":5451,"depth":48,"text":3270,"children":6670},[6671,6672,6673,6674,6675,6676],{"id":5492,"depth":55,"text":5493},{"id":5847,"depth":55,"text":5848},{"id":5860,"depth":55,"text":5861},{"id":6379,"depth":55,"text":6380},{"id":6413,"depth":55,"text":6414},{"id":6450,"depth":55,"text":6451},{"id":6472,"depth":48,"text":6473,"children":6678},[6679,6680],{"id":6618,"depth":55,"text":6619},{"id":3494,"depth":55,"text":3495},{"id":3514,"depth":48,"text":3515},"04\u002F08\u002F2025","An overview of Systems and Queries in Godot Entity Component System GECS.",{},{"title":3224,"description":6683},"blog\u002Fgecs-systems-queries",[3542,3543,3544,3235],"-0Dj7No_TNITeKw6Ls1c3Ztk1_Qlvveok6rkKQXdqJc",{"id":6690,"title":6691,"body":6692,"date":6710,"description":6711,"extension":381,"meta":6712,"navigation":51,"path":6713,"seo":6714,"stem":6715,"tags":6716,"__hash__":6717},"blog\u002Fblog\u002Fgodot-editor-icons.md","Godot Editor Icons Cheat-Sheet",{"type":7,"value":6693,"toc":6708},[6694,6700],[10,6695,6696,6697],{},"These can be used for the ",[36,6698,6699],{},"@export_tool_button('Button Text', 'IconName')",[28,6701,6706],{"className":6702,"code":6704,"language":6705},[6703],"language-text","2D\n2DNodes\n3D\nAABB\nAcceptDialog\nActionCopy\nActionCut\nActionPaste\nAdd\nAimModifier3D\nAnchor\nAnimatableBody2D\nAnimatableBody3D\nAnimatedSprite2D\nAnimatedSprite3D\nAnimatedTexture\nAnimation\nAnimationAutoFit\nAnimationAutoFitBezier\nAnimationFilter\nAnimationLibrary\nAnimationMixer\nAnimationPlayer\nAnimationTrackGroup\nAnimationTrackList\nAnimationTree\nArea2D\nArea3D\nArray\nArrayMesh\nArrayOccluder3D\nArrowDown\nArrowLeft\nArrowRight\nArrowUp\nAspectRatioContainer\nAssetLib\nAtlasTexture\nAudioBusBypass\nAudioBusLayout\nAudioBusMute\nAudioBusSolo\nAudioListener2D\nAudioListener3D\nAudioMute\nAudioStream\nAudioStreamGenerator\nAudioStreamMP3\nAudioStreamMicrophone\nAudioStreamOggVorbis\nAudioStreamPlayer\nAudioStreamPlayer2D\nAudioStreamPlayer3D\nAudioStreamPolyphonic\nAudioStreamRandomizer\nAudioStreamWAV\nAutoEnd\nAutoKey\nAutoPlay\nAutoTriangle\nBack\nBackBufferCopy\nBake\nBaseButton\nBasis\nBezierHandlesBalanced\nBezierHandlesFree\nBezierHandlesLinear\nBezierHandlesMirror\nBitMap\nBlend\nBone\nBone2D\nBoneAttachment3D\nBoneConstraint3D\nBoneMapHumanBody\nBoneMapHumanFace\nBoneMapHumanLeftHand\nBoneMapHumanRightHand\nBoneMapperHandle\nBoneMapperHandleCircle\nBoneMapperHandleSelected\nBoxContainer\nBoxMesh\nBoxOccluder3D\nBoxShape3D\nBreakpoint\nBucket\nBusVuActive\nBusVuFrozen\nButton\nButtonGroup\nCPUParticles2D\nCPUParticles3D\nCallable\nCamera\nCamera2D\nCamera3D\nCameraAttributes\nCameraAttributesPhysical\nCameraAttributesPractical\nCameraTexture\nCanvasGroup\nCanvasItem\nCanvasItemMaterial\nCanvasLayer\nCanvasModulate\nCanvasTexture\nCapsuleMesh\nCapsuleShape2D\nCapsuleShape3D\nCenterContainer\nCenterView\nCharacterBody2D\nCharacterBody3D\nCheckBox\nCheckButton\nCheckerboard\nCircleShape2D\nClassList\nClear\nClose\nCodeEdit\nCodeFoldDownArrow\nCodeFoldedRightArrow\nCodeHighlighter\nCodeRegionFoldDownArrow\nCodeRegionFoldedRightArrow\nCollapse\nCollapseTree\nCollisionObject2D\nCollisionObject3D\nCollisionPolygon2D\nCollisionPolygon3D\nCollisionShape2D\nCollisionShape3D\nColor\nColorPick\nColorPicker\nColorPickerBarArrow\nColorPickerButton\nColorRect\nColorTrackVu\nCombineLines\nCompressedTexture2D\nCompressedTexture3D\nConcavePolygonShape2D\nConcavePolygonShape3D\nConeTwistJoint3D\nConfirmationDialog\nContainer\nContainerLayout\nControl\nControlAlignBottomLeft\nControlAlignBottomRight\nControlAlignBottomWide\nControlAlignCenter\nControlAlignCenterBottom\nControlAlignCenterLeft\nControlAlignCenterRight\nControlAlignCenterTop\nControlAlignFullRect\nControlAlignHCenterWide\nControlAlignLeftWide\nControlAlignRightWide\nControlAlignTopLeft\nControlAlignTopRight\nControlAlignTopWide\nControlAlignVCenterWide\nControlLayout\nConvertTransformModifier3D\nConvexPolygonShape2D\nConvexPolygonShape3D\nCopyNodePath\nCopyTransformModifier3D\nCreateNewSceneFrom\nCryptoKey\nCubemap\nCubemapArray\nCurve\nCurve2D\nCurve3D\nCurveCenter\nCurveClose\nCurveConstant\nCurveCreate\nCurveCurve\nCurveDelete\nCurveEdit\nCurveIn\nCurveInOut\nCurveLinear\nCurveOut\nCurveOutIn\nCurveTexture\nCurveTilt\nCurveXYZTexture\nCylinderMesh\nCylinderShape3D\nDampedSpringJoint2D\nDebug\nDebugContinue\nDebugNext\nDebugStep\nDecal\nDefaultProjectIcon\nDictionary\nDirAccess\nDirectionalLight2D\nDirectionalLight3D\nDistractionFree\nDragHandle\nDuplicate\nEdit\nEditAddRemove\nEditBezier\nEditInternal\nEditKey\nEditPivot\nEditor3DHandle\nEditorBoneHandle\nEditorControlAnchor\nEditorCurveHandle\nEditorFileDialog\nEditorHandle\nEditorHandleAdd\nEditorHandleDisabled\nEditorPathNullHandle\nEditorPathSharpHandle\nEditorPathSmoothHandle\nEditorPivot\nEditorPlugin\nEditorPosition\nEditorPositionPrevious\nEditorPositionUnselected\nEnum\nEnvironment\nEraser\nError\nErrorWarning\nExpandBottomDock\nExpandTree\nExternalLink\nFPS\nFadeCross\nFadeDisabled\nFadeIn\nFadeOut\nFavorites\nFile\nFileAccess\nFileBigThumb\nFileBroken\nFileBrokenBigThumb\nFileBrowse\nFileDead\nFileDeadBigThumb\nFileDeadMediumThumb\nFileDialog\nFileList\nFileMediumThumb\nFileThumbnail\nFileTree\nFilenameFilter\nFilesystem\nFixedSize\nFlipWinding\nFlowContainer\nFogMaterial\nFogVolume\nFoldableContainer\nFolder\nFolderBigThumb\nFolderBrowse\nFolderCreate\nFolderMediumThumb\nFont\nFontFile\nFontItem\nFontSize\nFontVariation\nForward\nGPUParticles2D\nGPUParticles3D\nGPUParticlesAttractorBox3D\nGPUParticlesAttractorSphere3D\nGPUParticlesAttractorVectorField3D\nGPUParticlesCollisionBox3D\nGPUParticlesCollisionHeightField3D\nGPUParticlesCollisionSDF3D\nGPUParticlesCollisionSphere3D\nGame\nGeneric6DOFJoint3D\nGeometry2D\nGeometry3D\nGeometryInstance3D\nGizmo3DSamplePlayer\nGizmoAudioListener3D\nGizmoCPUParticles3D\nGizmoCamera3D\nGizmoDecal\nGizmoDirectionalLight\nGizmoFogVolume\nGizmoGPUParticles3D\nGizmoLight\nGizmoLightmapGI\nGizmoLightmapProbe\nGizmoReflectionProbe\nGizmoSpotLight\nGizmoVoxelGI\nGodot\nGodotFile\nGodotMonochrome\nGradient\nGradientTexture1D\nGradientTexture2D\nGraphEdit\nGraphElement\nGraphFrame\nGraphNode\nGrid\nGridContainer\nGridLayout\nGridMinimap\nGridToggle\nGrooveJoint2D\nGroup\nGroupViewport\nGroups\nGuiArrowUp\nGuiChecked\nGuiCheckedDisabled\nGuiClose\nGuiDropdown\nGuiEllipsis\nGuiGraphNodePort\nGuiHsplitter\nGuiIndeterminate\nGuiIndeterminateDisabled\nGuiMiniCheckerboard\nGuiOptionArrow\nGuiProgressBar\nGuiProgressFill\nGuiRadioChecked\nGuiRadioCheckedDisabled\nGuiRadioUnchecked\nGuiRadioUncheckedDisabled\nGuiResizer\nGuiResizerTopLeft\nGuiScrollArrowLeft\nGuiScrollArrowLeftHl\nGuiScrollArrowRight\nGuiScrollArrowRightHl\nGuiScrollBg\nGuiScrollGrabber\nGuiScrollGrabberHl\nGuiScrollGrabberPressed\nGuiSliderGrabber\nGuiSliderGrabberHl\nGuiSpace\nGuiSpinboxDown\nGuiSpinboxUp\nGuiSpinboxUpdown\nGuiSpinboxUpdownDisabled\nGuiTab\nGuiTabDropMark\nGuiTabMenu\nGuiTabMenuHl\nGuiToggleOff\nGuiToggleOffDisabled\nGuiToggleOffDisabledMirrored\nGuiToggleOffMirrored\nGuiToggleOn\nGuiToggleOnDisabled\nGuiToggleOnDisabledMirrored\nGuiToggleOnMirrored\nGuiTreeArrowDown\nGuiTreeArrowLeft\nGuiTreeArrowRight\nGuiTreeUpdown\nGuiUnchecked\nGuiUncheckedDisabled\nGuiViewportHdiagsplitter\nGuiViewportVdiagsplitter\nGuiViewportVhsplitter\nGuiVisibilityHidden\nGuiVisibilityVisible\nGuiVisibilityXray\nGuiVsplitter\nHBoxContainer\nHFlowContainer\nHScrollBar\nHSeparator\nHSlider\nHSplitContainer\nHTTPRequest\nHeart\nHeightMapShape3D\nHelp\nHelpSearch\nHingeJoint3D\nHistory\nHsize\nIOSDeviceWired\nIOSDeviceWireless\nIOSSimulator\nImage\nImageTexture\nImageTexture3D\nImmediateMesh\nImportCheck\nImportFail\nImporterMeshInstance3D\nInfo\nInputEventAction\nInputEventJoypadButton\nInputEventJoypadMotion\nInputEventKey\nInputEventMIDI\nInputEventMagnifyGesture\nInputEventMouseButton\nInputEventMouseMotion\nInputEventPanGesture\nInputEventScreenDrag\nInputEventScreenTouch\nInputEventShortcut\nInsertAfter\nInsertBefore\nInstance\nInstanceOptions\nInterpCubic\nInterpCubicAngle\nInterpLinear\nInterpLinearAngle\nInterpRaw\nInterpWrapClamp\nInterpWrapLoop\nItemList\nJoyAxis\nJoyButton\nJoypad\nKeepAspect\nKey\nKeyAnimation\nKeyAudio\nKeyBezier\nKeyBezierHandle\nKeyBezierPoint\nKeyBezierSelected\nKeyBlendShape\nKeyCall\nKeyEasedSelected\nKeyInvalid\nKeyNext\nKeyPosition\nKeyRotation\nKeyScale\nKeySelected\nKeyTrackBlendShape\nKeyTrackPosition\nKeyTrackRotation\nKeyTrackScale\nKeyValue\nKeyValueEased\nKeyXPosition\nKeyXRotation\nKeyXScale\nKeyboard\nKeyboardError\nKeyboardLabel\nKeyboardPhysical\nKinematicCollision2D\nKinematicCollision3D\nLabel\nLabel3D\nLabelSettings\nLightOccluder2D\nLightmapGI\nLightmapGIData\nLightmapProbe\nLine\nLine2D\nLineEdit\nLinkButton\nLinkOverlay\nListSelect\nLoad\nLocalVariable\nLock\nLockViewport\nLogo\nLookAtModifier3D\nLoop\nMainMovieWrite\nMainPlay\nMakeFloating\nMarginContainer\nMarker\nMarker2D\nMarker3D\nMarkerSelected\nMatchCase\nMaterialPreviewCube\nMaterialPreviewLight1\nMaterialPreviewLight2\nMaterialPreviewQuad\nMaterialPreviewSphere\nMemberAnnotation\nMemberConstant\nMemberConstructor\nMemberMethod\nMemberOperator\nMemberProperty\nMemberSignal\nMemberTheme\nMenuBar\nMenuButton\nMesh\nMeshInstance2D\nMeshInstance3D\nMeshItem\nMeshLibrary\nMeshTexture\nMethodOverride\nMethodOverrideAndSlot\nMiniObject\nMirrorX\nMirrorY\nMissingNode\nMissingResource\nModifierBoneTarget3D\nModifiers\nMouse\nMoveDown\nMoveLeft\nMovePoint\nMoveRight\nMoveUp\nMultiMesh\nMultiMeshInstance2D\nMultiMeshInstance3D\nMultiplayerSpawner\nMultiplayerSynchronizer\nNavigationAgent2D\nNavigationAgent3D\nNavigationLink2D\nNavigationLink3D\nNavigationMesh\nNavigationObstacle2D\nNavigationObstacle3D\nNavigationPolygon\nNavigationRegion2D\nNavigationRegion3D\nNew\nNewKey\nNewRoot\nNextFrame\nNil\nNinePatchRect\nNode\nNode2D\nNode3D\nNodeDisabled\nNodeInfo\nNodePath\nNodeWarning\nNodeWarnings2\nNodeWarnings3\nNodeWarnings4Plus\nNonFavorite\nNotification\nNotificationDisabled\nORMMaterial3D\nObject\nObjectDisabled\nOccluder3D\nOccluderInstance3D\nOccluderPolygon2D\nOmniLight3D\nOneWayTile\nOnion\nOptionButton\nOrientation\nOverbrightIndicator\nOverride\nPackedByteArray\nPackedColorArray\nPackedDataContainer\nPackedFloat32Array\nPackedFloat64Array\nPackedInt32Array\nPackedInt64Array\nPackedScene\nPackedStringArray\nPackedVector2Array\nPackedVector3Array\nPackedVector4Array\nPageFirst\nPageLast\nPageNext\nPagePrevious\nPaint\nPanel\nPanelContainer\nPanels1\nPanels2\nPanels2Alt\nPanels3\nPanels3Alt\nPanels4\nPanoramaSkyMaterial\nParallax2D\nParallaxBackground\nParallaxLayer\nParticleProcessMaterial\nPath2D\nPath3D\nPathFollow2D\nPathFollow3D\nPause\nPerformance\nPhysicalBone2D\nPhysicalBone3D\nPhysicalBoneSimulator3D\nPhysicalSkyMaterial\nPhysicsBody2D\nPhysicsBody3D\nPhysicsMaterial\nPickerCursor\nPickerCursorBg\nPickerShapeCircle\nPickerShapeRectangle\nPickerShapeRectangleWheel\nPin\nPinJoint2D\nPinJoint3D\nPinPressed\nPingPongLoop\nPlaceholderMaterial\nPlaceholderMesh\nPlaceholderTexture2D\nPlaceholderTexture3D\nPlane\nPlaneMesh\nPlay\nPlayBackwards\nPlayCustom\nPlayOverlay\nPlayRemote\nPlayScene\nPlayStart\nPlayStartBackwards\nPlayTravel\nPluginScript\nPointLight2D\nPointMesh\nPolygon2D\nPolygonOccluder3D\nPolygonPathFinder\nPopup\nPopupMenu\nPopupPanel\nPortableCompressedTexture2D\nPreviewEnvironment\nPreviewRotate\nPreviewSun\nPrismMesh\nProceduralSkyMaterial\nProfilerAutostartWarning\nProgress1\nProgress2\nProgress3\nProgress4\nProgress5\nProgress6\nProgress7\nProgress8\nProgressBar\nProjectIconLoading\nProjectList\nProjection\nQuad\nQuadMesh\nQuadOccluder3D\nQuaternion\nREADME\nRID\nRandomNumberGenerator\nRange\nRangeSliderLeft\nRangeSliderRight\nRayCast2D\nRayCast3D\nRect2\nRect2i\nRectangle\nRectangleShape2D\nRedo\nReferenceRect\nReflectionProbe\nRegionEdit\nReload\nReloadSmall\nRemoteTransform2D\nRemoteTransform3D\nRemove\nRemoveInternal\nRename\nReparent\nReparentToNewNode\nResourcePreloader\nRetargetModifier3D\nReverseGradient\nRibbonTrailMesh\nRichTextEffect\nRichTextLabel\nRigidBody2D\nRigidBody3D\nRootMotionView\nRotateLeft\nRotateRight\nRuler\nSCsub\nSVGTexture\nSampleLibrary\nSave\nSceneUniqueName\nScript\nScriptCreate\nScriptCreateDialog\nScriptExtend\nScriptRemove\nScrollContainer\nSearch\nSegmentShape2D\nSeparationRayShape2D\nSeparationRayShape3D\nShader\nShaderGlobalsOverride\nShaderInclude\nShaderMaterial\nShape2D\nShape3D\nShapeCast2D\nShapeCast3D\nShortcut\nShowInFileSystem\nSignal\nSignals\nSignalsAndGroups\nSkeleton2D\nSkeleton3D\nSkeletonIK3D\nSkeletonModifier3D\nSkeletonPreview\nSky\nSliderJoint3D\nSlot\nSnap\nSnapDisable\nSnapGrid\nSnapKeys\nSnapTimeline\nSoftBody3D\nSort\nSphereMesh\nSphereOccluder3D\nSphereShape3D\nSpinBox\nSplitContainer\nSpotLight3D\nSpringArm3D\nSpringBoneCollision3D\nSpringBoneCollisionCapsule3D\nSpringBoneCollisionPlane3D\nSpringBoneCollisionSphere3D\nSpringBoneSimulator3D\nSprite2D\nSprite3D\nSpriteFrames\nSpriteSheet\nStandardMaterial3D\nStaticBody2D\nStaticBody3D\nStatusError\nStatusIndicator\nStatusSuccess\nStatusWarning\nStop\nStretch\nString\nStringName\nStyleBoxEmpty\nStyleBoxFlat\nStyleBoxGrid\nStyleBoxLine\nStyleBoxTexture\nSubViewport\nSubViewportContainer\nSyntaxHighlighter\nSystemFont\nTabBar\nTabContainer\nTerminal\nTerrainConnect\nTerrainMatchCorners\nTerrainMatchCornersAndSides\nTerrainMatchSides\nTerrainPath\nTextEdit\nTextEditorPlay\nTextFile\nTextMesh\nTexture2D\nTexture2DArray\nTexture3D\nTextureButton\nTexturePreviewChannels\nTextureProgressBar\nTextureRect\nTheme\nThemeDeselectAll\nThemeRemoveAllItems\nThemeRemoveCustomItems\nThemeSelectAll\nThemeSelectFull\nThumbnailWait\nTileChecked\nTileMap\nTileMapHighlightSelected\nTileMapLayer\nTileSelection\nTileSet\nTileUnchecked\nTime\nTimelineIndicator\nTimer\nTitleBarLogo\nToolAddNode\nToolBoneSelect\nToolConnect\nToolMove\nToolPan\nToolRotate\nToolScale\nToolSelect\nToolTriangle\nTools\nTorusMesh\nTouchScreenButton\nTrackCapture\nTrackColor\nTrackContinuous\nTrackDiscrete\nTransform2D\nTransform3D\nTransitionEnd\nTransitionEndAuto\nTransitionEndAutoBig\nTransitionEndBig\nTransitionImmediate\nTransitionImmediateAuto\nTransitionImmediateAutoBig\nTransitionImmediateBig\nTransitionSync\nTransitionSyncAuto\nTransitionSyncAutoBig\nTransitionSyncBig\nTranslation\nTree\nTripleBar\nTubeTrailMesh\nTween\nUID\nUndoRedo\nUnfavorite\nUngroup\nUnlinked\nUnlock\nUseBlendDisable\nUseBlendEnable\nUv\nVBoxContainer\nVFlowContainer\nVScrollBar\nVSeparator\nVSlider\nVSplitContainer\nVariant\nVcsBranches\nVector2\nVector2i\nVector3\nVector3i\nVector4\nVector4i\nVehicleBody3D\nVehicleWheel3D\nVideoStream\nVideoStreamPlayer\nVideoStreamTheora\nViewport\nViewportSpeed\nViewportTexture\nViewportZoom\nVisibleOnScreenEnabler2D\nVisibleOnScreenEnabler3D\nVisibleOnScreenNotifier2D\nVisibleOnScreenNotifier3D\nVisualInstance3D\nVisualShader\nVisualShaderGraphTextureUniform\nVisualShaderNodeBooleanUniform\nVisualShaderNodeColorConstant\nVisualShaderNodeColorOp\nVisualShaderNodeColorUniform\nVisualShaderNodeComment\nVisualShaderNodeCubemap\nVisualShaderNodeCubemapUniform\nVisualShaderNodeCurveTexture\nVisualShaderNodeCurveXYZTexture\nVisualShaderNodeExpression\nVisualShaderNodeFloatFunc\nVisualShaderNodeFloatOp\nVisualShaderNodeFloatUniform\nVisualShaderNodeGlobalExpression\nVisualShaderNodeInput\nVisualShaderNodeIntFunc\nVisualShaderNodeIntOp\nVisualShaderNodeIntUniform\nVisualShaderNodeTexture2DArrayUniform\nVisualShaderNodeTexture3DUniform\nVisualShaderNodeTextureUniform\nVisualShaderNodeTextureUniformTriplanar\nVisualShaderNodeTransformCompose\nVisualShaderNodeTransformDecompose\nVisualShaderNodeTransformUniform\nVisualShaderNodeTransformVecMult\nVisualShaderNodeVec3Uniform\nVisualShaderNodeVectorCompose\nVisualShaderNodeVectorDecompose\nVisualShaderNodeVectorDistance\nVisualShaderNodeVectorFunc\nVisualShaderNodeVectorLen\nVisualShaderPort\nVoxelGI\nVoxelGIData\nWarning\nWarningPattern\nWindow\nWorld2D\nWorld3D\nWorldBoundaryShape2D\nWorldBoundaryShape3D\nWorldEnvironment\nX509Certificate\nXRAnchor3D\nXRBodyModifier3D\nXRCamera3D\nXRController3D\nXRFaceModifier3D\nXRHandModifier3D\nXRNode3D\nXROrigin3D\nYSort\nZoom\nZoomLess\nZoomMore\nZoomReset\nbool\neditor_icons_builders\nfloat\nint\nuint\n","text",[36,6707,6704],{"__ignoreMap":34},{"title":34,"searchDepth":48,"depth":48,"links":6709},[],"07\u002F21\u002F2025","I kept needing to find the list of icons I can use for the @export_tool_button so I made this.",{},"\u002Fblog\u002Fgodot-editor-icons",{"title":6691,"description":6711},"blog\u002Fgodot-editor-icons",[3544,1738],"g6L_GMIbxo58DMF90_n0g0pNN6JAFm3YvKs27KztITo",{"id":6719,"title":6720,"body":6721,"date":6731,"description":6732,"extension":381,"meta":6733,"navigation":51,"path":6734,"seo":6735,"stem":6736,"tags":6737,"__hash__":6741},"blog\u002Fblog\u002Fmodeler-2025-hotkeys.md","Modeler 2025 For Houdini: Hotkey Cheatsheet",{"type":7,"value":6722,"toc":6729},[6723],[10,6724,6725],{},[358,6726],{"alt":6727,"src":6728},"Hotkey Cheat Sheets","https:\u002F\u002Fcsprance.com\u002Fimages\u002Fhotkey-cheatsheets.png",{"title":34,"searchDepth":48,"depth":48,"links":6730},[],"12\u002F22\u002F2025","A helpful hotkey cheat sheet for the direct modeling plugin for Houdini Modeler 2025. This cheat sheet will help you quickly get up to speed with the addon.",{},"\u002Fblog\u002Fmodeler-2025-hotkeys",{"title":6720,"description":6732},"blog\u002Fmodeler-2025-hotkeys",[3185,6738,6739,6740],"cheatsheet","hotkeys","modeler","0sqLg2w3JcysiExT4bAbWUVpTjdGZSfQFqUk446a5HA",{"id":6743,"title":6744,"body":6745,"date":7019,"description":6749,"extension":381,"meta":7020,"navigation":51,"path":7021,"seo":7022,"stem":7023,"tags":7024,"__hash__":7027},"blog\u002Fblog\u002Fmodo-select-by-weights.md","Modo Select By Weights",{"type":7,"value":6746,"toc":7017},[6747,6750,7015],[10,6748,6749],{},"I wrote a quick modo script because I had a need to select some verts if they had weights within a specific range.",[28,6751,6754],{"className":30,"code":6752,"filename":6753,"language":33,"meta":34,"style":34},"import lx\nimport lxu\nimport modo\nimport sys\n\n\ndef get_selected_weight_maps():\n    \"\"\"\n    Get the selected weight maps from the scene\n    :return: List of selected weight maps\n    \"\"\"\n    scene = modo.Scene()\n    vmaps = []\n    v_map_indices = lx.eval('query layerservice vmaps ? all')\n\n    for index in v_map_indices:\n        vmap_type = lx.eval('query layerservice vmap.type ? %s' % str(index))\n        vmap_sel = lx.eval('query layerservice vmap.selected ? %s' % str(index))\n        vmap_name = lx.eval('query layerservice vmap.name ? %s' % str(index))\n\n        if vmap_type == 'weight' and vmap_sel:\n            vmaps.append(vmap_name)\n\n    return [\n        w\n        for w in sum(\n            [\n                list(item.geometry.vmaps.weightMaps)\n                for item in scene.selectedByType(lx.symbol.sTYPE_MESH)\n            ],\n            [],\n        )\n        if w.name in vmaps\n    ]\n\n\ndef select_by_weight(min_t, max_t):\n    \"\"\"\n    With a min and max value select all the verts where the weights are within the range\n    :param min_t: The min value to select\n    :param max_t: The max value to select\n    :return: None This function has side effects only of selecting all verts within the range of min_t, max_t\n    \"\"\"\n    scene = modo.Scene()\n    # get the selected weights\n    weights = get_selected_weight_maps()\n    # for each weight map find all the weights over the threshold\n    for weight_map in weights:\n        for vert in weight_map._geometry.vertices:\n            (weight,) = weight_map[vert.index]\n            if min_t \u003C= weight \u003C= max_t:\n                vert.select()\n\n\nselect_by_weight(0.9, 1.0)\n\n","select_by_weight.py",[36,6755,6756,6761,6766,6771,6776,6780,6784,6789,6793,6798,6803,6807,6812,6817,6822,6826,6831,6836,6841,6846,6850,6855,6860,6864,6869,6874,6879,6884,6889,6894,6899,6904,6908,6913,6917,6921,6925,6930,6934,6939,6944,6949,6954,6958,6962,6967,6972,6977,6982,6987,6992,6997,7002,7006,7010],{"__ignoreMap":34},[39,6757,6758],{"class":41,"line":42},[39,6759,6760],{},"import lx\n",[39,6762,6763],{"class":41,"line":48},[39,6764,6765],{},"import lxu\n",[39,6767,6768],{"class":41,"line":55},[39,6769,6770],{},"import modo\n",[39,6772,6773],{"class":41,"line":61},[39,6774,6775],{},"import sys\n",[39,6777,6778],{"class":41,"line":67},[39,6779,52],{"emptyLinePlaceholder":51},[39,6781,6782],{"class":41,"line":73},[39,6783,52],{"emptyLinePlaceholder":51},[39,6785,6786],{"class":41,"line":79},[39,6787,6788],{},"def get_selected_weight_maps():\n",[39,6790,6791],{"class":41,"line":85},[39,6792,134],{},[39,6794,6795],{"class":41,"line":91},[39,6796,6797],{},"    Get the selected weight maps from the scene\n",[39,6799,6800],{"class":41,"line":97},[39,6801,6802],{},"    :return: List of selected weight maps\n",[39,6804,6805],{"class":41,"line":103},[39,6806,134],{},[39,6808,6809],{"class":41,"line":109},[39,6810,6811],{},"    scene = modo.Scene()\n",[39,6813,6814],{"class":41,"line":115},[39,6815,6816],{},"    vmaps = []\n",[39,6818,6819],{"class":41,"line":120},[39,6820,6821],{},"    v_map_indices = lx.eval('query layerservice vmaps ? all')\n",[39,6823,6824],{"class":41,"line":125},[39,6825,52],{"emptyLinePlaceholder":51},[39,6827,6828],{"class":41,"line":131},[39,6829,6830],{},"    for index in v_map_indices:\n",[39,6832,6833],{"class":41,"line":137},[39,6834,6835],{},"        vmap_type = lx.eval('query layerservice vmap.type ? %s' % str(index))\n",[39,6837,6838],{"class":41,"line":143},[39,6839,6840],{},"        vmap_sel = lx.eval('query layerservice vmap.selected ? %s' % str(index))\n",[39,6842,6843],{"class":41,"line":149},[39,6844,6845],{},"        vmap_name = lx.eval('query layerservice vmap.name ? %s' % str(index))\n",[39,6847,6848],{"class":41,"line":155},[39,6849,52],{"emptyLinePlaceholder":51},[39,6851,6852],{"class":41,"line":161},[39,6853,6854],{},"        if vmap_type == 'weight' and vmap_sel:\n",[39,6856,6857],{"class":41,"line":167},[39,6858,6859],{},"            vmaps.append(vmap_name)\n",[39,6861,6862],{"class":41,"line":172},[39,6863,52],{"emptyLinePlaceholder":51},[39,6865,6866],{"class":41,"line":178},[39,6867,6868],{},"    return [\n",[39,6870,6871],{"class":41,"line":184},[39,6872,6873],{},"        w\n",[39,6875,6876],{"class":41,"line":190},[39,6877,6878],{},"        for w in sum(\n",[39,6880,6881],{"class":41,"line":196},[39,6882,6883],{},"            [\n",[39,6885,6886],{"class":41,"line":202},[39,6887,6888],{},"                list(item.geometry.vmaps.weightMaps)\n",[39,6890,6891],{"class":41,"line":208},[39,6892,6893],{},"                for item in scene.selectedByType(lx.symbol.sTYPE_MESH)\n",[39,6895,6896],{"class":41,"line":214},[39,6897,6898],{},"            ],\n",[39,6900,6901],{"class":41,"line":220},[39,6902,6903],{},"            [],\n",[39,6905,6906],{"class":41,"line":226},[39,6907,1708],{},[39,6909,6910],{"class":41,"line":232},[39,6911,6912],{},"        if w.name in vmaps\n",[39,6914,6915],{"class":41,"line":238},[39,6916,6009],{},[39,6918,6919],{"class":41,"line":244},[39,6920,52],{"emptyLinePlaceholder":51},[39,6922,6923],{"class":41,"line":250},[39,6924,52],{"emptyLinePlaceholder":51},[39,6926,6927],{"class":41,"line":256},[39,6928,6929],{},"def select_by_weight(min_t, max_t):\n",[39,6931,6932],{"class":41,"line":262},[39,6933,134],{},[39,6935,6936],{"class":41,"line":268},[39,6937,6938],{},"    With a min and max value select all the verts where the weights are within the range\n",[39,6940,6941],{"class":41,"line":274},[39,6942,6943],{},"    :param min_t: The min value to select\n",[39,6945,6946],{"class":41,"line":280},[39,6947,6948],{},"    :param max_t: The max value to select\n",[39,6950,6951],{"class":41,"line":286},[39,6952,6953],{},"    :return: None This function has side effects only of selecting all verts within the range of min_t, max_t\n",[39,6955,6956],{"class":41,"line":292},[39,6957,134],{},[39,6959,6960],{"class":41,"line":298},[39,6961,6811],{},[39,6963,6964],{"class":41,"line":304},[39,6965,6966],{},"    # get the selected weights\n",[39,6968,6969],{"class":41,"line":310},[39,6970,6971],{},"    weights = get_selected_weight_maps()\n",[39,6973,6974],{"class":41,"line":316},[39,6975,6976],{},"    # for each weight map find all the weights over the threshold\n",[39,6978,6979],{"class":41,"line":322},[39,6980,6981],{},"    for weight_map in weights:\n",[39,6983,6984],{"class":41,"line":328},[39,6985,6986],{},"        for vert in weight_map._geometry.vertices:\n",[39,6988,6989],{"class":41,"line":333},[39,6990,6991],{},"            (weight,) = weight_map[vert.index]\n",[39,6993,6994],{"class":41,"line":338},[39,6995,6996],{},"            if min_t \u003C= weight \u003C= max_t:\n",[39,6998,6999],{"class":41,"line":344},[39,7000,7001],{},"                vert.select()\n",[39,7003,7004],{"class":41,"line":350},[39,7005,52],{"emptyLinePlaceholder":51},[39,7007,7008],{"class":41,"line":2390},[39,7009,52],{"emptyLinePlaceholder":51},[39,7011,7012],{"class":41,"line":2404},[39,7013,7014],{},"select_by_weight(0.9, 1.0)\n",[375,7016,377],{},{"title":34,"searchDepth":48,"depth":48,"links":7018},[],"10\u002F24\u002F2021",{},"\u002Fblog\u002Fmodo-select-by-weights",{"title":6744,"description":6749},"blog\u002Fmodo-select-by-weights",[7025,388,7026],"modo","rigging","MdNRw-SvhSMPKDksAW_snP6WWxDOTuM4bkLciJnlzbQ",{"id":7029,"title":7030,"body":7031,"date":7589,"description":7590,"extension":381,"meta":7591,"navigation":51,"path":7592,"seo":7593,"stem":7594,"tags":7595,"__hash__":7599},"blog\u002Fblog\u002Fmodo-to-substance-painter.md","Modo to Substance Painter",{"type":7,"value":7032,"toc":7571},[7033,7038,7053,7056,7059,7063,7066,7069,7073,7087,7090,7093,7098,7101,7120,7125,7128,7178,7181,7184,7189,7191,7213,7218,7221,7286,7289,7292,7297,7299,7319,7324,7329,7332,7385,7388,7391,7396,7398,7402,7405,7409,7412,7417,7449,7453,7456,7460,7463,7466,7470,7482,7485,7489,7492,7495,7499,7502,7505,7514,7517,7520,7549,7553,7556,7559,7566],[10,7034,7035],{},[358,7036],{"alt":34,"src":7037},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F800\u002F1*X3SGa5puVgEOyFdCPe-0pg.png",[10,7039,7040,7048,7049,7052],{},[17,7041,7045],{"href":7042,"rel":7043,"title":7044},"https:\u002F\u002Fmedium.com\u002F@Csprance\u002Fmodo-12-to-substance-painter-2018-e5f5f5ff7a92",[21],"Modo",[419,7046,7047],{},"This story is also on medium which may or may not provide a better reading experience"," ¯\\",[419,7050,7051],{},"(ツ)","\u002F¯",[10,7054,7055],{},"Modo is totally awesome. I use it for everything except texture painting because Allegorithmic is the king of that neighborhood. No doubt about it.",[10,7057,7058],{},"Modo has lots of awesome features and shader techniques as well as procedural modelling techniques that you can only use inside of modo. Trying to export a lot of that stuff out and freeze it down is an unnecessary step and prone to lots of errors. It should be cut from the pipeline. As cool as xNormal is if you’re using modo bake in modo and save yourself a huge hassle.",[407,7060,7062],{"id":7061},"starting-out","Starting out",[10,7064,7065],{},"There is a small amount of setup that must be done each time. Rather than use any sort of bake wizard, lets just go through each part and analyze them.",[10,7067,7068],{},"Substance Painter only needs a few maps and then it can bake the rest of them from those maps. I see no reason to bake them all in modo. Substances baker is a bit faster when it comes to rendering those maps, so lets go for speed here because painter allows us to iterate and change our model rebake and jump back and forth lots of times so we should use that to our advantage. Speed is king here.",[407,7070,7072],{"id":7071},"maps-needed","Maps Needed:",[534,7074,7075,7078,7081,7084],{},[537,7076,7077],{},"Tangent Space Normal Map",[537,7079,7080],{},"Ambient Occlusion Map",[537,7082,7083],{},"Thickness Map",[537,7085,7086],{},"ID Map",[407,7088,7077],{"id":7089},"tangent-space-normal-map",[10,7091,7092],{},"Creating a normal map is very simple.",[10,7094,7095],{},[358,7096],{"alt":34,"src":7097},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F800\u002F1*94GnHhuwef7J8cmYagrOYw.png",[10,7099,7100],{},"Normal Map",[534,7102,7103,7110],{},[537,7104,7105,7106,7109],{},"Create a new image named ",[569,7107,7108],{},"NRM"," and add it to the low poly material set the effect as Normal",[537,7111,7112,7113,7116,7117],{},"Add a ",[419,7114,7115],{},"Texture Bake Item"," and name it ",[569,7118,7119],{},"Normal Bake",[10,7121,7122],{},[358,7123],{"alt":34,"src":7124},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F600\u002F1*2fxP_YddD35n177a4SCKzg.png",[10,7126,7127],{},"Texture Bake Item Settings",[534,7129,7130,7136,7142,7148,7154,7160,7166,7172],{},[537,7131,7132,7135],{},[569,7133,7134],{},"Target Meshes"," should be the low poly meshes you want to bake to. Put these in a group to make it easy.",[537,7137,7138,7141],{},[569,7139,7140],{},"Source Meshes"," are your hi poly meshes you want to bake from. Put these in a group to make it easy",[537,7143,7144,7147],{},[569,7145,7146],{},"Texture Outputs"," should be the image named NRM we created and set as our low poly normal map in the material.",[537,7149,7150,7153],{},[569,7151,7152],{},"Bake From Source"," should be ticked",[537,7155,7156,7159],{},[569,7157,7158],{},"Cage"," is the morph you created that is to be used as the cage for the normal map baking process.",[537,7161,7162,7165],{},[569,7163,7164],{},"Distance"," is ignored if we use a cage so leave that alone. I always use a cage.",[537,7167,7168,7171],{},[569,7169,7170],{},"Use Normal Map Preset"," should be checked.",[537,7173,7174,7177],{},[569,7175,7176],{},"Save Outputs To File"," should be ticked the rest are optional",[407,7179,7080],{"id":7180},"ambient-occlusion-map",[10,7182,7183],{},"Modo has a quick fast Ambient Occlusion output you can use.",[10,7185,7186],{},[358,7187],{"alt":34,"src":7188},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F800\u002F1*tCLwtlXLqcxQVji0fJ_Rsg.png",[10,7190,7080],{},[534,7192,7193,7201],{},[537,7194,7112,7195,7116,7198],{},[419,7196,7197],{},"Render Output Bake Item",[569,7199,7200],{},"AO Bake",[537,7202,7112,7203,7116,7206,7209,7210],{},[419,7204,7205],{},"Render Output",[569,7207,7208],{},"AO"," Change the effect to ",[419,7211,7212],{},"Ambient Occlusion",[10,7214,7215],{},[358,7216],{"alt":34,"src":7217},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F600\u002F1*wiGDkdgNYU7hpRifGtyW0A.png",[10,7219,7220],{},"AO Render Bake Item Settings",[534,7222,7223,7228,7233,7239,7244,7249,7254,7258,7268,7274,7280],{},[537,7224,7225,7227],{},[569,7226,7134],{}," Low poly Meshes",[537,7229,7230,7232],{},[569,7231,7140],{}," Hi Poly Meshes",[537,7234,7235,7238],{},[569,7236,7237],{},"Render Outputs"," Select the AO Render output we created",[537,7240,7241,7243],{},[569,7242,7152],{}," Ticked",[537,7245,7246,7248],{},[569,7247,7158],{}," Cage Morph Map",[537,7250,7251,7253],{},[569,7252,7164],{}," ignore",[537,7255,7256,7177],{},[569,7257,7176],{},[537,7259,7260,7263,7264],{},[569,7261,7262],{},"Output Filename"," ",[7265,7266,7267],"output",{}," This forces the output that is created to be named the same as the Render Output",[537,7269,7270,7273],{},[569,7271,7272],{},"Output Directory"," where to store the output",[537,7275,7276,7279],{},[569,7277,7278],{},"Image Format"," whatever you want",[537,7281,7282,7285],{},[569,7283,7284],{},"Resolution"," Texture resolution use a small resolution for testing",[407,7287,7083],{"id":7288},"thickness-map",[10,7290,7291],{},"Thickness takes a very long time to bake and requires a few tweaks from the default occlusion shader.",[10,7293,7294],{},[358,7295],{"alt":34,"src":7296},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F800\u002F1*O-mBAF_lQtpNrUrzd1tDdg.png",[10,7298,7083],{},[534,7300,7301,7308],{},[537,7302,7112,7303,7116,7305],{},[419,7304,7197],{},[569,7306,7307],{},"Thickness Bake",[537,7309,7112,7310,7116,7312,7315,7316],{},[419,7311,7205],{},[569,7313,7314],{},"THK"," Set the effect to ",[419,7317,7318],{},"Driver A",[10,7320,7321],{},[358,7322],{"alt":34,"src":7323},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F600\u002F1*XILNZVP7cogLrzZd0eM6ZQ.png",[10,7325,7326],{},[358,7327],{"alt":34,"src":7328},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F600\u002F1*kcC1oE4IeXY94u4spPZZyg.png",[10,7330,7331],{},"Thickness Shader Settings",[534,7333,7334,7340,7348,7357,7363,7371],{},[537,7335,7336,7337],{},"Create an Occlusion Material (Add Layer -> Processing -> Occlusion) near the top of the stack after the Render Outputs and Name it ",[569,7338,7339],{},"Thickness",[537,7341,7342,7343,7345,7346],{},"Change the effect on the ",[569,7344,7339],{}," to ",[419,7347,7318],{},[537,7349,7350,7353,7354],{},[569,7351,7352],{},"Type"," change ",[419,7355,7356],{},"from Uniform to Thickness",[537,7358,7359,7362],{},[569,7360,7361],{},"Occlusion Distance"," adjust based on size of model",[537,7364,7365,7345,7368],{},[569,7366,7367],{},"Spread Angle",[419,7369,7370],{},"180 degrees",[537,7372,7373,7376,7377,7263,7379,7263,7382],{},[569,7374,7375],{},"Cutoff Angle"," adjust as needed from between ",[569,7378,1467],{},[419,7380,7381],{},"and",[569,7383,7384],{},"10",[407,7386,7086],{"id":7387},"id-map",[10,7389,7390],{},"There are two quick ways to do this an automatic way and a manual way.",[10,7392,7393],{},[358,7394],{"alt":34,"src":7395},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F800\u002F1*ENPuSJycScMa-DWxW0ehIQ.png",[10,7397,7086],{},[699,7399,7401],{"id":7400},"automatic","Automatic:",[10,7403,7404],{},"Surface ID all the way.",[699,7406,7408],{"id":7407},"manual","Manual:",[10,7410,7411],{},"Diffuse Color is your friend here. In my case I use a little script I wrote to quickly assign colors to meshes but if you already have it set up it’s just as easy to start using this.",[10,7413,7414],{},[358,7415],{"alt":34,"src":7416},"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F600\u002F1*GPNVW699zy9ShewM5sSawg.png",[534,7418,7419,7426,7446],{},[537,7420,7112,7421,7116,7423],{},[419,7422,7197],{},[569,7424,7425],{},"ID Bake",[537,7427,7112,7428,7116,7430,7433,7434,7439,7440,7445],{},[419,7429,7205],{},[569,7431,7432],{},"ID"," set the effect to ",[569,7435,7436],{},[419,7437,7438],{},"Diffuse Color"," if you have manually set your diffuse colors on your hi poly materials or ",[569,7441,7442],{},[419,7443,7444],{},"Surface ID"," if you have some preexisting materials you want to use",[537,7447,7448],{},"Use all the same settings as the AO\u002FTHK Bake items we created earlier.",[407,7450,7452],{"id":7451},"baking-the-maps","Baking the Maps",[10,7454,7455],{},"Ok so we’ve created all the Bake Items we need and we’re ready to go. The next step is to click the Bake Select item button in any one of the Bake items we created. Wait for the bake to complete. For the normal map you can either save it from the render output window or right click on the clip in the Clip Browser and select Save and then Reload. The AO, THK, ID will all save automatically where you told them to.",[407,7457,7459],{"id":7458},"exporting-the-mesh","Exporting the mesh",[10,7461,7462],{},"This can be a real pain in the ass because there are a lot of different options to choose from. Fortunately with the latest updates and software evolving as it does the FBX 2018 exporter works really well here.",[10,7464,7465],{},"Now that normals are a part of modo from a native standpoint your surface normals should always nicely match up when importing it into substance painter or other 3d software like Maya or Max.",[407,7467,7469],{"id":7468},"inside-painter","Inside Painter",[10,7471,7472,7473,7478,7479],{},"Now that we’ve baked our maps and exported our low poly mesh, we’re in a position to start playing around with our base materials. I make it a rule to try and do 90% of the work procedurally and only at the very last part of the project do I come in and do non-procedural dirty things like hand painting and beauty passess. Needs change in production as well as often times understanding of what you were even supposed to be doing in the first place. Other times you pour your heart into a project only to find out you have a major geometry issue to deal with. Using modo and substance painter this is super easy ",[569,7474,7475],{},[419,7476,7477],{},"IF"," you stayed as procedural as possible in your material creation. Once everyone is happy with how it looks and feels in game then you can always do the final 10% of “",[419,7480,7481],{},"arting by hand”.",[10,7483,7484],{},"So lets import the mesh ya?",[699,7486,7488],{"id":7487},"create-a-new-project","Create a new project",[10,7490,7491],{},"In my case I am always going to CRYENGINE as my game engine. So I’ll focus on that. The options should only vary mildly for other uses cases so try and use your head and figure out what’s best.",[10,7493,7494],{},"Import all of your baked maps we created in modo and then select the Bake Textures button from within Substance Painter. Deselect all the maps we’ve already baked, set your bake size to 4096x4096 and click bake. Now you’re ready to work on your model.",[699,7496,7498],{"id":7497},"material-groups","Material Groups",[10,7500,7501],{},"After everything is imported it’s time to start creating groups of materials that correspond to your id map color.",[10,7503,7504],{},"I find that the default color mask selection utility in substance painter is a bit lacking, fortunately Substance Share is a great resource for finding substance stuff so a quick look there and I found the awesome Color Mask Generator by Will Fuller.",[2911,7506,7507],{},[10,7508,7509],{},[17,7510,7513],{"href":7511,"rel":7512},"https:\u002F\u002Fshare.allegorithmic.com\u002Flibraries\u002F1843",[21],"Color Mask Generator— Substance Share",[10,7515,7516],{},"It really helps to smooth out lots of little small errors. One feature I use all the time is the pixel fill.",[10,7518,7519],{},"It is a bit of a pain to get it set up so I’ll go over quickly how to use it. I always add a fill layer at the very top of the stack and set the diffuse to the ID Map from modo we imported. This makes it easy to select a color for the mask generator by turning on the layer and viewing only the diffuse channel and color picking the color in the viewport.",[534,7521,7522,7525,7528,7531,7534,7537,7540,7543,7546],{},[537,7523,7524],{},"Create a Group Folder",[537,7526,7527],{},"Name the group something descriptive (Painted Steel, Rough Plastic)",[537,7529,7530],{},"Add a black mask to the group",[537,7532,7533],{},"Add a generator to the group mask",[537,7535,7536],{},"Select the Color Mask Generator",[537,7538,7539],{},"Choose the ID Map from modo for the ID input.",[537,7541,7542],{},"Select the color and active the ID Layer we created at the top of the stack to color pick the right color.",[537,7544,7545],{},"Adjust the Tolerance level",[537,7547,7548],{},"Give it a slight pixel fill if needed",[407,7550,7552],{"id":7551},"there-and-back-again","There and Back Again",[10,7554,7555],{},"So now lets say after we’ve been working on our textures in Substance painter that we want to make a model change or the art director wanted some change made. We’re screwed right?",[10,7557,7558],{},"Nope.",[10,7560,7561,7562,7565],{},"As long as your ID’s don’t change you can quickly and easily bake all of your textures again after changing your model. Export the model and reload all of your changes inside of substance painter. It will reproject all of the work you’ve done and in many cases (assuming the size of the model doesn’t change) keep your hand painted strokes. (",[419,7563,7564],{},"This is why I always save any hand painting for when I know for a fact the model will not change",")",[10,7567,7568],{},[569,7569,7570],{},"Hopefully this guide is helpful. I’ll include the video as soon as it’s ready in the future.",{"title":34,"searchDepth":48,"depth":48,"links":7572},[7573,7574,7575,7576,7577,7578,7582,7583,7584,7588],{"id":7061,"depth":48,"text":7062},{"id":7071,"depth":48,"text":7072},{"id":7089,"depth":48,"text":7077},{"id":7180,"depth":48,"text":7080},{"id":7288,"depth":48,"text":7083},{"id":7387,"depth":48,"text":7086,"children":7579},[7580,7581],{"id":7400,"depth":55,"text":7401},{"id":7407,"depth":55,"text":7408},{"id":7451,"depth":48,"text":7452},{"id":7458,"depth":48,"text":7459},{"id":7468,"depth":48,"text":7469,"children":7585},[7586,7587],{"id":7487,"depth":55,"text":7488},{"id":7497,"depth":55,"text":7498},{"id":7551,"depth":48,"text":7552},"01\u002F30\u002F2021","Modo is totally awesome. I use it for everything except texture painting because Allegorithmic is the king of that neighborhood. No doubt about it. Modo has lots of awesome features and shader techniques as well as procedural modelling techniques that you can only use inside of modo. Trying to export a lot of that stuff out and freeze it down is an unnecessary step and prone to lots of errors. It should be cut from the pipeline. As cool as xNormal is if you’re using modo bake in modo and save yourself a huge hassle.",{},"\u002Fblog\u002Fmodo-to-substance-painter",{"title":7030,"description":7590},"blog\u002Fmodo-to-substance-painter",[7025,7596,7597,7598],"substance","pbr","pipeline","H04VG6TyWQYI18QgR-o1G3rkD7HsCBd_xNrAce_2td0",{"id":7601,"title":7602,"body":7603,"date":7740,"description":7741,"extension":381,"meta":7742,"navigation":51,"path":7743,"seo":7744,"stem":7745,"tags":7746,"__hash__":7749},"blog\u002Fblog\u002Fmy-robot-army.md","My Robot Army",{"type":7,"value":7604,"toc":7726},[7605,7608,7611,7631,7635,7638,7664,7668,7672,7675,7678,7682,7686,7689,7692,7695,7700,7704,7707,7711,7714,7717,7720,7723],[10,7606,7607],{},"While on Miscreated we encountered lots of situations where we wanted to create a system to monitor some \"thing\" or allow some user to restart some server or run some commands on any number of servers.",[10,7609,7610],{},"The company uses discord as our main form of communication and because of this we were able to take advantage of the Discord API to create a bot to do any number of company functions. This has a couple of benefits:",[3206,7612,7613,7616,7619,7622,7625,7628],{},[537,7614,7615],{},"It's really easy to keep grant a permission to do some task using roles in discord",[537,7617,7618],{},"Inversely that means it's easy to revoke a permission.",[537,7620,7621],{},"By nature of a chat room every interaction is logged for all to see.",[537,7623,7624],{},"No user authentication needs to be handled.",[537,7626,7627],{},"Communication with the bot outside of approved channels isn't possible.",[537,7629,7630],{},"All bot facilities can be accessed from one simple command line in a chat room",[407,7632,7634],{"id":7633},"framework","Framework",[10,7636,7637],{},"The bot had a few different types of \"things\" it could do.",[534,7639,7640,7648,7656],{},[537,7641,7642,7643],{},"Extensions\n",[534,7644,7645],{},[537,7646,7647],{},"Extensions are generally self contained tools that only use the bot messaging facilities to report but otherwise are self contained tools",[537,7649,7650,7651],{},"Middleware\n",[534,7652,7653],{},[537,7654,7655],{},"Middleware run on specific chat events: messageRecieved, userJoined, userLeft, etc, etc and allow you to react to things happening but aren't something a user would specifically execute they just happen.",[537,7657,7658,7659],{},"Commands\n",[534,7660,7661],{},[537,7662,7663],{},"A command is something a user explicitly executes by calling it from chat addressing it to the bot",[407,7665,7667],{"id":7666},"middleware","Middleware",[699,7669,7671],{"id":7670},"profanity","Profanity",[10,7673,7674],{},"We used the bot to control profanity in all of the community discord channels. We've found that generally most messages with profanity in them tend to be fairly useless as far as feedback goes.",[10,7676,7677],{},"It also had the surprising effect of someone coming in angry and typing a profanity laced message. Have it removed and then they type a more coherent message without profanity because they've been given a chance to cool down. This works better then a moderator policing these kinds of things because then there is never any one person to direct ire at. It's just a robot how angry can you be at a bot?",[407,7679,7681],{"id":7680},"extensions","Extensions",[699,7683,7685],{"id":7684},"server-health-checks","Server Health Checks",[10,7687,7688],{},"I wrote a script to ping all of the game servers with an RCON command every 5 minutes. If the server fails the ping command 3 times in a row it will send out a notification in a special health-check channel containg a few moderators with special permissions to send a command to those servers to force a restart or link to click on that will redirect to a server control panel to look at more metrics.",[10,7690,7691],{},"It also checked to see if the official servers were registered on the steam master servers because occasionally there were situations in which a server would be working just fine but for whatever reason fail to register on the master server making it look like the server was down.",[10,7693,7694],{},"If this happened any of the moderators in the health-check channel could easily send a command to restart the server:",[10,7696,7697],{},[36,7698,7699],{},"bot restart-server [SERVERID]",[699,7701,7703],{"id":7702},"perfmon","PerfMon",[10,7705,7706],{},"This extension will send a command every few minutes to all server and gather specific data about that server and store it in a database. This database could then be used to query with the a bot command and compile a graph or in real time using a grafana dashboard. This helped us analyze changes we made to server code and track performance over longer periods of time as well as give as an estimate of primetimes for each specific server and adjust restart times if those times intersected with the maximum amount of players on a server.",[407,7708,7710],{"id":7709},"commands","Commands",[699,7712,7713],{"id":7713},"restart-server",[10,7715,7716],{},"This command when executed will send a restart command using a specific API to restart a server. It will then report back if the command was executed",[699,7718,7719],{"id":7719},"sysinfo",[10,7721,7722],{},"This command when executed and provided a server id will collect data from a server using RCON parsing the response and then formatting it and sending it in chat. This is a useful command to gather data on what is happening internally with a server.",[10,7724,7725],{},"Often in chat people would complain that a specific server was messing up or had bad performance. Rather than forcing a moderator or developer to jump in to the game (a process that could take anywhere from 5-10 minutes) you just send a command and take a look at the data. It's usually fairly obvious when a server is having problem and when it's just a user experiencing issues or internet connectivity problems",{"title":34,"searchDepth":48,"depth":48,"links":7727},[7728,7729,7732,7736],{"id":7633,"depth":48,"text":7634},{"id":7666,"depth":48,"text":7667,"children":7730},[7731],{"id":7670,"depth":55,"text":7671},{"id":7680,"depth":48,"text":7681,"children":7733},[7734,7735],{"id":7684,"depth":55,"text":7685},{"id":7702,"depth":55,"text":7703},{"id":7709,"depth":48,"text":7710,"children":7737},[7738,7739],{"id":7713,"depth":55,"text":7713},{"id":7719,"depth":55,"text":7719},"02\u002F10\u002F2021","Or how I learned to stop worrying and love the automation. Some of my thoughts and processes on how I keep things flowing using bots. This is a constantly updated and work in progress post.",{},"\u002Fblog\u002Fmy-robot-army",{"title":7602,"description":7741},"blog\u002Fmy-robot-army",[7747,387,7748],"automation","bots","lPE-huz8WyZQ5SYLb2UCZAtyT4KXt7MTypyPXOyEu4s",{"id":7751,"title":7752,"body":7753,"date":10950,"description":10951,"extension":381,"meta":10952,"navigation":51,"path":10953,"seo":10954,"stem":10955,"tags":10956,"__hash__":10959},"blog\u002Fblog\u002Fsvgame.md","SVGame - SVG D3 Game Engine",{"type":7,"value":7754,"toc":10944},[7755,7758,7761,7765,7768,9613,9617,9620,10159,10163,10166,10873,10877,10880,10941],[10,7756,7757],{},"I recently start a new project I'm calling SVGame. It is a simple easy to learn game engine that uses d3.js to render SVG.\nI'm trying to follow a lot of the patterns I see in Godot, since I've had a lot of fun with that engine.",[10,7759,7760],{},"Let's break each part of it down and look at the source code. Eventually this will be on GitHub",[407,7762,7764],{"id":7763},"gameengine-class","GameEngine Class",[10,7766,7767],{},"This class is the meat and potatoes of the whole thing. It controls and stores all of the things you'd want to do with\nyour toy game engine here.",[28,7769,7774],{"className":7770,"code":7771,"filename":7772,"language":7773,"meta":34,"style":34},"language-ts shiki shiki-themes github-light github-dark","import { consola } from \"consola\";\nimport * as d3 from \"d3\";\nimport { Vec2 } from \"gl-matrix\u002Fdist\u002Fesm\";\nimport MainLoop from \"mainloop.js\";\nimport { EventBus } from \"~\u002Fhelpers\u002Fsvgame\u002FEventBus\";\nimport { useGameStore } from \"~\u002Fstores\u002Fgame\";\n\nimport { Entity } from \".\u002FEntity\";\nimport { Input } from \".\u002FInput\";\n\nexport type SVGD3Selection = d3.Selection\u003C\n  SVGSVGElement,\n  unknown,\n  HTMLElement,\n  any\n>;\n\n\u002F**\n * GameEngine is the core class responsible for managing the game's main loop, rendering,\n * and entity management. It integrates with various libraries and frameworks to facilitate\n * game development.\n *\u002F\nexport class GameEngine {\n  \u002F**\n   * 3 info, 4 debug, 5 trace\n   *\u002F\n  logger = consola.create({ level: 4 }).withTag(\"GameEngine\");\n  \u002F**\n   * Is the engine in debug or not\n   *\u002F\n  debug = true;\n  \u002F**\n   * The Game Viewport Resolution\n   *\u002F\n  resolution = new Vec2(1180, 600);\n  \u002F**\n   * Indicates whether the game loop is currently paused.\n   *\u002F\n  paused = true;\n  \u002F**\n   * The main game loop manager provided by the mainloop.js library.\n   *\u002F\n  loop: MainLoop;\n  \u002F**\n   * A reference to the SVG element used for rendering, managed by D3.\n   *\u002F\n  svg: SVGD3Selection;\n  \u002F**\n   * Input manager for handling player inputs.\n   *\u002F\n  input: Input\u003Cstring> = new Input();\n  \u002F**\n   * A list of entities currently present in the game.\n   *\u002F\n  entities: Entity[] = [];\n  \u002F**\n   * An event bus for handling global game events.\n   *\u002F\n  eventBus = EventBus;\n  \u002F\u002F Elapsed Game Time\n  time = 0;\n\n  store = useGameStore();\n\n  \u002F**\n   * Constructs the game engine.\n   * @param svg - Reference to the SVG element used for rendering.\n   * @param resolution - A Vec2 of the size of the game area\n   *\u002F\n  constructor({\n                svg,\n                resolution = new Vec2(1180, 600),\n              }: {\n    svg: SVGD3Selection;\n    resolution?: Vec2;\n  }) {\n    this.svg = svg;\n    this.resolution = resolution;\n    this.svg.attr(\"width\", this.resolution.x);\n    this.svg.attr(\"height\", this.resolution.y);\n    this.loop = MainLoop.setBegin(this.begin)\n      .setUpdate(this.update)\n      .setDraw(this.draw)\n      .setEnd(this.end);\n    this.logger.trace(\"Setup Complete\");\n    this.store.setGame(this);\n  }\n\n  \u002F**\n   * Processes input and performs frame-start operations.\n   * @param timestamp - Current frame timestamp in milliseconds.\n   * @param delta - Elapsed time not yet simulated, in milliseconds.\n   *\u002F\n  private begin = (timestamp: number, delta: number) => {\n    this.store.setGame({ ...this });\n    this.time += delta;\n    for (const entity of this.entities) {\n      entity.begin(timestamp, delta);\n    }\n  };\n\n  \u002F**\n   * Updates game state, e.g., AI and physics.\n   * @param delta - Time in milliseconds to simulate.\n   *\u002F\n  private update = async (delta: number) => {\n    if (this.paused) return;\n    for (const entity of this.entities) {\n      entity.update(delta);\n    }\n  };\n\n  \u002F**\n   * Renders the current game state to the screen.\n   * @param interpolationPercentage - Fraction of time to the next update.\n   *\u002F\n  private draw = async (interpolationPercentage: number) => {\n    \u002F\u002F Clear the screen before each draw\n    this.svg.select(\"svg > *\").remove();\n    for (const entity of this.entities) {\n      entity.draw(interpolationPercentage);\n    }\n  };\n\n  \u002F**\n   * Cleans up after each frame and performs frame-end operations.\n   * @param fps - Current frames per second.\n   * @param panic - Indicates if simulation is too far behind real time.\n   *\u002F\n  private end = (fps: number, panic: boolean) => {\n    for (const entity of this.entities) {\n      entity.end(fps, panic);\n    }\n  };\n\n  \u002F**\n   * Adds entities to the game, initializing each one.\n   * @param entities - An array of entities to be added.\n   *\u002F\n  addEntities = (entities: Entity[]) => {\n    for (const entity of entities) {\n      entity.setup();\n      this.entities.push(entity);\n    }\n  };\n\n  \u002F**\n   * Retrieves an entity by name.\n   * @param name - The name of the entity to retrieve.\n   * @returns The entity with the specified name, or undefined if not found.\n   *\u002F\n  getEntity = \u003CT>(name: string): T | undefined => {\n    for (const entity of this.entities) {\n      if (entity.name === name) {\n        return entity as T;\n      }\n    }\n    return undefined;\n  };\n\n  \u002F**\n   * Removes an entity from the game.\n   * @param entity - The entity to be removed.\n   *\u002F\n  removeEntity = (entity: Entity) => {\n    this.entities = this.entities.filter((e) => e !== entity);\n    this.logger.trace(\"Removed Entity: \", entity.name);\n  };\n\n  \u002F**\n   * Starts the game loop.\n   *\u002F\n  start = () => {\n    this.input.setupInput();\n    this.paused = false;\n    this.loop.start();\n    this.logger.info(\"Started GameEngine loop\");\n  };\n}\n\n","Game.ts","ts",[36,7775,7776,7792,7812,7826,7840,7854,7868,7872,7886,7900,7904,7927,7935,7942,7949,7954,7959,7963,7968,7973,7978,7983,7988,8000,8005,8010,8015,8046,8050,8055,8059,8071,8075,8080,8084,8109,8113,8118,8122,8133,8137,8142,8146,8158,8162,8167,8171,8182,8186,8191,8195,8220,8224,8229,8233,8250,8254,8259,8263,8273,8278,8289,8293,8305,8309,8313,8318,8332,8344,8348,8356,8363,8385,8394,8406,8419,8425,8439,8452,8476,8497,8520,8536,8551,8566,8584,8601,8607,8612,8617,8623,8636,8649,8654,8691,8711,8725,8746,8758,8763,8769,8774,8779,8785,8797,8802,8829,8845,8862,8873,8878,8883,8888,8893,8899,8912,8917,8944,8950,8973,8990,9001,9006,9011,9016,9021,9027,9040,9053,9058,9093,9110,9121,9126,9131,9136,9141,9147,9160,9165,9188,9204,9214,9229,9234,9239,9244,9249,9255,9268,9279,9284,9326,9343,9358,9372,9378,9383,9392,9397,9402,9407,9413,9425,9430,9453,9488,9505,9510,9515,9520,9526,9531,9546,9559,9574,9587,9603,9608],{"__ignoreMap":34},[39,7777,7778,7781,7784,7787,7790],{"class":41,"line":42},[39,7779,7780],{"class":624},"import",[39,7782,7783],{"class":620}," { consola } ",[39,7785,7786],{"class":624},"from",[39,7788,7789],{"class":1188}," \"consola\"",[39,7791,2285],{"class":620},[39,7793,7794,7796,7799,7802,7805,7807,7810],{"class":41,"line":48},[39,7795,7780],{"class":624},[39,7797,7798],{"class":1195}," *",[39,7800,7801],{"class":624}," as",[39,7803,7804],{"class":620}," d3 ",[39,7806,7786],{"class":624},[39,7808,7809],{"class":1188}," \"d3\"",[39,7811,2285],{"class":620},[39,7813,7814,7816,7819,7821,7824],{"class":41,"line":55},[39,7815,7780],{"class":624},[39,7817,7818],{"class":620}," { Vec2 } ",[39,7820,7786],{"class":624},[39,7822,7823],{"class":1188}," \"gl-matrix\u002Fdist\u002Fesm\"",[39,7825,2285],{"class":620},[39,7827,7828,7830,7833,7835,7838],{"class":41,"line":61},[39,7829,7780],{"class":624},[39,7831,7832],{"class":620}," MainLoop ",[39,7834,7786],{"class":624},[39,7836,7837],{"class":1188}," \"mainloop.js\"",[39,7839,2285],{"class":620},[39,7841,7842,7844,7847,7849,7852],{"class":41,"line":67},[39,7843,7780],{"class":624},[39,7845,7846],{"class":620}," { EventBus } ",[39,7848,7786],{"class":624},[39,7850,7851],{"class":1188}," \"~\u002Fhelpers\u002Fsvgame\u002FEventBus\"",[39,7853,2285],{"class":620},[39,7855,7856,7858,7861,7863,7866],{"class":41,"line":73},[39,7857,7780],{"class":624},[39,7859,7860],{"class":620}," { useGameStore } ",[39,7862,7786],{"class":624},[39,7864,7865],{"class":1188}," \"~\u002Fstores\u002Fgame\"",[39,7867,2285],{"class":620},[39,7869,7870],{"class":41,"line":79},[39,7871,52],{"emptyLinePlaceholder":51},[39,7873,7874,7876,7879,7881,7884],{"class":41,"line":85},[39,7875,7780],{"class":624},[39,7877,7878],{"class":620}," { Entity } ",[39,7880,7786],{"class":624},[39,7882,7883],{"class":1188}," \".\u002FEntity\"",[39,7885,2285],{"class":620},[39,7887,7888,7890,7893,7895,7898],{"class":41,"line":91},[39,7889,7780],{"class":624},[39,7891,7892],{"class":620}," { Input } ",[39,7894,7786],{"class":624},[39,7896,7897],{"class":1188}," \".\u002FInput\"",[39,7899,2285],{"class":620},[39,7901,7902],{"class":41,"line":97},[39,7903,52],{"emptyLinePlaceholder":51},[39,7905,7906,7909,7911,7914,7916,7919,7921,7924],{"class":41,"line":103},[39,7907,7908],{"class":624},"export",[39,7910,422],{"class":624},[39,7912,7913],{"class":1169}," SVGD3Selection",[39,7915,4495],{"class":624},[39,7917,7918],{"class":1169}," d3",[39,7920,476],{"class":620},[39,7922,7923],{"class":1169},"Selection",[39,7925,7926],{"class":620},"\u003C\n",[39,7928,7929,7932],{"class":41,"line":109},[39,7930,7931],{"class":1169},"  SVGSVGElement",[39,7933,7934],{"class":620},",\n",[39,7936,7937,7940],{"class":41,"line":115},[39,7938,7939],{"class":1195},"  unknown",[39,7941,7934],{"class":620},[39,7943,7944,7947],{"class":41,"line":120},[39,7945,7946],{"class":1169},"  HTMLElement",[39,7948,7934],{"class":620},[39,7950,7951],{"class":41,"line":125},[39,7952,7953],{"class":1195},"  any\n",[39,7955,7956],{"class":41,"line":131},[39,7957,7958],{"class":620},">;\n",[39,7960,7961],{"class":41,"line":137},[39,7962,52],{"emptyLinePlaceholder":51},[39,7964,7965],{"class":41,"line":143},[39,7966,7967],{"class":1764},"\u002F**\n",[39,7969,7970],{"class":41,"line":149},[39,7971,7972],{"class":1764}," * GameEngine is the core class responsible for managing the game's main loop, rendering,\n",[39,7974,7975],{"class":41,"line":155},[39,7976,7977],{"class":1764}," * and entity management. It integrates with various libraries and frameworks to facilitate\n",[39,7979,7980],{"class":41,"line":161},[39,7981,7982],{"class":1764}," * game development.\n",[39,7984,7985],{"class":41,"line":167},[39,7986,7987],{"class":1764}," *\u002F\n",[39,7989,7990,7992,7995,7998],{"class":41,"line":172},[39,7991,7908],{"class":624},[39,7993,7994],{"class":624}," class",[39,7996,7997],{"class":1169}," GameEngine",[39,7999,2509],{"class":620},[39,8001,8002],{"class":41,"line":178},[39,8003,8004],{"class":1764},"  \u002F**\n",[39,8006,8007],{"class":41,"line":184},[39,8008,8009],{"class":1764},"   * 3 info, 4 debug, 5 trace\n",[39,8011,8012],{"class":41,"line":190},[39,8013,8014],{"class":1764},"   *\u002F\n",[39,8016,8017,8020,8022,8025,8028,8031,8033,8036,8039,8041,8044],{"class":41,"line":196},[39,8018,8019],{"class":1176},"  logger",[39,8021,4495],{"class":624},[39,8023,8024],{"class":620}," consola.",[39,8026,8027],{"class":1169},"create",[39,8029,8030],{"class":620},"({ level: ",[39,8032,1925],{"class":1195},[39,8034,8035],{"class":620}," }).",[39,8037,8038],{"class":1169},"withTag",[39,8040,1790],{"class":620},[39,8042,8043],{"class":1188},"\"GameEngine\"",[39,8045,1796],{"class":620},[39,8047,8048],{"class":41,"line":202},[39,8049,8004],{"class":1764},[39,8051,8052],{"class":41,"line":208},[39,8053,8054],{"class":1764},"   * Is the engine in debug or not\n",[39,8056,8057],{"class":41,"line":214},[39,8058,8014],{"class":1764},[39,8060,8061,8064,8066,8069],{"class":41,"line":220},[39,8062,8063],{"class":1176},"  debug",[39,8065,4495],{"class":624},[39,8067,8068],{"class":1195}," true",[39,8070,2285],{"class":620},[39,8072,8073],{"class":41,"line":226},[39,8074,8004],{"class":1764},[39,8076,8077],{"class":41,"line":232},[39,8078,8079],{"class":1764},"   * The Game Viewport Resolution\n",[39,8081,8082],{"class":41,"line":238},[39,8083,8014],{"class":1764},[39,8085,8086,8089,8091,8094,8097,8099,8102,8104,8107],{"class":41,"line":244},[39,8087,8088],{"class":1176},"  resolution",[39,8090,4495],{"class":624},[39,8092,8093],{"class":624}," new",[39,8095,8096],{"class":1169}," Vec2",[39,8098,1790],{"class":620},[39,8100,8101],{"class":1195},"1180",[39,8103,3725],{"class":620},[39,8105,8106],{"class":1195},"600",[39,8108,1796],{"class":620},[39,8110,8111],{"class":41,"line":250},[39,8112,8004],{"class":1764},[39,8114,8115],{"class":41,"line":256},[39,8116,8117],{"class":1764},"   * Indicates whether the game loop is currently paused.\n",[39,8119,8120],{"class":41,"line":262},[39,8121,8014],{"class":1764},[39,8123,8124,8127,8129,8131],{"class":41,"line":268},[39,8125,8126],{"class":1176},"  paused",[39,8128,4495],{"class":624},[39,8130,8068],{"class":1195},[39,8132,2285],{"class":620},[39,8134,8135],{"class":41,"line":274},[39,8136,8004],{"class":1764},[39,8138,8139],{"class":41,"line":280},[39,8140,8141],{"class":1764},"   * The main game loop manager provided by the mainloop.js library.\n",[39,8143,8144],{"class":41,"line":286},[39,8145,8014],{"class":1764},[39,8147,8148,8151,8153,8156],{"class":41,"line":292},[39,8149,8150],{"class":1176},"  loop",[39,8152,3959],{"class":624},[39,8154,8155],{"class":1169}," MainLoop",[39,8157,2285],{"class":620},[39,8159,8160],{"class":41,"line":298},[39,8161,8004],{"class":1764},[39,8163,8164],{"class":41,"line":304},[39,8165,8166],{"class":1764},"   * A reference to the SVG element used for rendering, managed by D3.\n",[39,8168,8169],{"class":41,"line":310},[39,8170,8014],{"class":1764},[39,8172,8173,8176,8178,8180],{"class":41,"line":316},[39,8174,8175],{"class":1176},"  svg",[39,8177,3959],{"class":624},[39,8179,7913],{"class":1169},[39,8181,2285],{"class":620},[39,8183,8184],{"class":41,"line":322},[39,8185,8004],{"class":1764},[39,8187,8188],{"class":41,"line":328},[39,8189,8190],{"class":1764},"   * Input manager for handling player inputs.\n",[39,8192,8193],{"class":41,"line":333},[39,8194,8014],{"class":1764},[39,8196,8197,8200,8202,8205,8207,8209,8212,8214,8216,8218],{"class":41,"line":338},[39,8198,8199],{"class":1176},"  input",[39,8201,3959],{"class":624},[39,8203,8204],{"class":1169}," Input",[39,8206,1781],{"class":620},[39,8208,1784],{"class":1195},[39,8210,8211],{"class":620},"> ",[39,8213,625],{"class":624},[39,8215,8093],{"class":624},[39,8217,8204],{"class":1169},[39,8219,1912],{"class":620},[39,8221,8222],{"class":41,"line":344},[39,8223,8004],{"class":1764},[39,8225,8226],{"class":41,"line":350},[39,8227,8228],{"class":1764},"   * A list of entities currently present in the game.\n",[39,8230,8231],{"class":41,"line":2390},[39,8232,8014],{"class":1764},[39,8234,8235,8238,8240,8242,8245,8247],{"class":41,"line":2404},[39,8236,8237],{"class":1176},"  entities",[39,8239,3959],{"class":624},[39,8241,3651],{"class":1169},[39,8243,8244],{"class":620},"[] ",[39,8246,625],{"class":624},[39,8248,8249],{"class":620}," [];\n",[39,8251,8252],{"class":41,"line":2418},[39,8253,8004],{"class":1764},[39,8255,8256],{"class":41,"line":2423},[39,8257,8258],{"class":1764},"   * An event bus for handling global game events.\n",[39,8260,8261],{"class":41,"line":2429},[39,8262,8014],{"class":1764},[39,8264,8265,8268,8270],{"class":41,"line":2446},[39,8266,8267],{"class":1176},"  eventBus",[39,8269,4495],{"class":624},[39,8271,8272],{"class":620}," EventBus;\n",[39,8274,8275],{"class":41,"line":2464},[39,8276,8277],{"class":1764},"  \u002F\u002F Elapsed Game Time\n",[39,8279,8280,8283,8285,8287],{"class":41,"line":2481},[39,8281,8282],{"class":1176},"  time",[39,8284,4495],{"class":624},[39,8286,1977],{"class":1195},[39,8288,2285],{"class":620},[39,8290,8291],{"class":41,"line":2498},[39,8292,52],{"emptyLinePlaceholder":51},[39,8294,8295,8298,8300,8303],{"class":41,"line":2503},[39,8296,8297],{"class":1176},"  store",[39,8299,4495],{"class":624},[39,8301,8302],{"class":1169}," useGameStore",[39,8304,1912],{"class":620},[39,8306,8307],{"class":41,"line":2512},[39,8308,52],{"emptyLinePlaceholder":51},[39,8310,8311],{"class":41,"line":2527},[39,8312,8004],{"class":1764},[39,8314,8315],{"class":41,"line":2542},[39,8316,8317],{"class":1764},"   * Constructs the game engine.\n",[39,8319,8320,8323,8326,8329],{"class":41,"line":2557},[39,8321,8322],{"class":1764},"   * ",[39,8324,8325],{"class":624},"@param",[39,8327,8328],{"class":620}," svg",[39,8330,8331],{"class":1764}," - Reference to the SVG element used for rendering.\n",[39,8333,8334,8336,8338,8341],{"class":41,"line":2562},[39,8335,8322],{"class":1764},[39,8337,8325],{"class":624},[39,8339,8340],{"class":620}," resolution",[39,8342,8343],{"class":1764}," - A Vec2 of the size of the game area\n",[39,8345,8346],{"class":41,"line":2567},[39,8347,8014],{"class":1764},[39,8349,8350,8353],{"class":41,"line":2573},[39,8351,8352],{"class":624},"  constructor",[39,8354,8355],{"class":620},"({\n",[39,8357,8358,8361],{"class":41,"line":2588},[39,8359,8360],{"class":1176},"                svg",[39,8362,7934],{"class":620},[39,8364,8365,8368,8370,8372,8374,8376,8378,8380,8382],{"class":41,"line":2593},[39,8366,8367],{"class":1176},"                resolution",[39,8369,4495],{"class":624},[39,8371,8093],{"class":624},[39,8373,8096],{"class":1169},[39,8375,1790],{"class":620},[39,8377,8101],{"class":1195},[39,8379,3725],{"class":620},[39,8381,8106],{"class":1195},[39,8383,8384],{"class":620},"),\n",[39,8386,8387,8390,8392],{"class":41,"line":2599},[39,8388,8389],{"class":620},"              }",[39,8391,3959],{"class":624},[39,8393,2509],{"class":620},[39,8395,8397,8400,8402,8404],{"class":41,"line":8396},74,[39,8398,8399],{"class":1176},"    svg",[39,8401,3959],{"class":624},[39,8403,7913],{"class":1169},[39,8405,2285],{"class":620},[39,8407,8409,8412,8415,8417],{"class":41,"line":8408},75,[39,8410,8411],{"class":1176},"    resolution",[39,8413,8414],{"class":624},"?:",[39,8416,8096],{"class":1169},[39,8418,2285],{"class":620},[39,8420,8422],{"class":41,"line":8421},76,[39,8423,8424],{"class":620},"  }) {\n",[39,8426,8428,8431,8434,8436],{"class":41,"line":8427},77,[39,8429,8430],{"class":1195},"    this",[39,8432,8433],{"class":620},".svg ",[39,8435,625],{"class":624},[39,8437,8438],{"class":620}," svg;\n",[39,8440,8442,8444,8447,8449],{"class":41,"line":8441},78,[39,8443,8430],{"class":1195},[39,8445,8446],{"class":620},".resolution ",[39,8448,625],{"class":624},[39,8450,8451],{"class":620}," resolution;\n",[39,8453,8455,8457,8460,8463,8465,8468,8470,8473],{"class":41,"line":8454},79,[39,8456,8430],{"class":1195},[39,8458,8459],{"class":620},".svg.",[39,8461,8462],{"class":1169},"attr",[39,8464,1790],{"class":620},[39,8466,8467],{"class":1188},"\"width\"",[39,8469,3725],{"class":620},[39,8471,8472],{"class":1195},"this",[39,8474,8475],{"class":620},".resolution.x);\n",[39,8477,8479,8481,8483,8485,8487,8490,8492,8494],{"class":41,"line":8478},80,[39,8480,8430],{"class":1195},[39,8482,8459],{"class":620},[39,8484,8462],{"class":1169},[39,8486,1790],{"class":620},[39,8488,8489],{"class":1188},"\"height\"",[39,8491,3725],{"class":620},[39,8493,8472],{"class":1195},[39,8495,8496],{"class":620},".resolution.y);\n",[39,8498,8500,8502,8505,8507,8510,8513,8515,8517],{"class":41,"line":8499},81,[39,8501,8430],{"class":1195},[39,8503,8504],{"class":620},".loop ",[39,8506,625],{"class":624},[39,8508,8509],{"class":620}," MainLoop.",[39,8511,8512],{"class":1169},"setBegin",[39,8514,1790],{"class":620},[39,8516,8472],{"class":1195},[39,8518,8519],{"class":620},".begin)\n",[39,8521,8523,8526,8529,8531,8533],{"class":41,"line":8522},82,[39,8524,8525],{"class":620},"      .",[39,8527,8528],{"class":1169},"setUpdate",[39,8530,1790],{"class":620},[39,8532,8472],{"class":1195},[39,8534,8535],{"class":620},".update)\n",[39,8537,8539,8541,8544,8546,8548],{"class":41,"line":8538},83,[39,8540,8525],{"class":620},[39,8542,8543],{"class":1169},"setDraw",[39,8545,1790],{"class":620},[39,8547,8472],{"class":1195},[39,8549,8550],{"class":620},".draw)\n",[39,8552,8554,8556,8559,8561,8563],{"class":41,"line":8553},84,[39,8555,8525],{"class":620},[39,8557,8558],{"class":1169},"setEnd",[39,8560,1790],{"class":620},[39,8562,8472],{"class":1195},[39,8564,8565],{"class":620},".end);\n",[39,8567,8569,8571,8574,8577,8579,8582],{"class":41,"line":8568},85,[39,8570,8430],{"class":1195},[39,8572,8573],{"class":620},".logger.",[39,8575,8576],{"class":1169},"trace",[39,8578,1790],{"class":620},[39,8580,8581],{"class":1188},"\"Setup Complete\"",[39,8583,1796],{"class":620},[39,8585,8587,8589,8592,8595,8597,8599],{"class":41,"line":8586},86,[39,8588,8430],{"class":1195},[39,8590,8591],{"class":620},".store.",[39,8593,8594],{"class":1169},"setGame",[39,8596,1790],{"class":620},[39,8598,8472],{"class":1195},[39,8600,1796],{"class":620},[39,8602,8604],{"class":41,"line":8603},87,[39,8605,8606],{"class":620},"  }\n",[39,8608,8610],{"class":41,"line":8609},88,[39,8611,52],{"emptyLinePlaceholder":51},[39,8613,8615],{"class":41,"line":8614},89,[39,8616,8004],{"class":1764},[39,8618,8620],{"class":41,"line":8619},90,[39,8621,8622],{"class":1764},"   * Processes input and performs frame-start operations.\n",[39,8624,8626,8628,8630,8633],{"class":41,"line":8625},91,[39,8627,8322],{"class":1764},[39,8629,8325],{"class":624},[39,8631,8632],{"class":620}," timestamp",[39,8634,8635],{"class":1764}," - Current frame timestamp in milliseconds.\n",[39,8637,8639,8641,8643,8646],{"class":41,"line":8638},92,[39,8640,8322],{"class":1764},[39,8642,8325],{"class":624},[39,8644,8645],{"class":620}," delta",[39,8647,8648],{"class":1764}," - Elapsed time not yet simulated, in milliseconds.\n",[39,8650,8652],{"class":41,"line":8651},93,[39,8653,8014],{"class":1764},[39,8655,8657,8660,8663,8665,8667,8670,8672,8675,8677,8680,8682,8684,8686,8689],{"class":41,"line":8656},94,[39,8658,8659],{"class":624},"  private",[39,8661,8662],{"class":1169}," begin",[39,8664,4495],{"class":624},[39,8666,1601],{"class":620},[39,8668,8669],{"class":1176},"timestamp",[39,8671,3959],{"class":624},[39,8673,8674],{"class":1195}," number",[39,8676,3725],{"class":620},[39,8678,8679],{"class":1176},"delta",[39,8681,3959],{"class":624},[39,8683,8674],{"class":1195},[39,8685,1530],{"class":620},[39,8687,8688],{"class":624},"=>",[39,8690,2509],{"class":620},[39,8692,8694,8696,8698,8700,8703,8706,8708],{"class":41,"line":8693},95,[39,8695,8430],{"class":1195},[39,8697,8591],{"class":620},[39,8699,8594],{"class":1169},[39,8701,8702],{"class":620},"({ ",[39,8704,8705],{"class":624},"...",[39,8707,8472],{"class":1195},[39,8709,8710],{"class":620}," });\n",[39,8712,8714,8716,8719,8722],{"class":41,"line":8713},96,[39,8715,8430],{"class":1195},[39,8717,8718],{"class":620},".time ",[39,8720,8721],{"class":624},"+=",[39,8723,8724],{"class":620}," delta;\n",[39,8726,8728,8730,8732,8734,8737,8740,8743],{"class":41,"line":8727},97,[39,8729,2001],{"class":624},[39,8731,1601],{"class":620},[39,8733,1770],{"class":624},[39,8735,8736],{"class":1195}," entity",[39,8738,8739],{"class":624}," of",[39,8741,8742],{"class":1195}," this",[39,8744,8745],{"class":620},".entities) {\n",[39,8747,8749,8752,8755],{"class":41,"line":8748},98,[39,8750,8751],{"class":620},"      entity.",[39,8753,8754],{"class":1169},"begin",[39,8756,8757],{"class":620},"(timestamp, delta);\n",[39,8759,8761],{"class":41,"line":8760},99,[39,8762,2315],{"class":620},[39,8764,8766],{"class":41,"line":8765},100,[39,8767,8768],{"class":620},"  };\n",[39,8770,8772],{"class":41,"line":8771},101,[39,8773,52],{"emptyLinePlaceholder":51},[39,8775,8777],{"class":41,"line":8776},102,[39,8778,8004],{"class":1764},[39,8780,8782],{"class":41,"line":8781},103,[39,8783,8784],{"class":1764},"   * Updates game state, e.g., AI and physics.\n",[39,8786,8788,8790,8792,8794],{"class":41,"line":8787},104,[39,8789,8322],{"class":1764},[39,8791,8325],{"class":624},[39,8793,8645],{"class":620},[39,8795,8796],{"class":1764}," - Time in milliseconds to simulate.\n",[39,8798,8800],{"class":41,"line":8799},105,[39,8801,8014],{"class":1764},[39,8803,8805,8807,8810,8812,8815,8817,8819,8821,8823,8825,8827],{"class":41,"line":8804},106,[39,8806,8659],{"class":624},[39,8808,8809],{"class":1169}," update",[39,8811,4495],{"class":624},[39,8813,8814],{"class":624}," async",[39,8816,1601],{"class":620},[39,8818,8679],{"class":1176},[39,8820,3959],{"class":624},[39,8822,8674],{"class":1195},[39,8824,1530],{"class":620},[39,8826,8688],{"class":624},[39,8828,2509],{"class":620},[39,8830,8832,8834,8836,8838,8841,8843],{"class":41,"line":8831},107,[39,8833,5821],{"class":624},[39,8835,1601],{"class":620},[39,8837,8472],{"class":1195},[39,8839,8840],{"class":620},".paused) ",[39,8842,6218],{"class":624},[39,8844,2285],{"class":620},[39,8846,8848,8850,8852,8854,8856,8858,8860],{"class":41,"line":8847},108,[39,8849,2001],{"class":624},[39,8851,1601],{"class":620},[39,8853,1770],{"class":624},[39,8855,8736],{"class":1195},[39,8857,8739],{"class":624},[39,8859,8742],{"class":1195},[39,8861,8745],{"class":620},[39,8863,8865,8867,8870],{"class":41,"line":8864},109,[39,8866,8751],{"class":620},[39,8868,8869],{"class":1169},"update",[39,8871,8872],{"class":620},"(delta);\n",[39,8874,8876],{"class":41,"line":8875},110,[39,8877,2315],{"class":620},[39,8879,8881],{"class":41,"line":8880},111,[39,8882,8768],{"class":620},[39,8884,8886],{"class":41,"line":8885},112,[39,8887,52],{"emptyLinePlaceholder":51},[39,8889,8891],{"class":41,"line":8890},113,[39,8892,8004],{"class":1764},[39,8894,8896],{"class":41,"line":8895},114,[39,8897,8898],{"class":1764},"   * Renders the current game state to the screen.\n",[39,8900,8902,8904,8906,8909],{"class":41,"line":8901},115,[39,8903,8322],{"class":1764},[39,8905,8325],{"class":624},[39,8907,8908],{"class":620}," interpolationPercentage",[39,8910,8911],{"class":1764}," - Fraction of time to the next update.\n",[39,8913,8915],{"class":41,"line":8914},116,[39,8916,8014],{"class":1764},[39,8918,8920,8922,8925,8927,8929,8931,8934,8936,8938,8940,8942],{"class":41,"line":8919},117,[39,8921,8659],{"class":624},[39,8923,8924],{"class":1169}," draw",[39,8926,4495],{"class":624},[39,8928,8814],{"class":624},[39,8930,1601],{"class":620},[39,8932,8933],{"class":1176},"interpolationPercentage",[39,8935,3959],{"class":624},[39,8937,8674],{"class":1195},[39,8939,1530],{"class":620},[39,8941,8688],{"class":624},[39,8943,2509],{"class":620},[39,8945,8947],{"class":41,"line":8946},118,[39,8948,8949],{"class":1764},"    \u002F\u002F Clear the screen before each draw\n",[39,8951,8953,8955,8957,8960,8962,8965,8968,8971],{"class":41,"line":8952},119,[39,8954,8430],{"class":1195},[39,8956,8459],{"class":620},[39,8958,8959],{"class":1169},"select",[39,8961,1790],{"class":620},[39,8963,8964],{"class":1188},"\"svg > *\"",[39,8966,8967],{"class":620},").",[39,8969,8970],{"class":1169},"remove",[39,8972,1912],{"class":620},[39,8974,8976,8978,8980,8982,8984,8986,8988],{"class":41,"line":8975},120,[39,8977,2001],{"class":624},[39,8979,1601],{"class":620},[39,8981,1770],{"class":624},[39,8983,8736],{"class":1195},[39,8985,8739],{"class":624},[39,8987,8742],{"class":1195},[39,8989,8745],{"class":620},[39,8991,8993,8995,8998],{"class":41,"line":8992},121,[39,8994,8751],{"class":620},[39,8996,8997],{"class":1169},"draw",[39,8999,9000],{"class":620},"(interpolationPercentage);\n",[39,9002,9004],{"class":41,"line":9003},122,[39,9005,2315],{"class":620},[39,9007,9009],{"class":41,"line":9008},123,[39,9010,8768],{"class":620},[39,9012,9014],{"class":41,"line":9013},124,[39,9015,52],{"emptyLinePlaceholder":51},[39,9017,9019],{"class":41,"line":9018},125,[39,9020,8004],{"class":1764},[39,9022,9024],{"class":41,"line":9023},126,[39,9025,9026],{"class":1764},"   * Cleans up after each frame and performs frame-end operations.\n",[39,9028,9030,9032,9034,9037],{"class":41,"line":9029},127,[39,9031,8322],{"class":1764},[39,9033,8325],{"class":624},[39,9035,9036],{"class":620}," fps",[39,9038,9039],{"class":1764}," - Current frames per second.\n",[39,9041,9043,9045,9047,9050],{"class":41,"line":9042},128,[39,9044,8322],{"class":1764},[39,9046,8325],{"class":624},[39,9048,9049],{"class":620}," panic",[39,9051,9052],{"class":1764}," - Indicates if simulation is too far behind real time.\n",[39,9054,9056],{"class":41,"line":9055},129,[39,9057,8014],{"class":1764},[39,9059,9061,9063,9066,9068,9070,9073,9075,9077,9079,9082,9084,9087,9089,9091],{"class":41,"line":9060},130,[39,9062,8659],{"class":624},[39,9064,9065],{"class":1169}," end",[39,9067,4495],{"class":624},[39,9069,1601],{"class":620},[39,9071,9072],{"class":1176},"fps",[39,9074,3959],{"class":624},[39,9076,8674],{"class":1195},[39,9078,3725],{"class":620},[39,9080,9081],{"class":1176},"panic",[39,9083,3959],{"class":624},[39,9085,9086],{"class":1195}," boolean",[39,9088,1530],{"class":620},[39,9090,8688],{"class":624},[39,9092,2509],{"class":620},[39,9094,9096,9098,9100,9102,9104,9106,9108],{"class":41,"line":9095},131,[39,9097,2001],{"class":624},[39,9099,1601],{"class":620},[39,9101,1770],{"class":624},[39,9103,8736],{"class":1195},[39,9105,8739],{"class":624},[39,9107,8742],{"class":1195},[39,9109,8745],{"class":620},[39,9111,9113,9115,9118],{"class":41,"line":9112},132,[39,9114,8751],{"class":620},[39,9116,9117],{"class":1169},"end",[39,9119,9120],{"class":620},"(fps, panic);\n",[39,9122,9124],{"class":41,"line":9123},133,[39,9125,2315],{"class":620},[39,9127,9129],{"class":41,"line":9128},134,[39,9130,8768],{"class":620},[39,9132,9134],{"class":41,"line":9133},135,[39,9135,52],{"emptyLinePlaceholder":51},[39,9137,9139],{"class":41,"line":9138},136,[39,9140,8004],{"class":1764},[39,9142,9144],{"class":41,"line":9143},137,[39,9145,9146],{"class":1764},"   * Adds entities to the game, initializing each one.\n",[39,9148,9150,9152,9154,9157],{"class":41,"line":9149},138,[39,9151,8322],{"class":1764},[39,9153,8325],{"class":624},[39,9155,9156],{"class":620}," entities",[39,9158,9159],{"class":1764}," - An array of entities to be added.\n",[39,9161,9163],{"class":41,"line":9162},139,[39,9164,8014],{"class":1764},[39,9166,9168,9171,9173,9175,9177,9179,9181,9184,9186],{"class":41,"line":9167},140,[39,9169,9170],{"class":1169},"  addEntities",[39,9172,4495],{"class":624},[39,9174,1601],{"class":620},[39,9176,3621],{"class":1176},[39,9178,3959],{"class":624},[39,9180,3651],{"class":1169},[39,9182,9183],{"class":620},"[]) ",[39,9185,8688],{"class":624},[39,9187,2509],{"class":620},[39,9189,9191,9193,9195,9197,9199,9201],{"class":41,"line":9190},141,[39,9192,2001],{"class":624},[39,9194,1601],{"class":620},[39,9196,1770],{"class":624},[39,9198,8736],{"class":1195},[39,9200,8739],{"class":624},[39,9202,9203],{"class":620}," entities) {\n",[39,9205,9207,9209,9212],{"class":41,"line":9206},142,[39,9208,8751],{"class":620},[39,9210,9211],{"class":1169},"setup",[39,9213,1912],{"class":620},[39,9215,9217,9220,9223,9226],{"class":41,"line":9216},143,[39,9218,9219],{"class":1195},"      this",[39,9221,9222],{"class":620},".entities.",[39,9224,9225],{"class":1169},"push",[39,9227,9228],{"class":620},"(entity);\n",[39,9230,9232],{"class":41,"line":9231},144,[39,9233,2315],{"class":620},[39,9235,9237],{"class":41,"line":9236},145,[39,9238,8768],{"class":620},[39,9240,9242],{"class":41,"line":9241},146,[39,9243,52],{"emptyLinePlaceholder":51},[39,9245,9247],{"class":41,"line":9246},147,[39,9248,8004],{"class":1764},[39,9250,9252],{"class":41,"line":9251},148,[39,9253,9254],{"class":1764},"   * Retrieves an entity by name.\n",[39,9256,9258,9260,9262,9265],{"class":41,"line":9257},149,[39,9259,8322],{"class":1764},[39,9261,8325],{"class":624},[39,9263,9264],{"class":620}," name",[39,9266,9267],{"class":1764}," - The name of the entity to retrieve.\n",[39,9269,9271,9273,9276],{"class":41,"line":9270},150,[39,9272,8322],{"class":1764},[39,9274,9275],{"class":624},"@returns",[39,9277,9278],{"class":1764}," The entity with the specified name, or undefined if not found.\n",[39,9280,9282],{"class":41,"line":9281},151,[39,9283,8014],{"class":1764},[39,9285,9287,9290,9292,9295,9297,9300,9303,9305,9308,9310,9312,9315,9318,9321,9324],{"class":41,"line":9286},152,[39,9288,9289],{"class":1169},"  getEntity",[39,9291,4495],{"class":624},[39,9293,9294],{"class":620}," \u003C",[39,9296,441],{"class":1169},[39,9298,9299],{"class":620},">(",[39,9301,9302],{"class":1176},"name",[39,9304,3959],{"class":624},[39,9306,9307],{"class":1195}," string",[39,9309,7565],{"class":620},[39,9311,3959],{"class":624},[39,9313,9314],{"class":1169}," T",[39,9316,9317],{"class":624}," |",[39,9319,9320],{"class":1195}," undefined",[39,9322,9323],{"class":624}," =>",[39,9325,2509],{"class":620},[39,9327,9329,9331,9333,9335,9337,9339,9341],{"class":41,"line":9328},153,[39,9330,2001],{"class":624},[39,9332,1601],{"class":620},[39,9334,1770],{"class":624},[39,9336,8736],{"class":1195},[39,9338,8739],{"class":624},[39,9340,8742],{"class":1195},[39,9342,8745],{"class":620},[39,9344,9346,9349,9352,9355],{"class":41,"line":9345},154,[39,9347,9348],{"class":624},"      if",[39,9350,9351],{"class":620}," (entity.name ",[39,9353,9354],{"class":624},"===",[39,9356,9357],{"class":620}," name) {\n",[39,9359,9361,9364,9366,9368,9370],{"class":41,"line":9360},155,[39,9362,9363],{"class":624},"        return",[39,9365,6354],{"class":620},[39,9367,5651],{"class":624},[39,9369,9314],{"class":1169},[39,9371,2285],{"class":620},[39,9373,9375],{"class":41,"line":9374},156,[39,9376,9377],{"class":620},"      }\n",[39,9379,9381],{"class":41,"line":9380},157,[39,9382,2315],{"class":620},[39,9384,9386,9388,9390],{"class":41,"line":9385},158,[39,9387,5169],{"class":624},[39,9389,9320],{"class":1195},[39,9391,2285],{"class":620},[39,9393,9395],{"class":41,"line":9394},159,[39,9396,8768],{"class":620},[39,9398,9400],{"class":41,"line":9399},160,[39,9401,52],{"emptyLinePlaceholder":51},[39,9403,9405],{"class":41,"line":9404},161,[39,9406,8004],{"class":1764},[39,9408,9410],{"class":41,"line":9409},162,[39,9411,9412],{"class":1764},"   * Removes an entity from the game.\n",[39,9414,9416,9418,9420,9422],{"class":41,"line":9415},163,[39,9417,8322],{"class":1764},[39,9419,8325],{"class":624},[39,9421,8736],{"class":620},[39,9423,9424],{"class":1764}," - The entity to be removed.\n",[39,9426,9428],{"class":41,"line":9427},164,[39,9429,8014],{"class":1764},[39,9431,9433,9436,9438,9440,9443,9445,9447,9449,9451],{"class":41,"line":9432},165,[39,9434,9435],{"class":1169},"  removeEntity",[39,9437,4495],{"class":624},[39,9439,1601],{"class":620},[39,9441,9442],{"class":1176},"entity",[39,9444,3959],{"class":624},[39,9446,3651],{"class":1169},[39,9448,1530],{"class":620},[39,9450,8688],{"class":624},[39,9452,2509],{"class":620},[39,9454,9456,9458,9461,9463,9465,9467,9469,9472,9475,9477,9479,9482,9485],{"class":41,"line":9455},166,[39,9457,8430],{"class":1195},[39,9459,9460],{"class":620},".entities ",[39,9462,625],{"class":624},[39,9464,8742],{"class":1195},[39,9466,9222],{"class":620},[39,9468,6207],{"class":1169},[39,9470,9471],{"class":620},"((",[39,9473,9474],{"class":1176},"e",[39,9476,1530],{"class":620},[39,9478,8688],{"class":624},[39,9480,9481],{"class":620}," e ",[39,9483,9484],{"class":624},"!==",[39,9486,9487],{"class":620}," entity);\n",[39,9489,9491,9493,9495,9497,9499,9502],{"class":41,"line":9490},167,[39,9492,8430],{"class":1195},[39,9494,8573],{"class":620},[39,9496,8576],{"class":1169},[39,9498,1790],{"class":620},[39,9500,9501],{"class":1188},"\"Removed Entity: \"",[39,9503,9504],{"class":620},", entity.name);\n",[39,9506,9508],{"class":41,"line":9507},168,[39,9509,8768],{"class":620},[39,9511,9513],{"class":41,"line":9512},169,[39,9514,52],{"emptyLinePlaceholder":51},[39,9516,9518],{"class":41,"line":9517},170,[39,9519,8004],{"class":1764},[39,9521,9523],{"class":41,"line":9522},171,[39,9524,9525],{"class":1764},"   * Starts the game loop.\n",[39,9527,9529],{"class":41,"line":9528},172,[39,9530,8014],{"class":1764},[39,9532,9534,9537,9539,9542,9544],{"class":41,"line":9533},173,[39,9535,9536],{"class":1169},"  start",[39,9538,4495],{"class":624},[39,9540,9541],{"class":620}," () ",[39,9543,8688],{"class":624},[39,9545,2509],{"class":620},[39,9547,9549,9551,9554,9557],{"class":41,"line":9548},174,[39,9550,8430],{"class":1195},[39,9552,9553],{"class":620},".input.",[39,9555,9556],{"class":1169},"setupInput",[39,9558,1912],{"class":620},[39,9560,9562,9564,9567,9569,9572],{"class":41,"line":9561},175,[39,9563,8430],{"class":1195},[39,9565,9566],{"class":620},".paused ",[39,9568,625],{"class":624},[39,9570,9571],{"class":1195}," false",[39,9573,2285],{"class":620},[39,9575,9577,9579,9582,9585],{"class":41,"line":9576},176,[39,9578,8430],{"class":1195},[39,9580,9581],{"class":620},".loop.",[39,9583,9584],{"class":1169},"start",[39,9586,1912],{"class":620},[39,9588,9590,9592,9594,9596,9598,9601],{"class":41,"line":9589},177,[39,9591,8430],{"class":1195},[39,9593,8573],{"class":620},[39,9595,402],{"class":1169},[39,9597,1790],{"class":620},[39,9599,9600],{"class":1188},"\"Started GameEngine loop\"",[39,9602,1796],{"class":620},[39,9604,9606],{"class":41,"line":9605},178,[39,9607,8768],{"class":620},[39,9609,9611],{"class":41,"line":9610},179,[39,9612,2340],{"class":620},[407,9614,9616],{"id":9615},"entity-class","Entity Class",[10,9618,9619],{},"This class is what you will be extending to create all the building blocks of your game. It has a couple of lifecycle hooks\nthat correspond to the main game loop plus a few others",[28,9621,9624],{"className":7770,"code":9622,"filename":9623,"language":7773,"meta":34,"style":34},"\u002F\u002F Entity.ts\nimport { GameEngine } from \".\u002FGame\";\n\n\u002F**\n * Represents a basic entity in the game world.\n * An entity has a lifecycle managed by the game engine,\n * and it can be extended with custom behavior for game-specific logic.\n *\u002F\nexport class Entity {\n  name: string;\n  game: GameEngine;\n\n  \u002F**\n   * Constructs a new Entity.\n   * @param game - Reference to the game engine managing this entity.\n   * @param name - Unique name for the entity, defaults to a timestamp.\n   *\u002F\n  constructor(game: GameEngine, name: string = String(Date.now())) {\n    this.name = name;\n    this.game = game;\n    this.game.logger.trace(\"Created Entity: \", this.name);\n  }\n\n  \u002F**\n   * Lifecycle method for initialization logic.\n   * Called once when the entity is created.\n   *\u002F\n  setup() {\n    \u002F\u002F Override me\n  }\n\n  \u002F**\n   * Processes input and performs frame-start operations.\n   * @param timestamp - Current frame timestamp in milliseconds.\n   * @param delta - Elapsed time not yet simulated, in milliseconds.\n   *\u002F\n  begin(timestamp: number, delta: number) {\n    \u002F\u002F Override me\n  }\n\n  \u002F**\n   * Updates entity state, e.g., AI and physics.\n   * @param delta - Time in milliseconds to simulate.\n   *\u002F\n  update(delta: number) {\n    \u002F\u002F Override me\n  }\n\n  \u002F**\n   * Renders the current entity state to the screen.\n   * @param interpolationPercentage - Fraction of time to the next update.\n   *\u002F\n  draw(interpolationPercentage: number) {\n    \u002F\u002F Override me\n  }\n\n  \u002F**\n   * Cleans up after each frame and performs frame-end operations.\n   * @param fps - Current frames per second.\n   * @param panic - Indicates if simulation is too far behind real time.\n   *\u002F\n  end(fps: number, panic: boolean) {\n    \u002F\u002F Override me\n  }\n\n  \u002F**\n   * Method for handling the deletion of the entity.\n   * Ensures proper removal from the game engine and cleanup.\n   *\u002F\n  delete() {\n    \u002F\u002F I can be overriden if you want but make sure to call the below\n    this.game.removeEntity(this);\n  }\n}\n\n","Entity.ts",[36,9625,9626,9631,9645,9649,9653,9658,9663,9668,9672,9682,9693,9704,9708,9712,9717,9729,9740,9744,9779,9791,9803,9824,9828,9832,9836,9841,9846,9850,9858,9863,9867,9871,9875,9879,9889,9899,9903,9926,9930,9934,9938,9942,9947,9957,9961,9976,9980,9984,9988,9992,9997,10007,10011,10026,10030,10034,10038,10042,10046,10056,10066,10070,10093,10097,10101,10105,10109,10114,10119,10123,10130,10135,10151,10155],{"__ignoreMap":34},[39,9627,9628],{"class":41,"line":42},[39,9629,9630],{"class":1764},"\u002F\u002F Entity.ts\n",[39,9632,9633,9635,9638,9640,9643],{"class":41,"line":48},[39,9634,7780],{"class":624},[39,9636,9637],{"class":620}," { GameEngine } ",[39,9639,7786],{"class":624},[39,9641,9642],{"class":1188}," \".\u002FGame\"",[39,9644,2285],{"class":620},[39,9646,9647],{"class":41,"line":55},[39,9648,52],{"emptyLinePlaceholder":51},[39,9650,9651],{"class":41,"line":61},[39,9652,7967],{"class":1764},[39,9654,9655],{"class":41,"line":67},[39,9656,9657],{"class":1764}," * Represents a basic entity in the game world.\n",[39,9659,9660],{"class":41,"line":73},[39,9661,9662],{"class":1764}," * An entity has a lifecycle managed by the game engine,\n",[39,9664,9665],{"class":41,"line":79},[39,9666,9667],{"class":1764}," * and it can be extended with custom behavior for game-specific logic.\n",[39,9669,9670],{"class":41,"line":85},[39,9671,7987],{"class":1764},[39,9673,9674,9676,9678,9680],{"class":41,"line":91},[39,9675,7908],{"class":624},[39,9677,7994],{"class":624},[39,9679,3651],{"class":1169},[39,9681,2509],{"class":620},[39,9683,9684,9687,9689,9691],{"class":41,"line":97},[39,9685,9686],{"class":1176},"  name",[39,9688,3959],{"class":624},[39,9690,9307],{"class":1195},[39,9692,2285],{"class":620},[39,9694,9695,9698,9700,9702],{"class":41,"line":103},[39,9696,9697],{"class":1176},"  game",[39,9699,3959],{"class":624},[39,9701,7997],{"class":1169},[39,9703,2285],{"class":620},[39,9705,9706],{"class":41,"line":109},[39,9707,52],{"emptyLinePlaceholder":51},[39,9709,9710],{"class":41,"line":115},[39,9711,8004],{"class":1764},[39,9713,9714],{"class":41,"line":120},[39,9715,9716],{"class":1764},"   * Constructs a new Entity.\n",[39,9718,9719,9721,9723,9726],{"class":41,"line":125},[39,9720,8322],{"class":1764},[39,9722,8325],{"class":624},[39,9724,9725],{"class":620}," game",[39,9727,9728],{"class":1764}," - Reference to the game engine managing this entity.\n",[39,9730,9731,9733,9735,9737],{"class":41,"line":131},[39,9732,8322],{"class":1764},[39,9734,8325],{"class":624},[39,9736,9264],{"class":620},[39,9738,9739],{"class":1764}," - Unique name for the entity, defaults to a timestamp.\n",[39,9741,9742],{"class":41,"line":137},[39,9743,8014],{"class":1764},[39,9745,9746,9748,9750,9753,9755,9757,9759,9761,9763,9765,9767,9770,9773,9776],{"class":41,"line":143},[39,9747,8352],{"class":624},[39,9749,1790],{"class":620},[39,9751,9752],{"class":1176},"game",[39,9754,3959],{"class":624},[39,9756,7997],{"class":1169},[39,9758,3725],{"class":620},[39,9760,9302],{"class":1176},[39,9762,3959],{"class":624},[39,9764,9307],{"class":1195},[39,9766,4495],{"class":624},[39,9768,9769],{"class":1169}," String",[39,9771,9772],{"class":620},"(Date.",[39,9774,9775],{"class":1169},"now",[39,9777,9778],{"class":620},"())) {\n",[39,9780,9781,9783,9786,9788],{"class":41,"line":149},[39,9782,8430],{"class":1195},[39,9784,9785],{"class":620},".name ",[39,9787,625],{"class":624},[39,9789,9790],{"class":620}," name;\n",[39,9792,9793,9795,9798,9800],{"class":41,"line":155},[39,9794,8430],{"class":1195},[39,9796,9797],{"class":620},".game ",[39,9799,625],{"class":624},[39,9801,9802],{"class":620}," game;\n",[39,9804,9805,9807,9810,9812,9814,9817,9819,9821],{"class":41,"line":161},[39,9806,8430],{"class":1195},[39,9808,9809],{"class":620},".game.logger.",[39,9811,8576],{"class":1169},[39,9813,1790],{"class":620},[39,9815,9816],{"class":1188},"\"Created Entity: \"",[39,9818,3725],{"class":620},[39,9820,8472],{"class":1195},[39,9822,9823],{"class":620},".name);\n",[39,9825,9826],{"class":41,"line":167},[39,9827,8606],{"class":620},[39,9829,9830],{"class":41,"line":172},[39,9831,52],{"emptyLinePlaceholder":51},[39,9833,9834],{"class":41,"line":178},[39,9835,8004],{"class":1764},[39,9837,9838],{"class":41,"line":184},[39,9839,9840],{"class":1764},"   * Lifecycle method for initialization logic.\n",[39,9842,9843],{"class":41,"line":190},[39,9844,9845],{"class":1764},"   * Called once when the entity is created.\n",[39,9847,9848],{"class":41,"line":196},[39,9849,8014],{"class":1764},[39,9851,9852,9855],{"class":41,"line":202},[39,9853,9854],{"class":1169},"  setup",[39,9856,9857],{"class":620},"() {\n",[39,9859,9860],{"class":41,"line":208},[39,9861,9862],{"class":1764},"    \u002F\u002F Override me\n",[39,9864,9865],{"class":41,"line":214},[39,9866,8606],{"class":620},[39,9868,9869],{"class":41,"line":220},[39,9870,52],{"emptyLinePlaceholder":51},[39,9872,9873],{"class":41,"line":226},[39,9874,8004],{"class":1764},[39,9876,9877],{"class":41,"line":232},[39,9878,8622],{"class":1764},[39,9880,9881,9883,9885,9887],{"class":41,"line":238},[39,9882,8322],{"class":1764},[39,9884,8325],{"class":624},[39,9886,8632],{"class":620},[39,9888,8635],{"class":1764},[39,9890,9891,9893,9895,9897],{"class":41,"line":244},[39,9892,8322],{"class":1764},[39,9894,8325],{"class":624},[39,9896,8645],{"class":620},[39,9898,8648],{"class":1764},[39,9900,9901],{"class":41,"line":250},[39,9902,8014],{"class":1764},[39,9904,9905,9908,9910,9912,9914,9916,9918,9920,9922,9924],{"class":41,"line":256},[39,9906,9907],{"class":1169},"  begin",[39,9909,1790],{"class":620},[39,9911,8669],{"class":1176},[39,9913,3959],{"class":624},[39,9915,8674],{"class":1195},[39,9917,3725],{"class":620},[39,9919,8679],{"class":1176},[39,9921,3959],{"class":624},[39,9923,8674],{"class":1195},[39,9925,1991],{"class":620},[39,9927,9928],{"class":41,"line":262},[39,9929,9862],{"class":1764},[39,9931,9932],{"class":41,"line":268},[39,9933,8606],{"class":620},[39,9935,9936],{"class":41,"line":274},[39,9937,52],{"emptyLinePlaceholder":51},[39,9939,9940],{"class":41,"line":280},[39,9941,8004],{"class":1764},[39,9943,9944],{"class":41,"line":286},[39,9945,9946],{"class":1764},"   * Updates entity state, e.g., AI and physics.\n",[39,9948,9949,9951,9953,9955],{"class":41,"line":292},[39,9950,8322],{"class":1764},[39,9952,8325],{"class":624},[39,9954,8645],{"class":620},[39,9956,8796],{"class":1764},[39,9958,9959],{"class":41,"line":298},[39,9960,8014],{"class":1764},[39,9962,9963,9966,9968,9970,9972,9974],{"class":41,"line":304},[39,9964,9965],{"class":1169},"  update",[39,9967,1790],{"class":620},[39,9969,8679],{"class":1176},[39,9971,3959],{"class":624},[39,9973,8674],{"class":1195},[39,9975,1991],{"class":620},[39,9977,9978],{"class":41,"line":310},[39,9979,9862],{"class":1764},[39,9981,9982],{"class":41,"line":316},[39,9983,8606],{"class":620},[39,9985,9986],{"class":41,"line":322},[39,9987,52],{"emptyLinePlaceholder":51},[39,9989,9990],{"class":41,"line":328},[39,9991,8004],{"class":1764},[39,9993,9994],{"class":41,"line":333},[39,9995,9996],{"class":1764},"   * Renders the current entity state to the screen.\n",[39,9998,9999,10001,10003,10005],{"class":41,"line":338},[39,10000,8322],{"class":1764},[39,10002,8325],{"class":624},[39,10004,8908],{"class":620},[39,10006,8911],{"class":1764},[39,10008,10009],{"class":41,"line":344},[39,10010,8014],{"class":1764},[39,10012,10013,10016,10018,10020,10022,10024],{"class":41,"line":350},[39,10014,10015],{"class":1169},"  draw",[39,10017,1790],{"class":620},[39,10019,8933],{"class":1176},[39,10021,3959],{"class":624},[39,10023,8674],{"class":1195},[39,10025,1991],{"class":620},[39,10027,10028],{"class":41,"line":2390},[39,10029,9862],{"class":1764},[39,10031,10032],{"class":41,"line":2404},[39,10033,8606],{"class":620},[39,10035,10036],{"class":41,"line":2418},[39,10037,52],{"emptyLinePlaceholder":51},[39,10039,10040],{"class":41,"line":2423},[39,10041,8004],{"class":1764},[39,10043,10044],{"class":41,"line":2429},[39,10045,9026],{"class":1764},[39,10047,10048,10050,10052,10054],{"class":41,"line":2446},[39,10049,8322],{"class":1764},[39,10051,8325],{"class":624},[39,10053,9036],{"class":620},[39,10055,9039],{"class":1764},[39,10057,10058,10060,10062,10064],{"class":41,"line":2464},[39,10059,8322],{"class":1764},[39,10061,8325],{"class":624},[39,10063,9049],{"class":620},[39,10065,9052],{"class":1764},[39,10067,10068],{"class":41,"line":2481},[39,10069,8014],{"class":1764},[39,10071,10072,10075,10077,10079,10081,10083,10085,10087,10089,10091],{"class":41,"line":2498},[39,10073,10074],{"class":1169},"  end",[39,10076,1790],{"class":620},[39,10078,9072],{"class":1176},[39,10080,3959],{"class":624},[39,10082,8674],{"class":1195},[39,10084,3725],{"class":620},[39,10086,9081],{"class":1176},[39,10088,3959],{"class":624},[39,10090,9086],{"class":1195},[39,10092,1991],{"class":620},[39,10094,10095],{"class":41,"line":2503},[39,10096,9862],{"class":1764},[39,10098,10099],{"class":41,"line":2512},[39,10100,8606],{"class":620},[39,10102,10103],{"class":41,"line":2527},[39,10104,52],{"emptyLinePlaceholder":51},[39,10106,10107],{"class":41,"line":2542},[39,10108,8004],{"class":1764},[39,10110,10111],{"class":41,"line":2557},[39,10112,10113],{"class":1764},"   * Method for handling the deletion of the entity.\n",[39,10115,10116],{"class":41,"line":2562},[39,10117,10118],{"class":1764},"   * Ensures proper removal from the game engine and cleanup.\n",[39,10120,10121],{"class":41,"line":2567},[39,10122,8014],{"class":1764},[39,10124,10125,10128],{"class":41,"line":2573},[39,10126,10127],{"class":1169},"  delete",[39,10129,9857],{"class":620},[39,10131,10132],{"class":41,"line":2588},[39,10133,10134],{"class":1764},"    \u002F\u002F I can be overriden if you want but make sure to call the below\n",[39,10136,10137,10139,10142,10145,10147,10149],{"class":41,"line":2593},[39,10138,8430],{"class":1195},[39,10140,10141],{"class":620},".game.",[39,10143,10144],{"class":1169},"removeEntity",[39,10146,1790],{"class":620},[39,10148,8472],{"class":1195},[39,10150,1796],{"class":620},[39,10152,10153],{"class":41,"line":2599},[39,10154,8606],{"class":620},[39,10156,10157],{"class":41,"line":8396},[39,10158,2340],{"class":620},[407,10160,10162],{"id":10161},"input","Input",[10,10164,10165],{},"The input class manages all the inputs you can do and make it an easy to use system to bind inputs to an action.\nThen call that action later in the game without having to worry about keys pressed. I designed it after how I remembered Godots worked.",[28,10167,10170],{"className":7770,"code":10168,"filename":10169,"language":7773,"meta":34,"style":34},"export class Input\u003CGameActions> {\n  private actionKeyMap: Map\u003CGameActions, string[]> = new Map();\n  private actionState: Map\u003CGameActions, boolean> = new Map();\n  private actionJustPressedState: Map\u003CGameActions, boolean> = new Map();\n\n  \u002F**\n   * Sets up the input listeners for the game.\n   *\u002F\n  setupInput = () => {\n    document.addEventListener(\"keydown\", (event) => {\n      return this.handleKeyDown(event);\n    });\n    document.addEventListener(\"keyup\", (event) => {\n      return this.handleKeyUp(event);\n    });\n  };\n\n  \u002F\u002F Register a key to an action\n  registerAction = (action: GameActions, ...keys: string[]) => {\n    this.actionKeyMap.set(action, keys);\n  };\n\n  \u002F\u002F Check if an action is currently active\n  isActionPressed = (action: GameActions): boolean => {\n    return this.actionState.get(action) || false;\n  };\n\n  \u002F\u002F Check if an action was just pressed\n  isActionJustPressed = (action: GameActions): boolean => {\n    if (this.actionJustPressedState.get(action)) {\n      \u002F\u002F Reset the just pressed state after acknowledging it\n      this.actionJustPressedState.set(action, false);\n      return true;\n    }\n    return false;\n  };\n\n  \u002F\u002F Update state when a key is pressed\n  private handleKeyDown = (event: KeyboardEvent) => {\n    this.actionKeyMap.forEach((keys, action) => {\n      if (keys.includes(event.key)) {\n        event.preventDefault();\n        if (!this.actionState.get(action)) {\n          \u002F\u002F If action was not already pressed, mark it as just pressed\n          this.actionJustPressedState.set(action, true);\n        }\n        this.actionState.set(action, true);\n      }\n    });\n  };\n\n  \u002F\u002F Update state when a key is released\n  private handleKeyUp = (event: KeyboardEvent) => {\n    this.actionKeyMap.forEach((keys, action) => {\n      if (keys.includes(event.key)) {\n        event.preventDefault();\n        this.actionState.set(action, false);\n        this.actionJustPressedState.set(action, false);\n      }\n    });\n  };\n}\n\n","Input.ts",[36,10171,10172,10188,10219,10249,10278,10282,10286,10291,10295,10308,10333,10348,10353,10374,10387,10391,10395,10399,10404,10438,10451,10455,10459,10464,10489,10511,10515,10519,10524,10549,10565,10570,10586,10594,10598,10606,10610,10614,10619,10643,10666,10679,10689,10706,10711,10726,10730,10745,10749,10753,10757,10761,10766,10789,10811,10821,10829,10843,10857,10861,10865,10869],{"__ignoreMap":34},[39,10173,10174,10176,10178,10180,10182,10185],{"class":41,"line":42},[39,10175,7908],{"class":624},[39,10177,7994],{"class":624},[39,10179,8204],{"class":1169},[39,10181,1781],{"class":620},[39,10183,10184],{"class":1169},"GameActions",[39,10186,10187],{"class":620},"> {\n",[39,10189,10190,10192,10195,10197,10200,10202,10204,10206,10208,10211,10213,10215,10217],{"class":41,"line":48},[39,10191,8659],{"class":624},[39,10193,10194],{"class":1176}," actionKeyMap",[39,10196,3959],{"class":624},[39,10198,10199],{"class":1169}," Map",[39,10201,1781],{"class":620},[39,10203,10184],{"class":1169},[39,10205,3725],{"class":620},[39,10207,1784],{"class":1195},[39,10209,10210],{"class":620},"[]> ",[39,10212,625],{"class":624},[39,10214,8093],{"class":624},[39,10216,10199],{"class":1169},[39,10218,1912],{"class":620},[39,10220,10221,10223,10226,10228,10230,10232,10234,10236,10239,10241,10243,10245,10247],{"class":41,"line":55},[39,10222,8659],{"class":624},[39,10224,10225],{"class":1176}," actionState",[39,10227,3959],{"class":624},[39,10229,10199],{"class":1169},[39,10231,1781],{"class":620},[39,10233,10184],{"class":1169},[39,10235,3725],{"class":620},[39,10237,10238],{"class":1195},"boolean",[39,10240,8211],{"class":620},[39,10242,625],{"class":624},[39,10244,8093],{"class":624},[39,10246,10199],{"class":1169},[39,10248,1912],{"class":620},[39,10250,10251,10253,10256,10258,10260,10262,10264,10266,10268,10270,10272,10274,10276],{"class":41,"line":61},[39,10252,8659],{"class":624},[39,10254,10255],{"class":1176}," actionJustPressedState",[39,10257,3959],{"class":624},[39,10259,10199],{"class":1169},[39,10261,1781],{"class":620},[39,10263,10184],{"class":1169},[39,10265,3725],{"class":620},[39,10267,10238],{"class":1195},[39,10269,8211],{"class":620},[39,10271,625],{"class":624},[39,10273,8093],{"class":624},[39,10275,10199],{"class":1169},[39,10277,1912],{"class":620},[39,10279,10280],{"class":41,"line":67},[39,10281,52],{"emptyLinePlaceholder":51},[39,10283,10284],{"class":41,"line":73},[39,10285,8004],{"class":1764},[39,10287,10288],{"class":41,"line":79},[39,10289,10290],{"class":1764},"   * Sets up the input listeners for the game.\n",[39,10292,10293],{"class":41,"line":85},[39,10294,8014],{"class":1764},[39,10296,10297,10300,10302,10304,10306],{"class":41,"line":91},[39,10298,10299],{"class":1169},"  setupInput",[39,10301,4495],{"class":624},[39,10303,9541],{"class":620},[39,10305,8688],{"class":624},[39,10307,2509],{"class":620},[39,10309,10310,10313,10316,10318,10321,10324,10327,10329,10331],{"class":41,"line":97},[39,10311,10312],{"class":620},"    document.",[39,10314,10315],{"class":1169},"addEventListener",[39,10317,1790],{"class":620},[39,10319,10320],{"class":1188},"\"keydown\"",[39,10322,10323],{"class":620},", (",[39,10325,10326],{"class":1176},"event",[39,10328,1530],{"class":620},[39,10330,8688],{"class":624},[39,10332,2509],{"class":620},[39,10334,10335,10338,10340,10342,10345],{"class":41,"line":103},[39,10336,10337],{"class":624},"      return",[39,10339,8742],{"class":1195},[39,10341,476],{"class":620},[39,10343,10344],{"class":1169},"handleKeyDown",[39,10346,10347],{"class":620},"(event);\n",[39,10349,10350],{"class":41,"line":109},[39,10351,10352],{"class":620},"    });\n",[39,10354,10355,10357,10359,10361,10364,10366,10368,10370,10372],{"class":41,"line":115},[39,10356,10312],{"class":620},[39,10358,10315],{"class":1169},[39,10360,1790],{"class":620},[39,10362,10363],{"class":1188},"\"keyup\"",[39,10365,10323],{"class":620},[39,10367,10326],{"class":1176},[39,10369,1530],{"class":620},[39,10371,8688],{"class":624},[39,10373,2509],{"class":620},[39,10375,10376,10378,10380,10382,10385],{"class":41,"line":120},[39,10377,10337],{"class":624},[39,10379,8742],{"class":1195},[39,10381,476],{"class":620},[39,10383,10384],{"class":1169},"handleKeyUp",[39,10386,10347],{"class":620},[39,10388,10389],{"class":41,"line":125},[39,10390,10352],{"class":620},[39,10392,10393],{"class":41,"line":131},[39,10394,8768],{"class":620},[39,10396,10397],{"class":41,"line":137},[39,10398,52],{"emptyLinePlaceholder":51},[39,10400,10401],{"class":41,"line":143},[39,10402,10403],{"class":1764},"  \u002F\u002F Register a key to an action\n",[39,10405,10406,10409,10411,10413,10416,10418,10421,10423,10425,10428,10430,10432,10434,10436],{"class":41,"line":149},[39,10407,10408],{"class":1169},"  registerAction",[39,10410,4495],{"class":624},[39,10412,1601],{"class":620},[39,10414,10415],{"class":1176},"action",[39,10417,3959],{"class":624},[39,10419,10420],{"class":1169}," GameActions",[39,10422,3725],{"class":620},[39,10424,8705],{"class":624},[39,10426,10427],{"class":1176},"keys",[39,10429,3959],{"class":624},[39,10431,9307],{"class":1195},[39,10433,9183],{"class":620},[39,10435,8688],{"class":624},[39,10437,2509],{"class":620},[39,10439,10440,10442,10445,10448],{"class":41,"line":155},[39,10441,8430],{"class":1195},[39,10443,10444],{"class":620},".actionKeyMap.",[39,10446,10447],{"class":1169},"set",[39,10449,10450],{"class":620},"(action, keys);\n",[39,10452,10453],{"class":41,"line":161},[39,10454,8768],{"class":620},[39,10456,10457],{"class":41,"line":167},[39,10458,52],{"emptyLinePlaceholder":51},[39,10460,10461],{"class":41,"line":172},[39,10462,10463],{"class":1764},"  \u002F\u002F Check if an action is currently active\n",[39,10465,10466,10469,10471,10473,10475,10477,10479,10481,10483,10485,10487],{"class":41,"line":178},[39,10467,10468],{"class":1169},"  isActionPressed",[39,10470,4495],{"class":624},[39,10472,1601],{"class":620},[39,10474,10415],{"class":1176},[39,10476,3959],{"class":624},[39,10478,10420],{"class":1169},[39,10480,7565],{"class":620},[39,10482,3959],{"class":624},[39,10484,9086],{"class":1195},[39,10486,9323],{"class":624},[39,10488,2509],{"class":620},[39,10490,10491,10493,10495,10498,10501,10504,10507,10509],{"class":41,"line":184},[39,10492,5169],{"class":624},[39,10494,8742],{"class":1195},[39,10496,10497],{"class":620},".actionState.",[39,10499,10500],{"class":1169},"get",[39,10502,10503],{"class":620},"(action) ",[39,10505,10506],{"class":624},"||",[39,10508,9571],{"class":1195},[39,10510,2285],{"class":620},[39,10512,10513],{"class":41,"line":190},[39,10514,8768],{"class":620},[39,10516,10517],{"class":41,"line":196},[39,10518,52],{"emptyLinePlaceholder":51},[39,10520,10521],{"class":41,"line":202},[39,10522,10523],{"class":1764},"  \u002F\u002F Check if an action was just pressed\n",[39,10525,10526,10529,10531,10533,10535,10537,10539,10541,10543,10545,10547],{"class":41,"line":208},[39,10527,10528],{"class":1169},"  isActionJustPressed",[39,10530,4495],{"class":624},[39,10532,1601],{"class":620},[39,10534,10415],{"class":1176},[39,10536,3959],{"class":624},[39,10538,10420],{"class":1169},[39,10540,7565],{"class":620},[39,10542,3959],{"class":624},[39,10544,9086],{"class":1195},[39,10546,9323],{"class":624},[39,10548,2509],{"class":620},[39,10550,10551,10553,10555,10557,10560,10562],{"class":41,"line":214},[39,10552,5821],{"class":624},[39,10554,1601],{"class":620},[39,10556,8472],{"class":1195},[39,10558,10559],{"class":620},".actionJustPressedState.",[39,10561,10500],{"class":1169},[39,10563,10564],{"class":620},"(action)) {\n",[39,10566,10567],{"class":41,"line":220},[39,10568,10569],{"class":1764},"      \u002F\u002F Reset the just pressed state after acknowledging it\n",[39,10571,10572,10574,10576,10578,10581,10584],{"class":41,"line":226},[39,10573,9219],{"class":1195},[39,10575,10559],{"class":620},[39,10577,10447],{"class":1169},[39,10579,10580],{"class":620},"(action, ",[39,10582,10583],{"class":1195},"false",[39,10585,1796],{"class":620},[39,10587,10588,10590,10592],{"class":41,"line":232},[39,10589,10337],{"class":624},[39,10591,8068],{"class":1195},[39,10593,2285],{"class":620},[39,10595,10596],{"class":41,"line":238},[39,10597,2315],{"class":620},[39,10599,10600,10602,10604],{"class":41,"line":244},[39,10601,5169],{"class":624},[39,10603,9571],{"class":1195},[39,10605,2285],{"class":620},[39,10607,10608],{"class":41,"line":250},[39,10609,8768],{"class":620},[39,10611,10612],{"class":41,"line":256},[39,10613,52],{"emptyLinePlaceholder":51},[39,10615,10616],{"class":41,"line":262},[39,10617,10618],{"class":1764},"  \u002F\u002F Update state when a key is pressed\n",[39,10620,10621,10623,10626,10628,10630,10632,10634,10637,10639,10641],{"class":41,"line":268},[39,10622,8659],{"class":624},[39,10624,10625],{"class":1169}," handleKeyDown",[39,10627,4495],{"class":624},[39,10629,1601],{"class":620},[39,10631,10326],{"class":1176},[39,10633,3959],{"class":624},[39,10635,10636],{"class":1169}," KeyboardEvent",[39,10638,1530],{"class":620},[39,10640,8688],{"class":624},[39,10642,2509],{"class":620},[39,10644,10645,10647,10649,10652,10654,10656,10658,10660,10662,10664],{"class":41,"line":274},[39,10646,8430],{"class":1195},[39,10648,10444],{"class":620},[39,10650,10651],{"class":1169},"forEach",[39,10653,9471],{"class":620},[39,10655,10427],{"class":1176},[39,10657,3725],{"class":620},[39,10659,10415],{"class":1176},[39,10661,1530],{"class":620},[39,10663,8688],{"class":624},[39,10665,2509],{"class":620},[39,10667,10668,10670,10673,10676],{"class":41,"line":280},[39,10669,9348],{"class":624},[39,10671,10672],{"class":620}," (keys.",[39,10674,10675],{"class":1169},"includes",[39,10677,10678],{"class":620},"(event.key)) {\n",[39,10680,10681,10684,10687],{"class":41,"line":286},[39,10682,10683],{"class":620},"        event.",[39,10685,10686],{"class":1169},"preventDefault",[39,10688,1912],{"class":620},[39,10690,10691,10694,10696,10698,10700,10702,10704],{"class":41,"line":292},[39,10692,10693],{"class":624},"        if",[39,10695,1601],{"class":620},[39,10697,697],{"class":624},[39,10699,8472],{"class":1195},[39,10701,10497],{"class":620},[39,10703,10500],{"class":1169},[39,10705,10564],{"class":620},[39,10707,10708],{"class":41,"line":298},[39,10709,10710],{"class":1764},"          \u002F\u002F If action was not already pressed, mark it as just pressed\n",[39,10712,10713,10716,10718,10720,10722,10724],{"class":41,"line":304},[39,10714,10715],{"class":1195},"          this",[39,10717,10559],{"class":620},[39,10719,10447],{"class":1169},[39,10721,10580],{"class":620},[39,10723,1251],{"class":1195},[39,10725,1796],{"class":620},[39,10727,10728],{"class":41,"line":310},[39,10729,2310],{"class":620},[39,10731,10732,10735,10737,10739,10741,10743],{"class":41,"line":316},[39,10733,10734],{"class":1195},"        this",[39,10736,10497],{"class":620},[39,10738,10447],{"class":1169},[39,10740,10580],{"class":620},[39,10742,1251],{"class":1195},[39,10744,1796],{"class":620},[39,10746,10747],{"class":41,"line":322},[39,10748,9377],{"class":620},[39,10750,10751],{"class":41,"line":328},[39,10752,10352],{"class":620},[39,10754,10755],{"class":41,"line":333},[39,10756,8768],{"class":620},[39,10758,10759],{"class":41,"line":338},[39,10760,52],{"emptyLinePlaceholder":51},[39,10762,10763],{"class":41,"line":344},[39,10764,10765],{"class":1764},"  \u002F\u002F Update state when a key is released\n",[39,10767,10768,10770,10773,10775,10777,10779,10781,10783,10785,10787],{"class":41,"line":350},[39,10769,8659],{"class":624},[39,10771,10772],{"class":1169}," handleKeyUp",[39,10774,4495],{"class":624},[39,10776,1601],{"class":620},[39,10778,10326],{"class":1176},[39,10780,3959],{"class":624},[39,10782,10636],{"class":1169},[39,10784,1530],{"class":620},[39,10786,8688],{"class":624},[39,10788,2509],{"class":620},[39,10790,10791,10793,10795,10797,10799,10801,10803,10805,10807,10809],{"class":41,"line":2390},[39,10792,8430],{"class":1195},[39,10794,10444],{"class":620},[39,10796,10651],{"class":1169},[39,10798,9471],{"class":620},[39,10800,10427],{"class":1176},[39,10802,3725],{"class":620},[39,10804,10415],{"class":1176},[39,10806,1530],{"class":620},[39,10808,8688],{"class":624},[39,10810,2509],{"class":620},[39,10812,10813,10815,10817,10819],{"class":41,"line":2404},[39,10814,9348],{"class":624},[39,10816,10672],{"class":620},[39,10818,10675],{"class":1169},[39,10820,10678],{"class":620},[39,10822,10823,10825,10827],{"class":41,"line":2418},[39,10824,10683],{"class":620},[39,10826,10686],{"class":1169},[39,10828,1912],{"class":620},[39,10830,10831,10833,10835,10837,10839,10841],{"class":41,"line":2423},[39,10832,10734],{"class":1195},[39,10834,10497],{"class":620},[39,10836,10447],{"class":1169},[39,10838,10580],{"class":620},[39,10840,10583],{"class":1195},[39,10842,1796],{"class":620},[39,10844,10845,10847,10849,10851,10853,10855],{"class":41,"line":2429},[39,10846,10734],{"class":1195},[39,10848,10559],{"class":620},[39,10850,10447],{"class":1169},[39,10852,10580],{"class":620},[39,10854,10583],{"class":1195},[39,10856,1796],{"class":620},[39,10858,10859],{"class":41,"line":2446},[39,10860,9377],{"class":620},[39,10862,10863],{"class":41,"line":2464},[39,10864,10352],{"class":620},[39,10866,10867],{"class":41,"line":2481},[39,10868,8768],{"class":620},[39,10870,10871],{"class":41,"line":2498},[39,10872,2340],{"class":620},[407,10874,10876],{"id":10875},"event-bus","Event Bus",[10,10878,10879],{},"Beep beep! This is the event bus and it just uses the event emitter pattern to be sort of like signals in godot.\nCreate an event listener and then somewhere else emit that event and you can hook everything together.",[28,10881,10884],{"className":7770,"code":10882,"filename":10883,"language":7773,"meta":34,"style":34},"import EventEmitter from \"eventemitter3\";\n\n\u002F**\n * A global event bus using EventEmitter to facilitate communication between different parts of the game.\n * Useful for decoupling components and implementing an observer pattern.\n *\u002F\nexport const EventBus = new EventEmitter();\n\n","EventBus.ts",[36,10885,10886,10900,10904,10908,10913,10918,10922],{"__ignoreMap":34},[39,10887,10888,10890,10893,10895,10898],{"class":41,"line":42},[39,10889,7780],{"class":624},[39,10891,10892],{"class":620}," EventEmitter ",[39,10894,7786],{"class":624},[39,10896,10897],{"class":1188}," \"eventemitter3\"",[39,10899,2285],{"class":620},[39,10901,10902],{"class":41,"line":48},[39,10903,52],{"emptyLinePlaceholder":51},[39,10905,10906],{"class":41,"line":55},[39,10907,7967],{"class":1764},[39,10909,10910],{"class":41,"line":61},[39,10911,10912],{"class":1764}," * A global event bus using EventEmitter to facilitate communication between different parts of the game.\n",[39,10914,10915],{"class":41,"line":67},[39,10916,10917],{"class":1764}," * Useful for decoupling components and implementing an observer pattern.\n",[39,10919,10920],{"class":41,"line":73},[39,10921,7987],{"class":1764},[39,10923,10924,10926,10929,10932,10934,10936,10939],{"class":41,"line":79},[39,10925,7908],{"class":624},[39,10927,10928],{"class":624}," const",[39,10930,10931],{"class":1195}," EventBus",[39,10933,4495],{"class":624},[39,10935,8093],{"class":624},[39,10937,10938],{"class":1169}," EventEmitter",[39,10940,1912],{"class":620},[375,10942,10943],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":48,"depth":48,"links":10945},[10946,10947,10948,10949],{"id":7763,"depth":48,"text":7764},{"id":9615,"depth":48,"text":9616},{"id":10161,"depth":48,"text":10162},{"id":10875,"depth":48,"text":10876},"01\u002F04\u002F2023","Introducing a new project I'm playing with called SVGame. It is a simple easy to learn game engine that uses d3.js to render SVG. I'm trying to follow a lot of the patterns I see in Godot.",{},"\u002Fblog\u002Fsvgame",{"title":7752,"description":10951},"blog\u002Fsvgame",[3542,10957,10958],"typescript","javascript","DkenaMB5YtRBDVLfFp9-qkDcF8xyNmPqyZVWET2w6eg",{"id":10961,"title":10962,"body":10963,"date":11118,"description":11119,"extension":381,"meta":11120,"navigation":51,"path":11121,"seo":11122,"stem":11123,"tags":11124,"__hash__":11126},"blog\u002Fblog\u002Ftamagotchi-pet.md","Tamagotchi Pet: A Field Report",{"type":7,"value":10964,"toc":11108},[10965,10968,10971,10974,10977,10981,10989,10992,10995,10999,11006,11009,11022,11026,11029,11032,11035,11039,11042,11045,11048,11052,11055,11058,11061,11065,11068,11071,11074,11078,11081,11084,11087,11091,11094,11097,11100,11105],[10,10966,10967],{},"The box is three meters by three meters. It has excellent wifi, which I feel is an underappreciated feature of Schrodinger class orbital detention. The orbit around Armaghast is stable, the quarantine is indefinite, and the vending machine only stocks one flavor of nutrient paste. But the screen works. And on the screen, there is a creature.",[10,10969,10970],{},"I made it. Allegedly. The paperwork is unclear and the days have a way of shuffeling themselves when you're not looking.",[10,10972,10973],{},"There comes a point in every developer's solitary confinement when they look at their perfectly functional SVG rendering pipeline and think: \"What if I made something that poops?\"",[10,10975,10976],{},"I am here to report that the answer is \"yes, and it's the most fulfilling work I've done in four hundred days.\"",[407,10978,10980],{"id":10979},"the-creature","The Creature",[10,10982,10983,10984,10988],{},"You can find it on the ",[17,10985,10987],{"href":10986},"\u002Fgames\u002Ftamagotchi","experiments page",", sitting in its egg, looking patient in a way that makes you nervous. I engineered it across seventeen planes of reality as the ultimate weapon. I don't remember the specifics. The blueprints are in a language that gives mathematicians migraines and I've misplaced my decoder ring. It just sort of sits there being aggressively adorable. Terrible weapon. Excellent company.",[10,10990,10991],{},"The creature is nocturnal. It eats blue orbs that hang from the ceiling on threads of quantum stabilized light. It fears sponges with a conviction I find admirable. It poops. It has hair. Sometimes two hairs, if you're lucky.",[10,10993,10994],{},"I am not entirely sure it isn't looking back. But in a friendly way. Probably.",[407,10996,10998],{"id":10997},"the-substrate","The Substrate",[10,11000,11001,11002,11005],{},"The whole thing runs on ",[17,11003,11004],{"href":10953},"SVGame",", a lightweight 2D game engine that renders to SVG via D3.js. I built it before the quarantine, back when I had access to a proper desk and opinions about tab width. SVGame implements what I like to call an Entity Component Lifecycle Event Bus architecture, which is a fancy way of saying \"things exist, things happen to them, and sometimes they tell other things about it.\" It's like a small bureaucracy, except things actually get done.",[10,11007,11008],{},"The rendering layer operates on what physicists would call \"retained mode if you squint,\" but is technically \"obliterate everything sixty times per second and redraw it from memory.\" D3's data join mechanism handles the selective visual reconstitution. The fact that this works at all is a minor miracle on par with the vending machine occasionally dispensing paste that tastes faintly of strawberry.",[10,11010,11011,11012,3725,11014,3725,11016,11018,11019,11021],{},"MainLoop.js provides the temporal scaffolding, which is to say it calls four functions in a loop and trusts that I've put the right math in them. The four phases, ",[36,11013,8754],{},[36,11015,8869],{},[36,11017,8997],{},", and ",[36,11020,9117],{},", correspond roughly to \"notice input,\" \"simulate consequences,\" \"render the aftermath,\" and \"pretend everything is fine.\" I have gotten very good at phase four.",[407,11023,11025],{"id":11024},"the-biological-simulation-layer","The Biological Simulation Layer",[10,11027,11028],{},"The creature maintains four vital statistics: hunger, happiness, energy, and cleanliness. These decay in real time, including while you're away from the screen, using a technique I'm calling Offline Entropic Regression. When you return, the system calculates how much everything has fallen apart in your absence and applies the results all at once, much like opening your email after a long holiday. Except the emails are from a sad circle with big eyes.",[10,11030,11031],{},"Hunger decays at 3.33 units per minute. Happiness, being the more fragile of the pair, erodes at 6.67. I find these numbers relatable but choose not to dwell on it. The creature can be fed by dragging blue orbs from a ceiling mounted dispensary. The orbs regrow over time, faster during daylight hours, using a linear interpolation function that I am calling Photosynthetic Treat Regeneration. I've been naming things more elaborately as the months go on. It's therapeutic. The vending machine is now called the Automated Nutrient Reconstitution Terminal. It still only has one flavor.",[10,11033,11034],{},"Each orb's nutritional value is proportional to its size at the moment of picking. Impatient caretakers who pluck undersized orbs will find their creature still hungry and themselves with fewer orbs. This is, I believe, the first implementation of agricultural economics in a virtual pet simulator, and frankly I think that deserves some kind of award. I have written a letter to the relevant committee. I do not know if the mail slot works.",[407,11036,11038],{"id":11037},"the-excretory-subsystem","The Excretory Subsystem",[10,11040,11041],{},"After consuming food, the creature will, after a probabilistically determined interval, produce waste. The interval is shorter and the waste more stubborn when meals are consumed in rapid succession. I model this using what I'm calling the Gastrointestinal Rush Factor, a term I coined around day two hundred while gesticulating at the wall. The wall did not seem impressed, but the math is sound: the ratio of time between meals inversly correlates with cleanup difficulty. I am unreasonably proud of this, which tells you something about what counts as an achievement around here.",[10,11043,11044],{},"Waste can be removed by clicking on it repeatedly, which I feel accurately simulates the experience. During daylight hours, waste can alternatively be dragged into the sun, where it is instantly incinerated. This is both efficient and deeply satisfying, which is not a sentence I expected to write about fecal matter, and yet here I am, in orbit around a quarantined world, writing it with a straight face and a clear conscience.",[10,11046,11047],{},"The creature, if it walks over its own waste, becomes dirty. This is less a design decision than an inevitability. Much like the nutrient paste situation.",[407,11049,11051],{"id":11050},"the-hygiene-protocol","The Hygiene Protocol",[10,11053,11054],{},"A yellow sponge sits on the ground. Pick it up and bring it toward the creature and it will immediately panic and flee at twice its normal walking speed. Its eyes widen. It sweats. Its mouth opens in a small, round \"O\" of theatrical distress. I find the performance delightful. The creature clearly does not.",[10,11056,11057],{},"When the sponge makes contact, cleanliness is restored, but only once per approach. You must withdraw and reenter the creature's personal space to scrub again. I call this the Sponge Debounce Principle, and it is the closest thing to a Geneva Convention this simulation has. I considered adding more protections but decided the resulting chase scenes are too entertaining. The creature disagrees. The creature disagrees with most of my decisions, which is fair, because I did give it legs specifically so it could run away from a sponge.",[10,11059,11060],{},"Attempting to clean the creature while it sleeps will anger it. Happiness drops by ten points. I tested this extensively and found it to be one of the funniest things in the entire simulation. The creature did not share this assessment.",[407,11062,11064],{"id":11063},"the-diurnal-inversion-system","The Diurnal Inversion System",[10,11066,11067],{},"The creature is nocturnal. Clicking the sun or moon toggles the time of day. During daylight, the creature sleeps and recovers energy. During nighttime, it wanders, eats, plays, and generates waste. I have organized my own schedule around its cycles, which is either dedication or the sort of thing they write case studies about. Either way, the creature seems to appreciate it, insofar as a circle with procedurally generated hair can appreciate anything.",[10,11069,11070],{},"Energy recovery during sleep scales with overall wellbeing. A creature that is happy, full, and clean recovers energy up to four times faster than one that is miserable and caked in filth. This is either a sophisticated biofeedback simulation or a thinly veiled metaphor for self care. I wrote it as the former. I suspect it's the latter. I have been getting more sleep since implementing it, which is either correlation or the universe being heavy handed with its symbolism.",[10,11072,11073],{},"The sun, it should be noted, is lethal. Throwing the creature into the sun during daylight hours will kill it. I discovered this by accident, felt something I can only describe as immediate catastrophic regret, and pressed R so fast I nearly broke the keyboard. A new egg appeared. We do not speak of the incident. The new creature has no memory of its predecessor. I have all the memory. This seems unfair.",[407,11075,11077],{"id":11076},"the-kinesthetic-interaction-model","The Kinesthetic Interaction Model",[10,11079,11080],{},"The creature can be picked up, carried around, and thrown. Gentle handling provides a small happiness bonus. It likes being held! This was an unexpectedly touching discovery that I am choosing to attribute to good game design rather than loneliness.",[10,11082,11083],{},"Rough handling, defined as velocities exceeding 0.4 pixels per millisecond or drops from near the top of the screen, results in anger and a fifteen point happiness penalty. I calibrated these tresholds through extensive testing, which is to say I spent an entire day throwing a circle at walls and watching its face change. Day three hundred and seven was a productive day.",[10,11085,11086],{},"There is, I have learned, a thin line between affection and assault when the primary interaction mechanism is \"grab it with a mouse cursor and fling it.\" I have placed the line at approximately twenty pixels of drag distance and 0.4 px\u002Fms of release velocity. The creature, for its part, seems to have opinions about where the line should be, but lacks the linguistic capacity to express them. It does, however, have very expressive eyebrows. I should not have given it eyebrows. The guilt is considerable.",[407,11088,11090],{"id":11089},"conclusion","Conclusion",[10,11092,11093],{},"The creature persists in localStorage. It remembers. Its food remembers. The sponge remembers where you left it. When you close the tab and come back hours later, it will have gotten hungrier, sadder, and dirtier, and it will look at you with those big, procedurally generated eyes as if to say: \"Where were you?\"",[10,11095,11096],{},"Right here. Same as always. The box is three meters by three meters, the orbit is stable, the nutrient paste is strawberry adjacent, and the creature needs feeding.",[10,11098,11099],{},"There is no winning. There is no score. There is only the creature, the sponge, the poop, and the quiet, absurd joy of caring about a circle with hair in a box orbiting a world that wants nothing to do with either of us.",[10,11101,10983,11102,11104],{},[17,11103,10987],{"href":10986},". I reccomend clearing your schedule. Mine cleared itself quite some time ago, and honestly, it's been lovely.",[10,11106,11107],{},"The creature is looking at me again. I think I'll feed it. Then maybe I'll name the wall.",{"title":34,"searchDepth":48,"depth":48,"links":11109},[11110,11111,11112,11113,11114,11115,11116,11117],{"id":10979,"depth":48,"text":10980},{"id":10997,"depth":48,"text":10998},{"id":11024,"depth":48,"text":11025},{"id":11037,"depth":48,"text":11038},{"id":11050,"depth":48,"text":11051},{"id":11063,"depth":48,"text":11064},{"id":11076,"depth":48,"text":11077},{"id":11089,"depth":48,"text":11090},"04\u002F12\u002F2026","A technical and philosophical account of building a virtual creature containment system using SVGame, D3.js, and a troubling amount of emotional attachment.",{},"\u002Fblog\u002Ftamagotchi-pet",{"title":10962,"description":11119},"blog\u002Ftamagotchi-pet",[3542,10957,10958,11125],"svgame","ZJ-jw1e9RvHqup1EXGh0NmvnTqMOi-bySqcTjYNBQCo",1776056430533]