Hello o/ - welcome to local space [SP, 3D, RPG]

Man it’s hard to get some motivation together these days but I made something… even if it took 4 days.

I grabbed myself some placeholder art and started working on the player. Since I hadn’t made a 2D game before I started with the animations, the state machines to drive them and “blend” between them. Then I added some shooting and aiming and made that drive the animations instead of the walk direction.

I’ve tried some control schemes before where you basically “steer” (accelerate in a forward direction and just change the vector on which you travel) but those were always a bit too complicated/annoying to control.

So now you move and aim independently and the character looks/aims at the mouse. Then I noticed that I could shoot 200bullets/second and realized that I needed to control the rate of fire. So I did that and added some firing modes: single, burst and auto for the shooting action.

So now that I could move around and shoot at stuff it quickly became obvious that all the sprites were on the same layer and i couldn’t just walk “behind” a tree or something. Simple solution: z order based on the worldspace y position; static for objects and dynamic for the player.
I might go with the “different rendertargets” approach later on so i can make the occluding objects sort of see-through instead of completely hiding the player character. For now it’s good enough.

After adding some sound each time I’m instantiating a new bullet to be shot the whole thing started to feel pretty alive already. Especially at 50 shot/second… sounds like your game froze and took the sound with it but it’s just so hilariously overkill :laughing:

Which brings me to where I’m currently at, building a simple little object pool for the bullets and then making them actually interact with stuff.

Once that’s in I’ll see about throwing it up somewhere so you can play with it already. I wanna build and test the central mechanics first and have people test them and play with them until they feel “just right”, then I can build the rest around that.

http://tourable.de/spaaaace/v1/

1 Like

Just ran it up and it works in FireFox Beta running natively in Wayland on ArchLinux! Looks good. I am interest in you progress with this. I want to try and do something similar with the Godot engine.

1 Like

Oh that’s nice. Some aloud thoughts about the systems, might be a good idea while I’m building them.

Right now the next thing I want to prototype is of course some enemies and then some teamates since I judge those to be crucial for the exploration gameloop. To build some “AI” (or more accurately an “expert system”) I first have to know how the world works. How far down I can abstract it so traverse it.
Unity for example has a tile system they added recently with which you can create tile sets and then brush IDs instead of having to place “objects” manually. Since I’ve not used it before, I have to check out if you can mix different tile sizes or, if it’s one per grid or one per layer… or if I may even want to not use that system altogether.
If all the little sprite tiles are the same size I’m working on a grid. And what is a grid if not a graph… and you know what that means: A* for traversing that graph.
If I want something dynamic that can adjusts to a changing and chaotic environment could use some sort of sensory approach using 2d raycasts, and some weighted steering. That would also take care of the “perfect path” problem (where you calculate a path and it becomes invalid half way through the actor following it)

Since I’m probably gonna go with A* on this here’s a little obstacle avoidance code:

public class ObstacleAvoidanceBehaviour : FlockBehaviour
{
  public LayerMask ObstacleMask;

  public override Vector3 CalculateMove(Vector3 pos, FlockAgent agent, Flock flock)
  {
    var up = agent.transform.up; 

    //// no neighbours, nothing to avoid
    //if (!IsHeadingForCollision(pos, up, agent.AgentCollider.radius, flock.ObstacleRadius))
    //  return Vector2.zero;


    Vector3[] rayDirections = BoidHelper.directions;

    for (int i = 0; i < rayDirections.Length; i++)
    {
      var dir = agent.transform.TransformDirection(rayDirections[i]);
      var hit = Physics2D.CircleCast(pos, agent.AgentCollider.radius, dir, flock.ObstacleRadius, ObstacleMask);

      if (hit == default)
        return dir;
    }

    return up;
  }

  public bool IsHeadingForCollision(Vector3 pos, Vector3 dir, float radius, float distance)
  {
    var hit = Physics2D.CircleCast(pos, radius, dir, distance, ObstacleMask);

    return hit != default(RaycastHit2D);
  }

}

public static class BoidHelper
{

  const int numViewDirections = 25;
  public static readonly Vector3[] directions;



