Jump to content
  • entries
    27
  • comments
    62
  • views
    26269

All Over The Place, Nailing Things Down, Finding My Way (Movement and Pathfinding)

Sign in to follow this  
JEJoll

1954 views

All Over The Place

In my last post I discussed an Action/RPG/Platformer/Diabloesque/JRPG game that I had started working on, and showed a short prototype video. As if the genre of this game wasn't already all over the place enough, I've since dropped the project (for now at least), and started work on something else.

The main reason for this was simply the lack of assets available to me for the game I wanted to make. I wanted to have fully modular armor for my character, but I simply couldn't find the assets that I needed, even paid assets, and I can't afford to hire an artist to create the level of sophistication for the armor that I wanted. This aspect of the game was really important to me, and without it, it just wouldn't have felt complete. However, I happened upon a large number of highly reusable, customizable, affordable and high quality assets during my search which inspired me to go a slightly different route. Thanks to the prepaid Visa cards I got for Christmas, I was even able to buy a decent number of them ;).

Ever since I was a kid, I've been a huge fan of First Person Dungeon Crawlers. The first that comes to mind is SSI's 1991 game Eye of The Beholder which was awesome (somehow I thought that flipping through a book to find the copyright protection phrase actually added to the fun). Even writing this I get nostalgic thinking about similar games like Catacomb 3D, Arcana, Ultima Underworld, and of course, Daggerfall. This is a genre I'm always returning to. Even with AAA titles like Skyrim and Far Cry sitting on my computer, I'll find myself putting them down in favor of playing old Dungeon Crawlers that I'd never heard of (like Anvil of Dawn).

In the last few years, I've been delighted whenever I've found a new title in the same vein as these classics, and to see how well they've been received.

Legend Of Grimrock did an amazing job of remaining true to these old classics while providing the added bonus of beautiful modern graphics, and has done incredibly well in terms of reception and sales.

