Assignment Goals

The primary goals of this assignment are:

  1. Use a template code project.
  2. Extend an Unreal Engine Pawn with C++ code to add behavior.
  3. Use delegate functions to set up collision response.

For this assignment, you'll be creating C++ AI Controller, which is Unreal's base class for controlling any Pawn. Each instance of your controller will drive a pawn following a simple behavioral model. You'll use an expanded collision volume to detect other nearby AI pawns, and based on their positions and/or velocities attempt to follow your own behavioral motion rules.

Details

Once again, this assignment leaves a little more of function discovery and engine navigation to you as compared to the previous assignments.

Create a project

  1. This time, create a C++ project using the Top Down template called assn3, at the top level of your git repository.
  2. This project already has a C++ character pawn and player controller, with a Blueprint data-only pawn and controller derived from them solely for parameter tweaking. Look at the controller code in assn3PlayerController.h and assn3PlayerController.cpp for some examples of how controllers typically work.

Pawn

  1. Create a data-only Blueprint Pawn, derived from the DefaultPawn class (not the Pawn base class).
    • You can search for that class in the "All Classes" section of the Blueprint Parent Class selection dialog.
    • If you choose the correct one, it should show up as a small sphere when you drag it into the scene
    • To enable it as a "data-only" blueprint actor, open the blueprint editor, and immediately close without changing anything, then re-open to just the data properties
  2. Enable the "Simulate Physics" option in your data-only pawn under Physics/CollisionComponent.
    • You can also search in the search box at the top of the blueprint properties
    • At this point, if you place one or more of these pawns in the scene a little ways off the floor and hit play, they should drop to the floor. Also, the player should be able to kick them around.

Controller

  1. Create a new C++ class derived from AIController, and a new data-only Blueprint class derived from your new C++ class
  2. Set your data-only blueprint controller as the "AI Controller Class" in your data-only Blueprint pawn
    • When you run with an instance of your pawn in the scene, it should create a temporary instance of your controller, which you can find by name in the "Outliner" window.

Neighbors

  1. Neighborhood tracking
    • Add a neighborhood radius member variable set to EditAnywhere (so you can adjust it in your data-only controller).
    • Add an AttachToPawn function to your controller. In this function:
      1. Call Super::AttachToPawn
      2. Use NewObject to create a new USphereComponent. Use the pawn as the parent ("outer" in UE5 terminology).
        • You use CreateDefaultSubobject to make new objects in the constructor, but NewObject in other member functions
      3. Use the USphereComponent InitSphereRadius to set the sphere size from your neighborhood radius parameter.
      4. Use AttachToComponent to attach the USphereComponent to the Pawn's RootComponent.
      5. Call the USphereComponent RegisterComponent function to initialize it.
    • If you want to be able to change the radius while running (totally optional), you can compare the current neighborhood radius to USphereComponent::GetUnscaledRadius to find out if it has changed, and USphereComponent::SetSphereRadius to update it. Since this will re-check all of the neighbor collisions, be sure to only do this when the radius actually changes
  2. Neighbor set
    • Create a TSet<AActor*> in your controller to hold the list of neighboring actors that overlap your USphereComponent.
    • This is transient data, and should not be serialized with your pawn.
      • You can just not mark it as a UPROPERTY at all.
      • If you want to make it visible for debugging, mark it UPROPERTY(VisibleAnywhere,Transient).
  3. Neighbor tracking
    • Create OnComponentBeginOverlap and OnComponentEndOverlap delegate functions in your controller to add nearby objects to the set as they come in range, and remove them when they leave.
    • You can find the signature for these in delegate declarations in PrimitiveComponent.h.
    • Register them with the USphereComponent using AddDynamic() in your AI Controller's AttachToPawn function.
    • You can find a few examples creating and registering ComponentBeginOverlap delegate functions in the engine code.
    • Note that each pawn will also register as overlapping itself (since the neighbor-detecting sphere and physical sphere overlap). Check if the "Other" Actor in the overlap function is equal to GetPawn() to avoid adding yourself to your own neighbors list.
    • Depending on what you choose for your behavior model, you may also want to exclude the player from the neighbor list. If you do, look into the "Get Player Pawn" BluePrint node.

Neighbor forces

  1. In your AI Controller Tick() function, loop through the actors in the TSet to drive the Pawn direction.
    • You'll ultimately compute a force vector to apply to the pawn as weighted sum of one or several terms. I'm leaving the specifics of the behavior you create up to you. Some common choices include moving toward the average position of your neighbors; moving away from nearby neighbors, often with a force that scales as with a function of the distance from each neighbor; trying to match the average velocity of the neighbors; trying to steer away from neighbors in the path of your current velocity; trying to move toward the player character; trying to run away from the player character; etc. You can look online at various boid or crowd behavior models for other ideas.
    • You can use the AActor GetActorLocation() and GetVelocity() functions to find out position, direction and speed of motion for your pawn and the neighboring pawns.
  2. Build one or several force vectors you want to apply to the controlled pawn, and use GetPawn()->AddMovementInput() to apply the forces
    • For the second (scale) argument to this function, use DeltaTime to scale the force with frame rate. This produces more consistent behavior as frame rate changes.
  3. Whatever behavior model(s) you choose, each ball should at the least consider the location or velocity of all of its nearby neighbors in some way.
  4. You will likely have several terms you need to balance and tune to get the behaviour you want. Too small, and you won't get any motion at all. Too large, and the balls will fly off of the map. Make UPROPERTY control coefficients for each.
    • I'd start with all of the coefficients at 0, then adjust them one-by-one to debug their behavior and find suitable values. If you type the controller object name in the search bar for the Outliner window, it'll filter to just those. You can select all of them and change them while the game is running to test the behavior live.
  5. Once you find a good set of behavior parameters, set them in your AI controller's data-only blueprint

Grad Students

In addition to the ball pawn, set up a Character (instead of just Pawn) with the SKM_Quinn model and ABP_Quinn_C blueprint animation class. This will give bunch of walking characters instead of rolling balls as the AI-controlled objects. The same AI controller class should work with either Quinn or the ball. Be sure to include some of both in your video.

Submission

  1. For full credit, you must have multiple incremental commits during your development.
  2. Record a video of the player interacting with at least a dozen AI balls. Put the video somewhere like google drive, box, or youtube (not checked directly into your repository).
  3. Add an assn3.txt. Tell us what works and what doesn't, and anything else you think we should know for grading. Include a link to video demonstrating your project.
  4. Push to your repository, and tag your final commit with an assn3 tag (git tag assn3 followed by git push origin assn3).