  static BoidHelper()
  {
    directions = new Vector3[BoidHelper.numViewDirections];

    float fullSlice = Mathf.PI * 1.5f;
    float midPoint = Mathf.PI / 2 ;

    float slicesLeft = 0;
    float slicesRight = 0;

    bool left = true;

    for (int i = 0; i < numViewDirections; i++)
    {
      float theta;

      if (left)
      {
        theta = midPoint + fullSlice * slicesLeft / numViewDirections;
        slicesLeft++;
      }
      else
      {
        theta = midPoint - fullSlice * slicesRight / numViewDirections;
        slicesRight++;
      }

      directions[i] = new Vector3(Mathf.Cos(theta), Mathf.Sin(theta), 0).normalized;

      left = !left;
    }
  }

  //static BoidHelper()
  //{
  //  directions = new Vector3[BoidHelper.numViewDirections];

  //  float goldenRatio = (1 + Mathf.Sqrt(5)) / 2;
  //  float angleIncrement = Mathf.PI * 2 * goldenRatio;

  //  for (int i = 0; i < numViewDirections; i++)
  //  {
  //    float t = (float)i / numViewDirections;
  //    float inclination = Mathf.Acos(1 - 2 * t);
  //    float azimuth = angleIncrement * i;

  //    float x = Mathf.Sin(inclination) * Mathf.Cos(azimuth);
  //    float y = Mathf.Sin(inclination) * Mathf.Sin(azimuth);
  //    //float z = Mathf.Cos(inclination);
  //    directions[i] = new Vector3(x, y, 0).normalized;
  //  }
  //}

}

to stack behaviours you need:

public class CompoundBehaviour : FlockBehaviour
{
  public FlockBehaviour[] Behaviours;
  public float[] Weights;

  public override Vector3 CalculateMove(Vector3 pos, FlockAgent agent, Flock flock)
  {
    var movement = Vector3.zero;

    for (int i = 0; i < Behaviours.Length; i++)
    {
      var partialMove = Behaviours[i].CalculateMove(pos, agent, flock) * Weights[i];

      if (partialMove != Vector3.zero)
      {
        if (partialMove.sqrMagnitude > Weights[i] * Weights[i])
        {
          partialMove.Normalize();
          partialMove *= Weights[i];
        }
      }

      movement += partialMove;
    }

    return movement;
  }
}

then some cohesion and alignment for a basic boid system:

public class SteeredCohesionBehaviour : FlockBehaviour
{
  Vector3 currentVelocity;
  public float AgentSmoothTime = 0.5f;

  public override Vector3 CalculateMove(Vector3 pos, FlockAgent agent, Flock flock)
  {
    // no neighbours, no center to move towards
    if (agent.Context.Count == 0)
      return Vector3.zero;

    // get neighbours' center of gravity
    var centerOfGravity = Vector3.zero;
    agent.Context.ForEach(p => centerOfGravity += p.position);
    centerOfGravity /= agent.Context.Count;
    // make it relative to agent position
    centerOfGravity -= pos;

    centerOfGravity = Vector3.SmoothDamp(agent.transform.up, centerOfGravity, ref currentVelocity, AgentSmoothTime );

    return centerOfGravity;
  }
}

public class AlignmentBehaviour : FlockBehaviour
{
  public override Vector3 CalculateMove(Vector3 pos, FlockAgent agent, Flock flock)
  {
    // no neighbours, maintain heading
    if (agent.Context.Count == 0)
      return agent.transform.up;

    // get the average direction of all neighbours in range
    var avgHeading = Vector3.zero;
    agent.Context.ForEach(p => avgHeading += p.up);
    avgHeading /= agent.Context.Count;

    return avgHeading;
  }
}

and of course a way to control all this mess:

public class Flock : MonoBehaviour
{


  public FlockAgent AgentPrefab;
  List<FlockAgent> agents = new List<FlockAgent>();
  public FlockBehaviour Behaviour;

  [Range(1, 500)]
  public int StartingCount = 250;
  const float agentDensity = 0.08f;

