ArgGrr gonna try Devtember

Day 8:

Somewhat solved the performance issues. I was getting the capsule/NPC/navagent to re-calculate the path to the target every frame, to account for the goal moving. However the target is not moving here, well only once when the maze is build.

So, I build the maze, move the goal to the end, move the NPC to the start, and call an NPC subroutine to get it to navigate to the goal. Now it will try and traverse a maze of any (maybe) size.

It seems to get stuck, go down dead ends and try and find a new path, so there is some inherent re-calculation happening. You can see an option for this in the navagent component properties.

Engagement challenge: Is this maze solvable, from bottom left to top right?
Maze:

5 Likes

Yes…

2 Likes

Yes it seems to be. AI be stupid and not really AI.

You can section it…
Divide it on smaller section, that have one exit. If it works fine with smaller labyrinths - give it smaller labyrinths…

Yeah perhaps I could tile a bunch of smaller mazes and have at each start/finish. Otherwise I would have to solve it in code and put waypoints along a solution path. This is beyond the scope of what the maze was for, since it is only there to test the navigation stuff. Pretty happy with how it went, I know there are some limitations.

For week 2, I think I will start making some humanoids and get them walking around. I can’t mocap, so I hope there is some built in stuff, or stuff that is easy to get. Otherwise they will be robots on wheels.

I would recommend giving Mixamo a try. It’s free as far as I’m aware, generates the rigging after uploading a 3D model and has a tonne of animations that you can assign. Then once you’ve chosen however many animations you want you can download the resulting FBX and plop that into Unity.
EDIT: It seems they also have a few character models that you can download if you don’t wish to use your own.

1 Like

Day 9:

Nice, that will give me a couple of characters to get going. It is an Adobe owned site, so their login worked.

Pretty much imported the models, crated a ragdoll on top of each one, which involves dragging the right joints into a configuration window.
Now they ragdoll like dead marionettes when I start the game, which is fun!
But that is as far as I can go without doing a bunch more reading/watching, so more later perhaps.

4 Likes

Day 9b: Did a little more looking into humanoid rigs and stuff.

Bit of a rabbit hole. Unity thinks the models shown are not in an exact t-pose, more specifically the first thumb knuckle on each hand… It will take a bit of time to get my head around it all, more tomorrow!

1 Like

This look more fun by the day! :sunglasses:
you’re making great progress!

1 Like

Day 10:

Cheers! Been watching videos on the humanoid animation / rigging system. There is a big pile of motion capture data you can download for free from the asset store and import. It is rough, so you need to cut and fine tune each animation. That is a fair bit of work, and probably beyond the scope for what I am doing. I will probably use the stuff from the Mixamo site above, which looks like it has already been fine tuned.
Once you have a clean set of loop-able animations for idle, walking, strafe, turn, jump etc, you can go on to building up an animation controller. Essentially a state machine that determines what animation should be played and transitions smoothly from one to another.
Then you can assign the animation controller to the robots or Unity Chan who might make an appearance etc.

Then, I assume, if you move an entity, the animation controller will handle all of the animations in the background! That’s sort of what I was hoping to achieve. No need to reinvent the wheel.

I will try to do this after work, see how I go.

On a side note, assigning the ragdoll to a rig / avatar like above doesn’t do much. Might be good to turn it on for dead or inactive bodies, but it doesn’t seem to do much for animation? Maybe it builds the colliders for the physics stuff though? Still to find this out.

4 Likes

Day 11 & 12: What day is it again?

Ugh this is getting messy. Been watching some tutorials, and trying to get a set of animations tied to a model, that will respond to the nav agent controls.

I.e. when the NavAgent stuff moves an asset around, have the asset automatically play the correct animations. Main issue is the NavAgent seems to navigate using tank controls, most of the tutorials don’t demo tank controls, so trying to work it out myself… Not much to show so far except the robot waving its arms in the air and jitterbugging on the spot :slight_smile: I wonder if it hurts.

I will keep chipping away at it each day.

I think another issue is all the examples use animation sets from the unity store, where as I am using them from a third party. They all appear to import fine, but do not automatically import as humanoid rigs/models etc. When I force them to humanoid, the animations break. Maybe it is no big issue leaving them as generic…

3 Likes

Day 1̴̨͚̖̹̙̝̱̗̽̅̆̏͂̋̊̈͂͜2̨̧̡͔͍̩̣̫̒͊̐̄͒̎̕͠ͅ?̸̡̨̧̻̗̩̰͍̞̔̔̐̽̕͜?̴̧̧̘̭̥͎͖̭̘̂͂͂̐̆͌͘͜͝

Followed some guides and got a robot running through the maze.

A few things to chain up here. Get a set of animations, one for each direction of travel more or less. I used a set of jogging ones. Set the import properties to rig for humanoid, and to pull that rig from the humanoid robot character’s avatar. That’s allot of words.


