Introduction
In part 1 of this tutorial, we built a basic AI controller for the game. That AI could already move the truck to random locations, and even seek and attack the opponent.
In this second part, we’re going to supercharge our AI! We’ll add two major new skills: collecting power-ups and avoiding tree obstacles. With these upgrades, our AI will become a much tougher competitor in battle.
Here is a demo of what we’ll be building:
detour2.gif
To get our truck to collect powerups, it first needs to know where they are. Then, it has to drive over to them. A key part of the logic will be deciding when to go for a powerup versus when to keep attacking the opponent.
We’ll be building on the AI controller we made in Part 1 (Version 3). Go ahead and open that project, and then save a copy of it. This way, you’ll always have the original version to go back to if you need it.
To collect powerups, our AI first needs to know that they’ve appeared on the map. It only makes sense to change the truck’s behavior after a powerup is available. The best spot for this check is right at the start of our main forever loop.
Let’s create a new custom block and name it check powerups. Important: Make sure to check the box for “Run without screen refresh.” This gives our code a nice little speed boost.
cbb87297-8004-41a3-a631-2aa8acb22c26-image.png
And we will place this new block at the beginning of the main loop:
45833864-1dcc-432a-a1e6-ece31fffedb9-image.png
Remember, there are two types of powerups in the game:
Sugar: Increases our truck’s life count (up to a max of 3). Gas: Boosts our truck’s speed (up to a max of 120).It makes sense to prioritize survival. If our truck has fewer than 3 lives, getting a Sugar powerup should be the top priority. Let’s start by adding a check for that.
6719afe5-2e75-42b8-90b3-9256da7445a2-image.png
Note that the ☁ my lives variable keeps track of the life count for our truck, which is identified by the my team ID. This sensing block is very handy when you need to access any private property of a clone so long as you know its ID.
If our truck needs health (meaning it has less than 3 lives), it’s time to scan the area for any Sugar powerups. We can do this using the special find clones of sensing block.
b4b1e8a3-906c-416f-a351-1fada5611c7f-image.png
This block is incredibly useful. It searches for all visible clones of a specific sprite within a certain distance. Since we want to find Sugar anywhere on the map, let’s use a large distance like 1000. Any Sugar clones it finds will be listed in the table1 variable.
If the find clones of block successfully located one or more Sugar sprites, it will add them as rows to table1. So, to see if we found anything, we just need to check if the number of rows in table1 is greater than 0.
c4cdd455-ad89-4022-a13b-8432f10c8804-image.png
Okay, we’ve found a Sugar powerup! The next step is to drive to it. The table1 is conveniently sorted by distance, meaning the very first row always points to the closest clone. For example, this screenshot shows that one Sugar clone was found. It has a clone ID of 1, is located at X: 160 and Y: -65, and is 96.43 units away from our truck.
9703166c-6275-4db8-b8bf-1bdf4fa5acb0-image.png
Now, we could create a whole new mode like “collect sugar,” but there’s a clever, simpler way. We can just reuse our existing wander mode! All we have to do is update the target x and target y variables with the location of the Sugar item. The existing code in the wander block will then automatically navigate our truck right to it.
4e730dc1-66f1-4de3-9955-88612152a8fa-image.png
Once we’ve spotted a Sugar clone and decided to go for it, our job inside the check powerups block is done for this cycle. There’s no need to continue and check for Gas powerups. We can use a return block to exit the custom block immediately.
1e04af61-9176-4c8e-ad32-c4316474f250-image.png
Pro Tip: It’s a common misunderstanding that the return block is only for reporter blocks (the ones that return a value). You can also use it in regular stack blocks like this one to simply stop the block from running any further. Its return value is just ignored. This is equivalent of using the stop [this script] block.
If our truck is at full health, or if there were no Sugar powerups to find, the code continues. Now we can check for Gas powerups. But, there’s no point in grabbing a Gas powerup if our speed is already maxed out at 120. So, let’s first check our ☁ my speed variable.
a794da49-9c47-4c70-9dd0-9cd7a8a22943-image.png
The logic for finding and collecting Gas clones is nearly identical to what we just did for Sugar. See if you can build the rest of the code yourself! It’s great practice.
Here is the complete solution for the check powerups block:
d2e9237d-676c-4501-b8f7-e548cec0e7d2-image.png
Time to test our new powerup-collecting AI! To focus just on this new skill, let’s keep the tree count at 0 for now (you can change this in the Setup sprite).
It’s also really helpful to make the table1 variable visible on the stage. This way, you can see exactly when the AI finds a powerup.
One last tip for faster testing: in the Sugar sprite, you can shorten the time it waits before reappearing. For example, try changing the random wait to between 3 and 9 seconds.
a5d7f091-2195-474d-ad4d-c21cdff56bd8-image.png
Now you can test your AI by opening the project in two browser tabs and letting them battle. It should look something like this:
collect.gif
Now for a real challenge: obstacle avoidance! Right now, our truck will just drive straight into trees and get stuck, completely stopping it from chasing the opponent or grabbing powerups. We’re going to fix this by teaching our AI how to detect when a tree is in the way and then plot a “detour” to get around it.
First thing’s first. Let’s make a backup of our working powerup-collecting version. Export your Controller sprite as a file (it will be named something like sprite3). This way, if anything goes wrong, you can easily import this version and start again.
a8868a00-4c11-4e0d-9e16-769f250b99c2-image.png
To avoid trees, our AI first needs a way to “see” if its path is blocked. We’ll create a new custom block that can answer a simple question: “Is this direction blocked?”. Let’s name it direction is blocked, and it will take the command as an input. Make sure to select “Run without screen refresh” and define it as a reporter block. A reporter block returns a value; ours will return 1 if the path is blocked and 0 if it’s clear.
44a2c9ee-ca6a-4c17-9804-997c0ab06b35-image.png
Before we jump into coding, let’s talk strategy. How can our Controller sprite, which only sends commands, know if the Truck sprite is about to hit a tree?
In standard Scratch, you might try a “ghosting” technique: quickly move the sprite forward, check for a collision, and move it back before the screen refreshes. But that won’t work here, because our Controller sprite and Truck sprite are separate. The Controller can’t directly move the Truck to test for collisions.
So, we need a different, more clever approach:
We’ll temporarily move our invisible Controller sprite to the exact location of our Truck. We’ll point the Controller sprite in the direction we want to move. From that position, we’ll use a new sensing block to scan a rectangular “detection area” in front of the sprite to see if any trees are inside it.f4986eed-18f1-4579-bb80-c3b2c3ffc0f1-image.png
Let’s implement this idea in the next few steps.
First, let’s get the current X and Y position of our truck using its my team clone ID. Then, we’ll use a go to x y block to instantly move our Controller sprite to that same spot.
175785d7-a156-4a1c-8912-5bf7c015687b-image.png
Remember, the Controller sprite is invisible, so you won’t see anything change on the stage. The whole point of this move is to establish a starting point for our tree-detection scan.
If the command we’re checking is 1 (move up), we need to check for trees above the truck. To do this, we’ll point our Controller sprite upwards (direction 0). This step is crucial because the detection area we’re about to create is always relative to the direction the sprite is facing.
7685dd78-7648-4247-91ed-471070c53c5d-image.png
Again, we’re just using the invisible sprite’s direction to set up our detection area.
Similarly, we’ll handle the other three possible commands (2 for down, 3 for left, and 4 for right), pointing the Controller sprite in the corresponding direction each time.
fbccfb61-6302-48cd-bf48-84755e14ddb7-image.png
With our Controller sprite now at the truck’s location and pointing in the direction of intended movement, we’re ready to scan for trees. We’ll use the find clones of sprite in rectangle block. Here’s how this powerful block works for our setup:
c23887b5-3c95-4658-b4dc-95616a62c3ce-image.png
Explanations:
e53ff571-b6ed-40ee-bf1a-ac615cddc752-image.png
We will store information about all clones found in the table1 variable.
Feel free to fine-tune the distance and width values. Think of distance as how far ahead the AI can “see,” and width as its “peripheral vision,” or how wide you want to scan.
If the center of any Tree clone is inside this rectangle (i.e. its center point falls into this rectangle), it will be listed in the table, which includes columns for clone ID, x, y, and distance.
884533a1-10be-42c5-b409-1fab2058027b-image.png
If more than one clone is found, the table will be sorted by distance, with the closest one listed first.
We don’t need the specific details of the trees, just whether any tree was found. So, we can simply check if table1 has any rows. If the row count is greater than 0, a tree is blocking the path, and our block should return 1. Otherwise, the path is clear, and it should return 0.
85f2ebe6-6b1d-4a3c-8d31-42ce5156a618-image.png
Now we can put our new reporter block to use! Back in the main loop, right before we set the new command, we’ll call direction is blocked to check the path.
Also, to make our AI more responsive and quicker to react to obstacles, let’s reduce the wait time in the main loop to 0.1 seconds. This allows it to check for blockages more frequently.
dd6c0245-3d1d-4259-bab7-207b0f9a1321-image.png
If our check finds that the intended direction is blocked, we need to calculate a detour. We’ll create another new custom block, calculate detour, to handle this logic. This block will figure out a new, unblocked detour command which will then temporarily override the new command.
3fb16588-9fe2-4fb8-b205-ac2716cf1e9c-image.png
So, how do we calculate a smart detour? There are many complex pathfinding algorithms, but we’re looking for a solution that is effective, fast, and simple to build.
Imagine the truck needs to get the Gas on the top right, but a tree is blocking the path to the right.
a412f367-612a-4b1e-b283-0d401eaac2ed-image.png
We have a few command options:
Our strategy will be this: when we’re blocked, we will check these alternatives in order. Once we find a clear path, we’ll execute a two-step detour (e.g., move sideways for 1 second, then move toward the target for 1 second) before returning to our normal logic. This simple strategy will handle most situations very effectively. Let’s build it!
Inside our new calculate detour block, the first thing we’ll do is set up a couple of variables to manage the detour process:
in detour: A “flag” variable. We’ll set this to 1 to signal that the AI is currently executing a detour. detour start time: We’ll record the current timer value here. This will help us time each step of the detour.f6ecfbf9-17a3-44ef-b20c-dd5562b93e8e-image.png
Next, we need to determine the best alternative command (alt command). This command should still move the truck generally closer to its ultimate target.
If our original new command was vertical (up/down), the alt command should be horizontal (left/right). We can pick the correct horizontal direction by looking at distance x. If distance x is positive, the target is to the right, so our alt command should be 4 (move right).
3a1a1f08-0484-49ec-a60d-4118f1640434-image.png
Similarly, if the new command was horizontal (left/right), the alt command should be vertical (up/down). We can use distance y to decide whether to move up or down.
c407b2cf-b9f4-472f-8e39-683bd1184d14-image.png
The alt command 2 is always the opposite of the alt command. We can use a neat little math trick for this: since (left=3) + (right=4) = 7, we can calculate the opposite by subtracting from 7. This logic works whether the new command was vertical or horizontal.
6d2c1877-59ee-4beb-adb6-64e82dbb4d8c-image.png
The back command is always the opposite of the new command. We can use a similar calculation based on whether the command is vertical or horizontal.
f3e0c2e3-979f-4070-90bc-3d5acd43ecb2-image.png
Now we’ll decide which detour path to take. Our first choice is always the alt command. We use our direction is blocked reporter to check if that path is clear. If it is, we’ve found our detour! We’ll set up a two-step plan:
detour command: Set to the alt command. detour command 2: Set to the original new command.This means the truck will first move sideways, then try to resume its original path. Since we’ve found a plan, we can use a return block to exit the calculate detour script.
d8dd9db1-4783-496e-9570-1dfe91045fa0-image.png
But what if the alt command path is also blocked? This means obstacles are in front of us AND on one side. Our next best option is to try alt command 2 (the other side). If that path is clear, we’ll set our detour plan to use alt command 2 first, followed by the original new command.
11d44863-29a4-4af4-9391-9e0010c78439-image.png
In the worst-case scenario, the paths forward, left, and right are all blocked. The truck is trapped! The only way out is to back up. We’ll set the detour plan to use the back command first, and then follow it up with the alt command to try and steer around the trap.
bbc8c789-da7f-4c2e-94e8-b7e3486d8d39-image.png
Now we need to modify our main loop to actually execute the detour. When the in detour flag is set to 1, we need to bypass all our normal logic (checking powerups, finding the opponent, etc.). Instead, we will run a separate branch of code dedicated to the detour.
The animation below shows how to restructure your main loop with an if/else block based on the in detour variable. Notice that the block that sends the command to the truck is now outside and after the if/else, so it runs in every situation.
restructure.gif
Inside the new “detour” branch of our if/else block, we’ll manage the two-step detour sequence. The whole detour will last for 2 seconds. By default, the new command will be our detour command. If the timer shows that 2 seconds have passed since the detour start time, the detour is over. We’ll set the in detour flag back to 0, so that on the next loop, the AI will go back to its normal behavior.
c01a326f-df6f-49de-b426-52a67b4b9c54-image.png
The detour itself has two parts. For the first second, the truck will follow detour command. After one second has passed, we’ll switch the new command to detour command 2 for the second half of the detour. This creates the “go sideways, then go forward” maneuver.
f3fed8c3-0722-4602-82f5-f816c64a6093-image.png
And that’s it! Our detour logic is complete. In short, when the AI “sees” a tree, it will now intelligently try to move sideways or even back up for a moment to get around it.
It’s time for the final test! Go to the Setup sprite and set the tree count to 6 (or even more to make it really challenging). Run the project and watch how your AI truck now cleverly navigates around the trees instead of getting stuck. You can also speed up the powerup spawn times again for a more action-packed test.
Here is a demo:
detour2.gif
This AI controller is already much smarter, but there’s always room for improvement! If you want to take your AI to the next level and win battles, consider these enhancements:
Smarter Attack and Dodge: Have you noticed your truck sometimes has a perfect shot lined up but decides to wander off instead? Improve the attack mode to recognize when it’s aligned with the opponent, take the shot, and then immediately move sideways to dodge any return fire.
Strategic Powerup Denial: Right now, your AI only grabs powerups it needs. But what if your opponent is low on health and a Sugar powerup appears? A truly smart AI would grab that powerup just to prevent the opponent from getting it! Modify your check powerups logic to consider the opponent’s status, too.
Don’t Waste Ammo: Firing donuts into a tree is a waste! Before shooting, you can use a similar detection method to the one we built for avoiding trees. Check if the line of sight to the opponent is clear. If it’s blocked by a tree, hold your fire and reposition.
Press the Advantage: When your truck is stronger (more lives, more speed), it should be more aggressive! Instead of firing one shot and then wandering away, why not make it stay in attack mode and keep firing as long as it has the upper hand?
These are just a few ideas to get you started. The best way to improve your AI is to watch it battle. What mistakes does it make? When does it get outsmarted? Every battle is a chance to learn and come up with your own unique strategies to build the ultimate champion