  [Range(1, 100)]
  public float DriveFactor = 10;
  [Range(1, 100)]
  public float MaxSpeed = 5;

  public Transform Target;

  [Range(1, 10)]
  public float NeighbourRadius = 1.5f;
  [Range(0, 1)]
  public float AvoidanceRadiusMultiplier = 0.5f;

  [Range(1, 50)]
  public float ObstacleRadius = 5f;
  [Range(0, 1)]
  public float ObstacleAvoidanceRadiusMultiplier = 0.5f;

  float squareMaxSpeed;
  float squareNeighbourRadius;
  float squareAgentAvoidanceRadius;

  float squareObstacleRadius;
  float squareObstacleAvoidanceRadius;

  public float SquareAgentAvoidanceRadius { get { return squareAgentAvoidanceRadius; } }

  public float SquareObstcleAvoidanceRadius { get { return squareObstacleAvoidanceRadius; } }
  public int seed;
  private void Start()
  {
    UnityEngine.Random.InitState(seed);

    //Behaviour = Instantiate(Behaviour);

    squareMaxSpeed = MaxSpeed * MaxSpeed;
    squareNeighbourRadius = NeighbourRadius * NeighbourRadius;
    squareAgentAvoidanceRadius = squareNeighbourRadius * AvoidanceRadiusMultiplier * AvoidanceRadiusMultiplier;
    squareObstacleRadius = ObstacleRadius * ObstacleRadius;
    squareObstacleAvoidanceRadius = squareObstacleRadius * ObstacleAvoidanceRadiusMultiplier * ObstacleAvoidanceRadiusMultiplier;

    var temp = Mathf.Max(1, (TemporalPartitions - 1f));
    if (StartingCount / (int)temp != StartingCount / temp)
    {
      var remainder = StartingCount % (TemporalPartitions - 1);
      StartingCount -= remainder;
    }

    //var numGroups = 3;

    //var agentsPerGroup = StartingCount / numGroups;

    //for (int i = 0; i < numGroups; i++)
    //{
    //  var groupContext = new List<Transform>();
    //  var groupAgents = new List<FlockAgent>();

    //  var groupStart = (Vector2)transform.position + UnityEngine.Random.insideUnitCircle * StartingCount * agentDensity;
    //  var groupColor = Color.Lerp(Color.white, Color.black, UnityEngine.Random.Range(0f, 1f));

    //  for (int n = 0; n < agentsPerGroup; n++)
    //  {
    //    var newAgent = Instantiate(
    //      AgentPrefab,
    //      groupStart,
    //      Quaternion.Euler(0, 0, UnityEngine.Random.Range(0, 360)),
    //      transform
    //    );

    //    newAgent.name = "Group"+i+ "Agent" + n;
    //    newAgent.Initialize(this);
    //    newAgent.tag = this.tag;
    //    newAgent.gameObject.layer = gameObject.layer;
    //    newAgent.GetComponentInChildren<SpriteRenderer>().color = groupColor;


    //    groupContext.Add(newAgent.transform);
    //    groupAgents.Add(newAgent);

    //    agents.Add(newAgent);
    //  }

    //  foreach (var agent in groupAgents)
    //  {
    //    agent.Context = groupContext;
    //  }

    //}

    for (int i = 0; i < StartingCount; i++)
    {
      var newAgent = Instantiate(
        AgentPrefab,
        (Vector2)transform.position + UnityEngine.Random.insideUnitCircle * StartingCount * agentDensity,
        Quaternion.Euler(0, 0, UnityEngine.Random.Range(0, 360)),
        transform
      );

      newAgent.name = "Agent" + i;
      newAgent.Initialize(this);
      newAgent.tag = this.tag;
      newAgent.gameObject.layer = gameObject.layer;

      agents.Add(newAgent);
    }

    //agents.ForEach(p => p.GetNeighbours(NeighbourRadius, NeighborMask));
  }

  public LayerMask NeighborMask, ObstacleMask;

  [Range(1, 5)]
  public int TemporalPartitions = 2;
  private int currentTemporalPartition = 0;

