Return to “[Archived] Daily Dev Logs, 2012 - 2015”

Post

Week of July 13, 2014

#1
Sunday, July 13, 2014

YES! I swore that I wouldn't post a dev log until I had the stack-based nodal UI working. It almost killed me, but I got it :lol: From now on I'll refer to it as layer-based instead of stack-based, because I think that's a bit more descriptive.

Layer-Based Nodal UI!!

Instead of a single nodal UI display, what we now have is essentially a set of composited layers of nodes. As I mentioned a few days ago, the primary motivation behind this change is to provide more context for the UI. Previously, selecting a node would 'zoom' to the location of that node in the UI. In doing so, we would often lose sight of all of the other nodes at the previous zoom level (because they would be far away in comparison to the zoomed-in node). This all happened in the same UI 'layer,' which is one of the cute tricks that I used to render the UI very quickly (because it required essentially no compositing).

With this change, I can now overlay an arbitrary number of node layers, and can apply transformations to them to create the appearance of depth. Now, instead of zooming to the location of a node, the default UI behavior for activating a node is simply to pop open a new layer displaying the contents of that node (and push the old layer into the background). The result is more contextual information, a better sense of where you are within the UI, and several cool possibilities for features that I'll discuss in a moment :D

One of the big advantages of the node UI was how quickly it was able to render loads of data. Again, that was a side-effect of the single-layer compositing. With the new layer-based UI, we can still render very quickly - nodes within a layer are batched and drawn in the same call, but we do have to render each layer separately to obtain reasonable results. Still, we retain most of the benefit of the node UI's batching, as having only a few draw calls per layer is still really efficient compared to a few draw calls per UI node.

Layer Cycling

With a set of layers available, it now makes perfect sense to allow the user to tab between them, essentially walking back and forth on one path in the node hierarchy. I can imagine a number of situations in which tabbing back and forth between parents and a particular branch of children would be useful :)

Layer Splitting

A while back I introduced the concept of split-screening the nodal UI, allowing players to do the equivalent of opening multiple windows side-by-side in a traditional UI. In due time I'm planning to revisit this concept in the context of the layer-based UI, as I can imagine a number of really nice synergies here.

Imagine, for example, if we could hold a key for "open node but don't switch to new layer." Then, imagine that if multiple nodes are opened from one layer, they all go into the next layer, and are automatically laid out according to some user-chosen layout mode (vertical splits, horizontal splits, tiled grid, etc.) Now, consider that I'm at the market and I see three different guns that I want to compare. I hold the key, click each of them, then tab to the new layer - voila, I've got a split layout showing me each of the three gun nodes, and can easily make my comparison! If I then want to add one more to the mix, I tab back to the market layer and click another item, thereby adding a forth element to the next layer. Don't know about you, but this functionality sounds really, really awesome to me :geek:

---

Awesome day today, hoping for another one tomorrow :) (By the way, I would definitely classify today's work under both playability and polish, as it adds a lot to both!)
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#2
Monday, July 14, 2014

Another big day...spread out all over the place! I'm really pushing to have a working game soon (preferably in two weeks). I've still got a ways to go before the economy will look like it's really working...but hoping that my work on colonies is going to make that happen.

Polish: Smooth UI Layer Animations

After yesterday's UI work, it made sense to spice it up a bit today with smoothness. UI layers smoothly move back and forth now as new layers are created or the active layer is changed via tabbing. Layers also open with a smooth scaling animation. The effects make the UI feel very polished :)

Playability: Easier Targeting Mechanics

Simple: use the same button to target and un-target objects. Just a minor tweak, but it feels a lot more intuitive than hitting a separate key to drop a target (although we should still have a key for dropping all targets).

Speaking of targeting, I've been thinking about how targeting individual subsystems should work. Right now my thoughts are: when you target an object, you of course get a HUD node that shows you details about that target. On that HUD node, we should overlay subsystem nodes. Simply click on a subsystem node to select that subsystem as the specific target. Simple enough? Sure. But it raises questions: can you target more than one subsystem at a time (you can target more than one object, so it feels natural..but is there any practical value in that?)? What exactly is your targeting computer going to aim at if you haven't selected a subsystem? Should it just aim at the hull, or should it intelligently pick a subsystem to target?

Layer Tabbing in UI

Implemented it :) Nothing else to say really...other than that it's great :D