You can play the animation to make sure it works.

There are a few ways of blending different animations together. In this example, X & Y motion are fed into this graph, and the amplitude of the input (red dot) determines what animations are played and how much they are blended together. Walking forward and heading left a bit, or slowly walking backwards etc.
This is all housed in an animation controller.

Once you set up some basic scripts to feed X & Y motion values from AI navigate or keyboard input, it sort of chains together.

In the top example, you click somewhere with the mouse, and that sets the AI navigation target. It will plot a path and navigate the robot there. Current velocity values of the robot are fed to the animation controller, which then plays the right animations for what is going on.

Pretty basic at this stage, but you could layer quite a few styles of locomotion in. Going 3D would add more complexity also, jumping and climbing etc.

8 Likes

Nothing today. Well nearly nothing.
I tracked down an archive of all Mixamo’s free stuff before they got bought out by Autodesk. The Mixamo website now seems harder to use now than it used to be from what I can see in some videos.

So I will play with that a bit more, add some more animations into the mix, and see if I can get an interesting demo going. Hopefully I get something I can share so others can be not amused by it.

4 Likes

:joy:

good job overall though

1 Like

All hail Zalgo.

Day 15:

Added in a new character, did some code refactoring, removing obsolete objects and scripts.

Still trying to figure out why characters sink into the plane. Probably no rigid bodies even though there is a collider attached to the character/avatar?
If I add a rigid body, that can add physics which might effect animations?

Made the camera fly around, got sick of resetting it’s position all the time.
I had to add another control in the input settings for crouching. I could have just added the crouch key to the negative axis of the jump input, since it is only for flying the camera around, but prefer to keep them separate.

 public class CameraMove : MonoBehaviour {

    Transform camerapos;

    Vector3 move;
    Vector3 rotation;

    float posx, posy;

    public float mouseSensitivity = 100.0f;
    public float clampAngle = 80.0f;

    private float rotY = 0.0f; // rotation around the up/y axis
    private float rotX = 0.0f; // rotation around the right/x axis

    public float MoveSpeed = 2.0f;
    public float RunSpeed = 20.0f;

    // Use this for initialization
    void Start () {

        camerapos = GetComponent<Transform>();

        Vector3 rot = transform.localRotation.eulerAngles;
        rotY = rot.y;
        rotX = rot.x;

    }

    // Update is called once per frame
    void Update () {

        move.x = Input.GetAxis("Horizontal") * Time.deltaTime;
        move.y = (Input.GetAxis("Jump") + Input.GetAxis("Crouch")) * Time.deltaTime;
        move.z = Input.GetAxis("Vertical") * Time.deltaTime;

        if (Input.GetKey(KeyCode.LeftShift))
        {
            move *= RunSpeed;
        } else
        {
            move *= MoveSpeed;
        }

        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = -Input.GetAxis("Mouse Y");

        rotY += mouseX * mouseSensitivity * Time.deltaTime;
        rotX += mouseY * mouseSensitivity * Time.deltaTime;

        rotX = Mathf.Clamp(rotX, -clampAngle, clampAngle);

        Quaternion localRotation = Quaternion.Euler(rotX, rotY, 0.0f);

        camerapos.Translate(move);
        camerapos.rotation = localRotation;

    }
}
3 Likes

Day 15 or 16 (16th Dec = Day 16 right?)

I have been going back over the NavMesh stuff, trying to get it working properly. It all seems to be stemming from the fact the characters are not running on the floor objects properly. They sink in and run on the underlying plane, which actually had the NavMesh baked onto it. Bit tricky.

My idea of instantiating a bunch of maze segments, assembling them, and then baking a navmesh on that wasn’t quite working right. So I am going back over that trying to get a better understanding of how to do it better.

Oh wait I think I fixed it.

Stay tuned. Taking a while to generate 500x500 maze.

Cancelled that, ran a 150x150 maze instead. Don’t have all day!

Things I have done differently…
The parts of the maze are cloned or instantiated under a parent, that Maze objects. I need to put a NavMeshSurface component on the top level of the maze, and modifiers on the walls to make them impassable.
Then after generating the maze, I can get the NavMeshSurface object to generate the mesh then. This frees up a bunch of CPU cycles and seems to allow the agents to work a bit better through the maze.

Previously the agent wouldn’t go through a 20x20, now it belts through a 150x150. So that is good.


They made it to the end!

Code:

using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.AI;

/*

    This script controls the scene, generates the maze, places end point etc.

*/

public class scenescripts : MonoBehaviour {

    public int Maze_Segments_x = 10;
    public int Maze_Segments_y = 10;
    Room[,] Rooms;

    public float  Map_Scale_factor = 2;

    public GameObject room_template;
    public GameObject wall_template;