  private void Update()
  {
    int agentsPerTemporalPartition = agents.Count / (TemporalPartitions);

    int startIndex = (currentTemporalPartition) * agentsPerTemporalPartition;
    int endIndex = startIndex + agentsPerTemporalPartition;

    for (int i = startIndex; i < endIndex; i++)
    {
      var agent = agents[i];

      agent.GetNeighbours(NeighbourRadius, NeighborMask);

      var move = Behaviour.CalculateMove(agent.transform.position, agent, this);

      if (agent.Context.Count > 0)
      {
        move *= DriveFactor;
        if (move.sqrMagnitude > squareMaxSpeed)
          move = move.normalized * MaxSpeed;
      }
      else
        move = move.normalized * MaxSpeed;


      agent.MoveVec = move;
    }

    if (currentTemporalPartition < TemporalPartitions - 1)
      currentTemporalPartition++;
    else
      currentTemporalPartition = 0;

    foreach (var agent in agents)
      agent.Move(agent.MoveVec);
  }


}
1 Like

Captions Log 28.11.2021: The natives are getting restless, a few already eyeing me for my supple meat. I should move on soon.

Captions Log 29.11.2021: The Tilemap approach really is MUCH nicer to author and quite a bit more performant, just as the ancient texts foretold. I must learn the language of the A* again and converse with the spirits to guide my minions on their path to destruction.

2 Likes

I like this post. I am glad that I am not the only one that does this while working on projects.

slowly descend into madness? :stuck_out_tongue:

Today I started learning about fire-team tactics, formations and bumping/bounding. It’s interesting and oh boy is it gonna be a challenge for me to build it.

First I’m gonna try to make a staggered column layout work… with a player that can just randomly change directions… so i gotta squish the grid around the player with the possible positions on it’s direction over time while moving or something like that.
This would be so much easier if I just got rid of one person and did a basic v formation like you get in mass effect or something.

edit: turns out it is a wee bit important if the guy i’m controlling is left or right handed but since they are set characters I am in luck and can simple declare the protagonist right handed.

now I just have to decide if I want to have a dynamic positioning or if a specific person/role will take a certain spot in the formation

edit2: seems to be working :slight_smile:


I’ve got a new thing to test: Pathfinding, Squadmates and Walking in formation: http://tourable.de/spaaaace/v2/

The little white dots are their desired positions, the placeholder graphics are the mates. They can avoid static obstacles, dynamic obstacles and eachother.

Next I’ll have to build a larger testlevel, some basic enemy behaviours and teach all those guys to shoot… hehe it’s gonna be chaos. I love it!

edit1: I set up a few statemachines and added the sprite animations to the crewmates. still random art from the internet but now they have a face lol.

now it’s on to create division between my children. Inspired by the way they did it in the cryengine I gave them all GroupIDs, just a numerical value for the team they’re on. Bullets shot will always have the groupID of the shooting entity and ignore collisions with other entities of that id. After all I don’t wanna encourage shooting your teamates in the neck. Now there really is no such thing as friendly fire :smiley:

2 Likes

The v2 link does not show anything except for a black screen. By the v1 link seems to have your updates. The Z-Layering looks pretty clever and old school. I am glad that you are making progress on this. It is inspiring.

Oh you’re right -.-
I split up my stuff into different scenes; put the fireteam and all it’s stuff into one scene and the environment into another… then I forgot to load the fireteam scene (which contains the camera) :sweat_smile:

I built and uploaded a quick fix so I can finish the basic ai architecture before I have to worry about scene management. Unfortunately I had already deleted the old test-environment.

No worries. Just wanted to give you a heads up just in case there was a FireFox/Linux breaking feature.

Thanks :slight_smile: luckily that wasn’t it… tho i did discover something interesting. The browser caching in ff works for different addresses, so no matter from where i loaded v2 it used the data file from v1 for me.
Properly versioning the builds fixed that but that’s something I hadn’t have to think about before with desktop or mobile development.

I’ve just updated the v2 with a bit of shooting from our ai friendos, pretty happy with how it’s going so far.