Star Crawlers (even though it's still in Early Access) manages to hang on to the meat of the genre while spicing it up with Sci-Fi flavors and innovative new features.

The Quest HD , a lesser known 2016 re-release of an older game is a full fledged first person dungeon crawler in beautiful 2D with open world environments and a great story. I might even argue that it outclasses many of the above titles in many ways. Really worth a look if you're into this kind of thing.

I digress.

The point is: I really love these kinds of games. I don't know if it's something about the atmosphere, the storytelling, looting and killing or just the nostalgia of it all, but I think they stand out as my favorite genre.

So I've decided to make one!


Nailing Things Down

Like all of the games I come up with and want to make (very few of which even see as much as a keystroke of development), I've been thinking about this game pretty much non-stop for a couple of weeks. I've got a ton of ideas for the game--some to do with how to 'stay true' to the genre, some to do with how to deviate to add something original and worthwhile.

A few things I know for sure:


  1. I'm doing this one right. No half-assed hacking just to get shit done. I'm going to need a proper outline and game design doc, and I'm going to have to establish a normal routine so the project doesn't rust and atrophy. Regular refactoring as well. I can't have the tangled messes I've let happen in other projects.
  2. Grid-Based movement with some kind of turn-based play (this could be either semi-turn-based or full-out 'wait for player action').
  3. Nice 3D graphics. I really want to take full advantage of Unity's new PBR and Lighting to make something that really looks great, and as I've said, I just got my hands on some really top-notch assets.

I'm tempted to add more in the above list, but, to stay on the safe side, I'll make a second list of potential features currently under consideration:

  1. Multiple Levels/Areas--I think it would be more fun to have the player have the opportunity to spelunk in a cave system, bushwhack in a jungle, pillage in a crypt as well as crawl in the good ol' dungeon.
  2. Open-world--I know what you're thinking. How is this possible in a Dungeon Crawler? Seriously, have a look at The Quest. It integrates dungeon crawling and open-worldedness perfectly. A part of this is having towns that can be visited where quests can be undertaken and loot can be bought and sold (a feature missing from many dungeon crawlers).
  3. Complex character development--If any of you have read my earlier journals and are familiar with the last game I completed, Pixel Zombie Shooter, then you'll know I'm keen on upgrades. I'd like to have a nice multi-branched skill tree for my players to fill up as they adventure.
  4. Questing--I briefly touched on this in number 2, but I'd really like to have some non-linear questing in this one as well. I think it would offer a lot of opportunity to develop lore and give me a chance to do some good storytelling.
  5. Procedural Generation--Yeah, I know. Buzz word. But seriously, I really want to get into this. I've already got some ideas for randomized weapon implementations like those found in Diablo, as well as for infinite guild quests and random dungeon areas. On that note, I'm not sure if I'd be interested in all hostile areas (dungeons, forests, etc.) being procedural, or just special 'grinding/looting' areas.

Finding My Way

Ok. You've listened to me rant about this new game idea I'm obsessed about, but talk is cheap. Here come a few things to look at as a reward for sticking through this post so far.

I've only had two development sessions on this game so far, and I think they both went well. In both cases, I spent a full day prior to my night-time dev session thinking on and off about how best to implement a feature, and in each session, I've been able to successfully implement at least a basically functional version of said feature in only a small amount of time.

The first of these is a simple movement controller.

The Code:

CharacterMovementController.cs:using UnityEngine;using System.Collections;public class CharacterMovementController : MonoBehaviour { [SerializeField] //How long it should take to rotate or move the camera private float animationTime = 0.35f; [SerializeField] //The amount to move the player by. Should be equal to the size of each level 'tile' private float unitSize = 3f; [SerializeField] //How much to rotate on each rotation (1 == 360?). This shouldn't change unless allowing diagonal movement private float rotationAmount = 0.25f; [SerializeField] //How high off the ground the player sits. private float playerHeight = 1.75f; [SerializeField] //The easeType to use for the iTween animation. private iTween.EaseType easeType = iTween.EaseType.linear; [SerializeField] //This is used to determine appropriate positioning when climbing/descending uneven terrain. private HeightProbe heightProbePrefab; private Hashtable iTweenArgs; private bool canMove = true; //Initialize itween args void Start() { iTweenArgs = new Hashtable(); iTweenArgs["time"] = animationTime; iTweenArgs["oncompletetarget"] = this.gameObject; iTweenArgs["oncompleteparams"] = true; iTweenArgs["oncomplete"] = "ToggleMovement"; iTweenArgs["easetype"] = easeType; } void Update() { /* Uncomment to allow these values to be manipulated via the inspector at runtime for testing iTweenArgs["time"] = animationTime; iTweenArgs["easetype"] = easeType; */ //Move or rotate the player based on input. if (canMove) { if (Input.GetKey(KeyCode.W)) { Move(transform.forward); } else if (Input.GetKey(KeyCode.S)) { Move(-transform.forward); } else if (Input.GetKey(KeyCode.D)) { Move(transform.right); } else if (Input.GetKey(KeyCode.A)) { Move(-transform.right); } else if (Input.GetKey(KeyCode.E)) { Rotate(Vector3.up); } else if (Input.GetKey(KeyCode.Q)) { Rotate(Vector3.down); } } } /* Move the player in the appropriate direction. First initialize newPosition based on direction, then create a HeightProbe to determine the appropriate y value for newPosition. This allows easy vertical movement along both even and uneven terrain. HeightProbe destroys itself after FindFloorHeight() is called. */ private void Move(Vector3 directionVector) { Vector3 newPosition = transform.position + directionVector * unitSize; HeightProbe heightProbe = Instantiate(heightProbePrefab, newPosition, Quaternion.identity) as HeightProbe; newPosition.y = heightProbe.FindFloorHeight() + playerHeight; iTweenArgs["position"] = newPosition; ToggleMovement(false); iTween.MoveTo(this.gameObject, iTweenArgs); } private void Rotate(Vector3 rotationVector) { iTweenArgs["amount"] = rotationVector * rotationAmount; ToggleMovement(false); iTween.RotateBy(this.gameObject, iTweenArgs); } private void ToggleMovement(bool allowMovement) { canMove = allowMovement; }}
HeightProbe.cs:using UnityEngine;using System.Collections;public class HeightProbe : MonoBehaviour { [SerializeField] private float destructionDelay = 0.25f; [SerializeField] // This should be set to only detect the floor in the future private LayerMask layerMask; public float FindFloorHeight() { StartCoroutine(SelfDestruct()); RaycastHit hit; Physics.Raycast(transform.position, -transform.up, out hit, Mathf.Infinity, layerMask); if (hit.transform) { return hit.point.y; } else { return Mathf.NegativeInfinity; } } private IEnumerator SelfDestruct() { yield return new WaitForSeconds(destructionDelay); Destroy(gameObject); }}
Both of these scripts are super simple and will need some updating, but they give a nice final effect:

czWdd3z.gif


Basically, when the user gives input, I spawn a HeightProbe in the appropriate neighboring space, which simply finds the height of the floor/ground in that location. The PlayerMovementController will then add playerHeight to this value and use that as the y position to move to. The reason I did it this way, rather than having a set step height was because I wanted the potential to have my player move on uneven terrain while maintaining the grid-based movement. This will be particularly useful for outdoor areas and will give me greater freedom in my level design.

Right now there are no checks to see if the player is trying to move through a wall, and I rely on iTween to do all of the heavy lifting in animating my player's rotation and translations (who wants to manually code that stuff? Not me). Obviously I'll have to do some checking for movement through walls. Also, based on what I've read, iTween is pretty inefficient (I do love working with it though), so I think I'll have to rework the code to use DoTween instead (also free, but apparently performs way better).

Also, I might get rid of the HeightProbe class altogether and put the logic right inside the PlayerMovementController.

Lastly, I don't like having my input hardcoded, so that'll have to change. I'll also want a bit of a mouselook functionality so the player can observe the environment more fully.

And yes, that was a baby dragon :wink:.

What I did in my next development session was implement a bit of pathfinding. Like I said, I thought about this all day, and I came up with my own algorithm. I have no idea what you would call it technically, but in demonstrations I've seen, it performs similarly to a concurrent Dijkstra implementation. e is an enemy, p is the player:


eLQC2m3.gifhTHDXyv.gif


I've never done any pathfinding before (aside from using Unity's built in AI stuff), so this is a first for me. And I think I did pretty good coming up with a solution completely by myself. I won't need a ton of pathfinding in the game (since most enemies will probably only move toward the player if they can see him, or move to a last known location), but I thought this would be fun, and it was.

Not to mention I really like watching my code solve a maze :P. There's something hypnotizing about these gifs.

The Code:

Pathfinder.cs:using UnityEngine;using System.Collections;using System.Collections.Generic;public class Pathfinder : MonoBehaviour { [SerializeField] private Color color = Color.red; [SerializeField] PathProbe pathProbePrefab; [SerializeField] private float unitSize = 3f; public float UnitSize { get { return unitSize; } } [SerializeField] private LayerMask layerMask; [SerializeField] private Vector3[] directions; public Vector3[] Directions { get { return directions; } } private List probedCells = new List(); public List ProbedCells { get { return probedCells; } } private List pathToTarget = new List(); public List PathToTarget { get { return pathToTarget; } set { pathToTarget = value; foreach (PathProbe pathProbe in GameObject.FindObjectsOfType()) { if (pathProbe.Master == this) { Destroy(pathProbe.gameObject); } } } } void Start() { InitiatePathFinding(); } private void InitiatePathFinding() { probedCells.Clear(); pathToTarget.Clear(); probedCells.Add(transform.position); foreach (Vector3 direction in directions) { CreateProbeAtNeighboringCell(transform.position, direction); } } void Update() { if (Input.GetKeyDown(KeyCode.F)) { InitiatePathFinding(); } } public PathProbe CreateProbeAtNeighboringCell(Vector3 position, Vector3 direction) { Vector3 cellPosition = position + (direction * unitSize); if (!probedCells.Contains(cellPosition)) { RaycastHit hit; Physics.Raycast(position, direction, out hit, unitSize/*, layerMask*/); Debug.DrawRay(position, direction * unitSize, color); if (!hit.transform) { PathProbe newPathProbe = Instantiate(pathProbePrefab, cellPosition, Quaternion.identity) as PathProbe; newPathProbe.Master = this; newPathProbe.Color = color; probedCells.Add(cellPosition); return newPathProbe; } } return null; }}
PathProbe.cs:using UnityEngine;using System.Collections;using System.Collections.Generic;public class PathProbe : MonoBehaviour { private Pathfinder master; public Pathfinder Master { get { return master; } set { master = value; StartCoroutine(PathFind()); } } private Color color = Color.red; public Color Color { set { color = value; } } private List pathPoints = new List(); public List PathPoints { set { pathPoints = value; pathPoints.Add(transform.position); } } private List myProbes = new List(); private IEnumerator PathFind() { if (master) { yield return new WaitForFixedUpdate(); if(master.PathToTarget.Count <= 0) { foreach (Vector3 direction in master.Directions) { PathProbe newPathProbe = master.CreateProbeAtNeighboringCell(transform.position, direction); myProbes.Add(newPathProbe); if (newPathProbe) { newPathProbe.PathPoints = this.pathPoints; } else { RaycastHit hit; Physics.Raycast(transform.position, direction, out hit, master.UnitSize); //Debug.DrawRay(transform.position, direction * master.UnitSize, Color.green); if (hit.transform && hit.transform.tag == "Player") { master.PathToTarget = pathPoints; Debug.Log("Path to player found in " + Time.time + " seconds!"); foreach (PathProbe probe in GameObject.FindObjectsOfType()) { Destroy(probe.gameObject); } } } } } Destroy(this.gameObject); } }}
Basically, I raycast in all four directions from the enemy's position, at a distance equal to my grid size. If my rays don't hit anything at those locations, I spawn a PathProbe at the appropriate place and repeat, adding each cell from which rays were cast to a probedCells list, and omitting those cells from future checks. Because each probe performs iterations at the same time as all of the others, the first path to reach the player is always the shortest, so I didn't even need to take that into consideration, and as soon as any path is found, I can stop the search.

Currently, my coroutine's yield is no good and it makes the whole process take a relatively long time (waiting for a new frame before performing another iteration). Essentially, only a single set of cells for each probe can be checked per frame, which can become a problem, particularly in long narrow paths. I need to allow a greater number of checks before yielding to speed up the whole process, but I also have to be careful not to overload it or things tend to crash.

Currently, both of the searches you see above take about 0.75 seconds each. Again, this has nothing to do with complexity, and more to do with how my coroutine operates. In fact, having multiple paths calculated at the same time has had zero impact on frame rate or calculation time so far (though I've only tested 3 concurrent paths to date).

However, I really need to rework this as .75 seconds is not acceptable. Presumably, to be safe, I'll have to recalculate the path each time the enemy moves to a new cell, just in case something has blocked the path or the player has moved. But like I said, I don't think I'll need a lot of pathfinding. I think most of it will be linear movement along a line of sight.

Again, I might remove my logic from PathProbe and put it right in Pathfinder to avoid Instantiate calls.

I was also considering using Unity's NavMeshAgent to calculate a path, and then writing some code to normalize that path to align with my grid (I assume Unity's pathfinding would be more efficient than anything I'll write). Another approach I'm considering is generating an array based on my level when it loads, and then traversing the array based on whether the given index is walkable or not. This would save on raycasting at least.

Anyhow, that's it for now!

Stay tuned for more, including the launch of an official website for Fidelum Games!

Sign in to follow this  


5 Comments


Recommended Comments

I'm very interested in seeing where this will go:)
And you're right it's pretty hard to find nice modular assets for main characters - I've also been searching quite a bit on the asset store and haven't really found anything that suits me perfectly.
 

Share this comment


Link to comment

I'm very interested in seeing where this will go:)
And you're right it's pretty hard to find nice modular assets for main characters - I've also been searching quite a bit on the asset store and haven't really found anything that suits me perfectly.
 

Thanks :)

 

The first 'game' I ever made was a super crappy 2.5D dungeon crawler using Windows Forms in VB in my first year of college. Literally all you could do was walk around a level with about 8 tiles :P. So I'm super excited to make a full-fledged dungeon crawler.

 

I've got big plans for this one, including a Kickstarter campaign once I have something that looks cool and is somewhat playable. I started on my design doc the other day, and it's already up to about 30 pages (I estimate 60 or more when everything's set in stone), so the game is going to be chock full of cool features, and should have quite a few gameplay hours. 

 

I think I'm going to talk about my design doc a bit in my next post, but if you're really keen on staying up to date on this project, might I suggest *Shameless plug warning* liking Fidelum Games' Facebook Page and Subscribing to my Youtube Channel

 

I'm going to start being really active on those again, talking about features and posting trailers, etc. as it makes sense. I've also got some Unity tutorials there, which I'm going to start actively adding to again.

 

Also, as far as assets go, have you looked at the Infinity PBR series of assets? I'm not sure if it would work for you, but the best pack I've found with modular components for characters is their Barbarian Pack. The price is right--you get a huge bang for your buck and it looks highly customizable. I haven't used this particular pack, but if it's anything like their other work, it's absolutely worth it. Also, they're constantly adding new packs, and I think I read that they're planning on releasing other character packs (elves, dwarves, etc.) which I assume would have different clothing/class sets.

 

I almost don't want to share these assets since I'm using a lot of the monsters and procedural models for my game, but I don't want to be greedy ;)

Share this comment


Link to comment
Looks nice! I hear you about making sure that you want the project to go smooth from the very beginning; that was always a pain for me as well. Avoiding code hacks and frequent refactoring is a good practice allright. However, it might not be necessary if one is only creating a prototype to test some ideas. About the lack of required assets: I guess that I never had/will have any issues with this. That is because I tend not to rely on too detailed visuals anyway. If the game mechanics are good enough, the visuals will have secondary meaning. On the other hand, they surely cannot be crappy, right? Good luck with the project - keep us posted!

Share this comment


Link to comment

Also, as far as assets go, have you looked at the Infinity PBR series of assets? I'm not sure if it would work for you, but the best pack I've found with modular components for characters is their Barbarian Pack. The price is right--you get a huge bang for your buck and it looks highly customizable. I haven't used this particular pack, but if it's anything like their other work, it's absolutely worth it.

I think that pack must have been updated quite a bit since I last looked at it? I remember thinking it was pretty nice looking but not exactly what I needed but when I look at it now it does look amazing. And yeah can't beat that price!
Have you tried working with it?

PS: Since you already have a grid then A* will not be hard to implement. The key to good performance is a good priority queue.

Share this comment


Link to comment

 

Also, as far as assets go, have you looked at the Infinity PBR series of assets? I'm not sure if it would work for you, but the best pack I've found with modular components for characters is their Barbarian Pack. The price is right--you get a huge bang for your buck and it looks highly customizable. I haven't used this particular pack, but if it's anything like their other work, it's absolutely worth it.

I think that pack must have been updated quite a bit since I last looked at it? I remember thinking it was pretty nice looking but not exactly what I needed but when I look at it now it does look amazing. And yeah can't beat that price!
Have you tried working with it?

PS: Since you already have a grid then A* will not be hard to implement. The key to good performance is a good priority queue.

 

Yeah, they're always updating their packs, so it's likely. And no, I haven't used that pack, but all of their assets that I have used are really great.

 

As far as future path finding solutions go, I might try to implement A*. I've always been interested in trying it anyway. However, I think I'm going to try normalizing a path generated by Unity's NavMeshAgent first, just out of curiosity to see how well that performs. 

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!