    public GameObject maze_floors;
    public GameObject maze_walls;
    //public GameObject maze_others;

    public NavMeshSurface maze_surface;

    public GameObject goal;
    //public NPCmove NPC;

    float counter;

    public float Map_Offset_x = 5f;
    public float Map_Offset_y = 5f;
    public float Map_Offset_z = 0.5f;


    // Use this for initialization
    void Start() {
        genMaze();
    }

    // Update is called once per frame
    void Update() {
    }

    class Room
    {
        public Wall Up;
        public Wall Down;
        public Wall Left;
        public Wall Right;

        public bool isVisited;
    }
    class Wall
    {
        public Room Room1;
        public Room Room2;

        public bool passage;

        public Wall(Room Room1,Room Room2 )
        {
            this.passage = false;
            this.Room1 = Room1;
            this.Room2 = Room2;
        }

    }

    void genMaze()
    {
        //Array of cells set to right size
        Rooms = new Room[Maze_Segments_x, Maze_Segments_y];



        List<Wall> Walls = new List<Wall>();
        int x, y;

        //Build grid of rooms
        for (y = 0; y < Maze_Segments_y; y++)
        {
            for (x = 0; x < Maze_Segments_x; x++)
            {
                Room tmproom = new Room();

                tmproom.isVisited = false;

                Rooms[x, y] = tmproom;
            }
        }
        //Run back over array and create walls between all the rooms, putting in the references to rooms etc.
        for (y = 0; y < Maze_Segments_y; y++)
        {
            for (x = 0; x < Maze_Segments_x; x++)
            {
                // fixed some bugs here in map generation
                if (x == 0)
                {
                    Rooms[x, y].Left = new Wall(Rooms[x, y], null);
                }
                else if (x == Maze_Segments_x - 1)
                {
                    Rooms[x, y].Right = new Wall(Rooms[x, y], null);
                }
                else
                {
                    Wall tmpwall = new Wall(Rooms[x, y], Rooms[x + 1, y]);
                    Rooms[x, y].Right = tmpwall;
                    Rooms[x + 1, y].Left = tmpwall;
                }


                if (y == 0)
                {
                    Rooms[x, y].Up = new Wall(Rooms[x, y], null);
                }
                else if (y == Maze_Segments_x - 1)
                {
                    Rooms[x, y].Down = new Wall(Rooms[x, y], null);
                }
                else
                {
                    Rooms[x, y].Down = new Wall(Rooms[x, y], null);
                    Wall tmpwall = new Wall(Rooms[x, y], Rooms[x, y + 1]);
                    Rooms[x, y].Down = tmpwall;
                    Rooms[x, y + 1].Up = tmpwall;
                }
            }
        }

        /*
        Modified Prim's algorithm from Wikipedia
        1. Start with a grid full of walls.
        2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
        3. While there are walls in the list:
            a. Pick a random wall from the list. If only one of the two cells that the wall divides is visited, then:
                i. Make the wall a passage and mark the unvisited cell as part of the maze.
                ii. Add the neighboring walls of the cell to the wall list.
            b. Remove the wall from the list.
        */


        //2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
        // Need to pick even index inside the maze

        //NOTE: Generating the maze starting at the [0,0] corner results in a terrible maze
        Rooms[1, 1].isVisited = true;

        Walls.Add(Rooms[1, 1].Up);
        Walls.Add(Rooms[1, 1].Down);
        Walls.Add(Rooms[1, 1].Left);
        Walls.Add(Rooms[1, 1].Right);


        // 3. While there are walls in the list:
        do
        {
            //a. Pick a random wall from the list. If only one of the two cells that the wall divides is visited, then:
            int r = Random.Range(0, Walls.Count - 1);

            if (Walls[r] == null) { }
            else if (Walls[r].Room1 == null || Walls[r].Room2 == null) { }
            else if (Walls[r].Room1.isVisited ^ Walls[r].Room2.isVisited) //either visited but not both
            {
                //i. Make the wall a passage and mark the unvisited cell as part of the maze.
                //ii. Add the neighboring walls of the cell to the wall list.
                Walls[r].passage = true;
                if (!Walls[r].Room1.isVisited)
                {
                    Walls[r].Room1.isVisited = true;
                    if (!Walls.Contains(Walls[r].Room1.Up)) Walls.Add(Walls[r].Room1.Up);
                    if (!Walls.Contains(Walls[r].Room1.Down)) Walls.Add(Walls[r].Room1.Down);
                    if (!Walls.Contains(Walls[r].Room1.Left)) Walls.Add(Walls[r].Room1.Left);
                    if (!Walls.Contains(Walls[r].Room1.Right)) Walls.Add(Walls[r].Room1.Right);
                }
                else
                {
                    Walls[r].Room2.isVisited = true;
                    if (!Walls.Contains(Walls[r].Room2.Up)) Walls.Add(Walls[r].Room2.Up);
                    if (!Walls.Contains(Walls[r].Room2.Down)) Walls.Add(Walls[r].Room2.Down);
                    if (!Walls.Contains(Walls[r].Room2.Left)) Walls.Add(Walls[r].Room2.Left);
                    if (!Walls.Contains(Walls[r].Room2.Right)) Walls.Add(Walls[r].Room2.Right);
                }

            }
            //2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
            Walls.Remove(Walls[r]);

        } while (Walls.Count > 0);

        //Done forming maze
        GameObject tmpobject;
        
        //Build maze out of 3d objects
        for (y = 0; y < Maze_Segments_y; y++)
        {
            for (x = 0; x < Maze_Segments_x; x++)
            {
                //No room for this tile! Shouldnt happen anymore.
                if (Rooms[x, y] == null)
                {
                    Debug.Log(string.Format("No room created for maze tile: {0},{1}", x, y));
                    continue;
                }

                //Create room on this maze tile.
                tmpobject = Instantiate(room_template,
                    new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
                    new Quaternion(0, 0, 0, 0),
                    maze_floors.transform);

                // Create four walls as required for each maze tile. Check to make sure it hasn't already been made by neighboring tile.
                if (Rooms[x, y].Up != null && !Rooms[x, y].Up.passage)
                {
                    tmpobject = Instantiate(wall_template,
                        new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
                        Quaternion.Euler(0, 270, 0),
                        maze_walls.transform);
                }
                
                if (Rooms[x, y].Right != null && !Rooms[x, y].Right.passage && x == Maze_Segments_x - 1)
                {
                    tmpobject = Instantiate(wall_template, 
                        new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2), 
                        Quaternion.Euler(0, 180, 0),
                        maze_walls.transform);
                }

                if (Rooms[x, y].Down != null && !Rooms[x, y].Down.passage && y == Maze_Segments_y - 1)
                {
                    tmpobject = Instantiate(wall_template,
                        new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
                        Quaternion.Euler(0, 90, 0),
                        maze_walls.transform);
                }

                if (Rooms[x, y].Left != null && !Rooms[x, y].Left.passage)
                {
                    tmpobject = Instantiate(wall_template,
                        new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
                        Quaternion.Euler(0, 0, 0),
                        maze_walls.transform);
                }
                
            }
        }

