├── images ├── grid_build.jpg ├── grid_path.jpg └── grid_expand.jpg ├── documentation └── docs.zip ├── CubeCoordinates ├── Runtime │ ├── ctrlshiftmake.CubeCoordinates.Library.asmdef │ ├── ctrlshiftmake.CubeCoordinates.Library.asmdef.meta │ ├── Cubes.cs.meta │ ├── Container.cs.meta │ ├── Coordinate.cs.meta │ ├── Coordinates.cs.meta │ ├── MeshCreator.cs.meta │ ├── Coordinate.cs │ ├── Container.cs │ ├── MeshCreator.cs │ ├── Cubes.cs │ └── Coordinates.cs ├── LICENSE.md.meta ├── README.md.meta ├── CHANGELOG.md.meta ├── package.json.meta ├── Runtime.meta ├── README.md ├── CHANGELOG.md ├── package.json └── LICENSE.md ├── LICENSE.md ├── .gitignore ├── examples ├── HappyFace.cs └── RandomPath.cs └── README.md /images/grid_build.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owmo-dev/cube-coordinates/HEAD/images/grid_build.jpg -------------------------------------------------------------------------------- /images/grid_path.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owmo-dev/cube-coordinates/HEAD/images/grid_path.jpg -------------------------------------------------------------------------------- /documentation/docs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owmo-dev/cube-coordinates/HEAD/documentation/docs.zip -------------------------------------------------------------------------------- /images/grid_expand.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owmo-dev/cube-coordinates/HEAD/images/grid_expand.jpg -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/ctrlshiftmake.CubeCoordinates.Library.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctrlshiftmake.CubeCoordinates.Library" 3 | } 4 | -------------------------------------------------------------------------------- /CubeCoordinates/LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a3408f6d06b9047d094d68bfbee92b20 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CubeCoordinates/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c3c7bd0e161b4af8a6840d121131ef7 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CubeCoordinates/CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1a9cf3cbb6bd14bd2a5470f564e01e4e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CubeCoordinates/package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8acd8c7e7369b4761bc6f5d330e0a666 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 57d941f0bbda942b09eca47241de49a3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/ctrlshiftmake.CubeCoordinates.Library.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 31c9aef723ed24b548531b8a824ae415 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CubeCoordinates/README.md: -------------------------------------------------------------------------------- 1 | # CubeCoordinates 2 | 3 | Unity package providing a cube coordinate system and methods for building hexagonal tile grids for interactive gameplay. See the following GitHub repository for documentation and examples. 4 | 5 | https://github.com/ctrlshiftmake/cube-coordinates 6 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Cubes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c3d9975ae8d1549a297bb9d8e3d9e4b1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CubeCoordinates/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # changelog 2 | 3 | ## v1.0.0 | January 11, 2021 4 | 5 | - Complete re-write for increased performance, useability and portability 6 | - Abstracted coordinate containers from cube coordinate calculations 7 | - Added ability to specify a prefab GameObject to instantiate 8 | - Tested on Unity `2019.4 17f1` 9 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Container.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e56bc30e1b55b494196051a44c09763e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Coordinate.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 056c74a4eaccd4edf903c989d52f019c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Coordinates.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38acb798ec01041f6b07480ed2016796 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/MeshCreator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2488f0273666847599f7f7ec361c82e2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CubeCoordinates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.ctrlshiftmake.cubecoordinates", 3 | "version": "1.0.0", 4 | "displayName": "Cube Coordinates", 5 | "description": "Unity package providing a cube coordinate system and methods for building hexagonal tile grids for interactive gameplay.", 6 | "unity": "2019.4", 7 | "unityRelease": "17f1", 8 | "keywords": [ 9 | "hexagonal", 10 | "tile", 11 | "grid", 12 | "coordinates" 13 | ], 14 | "author": { 15 | "name": "CtrlShiftMake Design", 16 | "email": "ctrlshiftmake@gmail.com", 17 | "url": "https://www.ctrlshiftmake.com" 18 | }, 19 | "dependencies": {} 20 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 CtrlShiftMake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CubeCoordinates/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 CtrlShiftMake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /[Ll]ogs/ 7 | /[Uu]ser[Ss]ettings/ 8 | 9 | # MemoryCaptures can get excessive in size. 10 | # They also could contain extremely sensitive data 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | /[Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.aab 57 | *.unitypackage 58 | 59 | # Crashlytics generated file 60 | crashlytics-build.properties 61 | 62 | # Packed Addressables 63 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 64 | 65 | # Temporary auto-generated Android Assets 66 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 67 | /[Aa]ssets/[Ss]treamingAssets/aa/* 68 | 69 | # OSX 70 | .DS_Store -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Coordinate.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace CubeCoordinates 4 | { 5 | /// 6 | /// Individual representation of a cube coordinate in the system 7 | /// 8 | public class Coordinate 9 | { 10 | public enum Type 11 | { 12 | None, 13 | Prefab, 14 | GenerateMesh 15 | } 16 | 17 | private Vector3 _cube = Vector3.zero; 18 | 19 | public Vector3 cube 20 | { 21 | get 22 | { 23 | return _cube; 24 | } 25 | } 26 | 27 | private Vector3 _position = Vector3.zero; 28 | 29 | public Vector3 position 30 | { 31 | get 32 | { 33 | return _position; 34 | } 35 | } 36 | 37 | public float gCost = 0.0f; 38 | 39 | public float hCost = 0.0f; 40 | 41 | public float fCost 42 | { 43 | get 44 | { 45 | return gCost + hCost; 46 | } 47 | } 48 | 49 | private GameObject _go; 50 | 51 | public GameObject go 52 | { 53 | get 54 | { 55 | return _go; 56 | } 57 | } 58 | 59 | /// 60 | /// Sets the Coordinate cube coordinate and world position 61 | /// 62 | /// 63 | /// 64 | public Coordinate(Vector3 cube, Vector3 position) 65 | { 66 | _cube = cube; 67 | _position = position; 68 | } 69 | 70 | /// 71 | /// Sets the GameObject which is associated with the Coordinate 72 | /// 73 | /// 74 | public void SetGameObject(GameObject gameObject) 75 | { 76 | _go = gameObject; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/HappyFace.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CubeCoordinates; 5 | using UnityEngine; 6 | 7 | public class HappyFace : MonoBehaviour 8 | { 9 | private Coordinates coordinates; 10 | 11 | private void Awake() 12 | { 13 | Coordinates.Instance.SetCoordinateType(Coordinate.Type.GenerateMesh); 14 | } 15 | 16 | private void Update() 17 | { 18 | if (Input.GetKeyDown(KeyCode.Space)) BuildMap(); 19 | } 20 | 21 | private void BuildMap() 22 | { 23 | Coordinates.Instance.Clear(); 24 | 25 | // Clear all Coordinates and GameObjects 26 | Coordinates.Instance.Clear(); 27 | 28 | // Cube coordinate radius of 10 from Vector3.zero 29 | List cubes = Cubes.GetNeighbors(Vector3.zero, 10); 30 | 31 | // Cube coordinate circles for the "eyes" 32 | List eye_L = Cubes.GetNeighbors(new Vector3(-4, 5, -1), 2); 33 | List eye_R = Cubes.GetNeighbors(new Vector3(4, 1, -5), 2); 34 | 35 | // Subtract a smaller circle from a larger 36 | List mouth = 37 | Cubes 38 | .BooleanDifference(Cubes.GetNeighbors(new Vector3(0, 1, -1), 8), 39 | Cubes.GetNeighbors(new Vector3(0, 2, -2), 7)); 40 | 41 | // Subtract two lines, to "fill in" the gaps in the first subtraction 42 | mouth = 43 | Cubes 44 | .BooleanDifference(mouth, 45 | Cubes.GetLine(new Vector3(8, 2, -10), new Vector3(8, -10, 2))); 46 | mouth = 47 | Cubes 48 | .BooleanDifference(mouth, 49 | Cubes 50 | .GetLine(new Vector3(-8, 10, -2), new Vector3(-8, -2, 10))); 51 | 52 | // Fille in & remove to make the mouth curved 53 | mouth.Add(new Vector3(-1, -4, 5)); 54 | mouth.Add(new Vector3(1, -5, 4)); 55 | mouth.Add(new Vector3(0, -5, 5)); 56 | mouth.Remove(new Vector3(0, -7, 7)); 57 | 58 | // Remove eyes and mouth from all cubes 59 | cubes = cubes.Except(eye_L).ToList(); 60 | cubes = cubes.Except(eye_R).ToList(); 61 | cubes = cubes.Except(mouth).ToList(); 62 | 63 | // Create Coordinates and Build 64 | Coordinates.Instance.CreateCoordinates (cubes); 65 | Coordinates.Instance.Build(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/RandomPath.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CubeCoordinates; 5 | using UnityEngine; 6 | 7 | public class RandomPath : MonoBehaviour 8 | { 9 | private Coordinates coordinates; 10 | 11 | private void Awake() 12 | { 13 | Coordinates.Instance.SetCoordinateType(Coordinate.Type.GenerateMesh); 14 | } 15 | 16 | private void Update() 17 | { 18 | if (Input.GetKeyDown(KeyCode.Space)) BuildMap(); 19 | } 20 | 21 | private void BuildMap() 22 | { 23 | // Clear all Coordinates and GameObjects 24 | Coordinates.Instance.Clear(); 25 | 26 | // Cube coordinate radius of 10 from Vector3.zero 27 | List cubes = Cubes.GetNeighbors(Vector3.zero, 10); 28 | 29 | // Randomly remove some of the coordinates 30 | for (int i = cubes.Count - 1; i >= 0; i--) 31 | { 32 | // Exclude Vector3.zero, our starting point 33 | if (cubes[i] == Vector3.zero) continue; 34 | 35 | if (Random.Range(0.0f, 100.0f) < 30.0f) cubes.RemoveAt(i); 36 | } 37 | 38 | // Create the Coordinate objects, they're necessary for `GetExpand` to work 39 | Coordinates.Instance.CreateCoordinates (cubes); 40 | 41 | // Get the origin Coordinate 42 | Coordinate origin = 43 | Coordinates.Instance.GetContainer().GetCoordinate(Vector3.zero); 44 | 45 | // Use `GetExpand` to find all Coordinates connected to Vector3.zero, removing others 46 | Coordinates 47 | .Instance 48 | .GetContainer() 49 | .RemoveCoordinates(Coordinates 50 | .Instance 51 | .BooleanDifference(Coordinates 52 | .Instance 53 | .GetContainer() 54 | .GetAllCoordinates(), 55 | Coordinates.Instance.GetExpand(origin, 10))); 56 | 57 | // Build instantiates the GameObjects, connecting them to Coordinate objects 58 | Coordinates.Instance.Build(); 59 | 60 | int steps = 10; 61 | bool found = false; 62 | do 63 | { 64 | if (steps <= 0) return; 65 | 66 | // Get a Ring (circle) of Coordinates at "steps" from origin, from existing Coordinates 67 | List ring = Coordinates.Instance.GetRing(origin, steps); 68 | if (ring.Count > 0) 69 | { 70 | // Get a Path of Coordinates from 'origin' to random Coordinate in Ring results 71 | List path = 72 | Coordinates 73 | .Instance 74 | .GetPath(origin, ring[Random.Range(0, ring.Count - 1)]); 75 | 76 | if (path.Count >= 2) 77 | { 78 | // Add Coordinates to a custom "path" container 79 | Coordinates 80 | .Instance 81 | .GetContainer("path") 82 | .AddCoordinates(path); 83 | found = true; 84 | } 85 | } 86 | 87 | // Reduce steps if Ring results in no Coordinates 88 | steps--; 89 | } 90 | while (!found); 91 | 92 | // Get the path Coordinates from the Container and add some color 93 | foreach (Coordinate 94 | c 95 | in 96 | Coordinates.Instance.GetContainer("path").GetAllCoordinates() 97 | ) 98 | c.go.GetComponent().material.color = Color.red; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CubeCoordinates 2 | 3 | Unity package providing a cube coordinate system and methods for building hexagonal tile grids for interactive gameplay. 4 | 5 | | ![Build a grid of cube coordinates](images/grid_build.jpg) | ![A* Path Tracing](images/grid_path.jpg) | ![Expand to connected coordinates](images/grid_expand.jpg) | 6 | | ---------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------- | 7 | 8 | ## Install 9 | 10 | To install, copy the `CubeCoordinates` Unity Package directory into your project's `Packages` directory. 11 | 12 | ```csharp 13 | using CubeCoordinates; 14 | 15 | ... 16 | 17 | Coordinates coordinates = Coordinates.Instance; 18 | coordinates.SetCoordinateType(Coordinate.Type.GenerateMesh); 19 | 20 | List cubes = Cubes.GetNeighbors(Vector3.zero, 10); 21 | coordinates.CreateCoordinates(cubes); 22 | 23 | coordinates.Build(); 24 | ``` 25 | 26 | Please check the [examples](examples) folder and [documentation zip](documentation/docs.zip) to learn more. 27 | 28 | --- 29 | 30 | ## How to Use 31 | 32 | #### Coordinates 33 | 34 | Used to create and manage `Coordinate` instances, it uses an `"all"` default `Container` as the master list for it's methods. 35 | 36 | ```csharp 37 | Coordinates coordinates = Coordinates.Instance; 38 | coordinates.SetCoordinateType(Coordinate.Type.Prefab, myGameObject); 39 | 40 | coordinates.CreateCoordinates( 41 | Cubes.GetNeighbors(Vector3.zero, 10) 42 | ); 43 | 44 | Coordinate origin = coordinates.GetContainer().GetCoordinate(Vector3.zero); 45 | Coordinate destination = coordinates.GetContainer().GetCoordinate(new Vector3(4,1,-5)); 46 | 47 | Coordinates.Instance.Build(); 48 | 49 | List diff = coordinates.BooleanDifference( 50 | coordinates.GetNeighbors(origin, 4), 51 | coordinates.GetNeighbors(origin, 2) 52 | ); 53 | 54 | List path = Coordinates.Instance.GetPath(origin, destination); 55 | ``` 56 | 57 | #### Coordinate 58 | 59 | Individual `Coordinate` instances represents tiles on the grid. It's purpose is to keep track of cube coordinates, transform position in world space, reference any GameObject instantiated to represent it, facilitate path finding by storing cost values and be supplied as an origin for many `Coordinate` operations. 60 | 61 | ```csharp 62 | Coordinate coordinate = Coordinates.Instance.GetContainer().GetCoordinate(new Vector3(4,1,-5)); 63 | myGameObject.transform.position = coordinate.position; 64 | coordinate.SetGameObject(myGameObject); 65 | ``` 66 | 67 | #### Container 68 | 69 | Any number of `Container` instances can be optionally created - except for `"all"`, which `Coordinates` uses as a master list internally - to manage your own lists of `Coordinate` instances that you can later retrieve for your own purposes. 70 | 71 | ```csharp 72 | Container moveArea = Coordinates.Instance.GetContainer("move_area"); 73 | moveArea.AddCoordinates( new List{coordinateA, coordinateB}); 74 | 75 | foreach(Coordinate c in moveArea.GetAllCoordinates()) 76 | c.go.GetComponent().DoSomething(); 77 | 78 | movement_range.RemoveAllCoordinates(); 79 | ``` 80 | 81 | #### Cubes 82 | 83 | Collection of cube coordinate system methods for calculating desired coordinate results. These methods _do not_ map to `Coordinate` instances; they simply calculate results from an infinite plane of coordinates as requested. Using `Cubes` directly is desirable when combining several operations together because you can calculate the results and last pull any matching `Coordinate` instances from a `Container` by supplying the results. 84 | 85 | ```csharp 86 | List attackShape = Cubes.BooleanCombine( 87 | Cubes.GetRing(Vector3.zero, 4), 88 | Cubes.GetDiagonalNeighbors(Vector3.zero, 3) 89 | ); 90 | 91 | Vector3 activeCube = Cubes.ConvertWorldPositionToCube(myGameObject.transform.position); 92 | 93 | List attackShapeOnMap = new List(); 94 | foreach (Vector3 cube in attackShape) 95 | attackShapeOnMap.Add(cube + activeCube); 96 | 97 | Container attackTiles = Coordinates.Instance.GetContainer("attack_tiles"); 98 | List attackShapeCoordinates = attackTiles.GetCoordinates(attackShapeOnMap); 99 | 100 | foreach (Coordinate c in attackShapeCoordinates) 101 | c.go.GetComponent().DoSomething(); 102 | ``` 103 | 104 | #### MeshCreator 105 | 106 | Used to generate hexagonal meshes when using `Coordinate.Type.GenerateMesh` which is useful for debugging. 107 | 108 | --- 109 | 110 | ### Credit 111 | 112 | This package was developed by largely following the explanations found on this post: 113 | 114 | https://www.redblobgames.com/grids/hexagons/ 115 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Container.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace CubeCoordinates 5 | { 6 | /// 7 | /// Used to manage lists of Coordinate instances for a given label 8 | /// 9 | public class Container 10 | { 11 | private string _label; 12 | 13 | public string label 14 | { 15 | get 16 | { 17 | return label; 18 | } 19 | } 20 | 21 | private Dictionary _contents; 22 | 23 | /// 24 | /// Constructor for Container which accepts a label and prepares the internal Dictionary 25 | /// 26 | /// Label string to name the Container 27 | public Container(string label) 28 | { 29 | _label = label; 30 | _contents = new Dictionary(); 31 | } 32 | 33 | /// 34 | /// Add a Coordinate instance to the Container 35 | /// 36 | /// Coordinate 37 | public void AddCoordinates(Coordinate coordinate) 38 | { 39 | AddCoordinates(new List { coordinate }); 40 | } 41 | 42 | /// 43 | /// Add a Coordinate List instances to the Container 44 | /// 45 | /// Coordinate List 46 | public void AddCoordinates(List coordinates) 47 | { 48 | foreach (Coordinate coordinate in coordinates) 49 | if (!_contents.ContainsKey(coordinate.cube)) 50 | _contents.Add(coordinate.cube, coordinate); 51 | } 52 | 53 | /// 54 | /// Remove a Coordinate instance from the Container 55 | /// 56 | /// Coordinate 57 | public void RemoveCoordinates(Coordinate coordinate) 58 | { 59 | RemoveCoordinates(new List { coordinate }); 60 | } 61 | 62 | /// 63 | /// Remove a Coordinate List from the Container 64 | /// 65 | /// Coordinate List 66 | public void RemoveCoordinates(List coordinates) 67 | { 68 | foreach (Coordinate coordinate in coordinates) 69 | if (_contents.ContainsKey(coordinate.cube)) 70 | _contents.Remove(coordinate.cube); 71 | } 72 | 73 | /// 74 | /// Remove all Coordinate instances from the Container 75 | /// 76 | public void RemoveAllCoordinates() 77 | { 78 | _contents.Clear(); 79 | } 80 | 81 | /// 82 | /// Gets a Coordinate matching the cube coordinate supplied, if found 83 | /// 84 | /// Vector3 cube coordinate 85 | /// Coordinate instance if found, null otherwise 86 | public Coordinate GetCoordinate(Vector3 cube) 87 | { 88 | Coordinate coordinate = null; 89 | _contents.TryGetValue(cube, out coordinate); 90 | return coordinate; 91 | } 92 | 93 | /// 94 | /// Gets a Coordinate matching the closest rounded cube coordinate to the world transform position (Coordinate may not exist at rounded cube coordinate) 95 | /// 96 | /// Vector3 transform position in world space 97 | /// Coordinate instance if found 98 | public Coordinate GetCoordinateFromWorldPosition(Vector3 position) 99 | { 100 | Vector3 cube = Cubes.ConvertWorldPositionToCube(position); 101 | return GetCoordinate(cube); 102 | } 103 | 104 | /// 105 | /// Get a Coordinate List that match any of the supplied cube coordinates 106 | /// 107 | /// Vector3 List of cube coordinates 108 | /// List of Coordinate instances found 109 | public List GetCoordinates(List cubes) 110 | { 111 | List results = new List(); 112 | foreach (Vector3 cube in cubes) 113 | { 114 | Coordinate coordinate = GetCoordinate(cube); 115 | if (coordinate != null) results.Add(coordinate); 116 | } 117 | return results; 118 | } 119 | 120 | /// 121 | /// Gets a Coordinate List of all Coordinate instances in the Container 122 | /// 123 | /// List of Coordinate instances 124 | public List GetAllCoordinates() 125 | { 126 | return new List(_contents.Values); 127 | } 128 | 129 | /// 130 | /// Gets a Vector3 cube coordinate List for all Coordinate instances in the Container 131 | /// 132 | /// List of Vector3 cube coordinates 133 | public List GetAllCubes() 134 | { 135 | return new List(_contents.Keys); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/MeshCreator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace CubeCoordinates 5 | { 6 | /// 7 | /// Generates hexagonal meshes useful for debugging and prototyping purposes 8 | /// 9 | public class MeshCreator : MonoBehaviour 10 | { 11 | private static MeshCreator _instance; 12 | 13 | private static readonly object Lock = new object(); 14 | 15 | public static MeshCreator Instance 16 | { 17 | get 18 | { 19 | lock (Lock) 20 | { 21 | if (_instance != null) return _instance; 22 | 23 | GameObject obj = 24 | new GameObject("{MonoBehaviour}<{" + 25 | typeof (MeshCreator).ToString() + 26 | "}>"); 27 | DontDestroyOnLoad (obj); 28 | return _instance = obj.AddComponent(); 29 | } 30 | } 31 | } 32 | 33 | /// 34 | /// Generates a GameObject with hexagonal and outline meshes 35 | /// 36 | /// Radius of the tile geometry 37 | /// GameObject 38 | public GameObject CreateGameObject(float radius) 39 | { 40 | GameObject go = new GameObject("generated"); 41 | go.hideFlags = HideFlags.HideInHierarchy; 42 | go.SetActive(false); 43 | 44 | AddBaseMeshToGameObject(go, radius, Color.white); 45 | 46 | GameObject go_outline = new GameObject("outline"); 47 | go_outline.transform.parent = go.transform; 48 | 49 | AddOutlineMeshToGameObject(go_outline, radius, Color.black); 50 | 51 | return go; 52 | } 53 | 54 | /// 55 | /// Generates the base hexagonal mesh and adds it to the specified GameObject 56 | /// 57 | /// GameObject to add mesh to 58 | /// Radius of the tile geometry 59 | /// Color to apply to the geometry 60 | public void AddBaseMeshToGameObject( 61 | GameObject go, 62 | float radius, 63 | Color color 64 | ) 65 | { 66 | Mesh mesh = MeshCreator.Instance.GetHexBase(radius); 67 | PrepareGameObject (go, mesh, color); 68 | } 69 | 70 | /// 71 | /// Generates the outline mesh and adds it to the specified GameObject 72 | /// 73 | /// GameObject to add mesh to 74 | /// Radius of the outline geometry 75 | /// Color to apply to the geometry 76 | public void AddOutlineMeshToGameObject( 77 | GameObject go, 78 | float radius, 79 | Color color 80 | ) 81 | { 82 | Mesh mesh = MeshCreator.Instance.GetHexOutline(radius); 83 | PrepareGameObject (go, mesh, color); 84 | } 85 | 86 | /// 87 | /// Prepares a GameObject with MeshRenderer and MeshFilter 88 | /// 89 | /// GameObject to add Components to 90 | /// Mesh to add to GameObject 91 | /// Color to apply to MeshRenderer 92 | private void PrepareGameObject(GameObject go, Mesh mesh, Color color) 93 | { 94 | MeshRenderer meshRenderer = go.GetComponent(); 95 | if (meshRenderer == null) 96 | meshRenderer = go.AddComponent(); 97 | meshRenderer.shadowCastingMode = 98 | UnityEngine.Rendering.ShadowCastingMode.Off; 99 | meshRenderer.receiveShadows = false; 100 | meshRenderer.material = new Material(Shader.Find("Standard")); 101 | 102 | MeshFilter meshFilter = go.GetComponent(); 103 | if (meshFilter == null) meshFilter = go.AddComponent(); 104 | meshRenderer.material.color = color; 105 | meshFilter.mesh = mesh; 106 | } 107 | 108 | /// 109 | /// Generates a hexagonal base Mesh 110 | /// 111 | /// Radius of the hexagonal mesh to create 112 | /// Mesh geometry 113 | private Mesh GetHexBase(float radius) 114 | { 115 | Mesh mesh = new Mesh(); 116 | mesh.name = "hex_base"; 117 | 118 | Vector3[] verts = new Vector3[6]; 119 | 120 | verts[0] = GetVertex(0, radius); 121 | verts[1] = GetVertex(1, radius); 122 | verts[2] = GetVertex(2, radius); 123 | verts[3] = GetVertex(3, radius); 124 | verts[4] = GetVertex(4, radius); 125 | verts[5] = GetVertex(5, radius); 126 | 127 | mesh.vertices = verts; 128 | 129 | int[] triangles = new int[12]; 130 | 131 | triangles[0] = 0; 132 | triangles[1] = 5; 133 | triangles[2] = 1; 134 | triangles[3] = 1; 135 | triangles[4] = 5; 136 | triangles[5] = 2; 137 | triangles[6] = 5; 138 | triangles[7] = 4; 139 | triangles[8] = 2; 140 | triangles[9] = 4; 141 | triangles[10] = 3; 142 | triangles[11] = 2; 143 | 144 | mesh.triangles = triangles; 145 | mesh.RecalculateNormals(); 146 | 147 | return mesh; 148 | } 149 | 150 | /// 151 | /// Generates a hexagonal outline Mesh 152 | /// 153 | /// Radius of the hexagonal outline mesh to create 154 | /// Mesh geometry 155 | private Mesh GetHexOutline(float radius) 156 | { 157 | Mesh mesh = new Mesh(); 158 | mesh.name = "hexTileOutline"; 159 | 160 | Vector3[] verts = new Vector3[6]; 161 | 162 | verts[0] = GetVertex(0, radius); 163 | verts[1] = GetVertex(1, radius); 164 | verts[2] = GetVertex(2, radius); 165 | verts[3] = GetVertex(3, radius); 166 | verts[4] = GetVertex(4, radius); 167 | verts[5] = GetVertex(5, radius); 168 | 169 | Vector3[] v = new Vector3[12]; 170 | 171 | float upOffset = radius * 0.005f; 172 | float innerRadius = radius * 0.8f; 173 | 174 | v[0] = verts[0] + (Vector3.up * upOffset); 175 | v[1] = verts[1] + (Vector3.up * upOffset); 176 | v[2] = verts[2] + (Vector3.up * upOffset); 177 | v[3] = verts[3] + (Vector3.up * upOffset); 178 | v[4] = verts[4] + (Vector3.up * upOffset); 179 | v[5] = verts[5] + (Vector3.up * upOffset); 180 | v[6] = (verts[0] * innerRadius) + (Vector3.up * upOffset); 181 | v[7] = (verts[1] * innerRadius) + (Vector3.up * upOffset); 182 | v[8] = (verts[2] * innerRadius) + (Vector3.up * upOffset); 183 | v[9] = (verts[3] * innerRadius) + (Vector3.up * upOffset); 184 | v[10] = (verts[4] * innerRadius) + (Vector3.up * upOffset); 185 | v[11] = (verts[5] * innerRadius) + (Vector3.up * upOffset); 186 | 187 | mesh.vertices = v; 188 | 189 | int[] triangles = new int[36]; 190 | 191 | triangles[0] = 0; 192 | triangles[1] = 5; 193 | triangles[2] = 11; 194 | 195 | triangles[3] = 0; 196 | triangles[4] = 11; 197 | triangles[5] = 6; 198 | 199 | triangles[6] = 1; 200 | triangles[7] = 0; 201 | triangles[8] = 6; 202 | 203 | triangles[9] = 1; 204 | triangles[10] = 6; 205 | triangles[11] = 7; 206 | 207 | triangles[12] = 2; 208 | triangles[13] = 1; 209 | triangles[14] = 7; 210 | 211 | triangles[15] = 2; 212 | triangles[16] = 7; 213 | triangles[17] = 8; 214 | 215 | triangles[18] = 2; 216 | triangles[19] = 8; 217 | triangles[20] = 9; 218 | 219 | triangles[21] = 2; 220 | triangles[22] = 9; 221 | triangles[23] = 3; 222 | 223 | triangles[24] = 3; 224 | triangles[25] = 9; 225 | triangles[26] = 10; 226 | 227 | triangles[27] = 3; 228 | triangles[28] = 10; 229 | triangles[29] = 4; 230 | 231 | triangles[30] = 4; 232 | triangles[31] = 10; 233 | triangles[32] = 11; 234 | 235 | triangles[33] = 4; 236 | triangles[34] = 11; 237 | triangles[35] = 5; 238 | 239 | mesh.triangles = triangles; 240 | mesh.RecalculateNormals(); 241 | 242 | return mesh; 243 | } 244 | 245 | /// 246 | /// Retruns a vertex for mesh generation 247 | /// 248 | /// index 249 | /// radius 250 | /// Vector3 251 | private Vector3 GetVertex(int i, float radius) 252 | { 253 | float angle_deg = 60.0f * (float) i; 254 | float angle_rad = (Mathf.PI / 180.0f) * angle_deg; 255 | return new Vector3((radius * Mathf.Cos(angle_rad)), 256 | 0.0f, 257 | (radius * Mathf.Sin(angle_rad))); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Cubes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | namespace CubeCoordinates 6 | { 7 | /// 8 | /// Cubes Coordinate System mathematics 9 | /// 10 | public static class Cubes 11 | { 12 | public static Vector3[] 13 | directions = 14 | { 15 | new Vector3(1.0f, -1.0f, 0.0f), 16 | new Vector3(1.0f, 0.0f, -1.0f), 17 | new Vector3(0.0f, 1.0f, -1.0f), 18 | new Vector3(-1.0f, 1.0f, 0.0f), 19 | new Vector3(-1.0f, 0.0f, 1.0f), 20 | new Vector3(0.0f, -1.0f, 1.0f) 21 | }; 22 | 23 | public static Vector3[] 24 | diagonals = 25 | { 26 | new Vector3(2.0f, -1.0f, -1.0f), 27 | new Vector3(1.0f, 1.0f, -2.0f), 28 | new Vector3(-1.0f, 2.0f, -1.0f), 29 | new Vector3(-2.0f, 1.0f, 1.0f), 30 | new Vector3(-1.0f, -1.0f, 2.0f), 31 | new Vector3(1.0f, -2.0f, 1.0f) 32 | }; 33 | 34 | /// 35 | /// Caculates adjacent cube coordinate for a given direction and distance 36 | /// 37 | /// Vector3 origin 38 | /// direction of neighbor (0-5 is valid) 39 | /// distance from origin (>=1 is valid) 40 | /// Vector3 cube coordinate 41 | public static Vector3 42 | GetNeighbor(Vector3 origin, int direction, int distance) 43 | { 44 | return origin + directions[direction] * (float) distance; 45 | } 46 | 47 | /// 48 | /// Calculates adjacent cube coordinates over a number of steps 49 | /// 50 | /// Vector3 origin 51 | /// steps from origin (>=1 is valid) 52 | /// List of Vector3 cube coordinates 53 | public static List GetNeighbors(Vector3 origin, int steps) 54 | { 55 | List results = new List(); 56 | 57 | for ( 58 | int x = (int)(origin.x - steps); 59 | x <= (int)(origin.x + steps); 60 | x++ 61 | ) 62 | for ( 63 | int y = (int)(origin.y - steps); 64 | y <= (int)(origin.y + steps); 65 | y++ 66 | ) 67 | for ( 68 | int z = (int)(origin.z - steps); 69 | z <= (int)(origin.z + steps); 70 | z++ 71 | ) 72 | if ((x + y + z) == 0) results.Add(new Vector3(x, y, z)); 73 | 74 | return results; 75 | } 76 | 77 | /// 78 | /// Caculates diagonally adjacent cube coordinate for a given direction and distance 79 | /// 80 | /// Vector3 origin 81 | /// direction of neighbor (0-5 is valid) 82 | /// distance from origin (>=1 is valid) 83 | /// Vector3 cube coordinate 84 | public static Vector3 85 | GetDiagonalNeighbor(Vector3 origin, int direction, int distance) 86 | { 87 | return origin + diagonals[direction] * (float) distance; 88 | } 89 | 90 | /// 91 | /// Calculates diagonally adjacent cube coordinates at a given distance from origin 92 | /// 93 | /// Vector3 origin 94 | /// distance from origin (>=1 is valid) 95 | /// List of Vector3 cube coordinates 96 | public static List 97 | GetDiagonalNeighbors(Vector3 origin, int distance) 98 | { 99 | List results = new List(); 100 | for (int i = 0; i < 6; i++) 101 | for (int s = 1; s <= distance; s++) 102 | results.Add(GetDiagonalNeighbor(origin, i, s)); 103 | return results; 104 | } 105 | 106 | /// 107 | /// Calculates a line of cube coordinates between two cube coordinates 108 | /// 109 | /// Vector3 start 110 | /// Vector3 end 111 | /// List of Vector3 cube coordinates 112 | public static List GetLine(Vector3 a, Vector3 b) 113 | { 114 | List results = new List(); 115 | float d = GetDistanceBetweenTwoCubes(a, b); 116 | for (int i = 0; i <= d; i++) 117 | results 118 | .Add(RoundCube(GetLerpBetweenTwoCubes(a, b, ((1.0f / d) * i)))); 119 | results.Add (a); 120 | return results; 121 | } 122 | 123 | /// 124 | /// Calculates a cube coordinate on a line of cube coordinates at a specified distance from origin 125 | /// 126 | /// Vector3 start 127 | /// Vector3 end 128 | /// distance from start 129 | /// Vector3 cube coordinate 130 | public static Vector3 GetPointOnLine(Vector3 a, Vector3 b, int distance) 131 | { 132 | float d = GetDistanceBetweenTwoCubes(a, b); 133 | return RoundCube(GetLerpBetweenTwoCubes(a, 134 | b, 135 | ((1.0f / d) * distance))); 136 | } 137 | 138 | /// 139 | /// Calculates a ring (hexagonal) of cube coordinates at a specified distance from origin 140 | /// 141 | /// Vector3 origin 142 | /// distance from origin (>=1 is valid) 143 | /// List of Vector3 cube coordinates 144 | public static List GetRing(Vector3 origin, int distance) 145 | { 146 | List results = new List(); 147 | Vector3 current = origin + directions[4] * (float) distance; 148 | for (int i = 0; i < 6; i++) 149 | { 150 | for (int j = 0; j < distance; j++) 151 | { 152 | results.Add (current); 153 | current += directions[i]; 154 | } 155 | } 156 | return results; 157 | } 158 | 159 | /// 160 | /// Calculates an ordered list of cube coordinates in a clockwise spiral pattern over a specified number of steps away from origin 161 | /// 162 | /// Vector3 origin 163 | /// steps from origin (>=1 is valid) 164 | /// List of Vector3 cube coordinates 165 | public static List GetSpiral(Vector3 origin, int steps) 166 | { 167 | List results = new List(); 168 | results.Add (origin); 169 | for (int i = 0; i <= steps; i++) 170 | results.AddRange(GetRing(origin, i)); 171 | return results; 172 | } 173 | 174 | /// 175 | /// Combines two lists of cube coordinates, without duplicates 176 | /// 177 | /// Vector3 List a 178 | /// Vector3 List b 179 | /// List of Vector3 cube coordinates 180 | public static List 181 | BooleanCombine(List a, List b) 182 | { 183 | List results = new List(); 184 | results.AddRange (a); 185 | foreach (Vector3 vb in b) if (!a.Contains(vb)) results.Add(vb); 186 | return results; 187 | } 188 | 189 | /// 190 | /// Subtracts cube coordinates from a if they are present in b 191 | /// 192 | /// Vector3 List a 193 | /// Vector3 List b 194 | /// List of Vector3 cube coordinates 195 | public static List 196 | BooleanDifference(List a, List b) 197 | { 198 | List results = new List(); 199 | results.AddRange (a); 200 | foreach (Vector3 vb in b) if (a.Contains(vb)) results.Remove(vb); 201 | return results; 202 | } 203 | 204 | /// 205 | /// Caculates which cube coordinates are share in both a and b 206 | /// 207 | /// Vector3 List a 208 | /// Vector3 List b 209 | /// List of Vector3 cube coordinates 210 | public static List 211 | BooleanIntersect(List a, List b) 212 | { 213 | List results = new List(); 214 | foreach (Vector3 va in a) 215 | foreach (Vector3 vb in b) if (va == vb) results.Add(va); 216 | return results; 217 | } 218 | 219 | /// 220 | /// Calculates which cube coordinates are mutually exclusive in a and b 221 | /// 222 | /// Vector3 List a 223 | /// Vector3 List b 224 | /// List of Vector3 cube coordinates 225 | public static List 226 | BooleanExclude(List a, List b) 227 | { 228 | return BooleanDifference(BooleanCombine(a, b), 229 | BooleanIntersect(a, b)); 230 | } 231 | 232 | /// 233 | /// Rounds a provided Vector3 to the nearest valid cube coordinate 234 | /// 235 | /// Vector3 input cube coordinate (does not have to be valid) 236 | /// Vector3 cube coordinate 237 | public static Vector3 RoundCube(Vector3 cube) 238 | { 239 | float rx = Mathf.Round(cube.x); 240 | float ry = Mathf.Round(cube.y); 241 | float rz = Mathf.Round(cube.z); 242 | 243 | float x_diff = Mathf.Abs(rx - cube.x); 244 | float y_diff = Mathf.Abs(ry - cube.y); 245 | float z_diff = Mathf.Abs(rz - cube.z); 246 | 247 | if (x_diff > y_diff && x_diff > z_diff) 248 | rx = -ry - rz; 249 | else if (y_diff > z_diff) 250 | ry = -rx - rz; 251 | else 252 | rz = -rx - ry; 253 | 254 | return new Vector3(rx, ry, rz); 255 | } 256 | 257 | /// 258 | /// Rotates the provided cube coordinate right, around Vector3.zero 259 | /// 260 | /// Vector3 cube coordinate 261 | /// Vector3 cube coordinate 262 | public static Vector3 RotateCubeCoordinatesRight(Vector3 cube) 263 | { 264 | return new Vector3(-cube.z, -cube.x, -cube.y); 265 | } 266 | 267 | /// 268 | /// Rotates the provided cube coordinate left, around Vector3.zero 269 | /// 270 | /// Vector3 cube coordinate 271 | /// Vector3 cube coordinate 272 | public static Vector3 RotateCubeCoordinatesLeft(Vector3 cube) 273 | { 274 | return new Vector3(-cube.y, -cube.z, -cube.x); 275 | } 276 | 277 | /// 278 | /// Converts a Vector3 cube coordinate to a Vector2 axial coordinate 279 | /// 280 | /// Vector3 cube coordinate 281 | /// Vector2 axial coordinate 282 | public static Vector2 ConvertCubeToAxial(Vector3 cube) 283 | { 284 | return new Vector2(cube.x, cube.z); 285 | } 286 | 287 | /// 288 | /// Converts a Vector2 axial coordinate to a Vector3 cube coordinate 289 | /// 290 | /// Vector2 axial coordinate 291 | /// Vector3 cube coordinate 292 | public static Vector3 ConvertAxialToCube(Vector2 axial) 293 | { 294 | return new Vector3(axial.x, (-axial.x - axial.y), axial.y); 295 | } 296 | 297 | /// 298 | /// Converts a Vector2 axial coordinate to a Vector3 world transform position 299 | /// 300 | /// Vector2 axial coordinte 301 | /// Vector3 world transform position 302 | public static Vector3 ConvertAxialToWorldPosition(Vector2 axial) 303 | { 304 | return new Vector3(axial.x * Coordinates.Instance.spacingX, 305 | 0.0f, 306 | -( 307 | (axial.x * Coordinates.Instance.spacingZ) + 308 | (axial.y * Coordinates.Instance.spacingZ * 2.0f) 309 | )); 310 | } 311 | 312 | /// 313 | /// Converts a Vector3 cube coordinate to a Vector3 world transform position 314 | /// 315 | /// Vector3 cube coordinate 316 | /// Vector3 world transform position 317 | public static Vector3 ConvertCubeToWorldPosition(Vector3 cube) 318 | { 319 | return new Vector3(cube.x * Coordinates.Instance.spacingX, 320 | 0.0f, 321 | -( 322 | (cube.x * Coordinates.Instance.spacingZ) + 323 | (cube.z * Coordinates.Instance.spacingZ * 2.0f) 324 | )); 325 | } 326 | 327 | /// 328 | /// Converts a Vector3 world transform position to Vector2 axial coordinate 329 | /// 330 | /// Vector3 world transform position 331 | /// Vector2 axial coordinate 332 | public static Vector2 ConvertWorldPositionToAxial(Vector3 position) 333 | { 334 | float q = 335 | (position.x * (2.0f / 3.0f)) / Coordinates.Instance.radius; 336 | float r = 337 | ((-position.x / 3.0f) + ((Mathf.Sqrt(3) / 3.0f) * position.z)) / 338 | Coordinates.Instance.radius; 339 | return RoundAxial(new Vector2(q, r)); 340 | } 341 | 342 | /// 343 | /// Converts a Vector3 world transform position to Vector3 cube coordinate 344 | /// 345 | /// Vector3 world transfor position 346 | /// Vector3 cube coordinate 347 | public static Vector3 ConvertWorldPositionToCube(Vector3 position) 348 | { 349 | return ConvertAxialToCube(ConvertWorldPositionToAxial(position)); 350 | } 351 | 352 | /// 353 | /// Rounds a calculated (may not be valid) axial coordinate to the nearest valid Axial coordinate 354 | /// 355 | /// Vector2 axial coordinate 356 | /// Vector2 axial coordinate 357 | public static Vector2 RoundAxial(Vector2 axial) 358 | { 359 | return RoundCube(ConvertAxialToCube(axial)); 360 | } 361 | 362 | /// 363 | /// Calculates the distance betwen two Vector3 cube coordinates 364 | /// 365 | /// Vector3 cube coordinate a 366 | /// Vector3 cube coordinate b 367 | /// float distance 368 | public static float GetDistanceBetweenTwoCubes(Vector3 a, Vector3 b) 369 | { 370 | return Mathf 371 | .Max(Mathf.Abs(a.x - b.x), 372 | Mathf.Abs(a.y - b.y), 373 | Mathf.Abs(a.z - b.z)); 374 | } 375 | 376 | /// 377 | /// Calcaultes a lerp between two Vector3 cube coordinates at a given distance 378 | /// 379 | /// Vector3 cube coordinate a 380 | /// Vector3 cube coordinate b 381 | /// float distance (0.0f to 1.0f) 382 | /// Vector3 cube coordinate 383 | public static Vector3 384 | GetLerpBetweenTwoCubes(Vector3 a, Vector3 b, float t) 385 | { 386 | Vector3 cube = 387 | new Vector3(a.x + (b.x - a.x) * t, 388 | a.y + (b.y - a.y) * t, 389 | a.z + (b.z - a.z) * t); 390 | return cube; 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /CubeCoordinates/Runtime/Coordinates.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | namespace CubeCoordinates 6 | { 7 | /// 8 | /// Manages Coordinate and Container instances, instantiation of GameObject tiles 9 | /// 10 | public class Coordinates : MonoBehaviour 11 | { 12 | private static Coordinates _instance; 13 | 14 | private static readonly object Lock = new object(); 15 | 16 | public static Coordinates Instance 17 | { 18 | get 19 | { 20 | lock (Lock) 21 | { 22 | if (_instance != null) return _instance; 23 | 24 | GameObject obj = 25 | new GameObject("{MonoBehaviour}<{" + 26 | typeof (Coordinates).ToString() + 27 | "}>"); 28 | DontDestroyOnLoad (obj); 29 | return _instance = obj.AddComponent(); 30 | } 31 | } 32 | } 33 | 34 | private GameObject _coordinateGroup; 35 | 36 | private GameObject _coordinateGameObject = null; 37 | 38 | private Coordinate.Type _coordinateType = Coordinate.Type.None; 39 | 40 | private Dictionary 41 | _containers = new Dictionary(); 42 | 43 | private float _scale = 1.0f; 44 | 45 | public float scale 46 | { 47 | get 48 | { 49 | return _scale; 50 | } 51 | } 52 | 53 | private float _radius = 1.0f; 54 | 55 | public float radius 56 | { 57 | get 58 | { 59 | return _radius; 60 | } 61 | } 62 | 63 | private float _spacingX = 0.0f; 64 | 65 | public float spacingX 66 | { 67 | get 68 | { 69 | return _spacingX; 70 | } 71 | } 72 | 73 | private float _spacingZ = 0.0f; 74 | 75 | public float spacingZ 76 | { 77 | get 78 | { 79 | return _spacingZ; 80 | } 81 | } 82 | 83 | private void Awake() 84 | { 85 | CalculateDimensions(); 86 | _coordinateGroup = new GameObject("(Group) Coordinates"); 87 | } 88 | 89 | /// 90 | /// Uses global radius and scale to calculate coordinate spacing 91 | /// 92 | private void CalculateDimensions() 93 | { 94 | _radius = _radius * _scale; 95 | _spacingX = _radius * 2 * 0.75f; 96 | _spacingZ = ((Mathf.Sqrt(3) / 2.0f) * (_radius * 2) / 2); 97 | } 98 | 99 | /// 100 | /// Destroys all Coordinate, Container and GameObject instances created by Coordinates 101 | /// 102 | public void Clear() 103 | { 104 | _containers.Clear(); 105 | Destroy (_coordinateGroup); 106 | _coordinateGroup = new GameObject("(Group) Coordinates"); 107 | } 108 | 109 | /// 110 | /// Sets the desired instantiation type and GameObject for the Build method 111 | /// 112 | /// Coordinate.Type enum (None, Prefab, GenerateMesh) 113 | /// Prefab GameObject to Instatiate at the Coordinate world position 114 | public void SetCoordinateType( 115 | Coordinate.Type type, 116 | GameObject gameObject = null 117 | ) 118 | { 119 | _coordinateType = type; 120 | 121 | if (_coordinateGameObject != null) Destroy(_coordinateGameObject); 122 | 123 | switch (_coordinateType) 124 | { 125 | case Coordinate.Type.None: 126 | _coordinateGameObject = null; 127 | break; 128 | case Coordinate.Type.Prefab: 129 | _coordinateGameObject = gameObject; 130 | break; 131 | case Coordinate.Type.GenerateMesh: 132 | _coordinateGameObject = 133 | MeshCreator.Instance.CreateGameObject(_radius); 134 | break; 135 | } 136 | } 137 | 138 | /// 139 | /// Creates Coordinate instances from a list of valid cube coordinates 140 | /// 141 | /// List of Vector3 cube coordinates 142 | public void CreateCoordinates(List cubes) 143 | { 144 | Container container = GetContainer(); 145 | List coordinates = new List(); 146 | 147 | foreach (Vector3 cube in cubes) 148 | { 149 | if (container.GetCoordinate(cube) != null) continue; 150 | Coordinate coordinate = 151 | new Coordinate(cube, 152 | Cubes.ConvertCubeToWorldPosition(cube)); 153 | coordinates.Add (coordinate); 154 | } 155 | 156 | container.AddCoordinates (coordinates); 157 | } 158 | 159 | /// 160 | /// Instantiates the GameObject specified in SetCoordinateType for all Coordinate instances in the "all" Container 161 | /// 162 | public void Build() 163 | { 164 | if (_coordinateType == Coordinate.Type.None) return; 165 | 166 | List coordinates = GetContainer().GetAllCoordinates(); 167 | foreach (Coordinate coordinate in coordinates) 168 | { 169 | string label = 170 | "Coordinate [" + 171 | coordinate.cube.x + 172 | "," + 173 | coordinate.cube.y + 174 | "," + 175 | coordinate.cube.z + 176 | "]"; 177 | GameObject go = 178 | (GameObject) 179 | Instantiate(_coordinateGameObject, 180 | coordinate.position, 181 | Quaternion.identity); 182 | go.name = label; 183 | go.transform.parent = _coordinateGroup.transform; 184 | go.SetActive(true); 185 | coordinate.SetGameObject (go); 186 | } 187 | } 188 | 189 | /// 190 | /// Returns a Container for the provided key (will create if not found) 191 | /// 192 | /// String used to identify the Container, defaults to "all" 193 | /// Container with label matching key 194 | public Container GetContainer(string key = "all") 195 | { 196 | Container container; 197 | if (!_containers.TryGetValue(key, out container)) 198 | { 199 | CreateContainer (key); 200 | _containers.TryGetValue(key, out container); 201 | } 202 | return container; 203 | } 204 | 205 | /// 206 | /// Creates a new Container instance, used by GetContainer internally 207 | /// /// 208 | /// String used to identify the Container 209 | private void CreateContainer(string key) 210 | { 211 | if (!_containers.ContainsKey(key)) 212 | _containers.Add(key, new Container(key)); 213 | } 214 | 215 | /// 216 | /// Gets a Coordinate instance adjacent to the origin Coordinate at a given distance and direction 217 | /// 218 | /// Coordinate 219 | /// Direction to get (0 to 5 valid) 220 | /// Distance from origin (>=1 valid) 221 | /// Container to get results from, defaults to "all" 222 | /// Coordinate instance if found, otherwise null 223 | public Coordinate 224 | GetNeighbor( 225 | Coordinate origin, 226 | int direction, 227 | int distance, 228 | string container_label = "all" 229 | ) 230 | { 231 | return GetContainer(container_label) 232 | .GetCoordinate(Cubes 233 | .GetNeighbor(origin.cube, direction, distance)); 234 | } 235 | 236 | /// 237 | /// Gets a List of Coordinate instances adjacent to the origin, for the specified number of steps 238 | /// 239 | /// Coordinate 240 | /// Number of iterations outwards to get (>=1 is valid) 241 | /// Container to get results from, defaults to "all" 242 | /// List of Coordinate instances found 243 | public List 244 | GetNeighbors( 245 | Coordinate origin, 246 | int steps, 247 | string container_label = "all" 248 | ) 249 | { 250 | return GetContainer(container_label) 251 | .GetCoordinates(Cubes.GetNeighbors(origin.cube, steps)); 252 | } 253 | 254 | /// 255 | /// Gets a Coordinate instance diagonally adjacent to the Origin at a given distance and direction 256 | /// 257 | /// Coordinate 258 | /// Direction to get (0 to 5 valid) 259 | /// Distance from origin (>=1 valid) 260 | /// Container to get results from, defaults to "all" 261 | /// Coordinate instance if found, otherwise null 262 | public Coordinate 263 | GetDiagonalNeighbor( 264 | Coordinate origin, 265 | int direction, 266 | int distance, 267 | string container_label = "all" 268 | ) 269 | { 270 | return GetContainer(container_label) 271 | .GetCoordinate(Cubes 272 | .GetDiagonalNeighbor(origin.cube, direction, distance)); 273 | } 274 | 275 | /// 276 | /// Gets a List of Coordinate instances diagonally adjacent to the origin, for the specified number of steps 277 | /// 278 | /// Coordinate 279 | /// Distance from origin (>=1 valid) 280 | /// Container to get results from, defaults to "all" 281 | /// List of Coordinate instances found 282 | public List 283 | GetDiagonalNeighbors( 284 | Coordinate origin, 285 | int distance, 286 | string container_label = "all" 287 | ) 288 | { 289 | List results = new List(); 290 | Container container = GetContainer(container_label); 291 | for (int i = 0; i < 6; i++) 292 | { 293 | Coordinate coordinate = 294 | GetDiagonalNeighbor(origin, i, distance, container_label); 295 | if (coordinate != null) results.Add(coordinate); 296 | } 297 | return results; 298 | } 299 | 300 | /// 301 | /// Gets a line of Coordinate instances from Coordinate a to Coordinate b, inclusive 302 | /// 303 | /// Coordinate start 304 | /// Coordinate end 305 | /// Container to get results from, defaults to "all" 306 | /// List of Coordinate instances found 307 | public List 308 | GetLine(Coordinate a, Coordinate b, string container_label = "all") 309 | { 310 | return GetContainer(container_label) 311 | .GetCoordinates(Cubes.GetLine(a.cube, b.cube)); 312 | } 313 | 314 | /// 315 | /// Gets a Coordinate on a line between Coordinate a to Coordinate b at a given distance 316 | /// 317 | /// Coordinate start 318 | /// Coordinate end 319 | /// Distance from Coordinate a to get (>=1 valid) 320 | /// Container to get results from, defaults to "all" 321 | /// List of Coordinate instances found 322 | public Coordinate 323 | GetPointOnLne( 324 | Coordinate a, 325 | Coordinate b, 326 | int distance, 327 | string container_label = "all" 328 | ) 329 | { 330 | return GetContainer(container_label) 331 | .GetCoordinate(Cubes.GetPointOnLine(a.cube, b.cube, distance)); 332 | } 333 | 334 | /// 335 | /// Gets a ring (hexagonal shape) of Coordinate instances at a specified distance from origin 336 | /// 337 | /// Coordinate 338 | /// Distance from origin (>=1 is valid) 339 | /// Container to get results from, defaults to "all" 340 | /// List of Coordinate instances found 341 | public List 342 | GetRing(Coordinate origin, int distance, string container_label = "all") 343 | { 344 | return GetContainer(container_label) 345 | .GetCoordinates(Cubes.GetRing(origin.cube, distance)); 346 | } 347 | 348 | /// 349 | /// Gets an ordered list of Coordinates in a clockwise spiral shape for a given number of concentric steps 350 | /// 351 | /// Coordinate 352 | /// Number of concentric steps (>=1 is valid) 353 | /// Container to get results from, defaults to "all" 354 | /// List of Coordinate instances found 355 | public List 356 | GetSpiral(Coordinate origin, int steps, string container_label = "all") 357 | { 358 | return GetContainer(container_label) 359 | .GetCoordinates(Cubes.GetSpiral(origin.cube, steps)); 360 | } 361 | 362 | /// 363 | /// Combines two Coordinate Lists into one, without duplicates 364 | /// 365 | /// Coordinate List a 366 | /// Coordinate List b 367 | /// Container to get results from, defaults to "all" 368 | /// List of Coordinate instances found 369 | public List 370 | BooleanCombine( 371 | List a, 372 | List b, 373 | string container_label = "all" 374 | ) 375 | { 376 | return GetContainer(container_label) 377 | .GetCoordinates(Cubes 378 | .BooleanCombine(a.Select(x => x.cube).ToList(), 379 | b.Select(x => x.cube).ToList())); 380 | } 381 | 382 | /// 383 | /// Subtracts Coordinate instances from a if they are present in b 384 | /// 385 | /// Coordinate List a 386 | /// Coordinate List b 387 | /// Container to get results from, defaults to "all" 388 | /// List of Coordinate instances found 389 | public List 390 | BooleanDifference( 391 | List a, 392 | List b, 393 | string container_label = "all" 394 | ) 395 | { 396 | return GetContainer(container_label) 397 | .GetCoordinates(Cubes 398 | .BooleanDifference(a.Select(x => x.cube).ToList(), 399 | b.Select(x => x.cube).ToList())); 400 | } 401 | 402 | /// 403 | /// Gets the Coordinate instances that are shared in both a and b 404 | /// 405 | /// Coordinate List a 406 | /// Coordinate List b 407 | /// Container to get results from, defaults to "all" 408 | /// List of Coordinate instances found 409 | public List 410 | BooleanIntersect( 411 | List a, 412 | List b, 413 | string container_label = "all" 414 | ) 415 | { 416 | return GetContainer(container_label) 417 | .GetCoordinates(Cubes 418 | .BooleanIntersect(a.Select(x => x.cube).ToList(), 419 | b.Select(x => x.cube).ToList())); 420 | } 421 | 422 | /// 423 | /// Gets the Coordinate instances that are mutually exclusive to a and b 424 | /// 425 | /// Coordinate List a 426 | /// Coordinate List b 427 | /// Container to get results from, defaults to "all" 428 | /// List of Coordinate instances found 429 | public List 430 | BooleanExclude( 431 | List a, 432 | List b, 433 | string container_label = "all" 434 | ) 435 | { 436 | return GetContainer(container_label) 437 | .GetCoordinates(Cubes 438 | .BooleanExclude(a.Select(x => x.cube).ToList(), 439 | b.Select(x => x.cube).ToList())); 440 | } 441 | 442 | /// 443 | /// Gets the Coordinate instances that are "walkable" from the origin over a number of steps 444 | /// 445 | /// Coordinate 446 | /// Steps over which to walk to get results 447 | /// Container to get results from, defaults to "all" 448 | /// List of Coordinate instances found 449 | public List 450 | GetExpand(Coordinate origin, int steps, string container_label = "all") 451 | { 452 | List results = new List(); 453 | Container container = GetContainer(container_label); 454 | 455 | List visited = new List(); 456 | visited.Add(origin.cube); 457 | 458 | List> fringes = new List>(); 459 | fringes.Add(new List()); 460 | fringes[0].Add(origin.cube); 461 | 462 | for (int i = 1; i <= steps; i++) 463 | { 464 | fringes.Add(new List()); 465 | foreach (Vector3 v in fringes[i - 1]) 466 | { 467 | Coordinate current = container.GetCoordinate(v); 468 | foreach (Coordinate 469 | c 470 | in 471 | GetNeighbors(current, 1, container_label) 472 | ) 473 | { 474 | if (!visited.Contains(c.cube)) 475 | { 476 | visited.Add(c.cube); 477 | fringes[i].Add(c.cube); 478 | } 479 | } 480 | } 481 | } 482 | 483 | foreach (Vector3 v in visited) 484 | { 485 | results.Add(container.GetCoordinate(v)); 486 | } 487 | 488 | return results; 489 | } 490 | 491 | /// 492 | /// Gets an A* path between two Coordinate instances (must be connected) 493 | /// 494 | /// Coordinate start 495 | /// Coordinate end 496 | /// Container to get results from, defaults to "all" 497 | /// List of Coordinate instances found 498 | public List 499 | GetPath( 500 | Coordinate origin, 501 | Coordinate target, 502 | string container_label = "all" 503 | ) 504 | { 505 | if (origin == target) new List(); 506 | 507 | Container container = GetContainer(container_label); 508 | List openSet = new List(); 509 | List closedSet = new List(); 510 | openSet.Add(origin.cube); 511 | 512 | Dictionary cameFrom = 513 | new Dictionary(); 514 | cameFrom.Add(origin.cube, Vector3.zero); 515 | 516 | Vector3 current = Vector3.zero; 517 | Coordinate coordinate = null; 518 | Coordinate currentCoordinate = null; 519 | Coordinate neighborCoordinate = null; 520 | float newCost = 0.0f; 521 | 522 | while (openSet.Count > 0) 523 | { 524 | current = openSet[0]; 525 | currentCoordinate = container.GetCoordinate(current); 526 | 527 | for (int i = 1; i < openSet.Count; i++) 528 | { 529 | coordinate = container.GetCoordinate(openSet[i]); 530 | if ( 531 | coordinate.fCost < currentCoordinate.fCost || 532 | coordinate.fCost == currentCoordinate.fCost && 533 | coordinate.hCost < currentCoordinate.hCost 534 | ) 535 | { 536 | current = openSet[i]; 537 | currentCoordinate = container.GetCoordinate(current); 538 | } 539 | } 540 | 541 | openSet.Remove (current); 542 | closedSet.Add (current); 543 | 544 | if (current == target.cube) break; 545 | 546 | List neighbors = 547 | GetNeighbors(container.GetCoordinate(current), 548 | 1, 549 | container_label); 550 | 551 | foreach (Vector3 552 | neighbor 553 | in 554 | neighbors.Select(x => x.cube).ToList() 555 | ) 556 | { 557 | coordinate = container.GetCoordinate(neighbor); 558 | if (coordinate == null || closedSet.Contains(neighbor)) 559 | continue; 560 | 561 | newCost = 562 | currentCoordinate.gCost + 563 | Cubes.GetDistanceBetweenTwoCubes(current, neighbor); 564 | neighborCoordinate = container.GetCoordinate(neighbor); 565 | 566 | if ( 567 | newCost < neighborCoordinate.gCost || 568 | !openSet.Contains(neighbor) 569 | ) 570 | { 571 | neighborCoordinate.gCost = newCost; 572 | neighborCoordinate.hCost = 573 | Cubes.GetDistanceBetweenTwoCubes(current, neighbor); 574 | cameFrom.Add (neighbor, current); 575 | 576 | if (!openSet.Contains(neighbor)) openSet.Add(neighbor); 577 | } 578 | } 579 | } 580 | 581 | List path = new List(); 582 | 583 | current = target.cube; 584 | path.Add(target.cube); 585 | 586 | while (current != origin.cube) 587 | { 588 | cameFrom.TryGetValue(current, out current); 589 | path.Add (current); 590 | } 591 | 592 | path.Reverse(); 593 | 594 | return container.GetCoordinates(path); 595 | } 596 | } 597 | } 598 | --------------------------------------------------------------------------------