47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |  |
55 |
56 |
57 |
58 | $projectname $projectnumber
59 |
60 | $projectbrief
61 | |
62 |
63 |
64 |
65 |
66 | $projectbrief
67 | |
68 |
69 |
70 |
71 |
72 |
73 | $searchbox |
74 |
75 |
76 |
77 |
78 |
79 |
80 | $searchbox |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Docs~/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020, Matt Pewsey
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e58cf9dd61e024c2bb4e02e9fc10fb6e
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HexagonalUI
2 |
3 | [](https://mpewsey.github.io/HexagonalUI)
4 |
5 | ## Purpose
6 |
7 | Unity currently does not offer a built-in layout group for creating stackings of hexagonal elements, such as those sometimes used for skill grids. This package aims to fill that gap by providing a hexagonal layout group that will page UI elements into a grid.
8 |
9 | 
10 |
11 | ## Installation
12 |
13 | In Unity, select `Window > Package Manager`.
14 |
15 | 
16 |
17 | Select `Add package from git URL...` and paste the following URL:
18 |
19 | ```
20 | https://github.com/mpewsey/HexagonalUI.git
21 | ```
22 |
23 | To lock into a specific version, append `#{VERSION_TAG}` to the end of the URL. For example:
24 |
25 | ```
26 | https://github.com/mpewsey/HexagonalUI.git#v1.3.1
27 | ```
28 |
29 | ## Usage
30 |
31 | To use, simply attach the `HexLayoutGroup` component to a Game Object, as you would with Unity's built-in layout group components. The cell orientation component setting is based on the direction of the hexagonal element's long diagonal. In the image above, the hexagons on the top feature a horizontal cell orientation, whereas the hexagons on the bottom feature a vertical orientation.
32 |
33 | Due to the staggering of the hexagonal elements, the default `Button` component input navigation tends to navigate randomly. Therefore, if buttons will serve as the children of the layout group, it is recommended that the `HexButton` component, which provides more regular navigation behaviour, be used instead.
34 |
35 | ## Tile Art Creation Guidance
36 |
37 | To match the dimensions assigned by the hexagonal layout group, it is recommended that hexagonal tile art have their long diagonals to short diagonals proportioned to 1 : 0.86602540378. For instance, the example tile images in this package are 100 px by 87 px, including the required rounding to the nearest pixel.
38 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3288ca16c42494a82a9acfb4141c9684
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Samples~/Prefabs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7008eef9316024460acce7df2e4704c8
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples~/Prefabs/HexButton_Horizontal.prefab:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1 &1414110579747643580
4 | GameObject:
5 | m_ObjectHideFlags: 0
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | serializedVersion: 6
10 | m_Component:
11 | - component: {fileID: 9179871002707573841}
12 | - component: {fileID: 1893308076118620010}
13 | m_Layer: 5
14 | m_Name: HexButton_Horizontal
15 | m_TagString: Untagged
16 | m_Icon: {fileID: 0}
17 | m_NavMeshLayer: 0
18 | m_StaticEditorFlags: 0
19 | m_IsActive: 1
20 | --- !u!224 &9179871002707573841
21 | RectTransform:
22 | m_ObjectHideFlags: 0
23 | m_CorrespondingSourceObject: {fileID: 0}
24 | m_PrefabInstance: {fileID: 0}
25 | m_PrefabAsset: {fileID: 0}
26 | m_GameObject: {fileID: 1414110579747643580}
27 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
28 | m_LocalPosition: {x: 0, y: 0, z: 0}
29 | m_LocalScale: {x: 1, y: 1, z: 1}
30 | m_Children:
31 | - {fileID: 6686599077041454343}
32 | m_Father: {fileID: 0}
33 | m_RootOrder: 0
34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
35 | m_AnchorMin: {x: 0.5, y: 0.5}
36 | m_AnchorMax: {x: 0.5, y: 0.5}
37 | m_AnchoredPosition: {x: 0, y: 0}
38 | m_SizeDelta: {x: 100, y: 86.6}
39 | m_Pivot: {x: 0.5, y: 0.5}
40 | --- !u!114 &1893308076118620010
41 | MonoBehaviour:
42 | m_ObjectHideFlags: 0
43 | m_CorrespondingSourceObject: {fileID: 0}
44 | m_PrefabInstance: {fileID: 0}
45 | m_PrefabAsset: {fileID: 0}
46 | m_GameObject: {fileID: 1414110579747643580}
47 | m_Enabled: 1
48 | m_EditorHideFlags: 0
49 | m_Script: {fileID: 11500000, guid: 8b95c1f04db9249c68fe7f31e9ec5b97, type: 3}
50 | m_Name:
51 | m_EditorClassIdentifier:
52 | m_Navigation:
53 | m_Mode: 3
54 | m_SelectOnUp: {fileID: 0}
55 | m_SelectOnDown: {fileID: 0}
56 | m_SelectOnLeft: {fileID: 0}
57 | m_SelectOnRight: {fileID: 0}
58 | m_Transition: 1
59 | m_Colors:
60 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
61 | m_HighlightedColor: {r: 0, g: 0.6422635, b: 0.7924528, a: 1}
62 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
63 | m_SelectedColor: {r: 0, g: 0.754717, b: 0.31667313, a: 1}
64 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
65 | m_ColorMultiplier: 1
66 | m_FadeDuration: 0.1
67 | m_SpriteState:
68 | m_HighlightedSprite: {fileID: 0}
69 | m_PressedSprite: {fileID: 0}
70 | m_SelectedSprite: {fileID: 0}
71 | m_DisabledSprite: {fileID: 0}
72 | m_AnimationTriggers:
73 | m_NormalTrigger: Normal
74 | m_HighlightedTrigger: Highlighted
75 | m_PressedTrigger: Pressed
76 | m_SelectedTrigger: Selected
77 | m_DisabledTrigger: Disabled
78 | m_Interactable: 1
79 | m_TargetGraphic: {fileID: 6300994564907357248}
80 | m_OnClick:
81 | m_PersistentCalls:
82 | m_Calls: []
83 | --- !u!1 &3245847638180075902
84 | GameObject:
85 | m_ObjectHideFlags: 0
86 | m_CorrespondingSourceObject: {fileID: 0}
87 | m_PrefabInstance: {fileID: 0}
88 | m_PrefabAsset: {fileID: 0}
89 | serializedVersion: 6
90 | m_Component:
91 | - component: {fileID: 6686599077041454343}
92 | - component: {fileID: 572344923113193797}
93 | - component: {fileID: 6300994564907357248}
94 | m_Layer: 5
95 | m_Name: Image
96 | m_TagString: Untagged
97 | m_Icon: {fileID: 0}
98 | m_NavMeshLayer: 0
99 | m_StaticEditorFlags: 0
100 | m_IsActive: 1
101 | --- !u!224 &6686599077041454343
102 | RectTransform:
103 | m_ObjectHideFlags: 0
104 | m_CorrespondingSourceObject: {fileID: 0}
105 | m_PrefabInstance: {fileID: 0}
106 | m_PrefabAsset: {fileID: 0}
107 | m_GameObject: {fileID: 3245847638180075902}
108 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
109 | m_LocalPosition: {x: 0, y: 0, z: 0}
110 | m_LocalScale: {x: 1, y: 1, z: 1}
111 | m_Children: []
112 | m_Father: {fileID: 9179871002707573841}
113 | m_RootOrder: 0
114 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
115 | m_AnchorMin: {x: 0, y: 0}
116 | m_AnchorMax: {x: 1, y: 1}
117 | m_AnchoredPosition: {x: 0, y: 0}
118 | m_SizeDelta: {x: 0, y: 0}
119 | m_Pivot: {x: 0.5, y: 0.5}
120 | --- !u!222 &572344923113193797
121 | CanvasRenderer:
122 | m_ObjectHideFlags: 0
123 | m_CorrespondingSourceObject: {fileID: 0}
124 | m_PrefabInstance: {fileID: 0}
125 | m_PrefabAsset: {fileID: 0}
126 | m_GameObject: {fileID: 3245847638180075902}
127 | m_CullTransparentMesh: 0
128 | --- !u!114 &6300994564907357248
129 | MonoBehaviour:
130 | m_ObjectHideFlags: 0
131 | m_CorrespondingSourceObject: {fileID: 0}
132 | m_PrefabInstance: {fileID: 0}
133 | m_PrefabAsset: {fileID: 0}
134 | m_GameObject: {fileID: 3245847638180075902}
135 | m_Enabled: 1
136 | m_EditorHideFlags: 0
137 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
138 | m_Name:
139 | m_EditorClassIdentifier:
140 | m_Material: {fileID: 0}
141 | m_Color: {r: 1, g: 1, b: 1, a: 1}
142 | m_RaycastTarget: 1
143 | m_Maskable: 1
144 | m_OnCullStateChanged:
145 | m_PersistentCalls:
146 | m_Calls: []
147 | m_Sprite: {fileID: 21300000, guid: df864bb51d4bb479d979f9a55dfc90c7, type: 3}
148 | m_Type: 0
149 | m_PreserveAspect: 0
150 | m_FillCenter: 1
151 | m_FillMethod: 4
152 | m_FillAmount: 1
153 | m_FillClockwise: 1
154 | m_FillOrigin: 0
155 | m_UseSpriteMesh: 0
156 | m_PixelsPerUnitMultiplier: 1
157 |
--------------------------------------------------------------------------------
/Samples~/Prefabs/HexButton_Horizontal.prefab.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ec79681e955294711a65cdffd0aaf109
3 | PrefabImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Samples~/Prefabs/HexButton_Vertical.prefab:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1001 &1214057738837990626
4 | PrefabInstance:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Modification:
8 | m_TransformParent: {fileID: 0}
9 | m_Modifications:
10 | - target: {fileID: 1414110579747643580, guid: ec79681e955294711a65cdffd0aaf109,
11 | type: 3}
12 | propertyPath: m_Name
13 | value: HexButton_Vertical
14 | objectReference: {fileID: 0}
15 | - target: {fileID: 6300994564907357248, guid: ec79681e955294711a65cdffd0aaf109,
16 | type: 3}
17 | propertyPath: m_Sprite
18 | value:
19 | objectReference: {fileID: 21300000, guid: 11f6b46684a9742c8ad3b50cd0c74c08,
20 | type: 3}
21 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
22 | type: 3}
23 | propertyPath: m_LocalPosition.x
24 | value: 0
25 | objectReference: {fileID: 0}
26 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
27 | type: 3}
28 | propertyPath: m_LocalPosition.y
29 | value: 0
30 | objectReference: {fileID: 0}
31 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
32 | type: 3}
33 | propertyPath: m_LocalPosition.z
34 | value: 0
35 | objectReference: {fileID: 0}
36 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
37 | type: 3}
38 | propertyPath: m_LocalRotation.x
39 | value: 0
40 | objectReference: {fileID: 0}
41 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
42 | type: 3}
43 | propertyPath: m_LocalRotation.y
44 | value: 0
45 | objectReference: {fileID: 0}
46 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
47 | type: 3}
48 | propertyPath: m_LocalRotation.z
49 | value: 0
50 | objectReference: {fileID: 0}
51 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
52 | type: 3}
53 | propertyPath: m_LocalRotation.w
54 | value: 1
55 | objectReference: {fileID: 0}
56 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
57 | type: 3}
58 | propertyPath: m_RootOrder
59 | value: 0
60 | objectReference: {fileID: 0}
61 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
62 | type: 3}
63 | propertyPath: m_LocalEulerAnglesHint.x
64 | value: 0
65 | objectReference: {fileID: 0}
66 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
67 | type: 3}
68 | propertyPath: m_LocalEulerAnglesHint.y
69 | value: 0
70 | objectReference: {fileID: 0}
71 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
72 | type: 3}
73 | propertyPath: m_LocalEulerAnglesHint.z
74 | value: 0
75 | objectReference: {fileID: 0}
76 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
77 | type: 3}
78 | propertyPath: m_AnchoredPosition.x
79 | value: 0
80 | objectReference: {fileID: 0}
81 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
82 | type: 3}
83 | propertyPath: m_AnchoredPosition.y
84 | value: 0
85 | objectReference: {fileID: 0}
86 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
87 | type: 3}
88 | propertyPath: m_SizeDelta.x
89 | value: 86.6
90 | objectReference: {fileID: 0}
91 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
92 | type: 3}
93 | propertyPath: m_SizeDelta.y
94 | value: 100
95 | objectReference: {fileID: 0}
96 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
97 | type: 3}
98 | propertyPath: m_AnchorMin.x
99 | value: 0.5
100 | objectReference: {fileID: 0}
101 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
102 | type: 3}
103 | propertyPath: m_AnchorMin.y
104 | value: 0.5
105 | objectReference: {fileID: 0}
106 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
107 | type: 3}
108 | propertyPath: m_AnchorMax.x
109 | value: 0.5
110 | objectReference: {fileID: 0}
111 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
112 | type: 3}
113 | propertyPath: m_AnchorMax.y
114 | value: 0.5
115 | objectReference: {fileID: 0}
116 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
117 | type: 3}
118 | propertyPath: m_Pivot.x
119 | value: 0.5
120 | objectReference: {fileID: 0}
121 | - target: {fileID: 9179871002707573841, guid: ec79681e955294711a65cdffd0aaf109,
122 | type: 3}
123 | propertyPath: m_Pivot.y
124 | value: 0.5
125 | objectReference: {fileID: 0}
126 | m_RemovedComponents: []
127 | m_SourcePrefab: {fileID: 100100000, guid: ec79681e955294711a65cdffd0aaf109, type: 3}
128 |
--------------------------------------------------------------------------------
/Samples~/Prefabs/HexButton_Vertical.prefab.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a02849c0da20443a581920e196b813cd
3 | PrefabImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Samples~/Scenes.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bb7812fb94da940aa9bfdbf6d7544762
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples~/Scenes/HexLayoutGroupExample.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8a268a14bb7d4ca58de364047d3c0b7
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Samples~/Sprites.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8edea272d9b59420e892f5486e295cc4
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples~/Sprites/Hexagon_Horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpewsey/HexagonalUI/c99404ac0f23f91a6d2cd582c3e722f9e9eefb95/Samples~/Sprites/Hexagon_Horizontal.png
--------------------------------------------------------------------------------
/Samples~/Sprites/Hexagon_Horizontal.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: df864bb51d4bb479d979f9a55dfc90c7
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 0
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: -1
36 | mipBias: -100
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: 1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 1
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 1
53 | spriteTessellationDetail: -1
54 | textureType: 8
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID: 5e97eb03825dee720800000000000000
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Samples~/Sprites/Hexagon_Vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpewsey/HexagonalUI/c99404ac0f23f91a6d2cd582c3e722f9e9eefb95/Samples~/Sprites/Hexagon_Vertical.png
--------------------------------------------------------------------------------
/Samples~/Sprites/Hexagon_Vertical.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 11f6b46684a9742c8ad3b50cd0c74c08
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 0
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: -1
36 | mipBias: -100
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: 1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 1
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 1
53 | spriteTessellationDetail: -1
54 | textureType: 8
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID: 5e97eb03825dee720800000000000000
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 72e43452e63244da294ffb8b3736e4ec
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f5cb75c7ff4f1405389775555d5b88fa
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Editor/HexLayoutGroupEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 |
4 | namespace MPewsey.HexagonalUI.Editor
5 | {
6 | ///
7 | /// The HexLayoutGroup custom inspector.
8 | ///
9 | [CustomEditor(typeof(HexLayoutGroup))]
10 | public class HexLayoutGroupEditor : UnityEditor.Editor
11 | {
12 | public override void OnInspectorGUI()
13 | {
14 | serializedObject.Update();
15 | var constraint = serializedObject.FindProperty("_constraint");
16 | var flexibleConstraint = constraint.enumValueIndex == (int)HexLayoutGroup.ConstraintType.Flexible;
17 | var prop = serializedObject.GetIterator();
18 | GUI.enabled = false;
19 | var enterChildren = true;
20 |
21 | while (prop.NextVisible(enterChildren))
22 | {
23 | if (prop.name != "_constraintCount" || !flexibleConstraint)
24 | EditorGUILayout.PropertyField(prop, true);
25 |
26 | GUI.enabled = true;
27 | enterChildren = false;
28 | }
29 |
30 | serializedObject.ApplyModifiedProperties();
31 | }
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/Scripts/Editor/HexLayoutGroupEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 26575e6ee72aa484f986c354a310a671
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/MPewsey.HexagonalUI.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MPewsey.HexagonalUI.Editor",
3 | "references": [
4 | "GUID:f440d987bce314b6eb721572e7cbdd60"
5 | ],
6 | "includePlatforms": [
7 | "Editor"
8 | ],
9 | "excludePlatforms": [],
10 | "allowUnsafeCode": false,
11 | "overrideReferences": false,
12 | "precompiledReferences": [],
13 | "autoReferenced": true,
14 | "defineConstraints": [],
15 | "versionDefines": [],
16 | "noEngineReferences": false
17 | }
--------------------------------------------------------------------------------
/Scripts/Editor/MPewsey.HexagonalUI.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 79ab1ce15d1094ee481d95d1b5a00f0f
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Scripts/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 99134e9ea7ab749eab4556cd6db02795
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Runtime/HexButton.cs:
--------------------------------------------------------------------------------
1 | /// Copyright (c) Matt Pewsey, All Rights Reserved.
2 |
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using UnityEngine.UI;
6 |
7 | namespace MPewsey.HexagonalUI
8 | {
9 | ///
10 | /// This component overrides the built in Button component's navigation to work
11 | /// more intuitively with the staggered element stacking of the HexLayoutGroup.
12 | /// This component should be used instead of the built in Button component
13 | /// for buttons that are immediate children of a HexLayoutGroup.
14 | ///
15 | public class HexButton : Button
16 | {
17 | ///
18 | /// A list of sibling selectables populated during a navigation query.
19 | ///
20 | private static List
SiblingSelectables { get; set; } = new List();
21 |
22 | ///
23 | /// Returns the axis for the specified direction.
24 | ///
25 | private static int GetAxis(Vector3 direction)
26 | {
27 | if (Mathf.Abs(direction.x) > Mathf.Abs(direction.y))
28 | return 0;
29 | return 1;
30 | }
31 |
32 | ///
33 | /// Returns the selectable on navigation up.
34 | ///
35 | public override Selectable FindSelectableOnUp()
36 | {
37 | if (navigation.mode == Navigation.Mode.Explicit)
38 | return navigation.selectOnUp;
39 | if ((navigation.mode & Navigation.Mode.Vertical) != Navigation.Mode.None)
40 | return GetSelectable(transform.rotation * Vector3.up);
41 | return null;
42 | }
43 |
44 | ///
45 | /// Returns the selectable on navigation down.
46 | ///
47 | public override Selectable FindSelectableOnDown()
48 | {
49 | if (navigation.mode == Navigation.Mode.Explicit)
50 | return navigation.selectOnDown;
51 | if ((navigation.mode & Navigation.Mode.Vertical) != Navigation.Mode.None)
52 | return GetSelectable(transform.rotation * Vector3.down);
53 | return null;
54 | }
55 |
56 | ///
57 | /// Returns the selectable on navigation left.
58 | ///
59 | public override Selectable FindSelectableOnLeft()
60 | {
61 | if (navigation.mode == Navigation.Mode.Explicit)
62 | return navigation.selectOnLeft;
63 | if ((navigation.mode & Navigation.Mode.Horizontal) != Navigation.Mode.None)
64 | return GetSelectable(transform.rotation * Vector3.left);
65 | return null;
66 | }
67 |
68 | ///
69 | /// Returns the selectable on navigation right.
70 | ///
71 | public override Selectable FindSelectableOnRight()
72 | {
73 | if (navigation.mode == Navigation.Mode.Explicit)
74 | return navigation.selectOnRight;
75 | if ((navigation.mode & Navigation.Mode.Horizontal) != Navigation.Mode.None)
76 | return GetSelectable(transform.rotation * Vector3.right);
77 | return null;
78 | }
79 |
80 | ///
81 | /// Sets the sibling selectables array with all active selectables.
82 | ///
83 | private void SetSiblingSelectables()
84 | {
85 | SiblingSelectables.Clear();
86 | var parent = transform.parent;
87 |
88 | for (int i = 0; i < parent.childCount; i++)
89 | {
90 | var child = parent.GetChild(i);
91 |
92 | if (child.gameObject.activeInHierarchy)
93 | {
94 | var selectable = child.GetComponent();
95 | SiblingSelectables.Add(selectable);
96 | }
97 | }
98 | }
99 |
100 | ///
101 | /// Returns the selectable in the specified direction.
102 | ///
103 | public Selectable GetSelectable(Vector3 direction)
104 | {
105 | var result = FindSelectable(direction);
106 |
107 | if (result == null || transform.parent == null || !IsActive())
108 | return result;
109 |
110 | var hexLayout = transform.parent.GetComponent();
111 | var directionAxis = GetAxis(direction);
112 |
113 | if (hexLayout == null || directionAxis != (int)hexLayout.CellOrientation)
114 | return result;
115 |
116 | SetSiblingSelectables();
117 | var startAxis = (int)hexLayout.StartAxis;
118 | var rows = hexLayout.RowCount(SiblingSelectables.Count);
119 | var columns = hexLayout.ColumnCount(SiblingSelectables.Count);
120 | var index = GetIndex(rows, columns, startAxis);
121 | var (row, column) = (index.x, index.y);
122 | Selectable selectable;
123 |
124 | if (row < 0 || column < 0)
125 | return result;
126 |
127 | if (directionAxis == 0)
128 | {
129 | if (direction.x > 0)
130 | selectable = GetSelectableOnRight(row, column, rows, columns, startAxis);
131 | else
132 | selectable = GetSelectableOnLeft(row, column, rows, columns, startAxis);
133 | }
134 | else
135 | {
136 | if (direction.y > 0)
137 | selectable = GetSelectableOnUp(row, column, rows, columns, startAxis);
138 | else
139 | selectable = GetSelectableOnDown(row, column, rows, columns, startAxis);
140 | }
141 |
142 | SiblingSelectables.Clear();
143 | return selectable != null ? selectable : result;
144 | }
145 |
146 | ///
147 | /// Returns the next active selectable above the specified row-column index.
148 | ///
149 | private Selectable GetSelectableOnUp(int row, int column, int rows, int columns, int startAxis)
150 | {
151 | for (int i = row - 1; i >= 0; i--)
152 | {
153 | var index = GetFlatIndex(i, column, rows, columns, startAxis);
154 |
155 | if (CanNavigateTo(SiblingSelectables[index]))
156 | return SiblingSelectables[index];
157 | }
158 |
159 | return null;
160 | }
161 |
162 | ///
163 | /// Returns the next active selectable below the specified row-column index.
164 | ///
165 | private Selectable GetSelectableOnDown(int row, int column, int rows, int columns, int startAxis)
166 | {
167 | for (int i = row + 1; i < rows; i++)
168 | {
169 | var index = GetFlatIndex(i, column, rows, columns, startAxis);
170 |
171 | if (index < SiblingSelectables.Count && CanNavigateTo(SiblingSelectables[index]))
172 | return SiblingSelectables[index];
173 | }
174 |
175 | return null;
176 | }
177 |
178 | ///
179 | /// Returns the next active selectable to the right of specified row-column index.
180 | ///
181 | private Selectable GetSelectableOnRight(int row, int column, int rows, int columns, int startAxis)
182 | {
183 | for (int i = column + 1; i < columns; i++)
184 | {
185 | var index = GetFlatIndex(row, i, rows, columns, startAxis);
186 |
187 | if (index < SiblingSelectables.Count && CanNavigateTo(SiblingSelectables[index]))
188 | return SiblingSelectables[index];
189 | }
190 |
191 | return null;
192 | }
193 |
194 | ///
195 | /// Returns the next active selectable to the left of the specified row-column index.
196 | ///
197 | private Selectable GetSelectableOnLeft(int row, int column, int rows, int columns, int startAxis)
198 | {
199 | for (int i = column - 1; i >= 0; i--)
200 | {
201 | var index = GetFlatIndex(row, i, rows, columns, startAxis);
202 |
203 | if (CanNavigateTo(SiblingSelectables[index]))
204 | return SiblingSelectables[index];
205 | }
206 |
207 | return null;
208 | }
209 |
210 | ///
211 | /// Returns true if the selectable can be navigated to.
212 | ///
213 | private bool CanNavigateTo(Selectable selectable)
214 | {
215 | return selectable != null
216 | && selectable != this
217 | && selectable.IsInteractable()
218 | && selectable.IsActive()
219 | && selectable.navigation.mode != Navigation.Mode.None;
220 | }
221 |
222 | ///
223 | /// Returns the flat index based on the
224 | ///
225 | private static int GetFlatIndex(int row, int column, int rows, int columns, int startAxis)
226 | {
227 | if (startAxis == 0)
228 | return row * columns + column;
229 |
230 | return column * rows + row;
231 | }
232 |
233 | ///
234 | /// Returns the row-column index of the button based on its flat
235 | /// index in the sibling selectables list.
236 | ///
237 | private Vector2Int GetIndex(int rows, int columns, int startAxis)
238 | {
239 | for (int i = 0; i < SiblingSelectables.Count; i++)
240 | {
241 | if (SiblingSelectables[i] == this)
242 | {
243 | if (startAxis == 0)
244 | return new Vector2Int(i / columns, i % columns);
245 |
246 | return new Vector2Int(i % rows, i / rows);
247 | }
248 | }
249 |
250 | return new Vector2Int(-1, -1);
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/Scripts/Runtime/HexButton.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8b95c1f04db9249c68fe7f31e9ec5b97
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Runtime/HexLayoutGroup.cs:
--------------------------------------------------------------------------------
1 | /// Copyright (c) Matt Pewsey, All Rights Reserved.
2 |
3 | using UnityEngine;
4 | using UnityEngine.UI;
5 |
6 | namespace MPewsey.HexagonalUI
7 | {
8 | ///
9 | /// Attach this component to a GameObject to apply hexagonal layout
10 | /// to its children. For buttons used as children, use the HexButton
11 | /// component in lieu of the built in Button component for more
12 | /// intuitive input navigation.
13 | ///
14 | [RequireComponent(typeof(RectTransform))]
15 | public class HexLayoutGroup : LayoutGroup
16 | {
17 | [SerializeField]
18 | private float _circumradius = 50.0f;
19 | ///
20 | /// The circumradius of the hexagonal elements.
21 | ///
22 | public float Circumradius
23 | {
24 | get => _circumradius;
25 | set => SetProperty(ref _circumradius, value);
26 | }
27 |
28 | [SerializeField]
29 | private Vector2 _spacing = new Vector2(20.0f, 20.0f);
30 | ///
31 | /// The horizontal and vertical spacing between hexagonal elements.
32 | ///
33 | public Vector2 Spacing
34 | {
35 | get => _spacing;
36 | set => SetProperty(ref _spacing, value);
37 | }
38 |
39 | [SerializeField]
40 | private Axis _startAxis;
41 | ///
42 | /// The start axis for tiling elements.
43 | ///
44 | public Axis StartAxis
45 | {
46 | get => _startAxis;
47 | set => SetProperty(ref _startAxis, value);
48 | }
49 |
50 | [SerializeField]
51 | private Axis _cellOrientation;
52 | ///
53 | /// The orientation of the hexagonal element long axes.
54 | ///
55 | public Axis CellOrientation
56 | {
57 | get => _cellOrientation;
58 | set => SetProperty(ref _cellOrientation, value);
59 | }
60 |
61 | [SerializeField]
62 | private ConstraintType _constraint;
63 | ///
64 | /// The constraint used for paging elements.
65 | ///
66 | public ConstraintType Constraint
67 | {
68 | get => _constraint;
69 | set => SetProperty(ref _constraint, value);
70 | }
71 |
72 | [SerializeField]
73 | private int _constraintCount = 10;
74 | ///
75 | /// The constaint count for non-flexible constraints.
76 | ///
77 | public int ConstraintCount
78 | {
79 | get => _constraintCount;
80 | set => SetProperty(ref _constraintCount, Mathf.Max(value, 1));
81 | }
82 |
83 | #if UNITY_EDITOR
84 | protected override void OnValidate()
85 | {
86 | base.OnValidate();
87 | ConstraintCount = Mathf.Max(ConstraintCount, 1);
88 | }
89 | #endif
90 |
91 | ///
92 | /// Calculates the minimum and preferred widths for the object.
93 | ///
94 | public override void CalculateLayoutInputHorizontal()
95 | {
96 | base.CalculateLayoutInputHorizontal();
97 | var totalMin = Mathf.Max(padding.horizontal + ContentWidth(MinColumnCount()), 0.0f);
98 | var totalPreferred = Mathf.Max(padding.horizontal + ContentWidth(PreferredColumnCount()), 0.0f);
99 | SetLayoutInputForAxis(totalMin, totalPreferred, -1.0f, 0);
100 | }
101 |
102 | ///
103 | /// Calculates the minimum and preferred heights for the object.
104 | ///
105 | public override void CalculateLayoutInputVertical()
106 | {
107 | var totalMin = Mathf.Max(padding.vertical + ContentHeight(MinRowCount()), 0.0f);
108 | var totalPreferred = Mathf.Max(padding.vertical + ContentHeight(PreferredRowCount()), 0.0f);
109 | SetLayoutInputForAxis(totalMin, totalPreferred, -1.0f, 1);
110 | }
111 |
112 | ///
113 | /// Sets the cell sizes for the hexagonal elements.
114 | ///
115 | public override void SetLayoutHorizontal()
116 | {
117 | SetCellSizes();
118 | }
119 |
120 | ///
121 | /// Sets the positions of the hexagonal elements.
122 | ///
123 | public override void SetLayoutVertical()
124 | {
125 | CalculateLayout();
126 | }
127 |
128 | ///
129 | /// Returns true if the value is even.
130 | ///
131 | private static bool IsEven(int value)
132 | {
133 | return (value & 1) == 0;
134 | }
135 |
136 | ///
137 | /// Sets the cell sizes for the hexagonal elements.
138 | ///
139 | private void SetCellSizes()
140 | {
141 | var cellSize = CellSize();
142 |
143 | for (int i = 0; i < rectChildren.Count; i++)
144 | {
145 | var rectChild = rectChildren[i];
146 | m_Tracker.Add(this, rectChild, DrivenTransformProperties.Anchors | DrivenTransformProperties.AnchoredPosition | DrivenTransformProperties.SizeDelta);
147 | rectChild.anchorMin = Vector2.up;
148 | rectChild.anchorMax = Vector2.up;
149 | rectChild.sizeDelta = cellSize;
150 | }
151 | }
152 |
153 | ///
154 | /// Returns the content height for the specified number of rows.
155 | ///
156 | private float ContentHeight(int rows)
157 | {
158 | if (rows <= 0)
159 | return 0.0f;
160 |
161 | float value;
162 | var cellSize = CellSize();
163 |
164 | if (CellOrientation == Axis.Horizontal)
165 | value = rows * (cellSize.y + Spacing.y) - Spacing.y + 0.5f * (cellSize.y + Spacing.y);
166 | else
167 | value = rows * (0.75f * cellSize.y + Spacing.y) - Spacing.y + 0.25f * cellSize.y;
168 |
169 | return Mathf.Max(value, 0.0f);
170 | }
171 |
172 | ///
173 | /// Returns the content width for the specified number of columns.
174 | ///
175 | private float ContentWidth(int columns)
176 | {
177 | if (columns <= 0)
178 | return 0.0f;
179 |
180 | float value;
181 | var cellSize = CellSize();
182 |
183 | if (CellOrientation == Axis.Horizontal)
184 | value = columns * (0.75f * cellSize.x + Spacing.x) - Spacing.x + 0.25f * cellSize.x;
185 | else
186 | value = columns * (cellSize.x + Spacing.x) - Spacing.x + 0.5f * (cellSize.x + Spacing.x);
187 |
188 | return Mathf.Max(value, 0.0f);
189 | }
190 |
191 | ///
192 | /// Sets the positions of the hexagonal elements.
193 | ///
194 | private void CalculateLayout()
195 | {
196 | var rows = RowCount();
197 | var columns = ColumnCount();
198 | var cellSize = CellSize();
199 | var x0 = GetStartOffset(0, ContentWidth(columns));
200 | var y0 = GetStartOffset(1, ContentHeight(rows));
201 |
202 | for (int i = 0; i < rectChildren.Count; i++)
203 | {
204 | int row, column;
205 | float x, y;
206 |
207 | if (StartAxis == Axis.Horizontal)
208 | (row, column) = (i / columns, i % columns);
209 | else
210 | (row, column) = (i % rows, i / rows);
211 |
212 | if (CellOrientation == Axis.Horizontal)
213 | {
214 | x = x0 + column * (0.75f * cellSize.x + Spacing.x);
215 | y = y0 + row * (cellSize.y + Spacing.y);
216 |
217 | if (IsEven(column))
218 | y += 0.5f * (cellSize.y + Spacing.y);
219 | }
220 | else
221 | {
222 | x = x0 + column * (cellSize.x + Spacing.x);
223 | y = y0 + row * (0.75f * cellSize.y + Spacing.y);
224 |
225 | if (IsEven(row))
226 | x += 0.5f * (cellSize.x + Spacing.x);
227 | }
228 |
229 | SetChildAlongAxis(rectChildren[i], 0, x, cellSize.x);
230 | SetChildAlongAxis(rectChildren[i], 1, y, cellSize.y);
231 | }
232 | }
233 |
234 | ///
235 | /// Returns the row count used for the minimum layout height.
236 | ///
237 | private int MinRowCount()
238 | {
239 | switch (Constraint)
240 | {
241 | case ConstraintType.FixedRow:
242 | return ConstraintCount;
243 | case ConstraintType.FixedColumn:
244 | return Mathf.Max(1, (rectChildren.Count - 1) / ConstraintCount + 1);
245 | default:
246 | return 1;
247 | }
248 | }
249 |
250 | ///
251 | /// Returns the row count used for the preferred layout height.
252 | ///
253 | private int PreferredRowCount()
254 | {
255 | switch (Constraint)
256 | {
257 | case ConstraintType.FixedRow:
258 | return ConstraintCount;
259 | case ConstraintType.FixedColumn:
260 | return Mathf.Max(1, (rectChildren.Count - 1) / ConstraintCount + 1);
261 | default:
262 | return Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count));
263 | }
264 | }
265 |
266 | ///
267 | /// Returns the column count used for the minimum layout width.
268 | ///
269 | private int MinColumnCount()
270 | {
271 | switch (Constraint)
272 | {
273 | case ConstraintType.FixedColumn:
274 | return ConstraintCount;
275 | case ConstraintType.FixedRow:
276 | return Mathf.Max(1, (rectChildren.Count - 1) / ConstraintCount + 1);
277 | default:
278 | return 1;
279 | }
280 | }
281 |
282 | ///
283 | /// Returns the column count used for the preferred layout width.
284 | ///
285 | private int PreferredColumnCount()
286 | {
287 | switch (Constraint)
288 | {
289 | case ConstraintType.FixedColumn:
290 | return ConstraintCount;
291 | case ConstraintType.FixedRow:
292 | return Mathf.Max(1, (rectChildren.Count - 1) / ConstraintCount + 1);
293 | default:
294 | return Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count));
295 | }
296 | }
297 |
298 | ///
299 | /// Returns the current row count.
300 | ///
301 | private int RowCount()
302 | {
303 | return RowCount(rectChildren.Count);
304 | }
305 |
306 | ///
307 | /// Returns the row count given a total element count.
308 | ///
309 | public int RowCount(int count)
310 | {
311 | var constraint = Mathf.Min(GetConstraintCount(), count);
312 |
313 | if (constraint > 0 && !ConstraintIsRowCount())
314 | return (count - 1) / constraint + 1;
315 |
316 | return constraint;
317 | }
318 |
319 | ///
320 | /// Returns the current column count.
321 | ///
322 | private int ColumnCount()
323 | {
324 | return ColumnCount(rectChildren.Count);
325 | }
326 |
327 | ///
328 | /// Returns the column count given a total element count.
329 | ///
330 | public int ColumnCount(int count)
331 | {
332 | var constraint = Mathf.Min(GetConstraintCount(), count);
333 |
334 | if (constraint > 0 && ConstraintIsRowCount())
335 | return (count - 1) / constraint + 1;
336 |
337 | return constraint;
338 | }
339 |
340 | ///
341 | /// Returns the hexagonal element width and height.
342 | ///
343 | public Vector2 CellSize()
344 | {
345 | var longDiagonal = 2.0f * Circumradius;
346 | var shortDiagonal = 0.86602540378f * longDiagonal;
347 |
348 | if (CellOrientation == Axis.Horizontal)
349 | return new Vector2(longDiagonal, shortDiagonal);
350 |
351 | return new Vector2(shortDiagonal, longDiagonal);
352 | }
353 |
354 | ///
355 | /// Returns true if the controlling constraint is the row count.
356 | /// Otherwise, the controlling constraint is the column count.
357 | ///
358 | public bool ConstraintIsRowCount()
359 | {
360 | if (Constraint == ConstraintType.Flexible)
361 | return StartAxis == Axis.Vertical;
362 |
363 | return Constraint == ConstraintType.FixedRow;
364 | }
365 |
366 | ///
367 | /// Returns the controlling constraint count.
368 | ///
369 | public int GetConstraintCount()
370 | {
371 | if (Constraint == ConstraintType.Flexible)
372 | return FlexibleConstraintCount();
373 |
374 | return ConstraintCount;
375 | }
376 |
377 | ///
378 | /// Returns the constraint count for the flexible constraint.
379 | ///
380 | private int FlexibleConstraintCount()
381 | {
382 | if (StartAxis == Axis.Horizontal)
383 | return FlexibleWidthConstraintCount();
384 |
385 | return FlexibleHeightConstraintCount();
386 | }
387 |
388 | ///
389 | /// Returns the constraint count for the flexible width constraint.
390 | ///
391 | private int FlexibleWidthConstraintCount()
392 | {
393 | float value;
394 | var cellSize = CellSize();
395 | var size = rectTransform.rect.size;
396 |
397 | if (CellOrientation == Axis.Horizontal)
398 | value = (size.x + Spacing.x - 0.25f * cellSize.x - padding.horizontal) / (0.75f * cellSize.x + Spacing.x);
399 | else
400 | value = (size.x + Spacing.x - 0.5f * (cellSize.x + Spacing.x) - padding.horizontal) / (cellSize.x + Spacing.x);
401 |
402 | return Mathf.Max(Mathf.FloorToInt(value), 1);
403 | }
404 |
405 | ///
406 | /// Returns the constraint count for the flexible height constraint.
407 | ///
408 | private int FlexibleHeightConstraintCount()
409 | {
410 | float value;
411 | var cellSize = CellSize();
412 | var size = rectTransform.rect.size;
413 |
414 | if (CellOrientation == Axis.Horizontal)
415 | value = (size.y + Spacing.y - 0.5f * (cellSize.y + Spacing.y) - padding.vertical) / (cellSize.y + Spacing.y);
416 | else
417 | value = (size.y + Spacing.y - 0.25f * cellSize.y - padding.vertical) / (0.75f * cellSize.y + Spacing.y);
418 |
419 | return Mathf.Max(Mathf.FloorToInt(value), 1);
420 | }
421 |
422 | public enum Axis
423 | {
424 | Horizontal = 0,
425 | Vertical = 1,
426 | }
427 |
428 | public enum ConstraintType
429 | {
430 | Flexible,
431 | FixedRow,
432 | FixedColumn,
433 | }
434 | }
435 | }
436 |
--------------------------------------------------------------------------------
/Scripts/Runtime/HexLayoutGroup.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7e777795a64934e3ebb6ed33ccc3c6ab
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Runtime/MPewsey.HexagonalUI.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MPewsey.HexagonalUI"
3 | }
4 |
--------------------------------------------------------------------------------
/Scripts/Runtime/MPewsey.HexagonalUI.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f440d987bce314b6eb721572e7cbdd60
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.mpewsey.hexagonal-ui",
3 | "author": "Matt Pewsey",
4 | "displayName": "HexagonalUI",
5 | "version": "1.3.1",
6 | "unity": "2018.1",
7 | "keywords": [
8 | "unity",
9 | "ui",
10 | "hexagonal-grids"
11 | ],
12 | "description": "A hexagonal grid layout group for Unity.",
13 | "samples": [
14 | {
15 | "displayName": "Samples",
16 | "description": "Samples for usage of the hexagonal layout group.",
17 | "path": "Samples~"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 791e81649db1d41c789836bba91eb26d
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------