Edit: I started refactoring the basic shooting into a separate vision sensor. First time writing something more complex than a swarm of fish… bit lost, fingers crossed.

While playing with that stuff I’ve also started getting some doubts about the 4 man team. If I construct the levels in a way that you “always” push to the right/top it works fine but traveling left/down I either got two people in front of me OR I have to HEAVILY make them switch positions when changing directions.
There’s also the fact that the last guy is pretty much out of frame when traveling upwards on desktop or right on mobile… at the cam-distance I had envisioned. :tired_face:

edit2: I needed a quick win so I added the ability to switch between different formations and change the team spread. honestly that’s amazing, i can’t believe I didn’t add that sooner.

so now you can do stuff like a standard patrol formation

sticking close and in line if you gotta go through difficult terrain

spreading out and using each teammate’s sensors to find something you might be searching for

1 Like

Alright, I think I’ve build a pretty okay little vision system now.

Each frame it checks what’s in range, then it filters out everything outside a 60° cone. If remembers the things it has seen before and not (yet) lost track off. Once something has been seen long enough it is recognized and the agent can do stuff with it. If a recognized object hasn’t been seen for a while it’s forgotten. If a seen but not yet recognized object breaks line of sight it’s forgotten. The speed at which an object is recognized scales with distance from view direction.

  IEnumerator Detect()
  {
    var previousTime = Time.time;
    var previousPos = Vector3.zero;

    var seenThisFrame = new Dictionary<Collider2D, float>();

    while (true)
    {
      var currentTime = Time.time;
      var deltaTime = currentTime - previousTime;

      var currentPos = transform.position;
      var velocity = (currentPos - previousPos).normalized;
      if (velocity.sqrMagnitude > 0.01f)
        _aim = velocity;

      seenThisFrame.Clear();

      var range = Physics2D.CircleCastAll(transform.position, Radius, _aim, Distance);

      // detection
      foreach (var hit in range)
      {
        var collider = hit.collider;
        var group = collider.GetComponent<GroupID>();
        if (!group)
          continue;

        var dir = (hit.transform.position - transform.position).normalized;
        var angle = Mathf.Abs(Vector3.Angle(_aim, dir));

        var l = 1 - angle / 60f; // the higher the l value the faster the object is going to be recognized
        if (angle < 60)
          seenThisFrame.Add(collider, l);
      }

      // recognition
      foreach (var item in seenThisFrame)
      {
        var collider = item.Key;

        if (!seenObjects.ContainsKey(collider)) // we just spotted something
        {
          seenObjects.Add(collider, 0);
        }
        else // here's something we've seen before 
        {
          seenObjects[collider] += deltaTime * item.Value;

          // we've seen something for long enough to recognize it
          if (seenObjects[collider] >= RecognitionTime)
            recognizedObjects.Add(collider, currentTime);
        }

        // update the last time I've seen a recognized object so i can forget it later 
        if (recognizedObjects.ContainsKey(item.Key))
          recognizedObjects[item.Key] = currentTime;
      }

      // forget the ones we didn't see this frame or already know off
      for (int i = seenObjects.Count - 1; i >= 0; i--)
      {
        var collider = seenObjects.ElementAt(i).Key;

        if (!seenThisFrame.ContainsKey(collider) || recognizedObjects.ContainsKey(collider))
          seenObjects.Remove(collider);
      }

      // slowly forget recognized objects we havent seen for a while
      for (int i = recognizedObjects.Count - 1; i >= 0; i--)
      {
        var item = recognizedObjects.ElementAt(i);
        var collider = item.Key;

        if (item.Value < Time.time - ForgetTime)
          recognizedObjects.Remove(collider);
      }

      previousTime = currentTime;
      previousPos = currentPos;

      yield return null;
    }



  }
1 Like

Not dropping personal projects when work hits is really not that easy. The last two days I made little progress; I built a better hierarchy for visible objects, actors and agents so my little guys can spot more than just “enemies”. I’ve also looked into behavior trees a bit an think they’re a wee bit overkill for my goals. So I’ll stick to state machines.

