The primary goals of this assignment are:
- Do some user-level C++ coding typical of what would be done for a game.
- Get some experience tracking down information in a large codebase.
For this assignment, you'll be creating C++ Actor to build a maze using Kruskal's algorithm. In your constructor, you will create a USceneComponent as the root component for the actor, and a UInstancedStaticMeshComponent, for the walls. In the OnConstruction() function, you will run the maze generation algorithm and add the resulting walls to the UInstancedStaticMeshComponent to create the maze.
I've given some fairly explicit steps to help you along, but this time I'm expecting you to do a little more this time in terms of following and generalizing examples of certain functionality in the engine code or online.
Create a project
- Create a Basic C++< project with no starter content called assn2
- Put it in top level of your git repository (alongside the Engine directory and your assn1 project directory).
- Create a level/map called assn2 and set it as your startup map.
- Create a new C++ actor
- You can do this from the Tools menu, or in the Content Drawer using the Add button, or right-click in the content pane when the C++ Classes directory is selected.
- UE5 will create a header and C++ file with starter code. Make sure the class it creates is derived from AActor.
- Test that you can drag it into the scene. Look in the "World Outliner" window to make sure it's there.
- You can get rid of the BeginPlay() and Tick() functions that it creates for you, since we will not be using them.
Testing and Debugging tips:
- For many changes, you'll want to delete your actor from the map before compiling and add it back in again afterwards. It'll try to save the state before the compile and re-load from that save afterwards. This will not typically do what you want if you've added data members, changed their types, or have made changes in the constructor.
- During development, it can be handy to tag data members with UPROPERTY(EditAnywhere) or UPROPERTY(VisibleAnywhere), so you can examine them in the editor. Don't forget to switch the ones that should not be exposed in the editor back to just UPROPERTY() when you're done.
- Once you get to the point where you're using an OnConstruction() function, you can add a dummy UPROPERTY(EditAnywhere) bool Update; to your class. That'll show as a checkbox, and clicking will force a re-run of OnConstruction().
- You can print log messages with UE_LOG(LogTemp, Warning, TEXT("printf format"), ...).
- If you disable optimization in your C++ file, you will be able to break and debug in your actor code. UE5 gloms a bunch of cpp files together for faster compiling, so you'll want to turn it back on again at the end of your file.
At the top of your file, add
#ifdef __clang__ #pragma clang optimize off #else #pragma optimize("", off) #endif
Then at the end of the file, add
#ifdef __clang__ #pragma clang optimize on #else #pragma optimize("", on) #endif
Add a SceneComponent and StaticMeshComponent
We'll start with a UStaticMeshComponent, which renders a single mesh before moving on to the UInstancedStaticMeshComponent.
- Create a USceneComponent for actor placement, and set it as the RootComponent. This will allow you to drag the position of the actor around in the map.
- See Manipulator.h and Manipulator.cpp for an example of an actor that creates a USceneComponent and UStaticMeshComponent
- Add a single UStaticMeshComponent* as a new member variable, tagged with the UPROPERTY() macro so it'll be serialized.
- I suggest initially loading a sphere as your mesh, since you don't need to worry about having a two-sided material on it like you do with a plane.
- In the "Content" window, you can select "Settings" and enable "Show Engine Content". Then in the "Engine" content folder search for "Sphere". Find the 100x100x100 one from Engine/BasicShapes, right click, and choose "Copy Reference" to get the name to paste in your code in the call to FObjectFinder()
- You don't really need to worry about setting a material at this stage. If you don't, you'll get the default grid material, which is fine for testing.
Make the Material changeable
- Add a UMaterial* member to your actor class, tagged as UPROPERTY(EditAnywhere). It'll show up in the detail window, and you'll be able to drag a material into it.
- Override the OnConstruction() virtual function and use MeshComponent->SetMaterial() to change your material. The manipulator example uses a dynamic material, which allows run-time animated changes to material parameters. We don't need that, so plain SetMaterial() is fine.
- Change the material to something two-sided (in the pictures here, I made a simple one based on Absolute World Position so the material continues seamlessly from wall to wall).
Fixed-sized Grid of walls
To render many copies of the same mesh, we'll want to switch to a UInstancedStaticMeshComponent. This has a TArray of FInstancedStaticMeshInstanceData, each with a transform providing the position and orientation for that instance.
- Change the shape to the 100x100 BasicShapes plane
- Switch from the UStaticMeshComponent* to a UInstancedStaticMeshComponent*.
- You'll need to add at least one instance using AddInstance() to see anything.
- Don't forget to clear instances before adding new ones in your OnConstruction() function, or you'll get an ever-growing set on top of each other.
- If you don't find good examples in the engine source for this component class (and you won't), look at the header that defines it to see what data and member functions are available.
- Use the FTransform constructor that takes an FRotator to set the instance orientation and FVector to set the instance position. You'll find those in the templated TTransform base class.
- The BasicShapes plane is aligned with the X/Y plane. To use it for your walls, you'll need to use a 90 degree rotations to orient it vertically along the X/Z or Y/Z planes, and translations that are multiples of 50.
- Once you have one plane working, build a grid of them.
Make your maze
- Create your maze data structures and implement the maze algorithm in your OnConstruction() function.
- I run the algorithm using its own data structures, then loop through the final data to add walls to the instance array
- Add an integer seed property to your class, visible in the editor, that you can change to generate different mazes (or re-create a good previously generated maze).
- You can use FRandomStream for random numbers.
Undergraduates can use a compile-time fixed sized maze. Graduate students should add two integer size properties to your class, visible in the editor, that you can change the dimensions of the generated mazes. C++ does not have dynamic 2D arrays, you will either need to do the indexing math into a dynamic 1D array by hand, or build yourself a new dynamic 2D array class.
For full credit, you must commit multiple times, showing your incremental development processs.
Capture images of your maze using several different seeds. You can use the windows or mac screen shot tools, or EditorScreenShot console command. Make sure your screen shots are from a view from above showing the full maze.
Add an assn2.txt to the top directory. Tell us what works and what doesn't, and anything else you think we should know for grading. Include links to your screen shot images and make sure they are accessible to both the instructor and grader.
Push to your repository, and tag your final commit with an assn2 tag.