        //Place goal at end of the maze
        goal.transform.Translate(new Vector3(Map_Offset_x + ((Maze_Segments_x - 1) * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + ((Maze_Segments_y-1) * Map_Scale_factor) + Map_Scale_factor / 2));

        //Generate the nav mesh once after maze built.
        maze_surface.BuildNavMesh();
    }
}
7 Likes

Day 17:

Not much today. Started looking at importing some more animations, and working on the state machines.

First, I think I’ll get a set of walking animations and add to the X/Y graph above, but with less amplitude, so the characters start walking and then running, depending on the velocity.
Also was getting a set of zombie walking animations in. These mostly need tank controls (i.e. walk forwards, turn left or right, and walk backwards). So I need to figure out how to make that work. Will do some research tonight if I get a chance.

It is good reinforcement going back over the animation stuff again, and trying to do something new with it.

I also tidied up the script again in the previous post. I added a function that will return a world position for a segment of the maze, based on all those parameters. Bit tidier.

4 Likes

Honestly you are doing better than me after an year or so playing with Unity, so theres that…

2 Likes

I had played with it before. I think the best I achieved was a ragdoll falling down a cliff, like that old demo of a guy falling down the stairs. Me and a friend had a short competition to make things in Unity. it kind of petered out back then.
So I get basically how it works. Just need to put in the time to get gud and learn how to make it do stuff.

2 Likes

Day 18:

Oh no, things are looking bad for Unity-Chan!

Two robots are making out next to her it seems. Not what I intended, but who am I to judge.

I forgot to enable early exits on some animations, like walking, turning etc. If you don’t enable this, it will typically play the whole animation clip when it is called before a state can transition back. Say a robot has been instructed to turn left for 0.5s, but there is a 5s animation clip that plays, it can make things look weird.

They get a good bite in when they first get to her position:

But the way the script is running they are heading to her initial position, and push her out of the way when they get close. So both robot zombies are attacking the same point which makes it look like they are biting each other.

So yeah need to fix that. That is easy enough. The zombie animation blend tree is rubbish though. They slide along the ground doing what looks like an idle animation. Need to figure out a better way to marry the motion to the animation, or vice versa. Which ever works.

Unity-Chan is the mascot for Unity in Japan or where ever. It is a free download from the store. Comes with scripts and animations which I haven’t used, they are mostly out of date. Seems fun to use in the development stage.

5 Likes