Hi, welcome to this new video on our ecosystem simulation series. This is Week 3. We're going to continue working on the behavior of our organisms. In this case, we're working with the herbivore, and we're going to be writing the wander behavior. You can probably find a wander behavior online. There's lots of great references of how to use a steering behavior to define a wander motion, which is a form of randomness, but it has some oscillation, and it creates the pattern of movement that might seem natural or a sense of wandering around. But we're actually going to use the opportunity to write a specific kind of wander that discretizes the different states that we can have to create something a bit more expressive, something that might have a sense of movement, hesitation, rotating, moving again. We're going to look at how we can actually start combining the notion of a state machine just to move between the wander state. The wander state is part of the decision tree, we reach to the leaf node, which is an action wander. There's no reason why that action needs to be one function. We can actually break it down into its own state machine. A few states. The states that we're going to be considering is idle, so stay still. Then move forward for a few seconds or a few frames, and then maybe stand still again, rotate, move again. This loop between movement, stopping, rotating. That would also similarly create a wander behavior. I think that this is where the design of your behaviors and the expressivity that you want to achieve is very much part of your own design intuition and what you want to communicate. If you're going for realism, this would be a very different function. But we're going to use the opportunity to mix the decision tree we been building and embed within a very simple state machine to dictate the wander motion. Let's jump into processing and see how we can do this. Let me bring you to your attention. I'm working continuing on the project. In the same project, we are on the herbivore script. Mostly, we're going to be doing most of the work here. We're at a point where the decision tree takes us to the current function and by default because the herbivore is not hungry. Currently, it's not hungry. It will default into wandering. The wander will go for a while until it reaches a hunger level, and it transitions to sick food. For now, just because we want to evaluate the wander behavior without transitioning out of hunger, we can comment out the transition. This is the line that resets and executes the decision tree. We're going to bring this back later. When we start moving to the sick food function, which we're going to write in the next few videos. But for now, we want to write the wander state. Let's go up here and let's declare a few variables that we're going to need to work on the wander state. Wander states. Again, I'm going to be using some of a topple as a state machine. We're going to say idle. It's going to be one of the possible states. Another one will be moving, and another one will be rotating. Then I'm also going to define the current action within that state machine, self dot. I'm going to call it current action. But maybe if you want to be more explicit, you could say current wander action. Let's just do that. Let's say the current wander state. Zero means that the current wander under state is going to be idle. Just stand still for a few seconds. Then we're going to say self.action duration. Let's start with a duration of 200 and self.action count. Similar to what we've been doing before, we're going to be counting. We're going to start counting. When we reach to this kind of threshold of 200, we're going to transition to the next state. We could do random. But I think that in this case, because we have such a simple loop, it's like we're going to go from idle to moving to rotating to idle to moving to rotating and so on. We're going to execute this kind of transition between states, and each one of those are going to be very simple motions. This is going to be just standing still. This is going to be moving forward, and then it's just a rotation, basically changing the orientation of our velocity into a new random vector. We've broken down the behavior to such a degree that those functions become very, very easy to write. We will start by writing something, let's go down here, we have wonder. Here, we're going to define what we're going to call the wander actions. This is the function that we're going to call here in the wander. But the wander actions is going to help us change between the different states. So we're going to say if self.wander_states. Wander states are all the possible states, and we're going to evaluate the current one, so self, which is an index, current_wander_state equals idle. If the current idle state we're in is idle, we do something if we can copy the same structure again here, and once more and then else, which shouldn't be an else because we're going to look between them. The first condition is the current state idle. The second one is moving, and the third one is rotating. That's what we spelled out, just make sure that rotating. If we didn't want to have that problem, basically, what we could do much simpler than this is say if current wander state is zero, one and two. But we're just trying to connect the index with the actual behavior so that we can actually have a more readable expression of the code here. Here, we could say we're going to need something like it's going to be self.idle. Here, it's going to be self.move, and self.new_rotation. Those are going to be functions we're going to write. The other thing that will need to happen, and I think that I'm going to try to extrapolate this to all of them, all of them have a very similar condition. So we're going to create a function here which is going to be trigger_action_change, so a function that would generalize action change, if self.action_count. Because what we're trying to say is idle take maybe 200 frames in Idle, so count until 200 and then reset and do the next action. We're going to say increase that by one every frame, and if that action count, it's bigger than this self.action_duration. What do we do? Well, the few things, we're going to say, go to next state, and the action count should be reset. We start the count over. We could also change the duration. Before the duration could be by default, we could say 200. But we could say that the duration, we could make it a new to give it a little bit more variations. Sometimes it could be 200, but sometimes it could be lower at 150 or 250. The amount of time it will take you to do an action might be slightly different, so you can play with these numbers. Between something like 150,250. But what we are missing here is the go to next action. If you remember, we have a tuple that is idle moving and rotating. So we basically need to create a function that allows us to move to the next current action. Let's also do another function for that; next_wander_state(self). Here what we're saying is, let's evaluate the current action state. Basically, this function is as simple as saying your current action state which is 0 is a plus 1. Plus 1 would be this one. But if you are in the tuple, go back to the first one. Let's do that. So self.current.wander_state += 1. If that is bigger than 2, because that's how big our list is, it's actually 0. This is the loop that we're creating. This is a very silly function. In a way it's just looping through the entities of the list. So let's just put this together. We're going to say self.next_wander_state(). We're saying, spend 200 frames doing one action. If you reach those 200 frames, go to the next state and reset the counter. Do that action for now maybe 150 frames; somewhere in-between 150 and 250 and then go to the next one and then start over. So it's like a loop but it's a loop that is delayed by a certain number of frames. Two-hundred frames, go to next. We're introducing a little bit of variability to those 200 frames. That's great. That is the trigger action. We want to do it in basically all of them. Self.trigger_action_change(). Spend 200 frames here, spend 200 frames here, and then spend 200 frames here. If you think about it, the idle is not doing anything. We don't really need this.idle. We just need to wait those 200 frames. Our idle is going to be not do anything. Let's just call it stay still. The movement, it's a single line. Let's just say, def mov(self). Sorry, self.pos.add(self.vel). That's going to be our move function. Just add your velocity to your current position. Let's just actually spell it right. Self.move() exists. If we are in this moving state, you move forward. That might be too fast. We could reduce the speed. Currently, the speed is set by a random value, but should work. Then the rotate which is the only one that we're really missing which is a new rotation. I think I called it new rotation. Let's just write this function. The new rotation again, it's going to be the simplest way we can define a new rotation. We're going to define a new angle which is going to be random from 0-360. Here, we're going to say self.velocity. How do we create a new vector or how do we pick a vector and rotate the vector? We don't have a rotate vector function. So it would be nice to at this point just pick the velocity as it is and rotate it. We can actually create a handy function of rotate_vector that takes a vector and an angle. Let's call it vec. The angle. If we're giving the angle in degrees before, we could say this is going to be a function that needs to operate as radiance. We're going to say radiance of the angle. If I give you like 360, it's going to convert it into radiance. The x, this is a function that is just a bit of trigonometry. But let's just go through it and understand. We use the cosine of the angle. I think I call it vec, vec.x minus vec.y times the sine of the angle. Y would be vec.x times sine of the angle. Because this is not like trigonometry kind of a course, and these are vector functions that might be really useful and you can come across. It's good to just, first, use them and internalize them, and if you really want to go into the trigonometry inside them, I invite you to revise how it's calculated. But most often you're going to be remembering, I had a rotate vector function somewhere and come back to it and use it. X and y now. This rotate vector takes the vector, and it gives us back a new vector. There's going to be the rotated version of that vector. We can call that function here, self.rotate vector, giving the self.velocity and the new angle that we just calculated. We calculated an angle with a random. We're saying maybe 180 or 175, whatever. We are providing the vector velocity and the new angle. We're updating the velocity by its rotation. We're going to be rotating it with this function. That's it. We basically have the new rotation, basically, rotates the velocity vector by a random amount. One thing that is interesting here is that the rotation action, if you think about it is 200 frames we want to be idle, 200 frames we want to be moving. But when we rotate, we don't want to spend 200 frames rotating. What we could do here is instead of triggering the action, that is, the delayed version of next wander state. We take 200 frames or a certain number of frames to activate the next state in the function. We're going to just call it right away. Meaning skip the timing. This is going to be a one-off execution. Rotate the vector and instantly move to the next one. This is very quick. It happens in a fraction in one frame. This one will take 200 frames, this one will take 200 frames. This is quick. That gives us that flexibility of saying, some actions are one-off action instantly triggers in the next state. I know that's been a lot. Let's see how this is running. We are not executing these wander actions yet, because we have to place them here, so self.wander actions. Here's where we would run into. There we go. It's moving. It stops, looks in a different direction. Moves, stops, and looks in a different direction. Moves, stops. Because we're not triggering the decision tree, currently, it's always wandering. This is going to be the wander state. You can reintroduce a lot of herbivores and all of them should behave in this way. Again, we're going to create some visualization to really understand their brain a little bit further. But we have, basically, a little state machine that dictates this wander behavior, and we're really mixing decision trees and state machines to actually start constructing the behavior of this entity. We're going to continue going in the seek hunger function. I'll see you in the next video for that.