NPCs Using Colonies!

Well, that was a nice surprise. I mentioned the other day that I wired colonies into the economy such that the AI can actually consider them. But sometimes it's still surprising when things just work without having ever worked before :shock:

I was watching some ships that seemed to be on a collision course with a planet, thinking, "where the heck is the station? I don't see anything...hope they don't blow themselves up." That's when I realized that the ships were disappearing and reappearing at the precise surface location of a colony :shock: I checked the colony's market and..sure enough..there was a booming ore trade going on :lol: Awesome! Never seen the AI interact with colonies before but...apparently it just works :D

---

If we keep pushing I really think July can be something special. I need to push for more hours than I've been getting in...but it's hard with the distractions of being home. Hopefully I'll settle in and fall into the groove this week :geek:
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#3
Tuesday, July 15, 2014

Gonna skimp a bit on the dev log today, as I'm in the middle of some exciting colony work right now that I want to wrap up :)

Colonies, Culture, and Local Item Generation

Most of my work today focused once again on getting colonies to a point where they're laying a foundation for a basic economy (that includes production). Part of that foundation includes, as I mentioned before, blueprint and assembly chip generation for specific items that are part of the colony's internal economy (which, remember, is a black box). But more importantly, that foundation includes the generation of a wide range of potential goods - not just BPs and ACs - that are specific to the colony / planet / system / region. We would like, of course, to have local trade goods like Cyladanian Ale, Burtazian Luxury Goods, etc. BPs and ACs are just one piece of a larger 'local item set.'

The interesting thing about colony item generation is that it's really a critical entrypoint for regional variation of both goods technology. This is where we can inject a sense of regional coherence into LT. Today I introduce something that I call the culture vector, which is, more or less, a snapshot of a colony's aggregate traits. The culture vector is actually identical to the personality vector which stores an AI player's personality traits. This means that a culture can be aggressive, creative, explorative, greedy, etc. in the same way that an individual AI player can. All of the traits that apply to an individual NPC can apply to an entire culture. More interestingly, we will generate this 'culture vector' in the same way that we generate the properties of system nebulae: using regional coherence. The culture vector will vary smoothly (more or less) over space in the same way that system properties do, which means that you will enter regions of space where most colonies are dominated by a certain trait (again, with variance).

