About 'A Breadcrumb Trail'!

In this video (objectives)…

  1. Thanks to Eddie for spotting an improvement
  2. Using queue.Contains(neighbour) to prevent duplicates
  3. Improving our color architecture
  4. Justifying the use of public vs [SerializeField]
  5. Leave a breadcrumb.

After watching (learning outcomes)…

Explain why you would sometimes use public

(Unique Video Reference: 16_RR_CU2)

We would love to know…

  • What you found good about this lecture?
  • What we could do better?

Remember that you can reply to this topic, or create a new topic. The easiest way to create a new topic is to follow the link in Resources. That way the topic will…

  • Be in the correct forum (for the course).
  • Be in the right sub-forum (for the section)
  • Have the correct lecture tag.

Enjoy your stay in our thriving community!

I’m thinking your reasoning on public vs. getters/setters in a data class is fine, except that there are often other reasons for using Properties instead of public fields, and planning ahead should be a big factor in making that decision…

I’ll give a great example right in this chapter… Waypoint… You want to keep the SetColor decoupled from Pathfinder… so the solution provided was to put an expensive SetColor call in Update… Imagine this instead:

 bool isExplored=false;
 bool IsExplored
 {
      get{return isExplored;}
      set
      {
           isExplored=value;
           if(value)
           { 
                 SetColor(color.blue);
           }
      }

Now, because you used a property with getters/setters instead of a public field, you’ve elminated the need for an Update() altogether. Color is set if and only if isExplored is set to true ONCE.

That’s just one example. Now if you know that there’s really nothing you’ll be triggering or doing based on altering a public field, let it go public, but always take the time to think about the possiblities. If you might want to do something with it later, a few extra lines of code now can save you a lot of refactoring later.

1 Like

Thanks for yet another option Brian. I’ll consider it in the full context and see if I can work it in.

I was about to post the same exact thing as Brian after finishing the optional challenge. It makes more sense to me using a getter/setter in this case so that you aren’t setting the color every frame in Update().

One other thing I noticed. When you prevent queuing an already queued or explored neighbor with this logic

    if (neighbor.IsExplored || queue.Contains(neighbor))
        //do nothing

There is some overhead associated with searching the queue every time like this, especially if the path is quite large. Instead, I think a better way would be to make use of the ExploredFrom variable on our Waypoint script like this

    if (neighbor.IsExplored || neighbor.ExploredFrom != null)
        return;

Since we mark where the waypoint was explored from as soon as we queue it, this will also protect against queuing a waypoint that’s already in the queue.

That’s a good cost saver at this stage in the search algorythm. I found I had to go another way, since my game design included multiple sources of enemies, and I wanted flexibility in the start/stop locations… I completely decoupled IsExplored and ExploredFrom from the Waypoints. I actually found, performance wise, that there wasn’t that much of a hit, but I’m still working on a relatively small map (about 20x20)… so that reasoning may have to change on say a 500x500 map.

Yea, there’s definitely some changes you would need to make to allow for multiple spawn locations. However, I think if you simply move the StartWaypoint serialized field to the EnemyMovement script, the scripts would work as is for multiple spawn locations. You would just have to make sure that the ExploredFrom and IsExplored fields on the Waypoints are first cleared when Pathfinder.GetPath is called, and you would pass StartWaypoint with Pathfinder.GetPath(StartWaypoint).

I assume the end waypoint wouldn’t change, but if you need multiple end waypoints as well, you could add that to the enemy movement script too.

Since startpoint for each enemy is actually its Grid location, which my enemies can calculate for themselves, I simply let the enemies specify the start point in the script… I also set it up for variable end points, as it seems to me that I can have enemies who have a specialty of attacking a tower…

By decoupling isExplored and Exploredfrom from the Waypoints, I think I actually save a little time… no clearing all the waypoints before I do a search. Cuts down on public variables (which I believe I’ve expressed my disdain for in other topics).

The const variable for gridSize inside the Waypoint class can be made public because you cannot change a const during runtime because it’s immutable :wink:

I like the idea about the optional challenges :blush: ,I think this will bring more ideas and perspectives to us for what we can do in the development of the game.

Because all points are stored and you know the start and end point, you can start the path finding with the end point. This way when you iterate over the points again to store the path, it’s already in the correct order.

This won’t prevent duplicate entries, as it is in an or statement, and if the first part of the statement iterates to true, the second part is ignore, the way I found to stop duplicates is to have a simple bool, that is set when the point is added to the queue, you know you have the point in the queue, and therefore it will be checked, just at a later date. You could just use the statement on its own but the bool is quicker.

1 Like

Comment about the video at ~3m40s: gridPos in Waypoint.cs is never actually used…

Just my 2 cents: I don’t like the way member function parameters are factored out and replaced with class member variables (e.g. searchCenter) in this video - this is not far off reverting to global variables. In my experience it’s better to write functions, even class members, as stateless functions where practical, as it makes it much easier to reason about the program’s behaviour when you only have to look at the inputs and outputs of each function. It also makes unit testing far easier by making the test cases simpler to write, without elaborate state construction.

Unfortunately languages like C# and Java discourage this style by the lack of support for first-class free functions.

In this codebase, it would be better, I think, to pass from into QueueNewNeighbours and then assign that to neighbour.exploredFrom, and avoid making searchCenter essentially global.

Privacy & Terms