I’ve honestly been struggling to build the behaviors… not the technical aspects but the whole action/reaction map… what would guy x do if y happened. Right now I’m still clambering to the idea of an überAI, one state machine to rule them all, the set of behaviours to bind them … and one to find 'em.

1 Like

If you build one state machine, it may make it harder if you want the team members to have different abilities/attributes Why not make a generic state machine for behavior that, when instantiated, takes some values and randomly generates “differences” like slightly farther vision, focus on enemies, focus on loot, and etc? Then you just have each character report back to the state machine when the AI character has has created an event. Basically an event driven engine that allows the individual ai state machines to report when they have encountered something.

1 Like

Alrighty, made a few tiny changes. Instead of clambering to absolute realism, I opted to go for gameplay first and redid and simplified the squad formation code so it’s more intuitive. Before I lerped between different sub-formations based on player direction; now I just rotate the current formation around the player. This way there’s no weird path cutting and they move in a much more visually pleasing way. I also forewent the delusion of having the player as the “team leader” in a realistic formation setup and have now demoted them to point man. Meaning the player is, in most formations, the spearhead poking the enemy bear.
Somewhere down the line I’ll have to dynamically map the squaddies to the potitons instead of having the static assignment. So I can grow and shrink the team based on events (injuries/downs, hrt, joins etc).

Here’s a little demo video:

Technically the teams can now be as large as they want to be but I’m pretty certain I’ll go with player + 2 teamates as the default size.

I have yet to continue on the behaviors, first I’ll add line of sighting to the ai vision system, so enemies and player can be properly occluded by obstacles.

3 Likes

Been sick with god-knows-what the past few days so here’s another update:

Originally I set out to add occlusion testing to my old vision sensor aaaand now I rewrote the way the ai perceives and recognizes the world. I had a really nice and fancy system there but for my type of game it didn’t really work out too well.

The new sensor is quite a bit simpler but works by the same principle: It sees something, after a while of seeing something without breaking line of sight(change blindness) it recognizes it and fires an event.
What’s changed is that I’m not doing spherecasts anymore to see what’s in range, that’s done by using a collider, next I check if the thing is on screen using the built in orthographic frustum culling on the sprite renderers. Turns out it’s annoying to have eagle eyed companions that go off and snipe enemies way before the player can even get a hint of their existence. For the finale I’m shooting a ray from the sensor to the target and make sure there’s nothing in the way.

The results are 360° vision for anything unobstructed, on screen in a circular range, which I think fits this type of game much better and is on par with the way the player experiences the world. Whereas before a squaddie could stand right next to an enemy and not see it if it was facing the wrong way. A side-benefit is some much cleaner and more maintainable code.

[RequireComponent(typeof(Collider2D))]
public class VisionSensorV2 : MonoBehaviour
{
  private Dictionary<Collider2D, float> _seenObjects;
  private Dictionary<Collider2D, float> _recognizedObjects;

  public LayerMask LayerMask;
  public float RecognitionTime = 2.5f;
  public float ForgetTime = 120;

  private void Awake()
  {
    _seenObjects = new Dictionary<Collider2D, float>();
    _recognizedObjects = new Dictionary<Collider2D, float>();
  }

  private bool IsVisible(Collider2D collider)
  {
    var renderer = collider.GetComponent<Renderer>();

    if (!renderer)
      renderer = GetComponentInChildren<Renderer>();

    // make sure it's on screen
    if (!renderer || !renderer.isVisible)
      return false;

    // check if we can see it   
    var dir = collider.transform.position - transform.position;
    var hit = Physics2D.Raycast(transform.position, dir, dir.magnitude + 1, LayerMask); // maybe instead cast for all, discount anything attached to this same body and then check for obstructions

    //print(string.Format("target: {0} hit: {1}", collider.name, hit.collider ? hit.collider.name : "undefined"));

    return (hit.collider == collider);
  }

  private void OnTriggerEnter2D(Collider2D collider)
  {
    print(collider.name + " entered " + name + "'s range");

    // we already recognized this one, no need for another round
    if (_recognizedObjects.ContainsKey(collider))
      return;

    // never seen this thing, let's start recognizing it
    if (!_seenObjects.ContainsKey(collider))
      _seenObjects.Add(collider, 0);
  }