The culture vector of a colony influences not only the way that local items are generated by that colony, but also the actual type of colony that gets generated. Not surprisingly, an aggressive culture vector means that the colony is more likely to be a militaristic powerhouse, building all manner of weaponry. A predominantly-intellectual culture vector means that the colony is likely to become a thinktank, pushing out blueprints and techlab-related equipment. The culture vector will also determine the types of workers that spawn at the colony (we haven't talked about workers & crew in a while, but trust me, that mechanic is still in!) This means that you may end up having to travel to aggressive regions of space to find the best fighter pilots, while you may want to head towards intellectual areas to pick up the best researchers.

Colonies as NPC Spawners

The question has often been asked from where do NPCs (AI players) come? Until now I have given no answer. Today I give the only answer which comes naturally from everything that we have in-game so far: NPCs come from colonies. Colonies are natural sources of not only basic goods and technology, but also of life itself. This means that the culture vector of a colony is not only an influence on the flavor of the goods and technology that it produces, but it is also the root of the personality of NPCs which come from that colony. An NPC's personality traits are generated by taking the culture's personality and adding a random (Gaussian) offset. This means that most NPCs born at an 'aggressive' colony will be aggressive (although the random Gaussian offset guarantees that any type of NPC can be born at any type of colony, it's simply a matter of probabilities).

The next question is: when do colonies spawn NPCs? For this question I will fall back on somewhat of a cheat answer: I will set a threshold for how many outstanding AI players any given colony can generate. When the number of living AI players generated by that colony is below the threshold, there will be some probabilistic chance that a new AI player will be generated at the colony (following a Poisson process). When the number of living AI players generated by the colony is at the threshold, no new players will be born of this colony. This mechanism ensures that we can put a hard cap on the number of AI players in any given system (which, of course, we need for performance reasons), while still maintaining a dynamism to the whole thing. Sounds really nice to me :geek:

One of the coolest parts of this system, IMO, is that if you know the culture of a given colony, and you know where a particular AI player was born (which will be information that is available to you in the NPC interaction menu), you can actually infer a bit about the AI's most likely personality traits. Rather awesome, is it not? :D

---

Err, I said I was going to skimp, but that didn't feel like a short dev log. Oh well :lol: :wave:
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#4
Wednesday, July 16, 2014

Another really interesting day with colonies and AI :) Let's have a look... :geek:

Item Traits and AI Affinities

With the introduction of colony culture vectors and local item generation comes a new concept: the 'traits' of an individual item. The basic question at hand is: how do we know how likely a given culture or NPC is to produce or affiliate with a certain (type of) item? For example, an aggressive culture obviously has a greater affinity toward weapons. A technological culture obviously has a greater affinity toward tech labs, blueprints, and assembly chips. A defensive culture has an affinity toward shields. An explorative culture has an affinity toward sensors. You get the idea!

The underlying implication is that items generally have their own traits or personalities that dictate which kinds of people / cultures affiliate strongly with them. Today I implement this concept into the engine, such that every item now has its own trait vector, in precisely the same way that a colony or AI player has a trait vector. The elegance here is obvious: to compute the 'affinity' of a culture or individual player toward any given item, we simply take the dot product of the culture / player traits with the item traits. A colony that is completely aggressive, therefore, will have a low affinity towards items that aren't related to aggression. This affinity makes them both less likely to care about these items on the market, as well as less likely to create their own local products of that type (if Cyladon is an entirely-aggressive culture, we will be much more likely to see Cyladonian Ripper Railguns than to see Cyladonian Exploratory Scanners, for example).

With the introduction of item traits and the resultant AI affinity toward them, we now have a relatively-simple mechanism for generating local, cultural items: generate a random item, compute the affinity A(item) with respect to the culture vector, then accept the item with a probability that is some (monotonically-increasing) function of A. We repeat this procedure until we're satisfied with the number of cultural items. Technically that's called rejection sampling: we just sample items from the entire space of possible procedural items, and reject them if they're not appropriate given the culture vector. Easy! :geek: Now the only hard part: I need an uberfunction Item_Random(int seed) that samples the entire item space. Haven't implemented it yet, but it's on the way!

Data: Specification vs. Generation

Gonna get a bit abstract again for a moment, because I want to mention this concept that I keep encountering and wondering about. Oftentimes in the code I face a choice: when creating an object or item, do I build the creation algorithm such that it accepts the attributes of the object, or do I allow the creation algorithm itself to generate those attributes? For example, do I have a function Object_Colony(Traits cultureTraits, int basePopulation, String name, ...) or do I have a function Object_Colony(Object parent, int seed, ...). Since the game is procedural, you might automatically think that the latter is the right option. But it's not necessarily a choice of whether the attributes are generated, it's a choice of where the attributes are generated. Consider a colony: in the system generating algorithm, I have a section of code that generates planets. For each planet, I have some code that generates colonies. Now, I can choose to generate the properties of the colony within that system generating algorithm, and then pass them directly to a function of the first form. Or, I could let the colony generate it's own properties, passing in only a seed number and reference to the system itself.

Both paradigms have drawbacks. With direct specification of attributes, you've clearly got more control, which is nice for debugging purposes: if I want to create a colony with a certain culture vector for testing purposes, I can do so. On the other hand, it means that I'm forced to specify and generate attributes whenever I need a colony. With internal generation of the attributes, I can easily create a colony at any point in the code, and it will 'just work' because the colony will take care of it's own attributes. However, in order to have things like system / regional coherence of culture, I then have to pass the 'parent' object to the colony, so that it can access system-wide or region-wide information. This implies that I must always have a completely-generated parent object before I can create a child. It also implies that I can't just bring a colony into existence for testing without having a full context for that colony (i.e., a system).

Oftentimes I wonder if one paradigm is more correct, more convenient, or more elegant than the other. Thus far, however, I've been unable to answer this question, even with two years of pondering it. I don't expect anyone can answer it without having worked with both for years and years. Maybe someday I will know the answer :) Or, perhaps the answer is to have both: have one creation algorithm that accepts direct specification of the attributes of an object, and then have another creation algorithm that accepts a context and a seed, generates the attributes, then calls the other (direct) generating algorithm to create the object. That sounds pretty nice - but is it elegant? Someday I'll know :geek:

---

Excited to see what tomorrow will bring...!! :wave:
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#5
Thursday, July 17, 2014

With some rather long dev logs as of late, I'm a bit tired out so going to be brief today! :oops: Couple that with the fact that today was one of those scattered days, and you've got the perfect storm brewing for...the mighty devlog list format!

  • Continued work on cultural item generation, though now debating whether to go with 'item traits' or 'item property traits' (assigning traits to individual properties of an item rather than the item as a whole)
  • More work on the layer-based UI, including a hotkey to pop all layers as well as better color & transparency effects to emphasize the active layer
  • Added nonlinear color grading to the UI for a subtle boost in beauty :)
  • Continued work on UDF syntax, closing in on final LT scripting syntax :geek:
Perhaps a more coherent day tomorrow? One can hope...
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#6
Friday, July 18, 2014

Yes! Yes yes! Very excited about today. Today I finally closed out my work on UDF formatting and, hence, LT script syntax. I'm excited to share what I've got, since I've been keeping it under wraps.

NOTE: Today's devlog will be completely technical. For those who are not interested in programming or programming languages, you might just want to skip today. Sorry! :ugeek:

UDF: LISP without the Parens

The fastest way that I can explain the finalized syntax and general ideology behind the LT scripting language is as LISP without the parentheses. In other words, a more syntactically-nice LISP. As I've hinted at before, the language is functional in spirit. However, that doesn't mean that we can't make the syntax a bit nicer than what is usually found in functional languages!

In UDF, instead of requiring parens to delimit all expressions, I introduce a similar concept as Python, wherein indentation can also be used to delimit them. The best way to explain is through example, so let's see an example of an LT script!

Code: Select all

Script System_Simple
  Param int asteroids
  Param RNG rng

  Var kSystemScale 1000
  Var system System_Create

  For i 0 (< i asteroids) (++ i)
    Var asteroid (Asteroid_Create (RNG_Int rng))
    Object_SetPos (* kSystemScale (RNG_Direction rng) (RNG_Exponential rng))
    Object_AddInterior system asteroid

  system
Whew! This is an ultra-simple little script that creates a system, scatters some asteroids in it, then returns it. Let's take a look at what's going on here! The first two lines should be clear enough: we're defining parameters that our script takes in. The first argument to the 'Param' command is the type of the variable, and second is the name. Notice that the newline delimits these two commands, while whitespace delimits the arguments. We're going to take in two parameters: an integer that tells our script how many asteroids to place in the system, and a random number generator that we'll use to get any randomness that we need.

I then define two variables that I'll use throughout the script - one is a constant that controls how big my system is going to be (i.e., how far asteroids are spread in this particular example). The other is the system itself. The variable system is initialized by calling the (no-parameter) function System_Create.

I then go on to perform a loop. This is the first time that we get to see something interesting happening that tells us a bit more about the language. The form of a 'For' command is "For varname varinitialvalue predicate operation ..." Again, notice that the arguments are delimited with whitespace. However, the predicate (the condition for continuing the loop) cannot be expressed without using whitespace. Hence, we wrap it in parens to indicate that '< i asteroids' is a complete sub-expression. Like many functional languages, notice that the operator is in prefix form vs infix. That's because < is only really an alias for the function Less, and function calls are written with the function as the first token. So < i asteroids is really Less i asteroids. It goes without saying that this returns true until i is no longer less than asteroids. By now hopefully the syntax is starting to make some sense. When I write (++ i) for the operation, it means that I'm calling the function ++ with the argument i for every iteration of the loop. Not surprisingly, ++ is just an alias for the function Increment.

Finally, the '...' part is where we get to see the indents and block format of UDF come into play. The For statement expects an arbitrary number of expressions after the operation component (the expressions are the body of the loop). Instead of writing the expressions on the same line or delimiting them with parens (as we would do in a functional language), we can simply drop down and start a new indent level. Every line of that new indent level is treated as a new expression that's an argument to the For statement. In other words, this:

Code: Select all

MyFunction arg1 arg2 (FunctionComputingArg3 blah blah (AnotherFunction blah))
Can be written like this:

Code: Select all

MyFunction arg1 arg2
  FunctionComputingArg3 blah blah (AnotherFunction blah)
Or:

Code: Select all

MyFunction arg1 arg2
  FunctionComputingArg3 blah blah
    AnotherFunction blah
Or even (not that you would want this):

Code: Select all

MyFunction
  arg1
  arg2
  FunctionComputingArg3
    blah
    blah
    AnotherFunction
      blah
Starting to make sense? The key here is that you can basically trade space for clarity anytime you want. With large expressions, it makes sense to pull things into indent form for the sake of clarity. With indents, you can also drop those nasty parens. But when you want to put simple little sub-expressions inline with a function call or statement, you can use parens just as you would in LISP.

IMO, this syntax offers a really nice combination of elegance and readability. It's succinct enough to be practical, but it still has that elegant functional feel to it. Above all else, it's both fast to write and fast to read. I guess the best part is that: it's implemented and working in the LT Engine :)

The rest of the script is fairly straightforward: I create an asteroid using a seed number generated by the rng. I then set its position by creating a random (unit-length) directional vector and multiplying it with an exponentially-distributed magnitude and the system's scale. Finally, I add the asteroid to the system. The procedure will create an exponential 'cloud' of asteroids at the center of the system, where 'most' of the asteroids will be within a kSystemScale radius of the center (although with plenty of outliers).

---

I'm sure serious programmers will have questions or critiques, and I'll try to respond to relevant threads if I get the time. Wherever there's a syntax, there must be a religious war!! :D :lol:

At any rate, with UDF's syntax finalized and implemented, I'm ready to start some 'serious' content scripting. I believe that this was really the last thing holding me back (I didn't want to write scripts when I knew that I would change the syntax later). So..let's go!! :geek:

PS ~ Sorry the dev log was so late today! It took me a heck of a long time to write :shock:
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#7
Saturday, July 19, 2014

Short log again for today since yesterday's took almost an hour of my work time away from me! :lol:

Quick note: it's been bothering me that I continue to call the LT scripting language 'UDF' (universal data format). What started as a data language has evolved to much more of a programming language. Instead of continuing to refer to it as UDF, I'll henceforth just go with the obvious: LTSL (Limit Theory Scripting Language). Has a nice feel to it, don't you think?

LTSL Automatically-Scoped Variables

The only feature from yesterday's post that actually wasn't working at the time of writing was the 'Var' keyword. The reason being is that the way I defined variables in yesterday's sample script is very different from the straightforward "let" that I had implemented already. With a "let" statement, you bind a variable, then the scope of that variable is the sub-expression of the let. In that sense, all code that uses the variable lives within the let expression. That's a very, very different way of thinking about scopes than most of us are used to from imperative programming. Traditionally, we declare a variable, and then expect that the variable lives after it is declared and until the scope in which it is declared goes away. In other words, if I declare a variable within a function, it should be available for the remainder of that function's scope.

Today I implemented the real 'Var' functionality that I showed yesterday, such that every scope now knows which local variables it owns (rather than simply having let statements own all the variables). Scoping is handled correctly and destructors are automatically called when scopes go out of existence. Variable shadowing (having variables with the same name that live in different scopes) still works.

---

Lots of other stuff done today as well (still spending most of my time on colonies), but LTSL is my favorite speaking point at the moment :geek:
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford
Post

Re: Week of July 13, 2014

#8
Summary of the Week of July 13, 2014
  • Implemented layer-based node UI, along with layer cycling
  • First successful NPC usage of planetary colony markets!
  • Started working on colony culture & item generation
  • Finalized and revealed the UDF / LTSL syntax
  • Implemented parser for said syntax :geek:
  • Implemented convenient in-scope variable declaration for LTSL
“Whether you think you can, or you think you can't--you're right.” ~ Henry Ford

Online Now

Users browsing this forum: No registered users and 0 guests

cron