  private void FixedUpdate()
  {
    for (int i = 0; i < _seenObjects.Count; i++)
    {
      var item = _seenObjects.ElementAt(i);

      if (!IsVisible(item.Key))
        continue;

      _seenObjects[item.Key] += Time.deltaTime;

      // we've started at it long enough to recognize it
      if (item.Value >= RecognitionTime)
      {
        print(name + " recognized " + item.Key.name);

        _recognizedObjects.Add(item.Key, Time.time);

        // TODO: fire off an event each time we recognize something new so the agents can sub to it and set their FSMs to react
      }
    }

    // now that we know what it is we don't need to analyze it further
    foreach (var item in _recognizedObjects)
      if (_seenObjects.ContainsKey(item.Key))
        _seenObjects.Remove(item.Key);
  }

  private void OnTriggerExit2D(Collider2D collider)
  {
    // line of sight was broken during recognition of the object, failed to recognize what it was.. fun fact works for humans irl too
    if (_seenObjects.ContainsKey(collider))
      _seenObjects.Remove(collider);
  }
}

I’ve got some ideas to modulate recognition time based on alertness and distance but that’s a story for another day.

Here’s a little video

1 Like

here’s the current state, play a bit and let me know what you think
http://tourable.de/spaaaace/v3/

image
soon ™

4 Likes

I started reading up on behavior trees again. Never used hem before BUT to be quite honest I wasn’t getting anywhere with the state machine approach. Every time I tried to design behaviors with FSMs in mind they ended up like amorphous blobs, where data could flow any direction it pleased, and I just couldn’t visualize that in my head. Behavior trees on the other hand are just a hierarchical depth first evaluation of nodes, some nodes control the flow of data, some test conditions and some just do stuff. This I can work with. This is what I’m used to.
I’m still learning the basics but so far I can already tell it’s going to be much easier albeit more complex.

The behaviors are the final piece to the combat loop now that they can see eachother, objects and the world. Can’t say I’m unhappy to be moving on to another part soon. The prototyping was quick and fun but rebuilding all the things properly is taking quite some time.
Buuut if I wanna make this work, that’s the way it has to be.
Because the same code with other goals can then be used to have your buddies explore some ruins, looking for clues; do autonomous archeological digs; create daily routines while on base and the like.

Some concepts of base-life are going to be next on my list. Either daily routines and jobs or the conversation system. Tho if I wanna do the conversation system I also have to plan the quest system (singleplayer game)… huh. Just thought about is while writing this and I guess a quest system ain’t that hard after all if we’re talking about lokal space but once you gotta move scope with the info it gets difficult.
The important parts are chopping them up into stages and giving the option to en/disable entities when stages start/finish… and maybe add an optional querry while in running state of quest.stage if it’s supposed to finish itself off… hehe… Otherwise an on top of that I’ll need a way to, from the ui and other entities, invoke methods and set stages on specific quest entities either lokal to the current world and it’s current state/stage or peristent and global.
Alright, conversation system is next after I’ve built the behavior tree system and some basic trees for companions and enemy grunts.

If you’ve got some favorite conversation systems in games let me know and I’ll check em out for inspiration. Right now my inspiration is basically what was already present in Gothic 1 :laughing:

2 Likes

Made some happy little bees :slight_smile:

I enjoy games where you can just sit back and enjoy the environment, watch the bees collect their pollen, chickens pecking for seeds and scratching for worms, birds flying, flocking and murmurating around… and then a swarm of techno-bugs runs across the field, razing everything :laughing:

Edit: Now these little buggers actually deposit the pollen they collect in the Hive. Which then converts to Honey and Wax resources. Once I write the basic plants and fruiting plants I’ll also have them actually pollinate the flowers. SO if you kill the bees or overfarm a hive and starve them your plants won’t get pollinated, you won’t have seeds and/or fruit.
Farming is going to be an important part to long term success, especially on the ship.

2 Likes