WorldGen
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8028fe033c76d4b4e82877336273ad43
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,315 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld.Editor
|
||||
{
|
||||
public class WorldGeneratorEditorWindow : EditorWindow
|
||||
{
|
||||
private enum BrushMode
|
||||
{
|
||||
Erase,
|
||||
Wall,
|
||||
Environment
|
||||
}
|
||||
|
||||
private WorldAutotileProfile profile;
|
||||
private ChunkTemplate template;
|
||||
private SerializedObject serializedProfile;
|
||||
private Vector2 profileScroll;
|
||||
private Vector2 chunkScroll;
|
||||
private BrushMode brushMode = BrushMode.Wall;
|
||||
private bool isPainting;
|
||||
private int pendingWidth = 16;
|
||||
private int pendingHeight = 16;
|
||||
|
||||
[MenuItem("Tools/Infinite World/World Builder")]
|
||||
public static void Open()
|
||||
{
|
||||
GetWindow<WorldGeneratorEditorWindow>("World Builder");
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawToolbar();
|
||||
EditorGUILayout.Space(6f);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
DrawChunkEditor();
|
||||
GUILayout.Space(8f);
|
||||
DrawProfileEditor();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawToolbar()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
template = (ChunkTemplate)EditorGUILayout.ObjectField(template, typeof(ChunkTemplate), false, GUILayout.Width(position.width * 0.38f));
|
||||
profile = (WorldAutotileProfile)EditorGUILayout.ObjectField(profile, typeof(WorldAutotileProfile), false, GUILayout.Width(position.width * 0.38f));
|
||||
|
||||
if (GUILayout.Button("New Chunk", EditorStyles.toolbarButton, GUILayout.Width(80f)))
|
||||
{
|
||||
CreateChunkAsset();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("New Profile", EditorStyles.toolbarButton, GUILayout.Width(80f)))
|
||||
{
|
||||
CreateProfileAsset();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawChunkEditor()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.52f));
|
||||
EditorGUILayout.LabelField("Chunk Painter", EditorStyles.boldLabel);
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Create or assign a ChunkTemplate asset. Paint walls and environment directly on the chunk grid and mark exits on each side.", MessageType.Info);
|
||||
EditorGUILayout.EndVertical();
|
||||
return;
|
||||
}
|
||||
|
||||
template.EnsureCellData();
|
||||
pendingWidth = EditorGUILayout.IntField("Width", pendingWidth == 0 ? template.width : pendingWidth);
|
||||
pendingHeight = EditorGUILayout.IntField("Height", pendingHeight == 0 ? template.height : pendingHeight);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Resize"))
|
||||
{
|
||||
Undo.RecordObject(template, "Resize Chunk Template");
|
||||
template.Resize(pendingWidth, pendingHeight);
|
||||
EditorUtility.SetDirty(template);
|
||||
}
|
||||
if (GUILayout.Button("Clear"))
|
||||
{
|
||||
Undo.RecordObject(template, "Clear Chunk Template");
|
||||
template.Clear();
|
||||
EditorUtility.SetDirty(template);
|
||||
}
|
||||
if (GUILayout.Button("Border From Exits"))
|
||||
{
|
||||
Undo.RecordObject(template, "Apply Border Walls");
|
||||
template.ApplyBorderWallsFromExits(3);
|
||||
EditorUtility.SetDirty(template);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.LabelField("Brush", EditorStyles.miniBoldLabel);
|
||||
brushMode = (BrushMode)GUILayout.Toolbar((int)brushMode, new[] { "Erase", "Wall", "Environment" });
|
||||
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.LabelField("Exits", EditorStyles.miniBoldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
DrawExitToggle("Top", ChunkExit.Top);
|
||||
DrawExitToggle("Right", ChunkExit.Right);
|
||||
DrawExitToggle("Bottom", ChunkExit.Bottom);
|
||||
DrawExitToggle("Left", ChunkExit.Left);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(8f);
|
||||
chunkScroll = EditorGUILayout.BeginScrollView(chunkScroll, GUILayout.ExpandHeight(true));
|
||||
Rect gridRect = GUILayoutUtility.GetRect(template.width * 24f, template.height * 24f, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
|
||||
DrawGrid(gridRect);
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
EditorGUILayout.HelpBox("Gray = floor, brown = wall, green = environment. Drag with the selected brush to paint the chunk visually.", MessageType.None);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawProfileEditor()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
EditorGUILayout.LabelField("Tile Profile", EditorStyles.boldLabel);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Assign a WorldAutotileProfile to map real tiles onto ground, walls, and environment.", MessageType.Info);
|
||||
EditorGUILayout.EndVertical();
|
||||
return;
|
||||
}
|
||||
|
||||
if (serializedProfile == null || serializedProfile.targetObject != profile)
|
||||
{
|
||||
serializedProfile = new SerializedObject(profile);
|
||||
}
|
||||
|
||||
serializedProfile.Update();
|
||||
profileScroll = EditorGUILayout.BeginScrollView(profileScroll);
|
||||
|
||||
EditorGUILayout.PropertyField(serializedProfile.FindProperty("baseGroundTile"));
|
||||
EditorGUILayout.Space(6f);
|
||||
EditorGUILayout.LabelField("Wall Autotile", EditorStyles.miniBoldLabel);
|
||||
SerializedProperty walls = serializedProfile.FindProperty("wallTiles");
|
||||
DrawWallGrid(walls);
|
||||
|
||||
EditorGUILayout.Space(6f);
|
||||
EditorGUILayout.PropertyField(serializedProfile.FindProperty("environmentTiles"), true);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
serializedProfile.ApplyModifiedProperties();
|
||||
if (GUI.changed)
|
||||
{
|
||||
EditorUtility.SetDirty(profile);
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawWallGrid(SerializedProperty walls)
|
||||
{
|
||||
DrawTriple(walls, "outerTopLeft", "top", "outerTopRight");
|
||||
DrawTriple(walls, "left", "center", "right");
|
||||
DrawTriple(walls, "outerBottomLeft", "bottom", "outerBottomRight");
|
||||
DrawDouble(walls, "innerTopLeft", "innerTopRight");
|
||||
DrawDouble(walls, "innerBottomLeft", "innerBottomRight");
|
||||
}
|
||||
|
||||
private void DrawTriple(SerializedProperty root, string a, string b, string c)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(a), GUIContent.none);
|
||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(b), GUIContent.none);
|
||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(c), GUIContent.none);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawDouble(SerializedProperty root, string a, string b)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(a), GUIContent.none);
|
||||
EditorGUILayout.PropertyField(root.FindPropertyRelative(b), GUIContent.none);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawExitToggle(string label, ChunkExit exit)
|
||||
{
|
||||
bool current = template.GetExit(exit);
|
||||
bool next = GUILayout.Toggle(current, label, "Button");
|
||||
if (next == current)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObject(template, "Toggle Chunk Exit");
|
||||
switch (exit)
|
||||
{
|
||||
case ChunkExit.Top:
|
||||
template.exitTop = next;
|
||||
break;
|
||||
case ChunkExit.Right:
|
||||
template.exitRight = next;
|
||||
break;
|
||||
case ChunkExit.Bottom:
|
||||
template.exitBottom = next;
|
||||
break;
|
||||
case ChunkExit.Left:
|
||||
template.exitLeft = next;
|
||||
break;
|
||||
}
|
||||
EditorUtility.SetDirty(template);
|
||||
}
|
||||
|
||||
private void DrawGrid(Rect rect)
|
||||
{
|
||||
const float cellSize = 24f;
|
||||
Event evt = Event.current;
|
||||
|
||||
for (int y = 0; y < template.height; y++)
|
||||
{
|
||||
for (int x = 0; x < template.width; x++)
|
||||
{
|
||||
Rect cellRect = new Rect(rect.x + x * cellSize, rect.y + (template.height - 1 - y) * cellSize, cellSize - 1f, cellSize - 1f);
|
||||
EditorGUI.DrawRect(cellRect, GetCellColor(x, y));
|
||||
|
||||
if ((evt.type == EventType.MouseDown || evt.type == EventType.MouseDrag) && cellRect.Contains(evt.mousePosition) && evt.button == 0)
|
||||
{
|
||||
isPainting = true;
|
||||
PaintCell(x, y);
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.type == EventType.MouseUp)
|
||||
{
|
||||
isPainting = false;
|
||||
}
|
||||
|
||||
if (isPainting)
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetCellColor(int x, int y)
|
||||
{
|
||||
if (template.GetWall(x, y))
|
||||
{
|
||||
return new Color(0.52f, 0.36f, 0.22f, 1f);
|
||||
}
|
||||
|
||||
if (template.GetEnvironment(x, y))
|
||||
{
|
||||
return new Color(0.2f, 0.54f, 0.25f, 1f);
|
||||
}
|
||||
|
||||
return new Color(0.4f, 0.4f, 0.4f, 1f);
|
||||
}
|
||||
|
||||
private void PaintCell(int x, int y)
|
||||
{
|
||||
Undo.RecordObject(template, "Paint Chunk Template");
|
||||
switch (brushMode)
|
||||
{
|
||||
case BrushMode.Erase:
|
||||
template.SetWall(x, y, false);
|
||||
template.SetEnvironment(x, y, false);
|
||||
break;
|
||||
case BrushMode.Wall:
|
||||
template.SetWall(x, y, true);
|
||||
break;
|
||||
case BrushMode.Environment:
|
||||
template.SetEnvironment(x, y, true);
|
||||
break;
|
||||
}
|
||||
EditorUtility.SetDirty(template);
|
||||
}
|
||||
|
||||
private void CreateChunkAsset()
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanelInProject("Create Chunk Template", "ChunkTemplate", "asset", "Choose where to save the chunk template.");
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ChunkTemplate asset = CreateInstance<ChunkTemplate>();
|
||||
asset.Resize(16, 16);
|
||||
asset.ApplyBorderWallsFromExits(3);
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
template = asset;
|
||||
pendingWidth = asset.width;
|
||||
pendingHeight = asset.height;
|
||||
Selection.activeObject = asset;
|
||||
}
|
||||
|
||||
private void CreateProfileAsset()
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanelInProject("Create World Profile", "WorldAutotileProfile", "asset", "Choose where to save the tile profile.");
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorldAutotileProfile asset = CreateInstance<WorldAutotileProfile>();
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
profile = asset;
|
||||
serializedProfile = new SerializedObject(profile);
|
||||
Selection.activeObject = asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff365d4e73b85cc4987c7984d5f8c7cb
|
||||
+279
-44
@@ -13,7 +13,7 @@ OcclusionCullingSettings:
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
@@ -38,13 +38,12 @@ RenderSettings:
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
m_GIWorkflowMode: 1
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
@@ -67,9 +66,6 @@ LightmapSettings:
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
@@ -104,7 +100,7 @@ NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 2
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
@@ -117,7 +113,7 @@ NavMeshSettings:
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
accuratePlacement: 0
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
@@ -135,6 +131,7 @@ GameObject:
|
||||
- component: {fileID: 519420031}
|
||||
- component: {fileID: 519420029}
|
||||
- component: {fileID: 519420030}
|
||||
- component: {fileID: 519420033}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
@@ -172,6 +169,7 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
m_Bits: 1
|
||||
m_VolumeTrigger: {fileID: 0}
|
||||
m_VolumeFrameworkUpdateModeOption: 2
|
||||
m_RenderPostProcessing: 0
|
||||
m_Antialiasing: 0
|
||||
m_AntialiasingQuality: 2
|
||||
@@ -179,8 +177,19 @@ MonoBehaviour:
|
||||
m_Dithering: 0
|
||||
m_ClearDepth: 1
|
||||
m_AllowXRRendering: 1
|
||||
m_AllowHDROutput: 1
|
||||
m_UseScreenCoordOverride: 0
|
||||
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_RequiresDepthTexture: 0
|
||||
m_RequiresColorTexture: 0
|
||||
m_TaaSettings:
|
||||
m_Quality: 3
|
||||
m_FrameInfluence: 0.1
|
||||
m_JitterScale: 1
|
||||
m_MipBias: 0
|
||||
m_VarianceClampScale: 0.9
|
||||
m_ContrastAdaptiveSharpening: 0
|
||||
m_Version: 2
|
||||
--- !u!20 &519420031
|
||||
Camera:
|
||||
@@ -196,9 +205,17 @@ Camera:
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_FocalLength: 50
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
@@ -232,14 +249,29 @@ Transform:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &519420033
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 98ee4fb5b3ebf80478e6e25afa8fd337, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::InfiniteWorld.CameraFollow2D
|
||||
target: {fileID: 0}
|
||||
smoothTime: 0.18
|
||||
offset: {x: 0, y: 0, z: -10}
|
||||
--- !u!1 &619394800
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -269,55 +301,29 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 073797afb82c5a1438f328866b10b3f0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_ComponentVersion: 1
|
||||
m_ComponentVersion: 2
|
||||
m_LightType: 4
|
||||
m_BlendStyleIndex: 0
|
||||
m_FalloffIntensity: 0.5
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Intensity: 1
|
||||
m_LightVolumeIntensity: 1
|
||||
m_LightVolumeIntensityEnabled: 0
|
||||
m_LightVolumeEnabled: 0
|
||||
m_ApplyToSortingLayers: 00000000
|
||||
m_LightCookieSprite: {fileID: 0}
|
||||
m_DeprecatedPointLightCookieSprite: {fileID: 0}
|
||||
m_LightOrder: 0
|
||||
m_AlphaBlendOnOverlap: 0
|
||||
m_OverlapOperation: 0
|
||||
m_NormalMapDistance: 3
|
||||
m_NormalMapQuality: 2
|
||||
m_UseNormalMap: 0
|
||||
m_ShadowIntensityEnabled: 0
|
||||
m_ShadowsEnabled: 0
|
||||
m_ShadowIntensity: 0.75
|
||||
m_ShadowSoftness: 0
|
||||
m_ShadowSoftnessFalloffIntensity: 0.5
|
||||
m_ShadowVolumeIntensityEnabled: 0
|
||||
m_ShadowVolumeIntensity: 0.75
|
||||
m_Vertices:
|
||||
- position: {x: 0.9985302, y: 0.9985302, z: 0}
|
||||
color: {r: 0.70710677, g: 0.70710677, b: 0, a: 0}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: 0.9985302, y: 0.9985302, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 1}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: -0.9985302, y: 0.9985302, z: 0}
|
||||
color: {r: -0.70710677, g: 0.70710677, b: 0, a: 0}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: -0.9985302, y: 0.9985302, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 1}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: -0.99853003, y: -0.9985304, z: 0}
|
||||
color: {r: -0.70710665, g: -0.7071069, b: 0, a: 0}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: -0.99853003, y: -0.9985304, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 1}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: 0.99853003, y: -0.9985304, z: 0}
|
||||
color: {r: 0.70710665, g: -0.7071069, b: 0, a: 0}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: 0.99853003, y: -0.9985304, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 1}
|
||||
uv: {x: 0, y: 0}
|
||||
- position: {x: 0, y: 0, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 1}
|
||||
uv: {x: 0, y: 0}
|
||||
m_Triangles: 030001000800020000000100030002000100050003000800040002000300050004000300070005000800060004000500070006000500010007000800000006000700010000000700
|
||||
m_LocalBounds:
|
||||
m_Center: {x: 0, y: -0.00000011920929, z: 0}
|
||||
m_Extent: {x: 0.9985302, y: 0.99853027, z: 0}
|
||||
@@ -342,11 +348,240 @@ Transform:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 619394800}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &669282973
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 669282975}
|
||||
- component: {fileID: 669282974}
|
||||
- component: {fileID: 669282978}
|
||||
- component: {fileID: 669282977}
|
||||
- component: {fileID: 669282976}
|
||||
m_Layer: 0
|
||||
m_Name: Player
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &669282974
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 669282973}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c9187e81c6ec8da4599e04a1694ec18b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::InfiniteWorld.SimplePlayerInputMover
|
||||
moveSpeed: 5
|
||||
--- !u!4 &669282975
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 669282973}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!70 &669282976
|
||||
CapsuleCollider2D:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 669282973}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Density: 1
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_ForceSendLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ForceReceiveLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_ContactCaptureLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_CallbackLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_IsTrigger: 0
|
||||
m_UsedByEffector: 0
|
||||
m_CompositeOperation: 0
|
||||
m_CompositeOrder: 0
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Size: {x: 0.5, y: 1}
|
||||
m_Direction: 0
|
||||
--- !u!212 &669282977
|
||||
SpriteRenderer:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 669282973}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 0
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 0
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_FlipX: 0
|
||||
m_FlipY: 0
|
||||
m_DrawMode: 0
|
||||
m_Size: {x: 1, y: 1}
|
||||
m_AdaptiveModeThreshold: 0.5
|
||||
m_SpriteTileMode: 0
|
||||
m_WasSpriteAssigned: 0
|
||||
m_SpriteSortPoint: 0
|
||||
--- !u!50 &669282978
|
||||
Rigidbody2D:
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 669282973}
|
||||
m_BodyType: 0
|
||||
m_Simulated: 1
|
||||
m_UseFullKinematicContacts: 0
|
||||
m_UseAutoMass: 0
|
||||
m_Mass: 1
|
||||
m_LinearDamping: 0
|
||||
m_AngularDamping: 0.05
|
||||
m_GravityScale: 1
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_Interpolate: 0
|
||||
m_SleepingMode: 1
|
||||
m_CollisionDetection: 0
|
||||
m_Constraints: 0
|
||||
--- !u!1 &947604398
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 947604400}
|
||||
- component: {fileID: 947604399}
|
||||
m_Layer: 0
|
||||
m_Name: World
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &947604399
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 947604398}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6bacd3a0ac13e6f4a94548426dd89ebb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::InfiniteWorld.InfiniteWorldGenerator
|
||||
player: {fileID: 0}
|
||||
profile: {fileID: 0}
|
||||
templates: []
|
||||
chunkSize: 16
|
||||
generationRadius: 2
|
||||
seed: 12345
|
||||
--- !u!4 &947604400
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 947604398}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 519420032}
|
||||
- {fileID: 619394802}
|
||||
- {fileID: 947604400}
|
||||
- {fileID: 669282975}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0444b525419aa14429d7e31a7e4b7fef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff30f393052d2674e94fd1d887712090
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
public class CameraFollow2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Transform target;
|
||||
[SerializeField] private float smoothTime = 0.18f;
|
||||
[SerializeField] private Vector3 offset = new Vector3(0f, 0f, -10f);
|
||||
|
||||
private Vector3 velocity;
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
SimplePlayerInputMover player = FindFirstObjectByType<SimplePlayerInputMover>();
|
||||
if (player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target = player.transform;
|
||||
}
|
||||
|
||||
Vector3 desiredPosition = target.position + offset;
|
||||
desiredPosition.z = offset.z;
|
||||
transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref velocity, smoothTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98ee4fb5b3ebf80478e6e25afa8fd337
|
||||
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
public class SimplePlayerInputMover : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float moveSpeed = 5f;
|
||||
|
||||
private InputAction moveAction;
|
||||
private Rigidbody2D rb;
|
||||
private Vector2 moveInput;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rb = GetComponent<Rigidbody2D>();
|
||||
if (rb == null)
|
||||
{
|
||||
rb = gameObject.AddComponent<Rigidbody2D>();
|
||||
}
|
||||
|
||||
ConfigurePhysics();
|
||||
EnsureVisual();
|
||||
|
||||
moveAction = new InputAction("Move", InputActionType.Value);
|
||||
moveAction.AddCompositeBinding("2DVector")
|
||||
.With("Up", "<Keyboard>/w")
|
||||
.With("Down", "<Keyboard>/s")
|
||||
.With("Left", "<Keyboard>/a")
|
||||
.With("Right", "<Keyboard>/d");
|
||||
moveAction.AddCompositeBinding("2DVector")
|
||||
.With("Up", "<Keyboard>/upArrow")
|
||||
.With("Down", "<Keyboard>/downArrow")
|
||||
.With("Left", "<Keyboard>/leftArrow")
|
||||
.With("Right", "<Keyboard>/rightArrow");
|
||||
moveAction.AddBinding("<Gamepad>/leftStick");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
moveAction?.Enable();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
moveAction?.Disable();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
moveAction?.Dispose();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (moveAction == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
moveInput = moveAction.ReadValue<Vector2>().normalized;
|
||||
if (moveInput.x != 0f)
|
||||
{
|
||||
Vector3 scale = transform.localScale;
|
||||
scale.x = Mathf.Abs(scale.x) * Mathf.Sign(moveInput.x);
|
||||
transform.localScale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (rb == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 target = rb.position + moveInput * (moveSpeed * Time.fixedDeltaTime);
|
||||
rb.MovePosition(target);
|
||||
}
|
||||
|
||||
private void ConfigurePhysics()
|
||||
{
|
||||
rb.gravityScale = 0f;
|
||||
rb.freezeRotation = true;
|
||||
rb.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
|
||||
CapsuleCollider2D collider = GetComponent<CapsuleCollider2D>();
|
||||
if (collider == null)
|
||||
{
|
||||
collider = gameObject.AddComponent<CapsuleCollider2D>();
|
||||
}
|
||||
collider.direction = CapsuleDirection2D.Vertical;
|
||||
collider.size = new Vector2(0.55f, 0.8f);
|
||||
collider.offset = new Vector2(0f, -0.05f);
|
||||
}
|
||||
|
||||
private void EnsureVisual()
|
||||
{
|
||||
SpriteRenderer renderer = GetComponent<SpriteRenderer>();
|
||||
if (renderer == null)
|
||||
{
|
||||
renderer = gameObject.AddComponent<SpriteRenderer>();
|
||||
}
|
||||
renderer.sprite = ProceduralWorldArt.CreatePlayerSprite();
|
||||
renderer.sortingOrder = 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9187e81c6ec8da4599e04a1694ec18b
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96adefd3df6641c40b9624eb12185ea4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Infinite World/Chunk Template", fileName = "ChunkTemplate")]
|
||||
public class ChunkTemplate : ScriptableObject
|
||||
{
|
||||
[Min(4)] public int width = 16;
|
||||
[Min(4)] public int height = 16;
|
||||
|
||||
[Header("Exits")]
|
||||
public bool exitTop = true;
|
||||
public bool exitRight = true;
|
||||
public bool exitBottom = true;
|
||||
public bool exitLeft = true;
|
||||
|
||||
[SerializeField] private List<ChunkCellData> cells = new List<ChunkCellData>();
|
||||
|
||||
public int CellCount => width * height;
|
||||
|
||||
public void EnsureCellData()
|
||||
{
|
||||
int target = Mathf.Max(1, width * height);
|
||||
while (cells.Count < target)
|
||||
{
|
||||
cells.Add(new ChunkCellData());
|
||||
}
|
||||
|
||||
while (cells.Count > target)
|
||||
{
|
||||
cells.RemoveAt(cells.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Resize(int newWidth, int newHeight)
|
||||
{
|
||||
newWidth = Mathf.Max(4, newWidth);
|
||||
newHeight = Mathf.Max(4, newHeight);
|
||||
|
||||
List<ChunkCellData> oldCells = new List<ChunkCellData>(cells);
|
||||
int oldWidth = width;
|
||||
int oldHeight = height;
|
||||
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
cells = new List<ChunkCellData>(newWidth * newHeight);
|
||||
|
||||
for (int i = 0; i < newWidth * newHeight; i++)
|
||||
{
|
||||
cells.Add(new ChunkCellData());
|
||||
}
|
||||
|
||||
for (int y = 0; y < Mathf.Min(oldHeight, newHeight); y++)
|
||||
{
|
||||
for (int x = 0; x < Mathf.Min(oldWidth, newWidth); x++)
|
||||
{
|
||||
int oldIndex = y * oldWidth + x;
|
||||
if (oldIndex < oldCells.Count)
|
||||
{
|
||||
cells[Index(x, y)] = oldCells[oldIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
EnsureCellData();
|
||||
for (int i = 0; i < cells.Count; i++)
|
||||
{
|
||||
cells[i] = new ChunkCellData();
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetWall(int x, int y)
|
||||
{
|
||||
return IsInBounds(x, y) && cells[Index(x, y)].wall;
|
||||
}
|
||||
|
||||
public bool GetEnvironment(int x, int y)
|
||||
{
|
||||
return IsInBounds(x, y) && cells[Index(x, y)].environment;
|
||||
}
|
||||
|
||||
public void SetWall(int x, int y, bool value)
|
||||
{
|
||||
if (!IsInBounds(x, y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCellData();
|
||||
ChunkCellData data = cells[Index(x, y)];
|
||||
data.wall = value;
|
||||
if (value)
|
||||
{
|
||||
data.environment = false;
|
||||
}
|
||||
cells[Index(x, y)] = data;
|
||||
}
|
||||
|
||||
public void SetEnvironment(int x, int y, bool value)
|
||||
{
|
||||
if (!IsInBounds(x, y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCellData();
|
||||
ChunkCellData data = cells[Index(x, y)];
|
||||
data.environment = value;
|
||||
if (value)
|
||||
{
|
||||
data.wall = false;
|
||||
}
|
||||
cells[Index(x, y)] = data;
|
||||
}
|
||||
|
||||
public bool GetExit(ChunkExit exit)
|
||||
{
|
||||
return exit switch
|
||||
{
|
||||
ChunkExit.Top => exitTop,
|
||||
ChunkExit.Right => exitRight,
|
||||
ChunkExit.Bottom => exitBottom,
|
||||
ChunkExit.Left => exitLeft,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public int ExitCount()
|
||||
{
|
||||
int count = 0;
|
||||
count += exitTop ? 1 : 0;
|
||||
count += exitRight ? 1 : 0;
|
||||
count += exitBottom ? 1 : 0;
|
||||
count += exitLeft ? 1 : 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
public void ApplyBorderWallsFromExits(int openingWidth = 3)
|
||||
{
|
||||
EnsureCellData();
|
||||
Clear();
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
SetWall(x, 0, true);
|
||||
SetWall(x, height - 1, true);
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
SetWall(0, y, true);
|
||||
SetWall(width - 1, y, true);
|
||||
}
|
||||
|
||||
CarveExit(ChunkExit.Top, openingWidth);
|
||||
CarveExit(ChunkExit.Right, openingWidth);
|
||||
CarveExit(ChunkExit.Bottom, openingWidth);
|
||||
CarveExit(ChunkExit.Left, openingWidth);
|
||||
}
|
||||
|
||||
public void CarveExit(ChunkExit exit, int openingWidth = 3)
|
||||
{
|
||||
if (!GetExit(exit))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int half = Mathf.Max(1, openingWidth) / 2;
|
||||
switch (exit)
|
||||
{
|
||||
case ChunkExit.Top:
|
||||
for (int x = width / 2 - half; x <= width / 2 + half; x++)
|
||||
{
|
||||
SetWall(x, height - 1, false);
|
||||
SetWall(x, height - 2, false);
|
||||
}
|
||||
break;
|
||||
case ChunkExit.Right:
|
||||
for (int y = height / 2 - half; y <= height / 2 + half; y++)
|
||||
{
|
||||
SetWall(width - 1, y, false);
|
||||
SetWall(width - 2, y, false);
|
||||
}
|
||||
break;
|
||||
case ChunkExit.Bottom:
|
||||
for (int x = width / 2 - half; x <= width / 2 + half; x++)
|
||||
{
|
||||
SetWall(x, 0, false);
|
||||
SetWall(x, 1, false);
|
||||
}
|
||||
break;
|
||||
case ChunkExit.Left:
|
||||
for (int y = height / 2 - half; y <= height / 2 + half; y++)
|
||||
{
|
||||
SetWall(0, y, false);
|
||||
SetWall(1, y, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int Index(int x, int y)
|
||||
{
|
||||
return y * width + x;
|
||||
}
|
||||
|
||||
private bool IsInBounds(int x, int y)
|
||||
{
|
||||
return x >= 0 && y >= 0 && x < width && y < height;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChunkExit
|
||||
{
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
Left
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct ChunkCellData
|
||||
{
|
||||
public bool wall;
|
||||
public bool environment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bae8eeae2da7d3f4396883671b297a47
|
||||
@@ -0,0 +1,582 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
public class InfiniteWorldGenerator : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private Transform player;
|
||||
[SerializeField] private WorldAutotileProfile profile;
|
||||
|
||||
[Header("Chunk Settings")]
|
||||
[SerializeField] private int chunkSize = 16;
|
||||
[SerializeField] private int generationRadius = 2;
|
||||
[SerializeField] private int seed = 12345;
|
||||
|
||||
[Header("Rock Noise")]
|
||||
[SerializeField] private float macroNoiseScale = 0.05f;
|
||||
[SerializeField] private float detailNoiseScale = 0.12f;
|
||||
[SerializeField] private float ridgeNoiseScale = 0.18f;
|
||||
[SerializeField] private float wallThreshold = 0.6f;
|
||||
[SerializeField] private float rockBias = 0.04f;
|
||||
[SerializeField] private int smoothingPasses = 2;
|
||||
|
||||
[Header("Global Passes")]
|
||||
[SerializeField] private float passNoiseScale = 0.018f;
|
||||
[SerializeField] private float passDetailScale = 0.041f;
|
||||
[SerializeField] private float passThreshold = 0.22f;
|
||||
[SerializeField] private float passFeather = 0.12f;
|
||||
|
||||
[Header("Environment")]
|
||||
[SerializeField] private float environmentNoiseScale = 0.19f;
|
||||
[SerializeField] private float environmentThreshold = 0.7f;
|
||||
|
||||
private readonly Dictionary<Vector2Int, GeneratedChunk> chunks = new Dictionary<Vector2Int, GeneratedChunk>();
|
||||
private Grid grid;
|
||||
private Transform chunkRoot;
|
||||
private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
|
||||
private WorldAutotileProfile runtimeFallbackProfile;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureSceneInfrastructure();
|
||||
EnsureRuntimeData();
|
||||
TryFindPlayer();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (player == null && !TryFindPlayer())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2Int playerChunk = WorldToChunk(player.position);
|
||||
if (playerChunk == lastGeneratedCenter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastGeneratedCenter = playerChunk;
|
||||
GenerateAround(playerChunk);
|
||||
}
|
||||
|
||||
private void EnsureSceneInfrastructure()
|
||||
{
|
||||
grid = GetComponentInChildren<Grid>();
|
||||
if (grid == null)
|
||||
{
|
||||
GameObject gridObject = new GameObject("Grid", typeof(Grid));
|
||||
gridObject.transform.SetParent(transform, false);
|
||||
grid = gridObject.GetComponent<Grid>();
|
||||
}
|
||||
|
||||
Transform existingChunkRoot = grid.transform.Find("Chunks");
|
||||
if (existingChunkRoot == null)
|
||||
{
|
||||
GameObject root = new GameObject("Chunks");
|
||||
root.transform.SetParent(grid.transform, false);
|
||||
chunkRoot = root.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
chunkRoot = existingChunkRoot;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureRuntimeData()
|
||||
{
|
||||
if (profile == null || !profile.HasAnyAssignedTiles())
|
||||
{
|
||||
runtimeFallbackProfile = RuntimeWorldProfileFactory.CreateFallbackProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryFindPlayer()
|
||||
{
|
||||
if (player != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SimplePlayerInputMover mover = FindFirstObjectByType<SimplePlayerInputMover>();
|
||||
if (mover == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
player = mover.transform;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GenerateAround(Vector2Int centerChunk)
|
||||
{
|
||||
for (int y = -generationRadius; y <= generationRadius; y++)
|
||||
{
|
||||
for (int x = -generationRadius; x <= generationRadius; x++)
|
||||
{
|
||||
Vector2Int coord = new Vector2Int(centerChunk.x + x, centerChunk.y + y);
|
||||
if (chunks.ContainsKey(coord))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GeneratedChunk chunk = CreateChunk(coord);
|
||||
chunks.Add(coord, chunk);
|
||||
BuildChunkData(coord, chunk);
|
||||
RenderChunk(coord);
|
||||
RefreshNeighborBorders(coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GeneratedChunk CreateChunk(Vector2Int coord)
|
||||
{
|
||||
GameObject chunkObject = new GameObject($"Chunk_{coord.x}_{coord.y}");
|
||||
chunkObject.transform.SetParent(chunkRoot, false);
|
||||
chunkObject.transform.localPosition = new Vector3(coord.x * chunkSize, coord.y * chunkSize, 0f);
|
||||
|
||||
Tilemap ground = CreateTilemap("Ground", chunkObject.transform, 0, false);
|
||||
Tilemap walls = CreateTilemap("Walls", chunkObject.transform, 1, true);
|
||||
Tilemap environment = CreateTilemap("Environment", chunkObject.transform, 2, false);
|
||||
|
||||
return new GeneratedChunk(chunkObject.transform, ground, walls, environment, new bool[chunkSize, chunkSize], new bool[chunkSize, chunkSize]);
|
||||
}
|
||||
|
||||
private Tilemap CreateTilemap(string name, Transform parent, int sortingOrder, bool addCollision)
|
||||
{
|
||||
GameObject tilemapObject = new GameObject(name, typeof(Tilemap), typeof(TilemapRenderer));
|
||||
tilemapObject.transform.SetParent(parent, false);
|
||||
|
||||
TilemapRenderer renderer = tilemapObject.GetComponent<TilemapRenderer>();
|
||||
renderer.sortingOrder = sortingOrder;
|
||||
|
||||
if (addCollision)
|
||||
{
|
||||
Rigidbody2D rb = tilemapObject.GetComponent<Rigidbody2D>();
|
||||
if (rb == null)
|
||||
{
|
||||
rb = tilemapObject.AddComponent<Rigidbody2D>();
|
||||
}
|
||||
rb.bodyType = RigidbodyType2D.Static;
|
||||
|
||||
CompositeCollider2D composite = tilemapObject.GetComponent<CompositeCollider2D>();
|
||||
if (composite == null)
|
||||
{
|
||||
composite = tilemapObject.AddComponent<CompositeCollider2D>();
|
||||
}
|
||||
composite.geometryType = CompositeCollider2D.GeometryType.Polygons;
|
||||
|
||||
TilemapCollider2D collider = tilemapObject.GetComponent<TilemapCollider2D>();
|
||||
if (collider == null)
|
||||
{
|
||||
collider = tilemapObject.AddComponent<TilemapCollider2D>();
|
||||
}
|
||||
collider.compositeOperation = Collider2D.CompositeOperation.Merge;
|
||||
}
|
||||
|
||||
return tilemapObject.GetComponent<Tilemap>();
|
||||
}
|
||||
|
||||
private void BuildChunkData(Vector2Int coord, GeneratedChunk chunk)
|
||||
{
|
||||
int margin = Mathf.Max(2, smoothingPasses + 1);
|
||||
int sampleSize = chunkSize + margin * 2;
|
||||
bool[,] sampled = new bool[sampleSize, sampleSize];
|
||||
|
||||
for (int y = 0; y < sampleSize; y++)
|
||||
{
|
||||
for (int x = 0; x < sampleSize; x++)
|
||||
{
|
||||
int localX = x - margin;
|
||||
int localY = y - margin;
|
||||
Vector2Int worldCell = ChunkToWorldCell(coord, localX, localY);
|
||||
sampled[x, y] = SampleRock(worldCell);
|
||||
}
|
||||
}
|
||||
|
||||
for (int pass = 0; pass < smoothingPasses; pass++)
|
||||
{
|
||||
sampled = SmoothSampledMask(sampled);
|
||||
}
|
||||
|
||||
for (int y = 0; y < chunkSize; y++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
chunk.WallMask[x, y] = sampled[x + margin, y + margin];
|
||||
}
|
||||
}
|
||||
|
||||
BuildEnvironment(coord, chunk);
|
||||
}
|
||||
|
||||
private bool[,] SmoothSampledMask(bool[,] source)
|
||||
{
|
||||
int width = source.GetLength(0);
|
||||
int height = source.GetLength(1);
|
||||
bool[,] result = new bool[width, height];
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int solidNeighbors = CountSampledWallNeighbors(source, x, y);
|
||||
if (solidNeighbors >= 5)
|
||||
{
|
||||
result[x, y] = true;
|
||||
}
|
||||
else if (solidNeighbors <= 2)
|
||||
{
|
||||
result[x, y] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[x, y] = source[x, y];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int CountSampledWallNeighbors(bool[,] sampled, int x, int y)
|
||||
{
|
||||
int width = sampled.GetLength(0);
|
||||
int height = sampled.GetLength(1);
|
||||
int count = 0;
|
||||
|
||||
for (int oy = -1; oy <= 1; oy++)
|
||||
{
|
||||
for (int ox = -1; ox <= 1; ox++)
|
||||
{
|
||||
if (ox == 0 && oy == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int nx = x + ox;
|
||||
int ny = y + oy;
|
||||
if (nx < 0 || ny < 0 || nx >= width || ny >= height)
|
||||
{
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sampled[nx, ny])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private bool SampleRock(Vector2Int worldCell)
|
||||
{
|
||||
float macro = Mathf.PerlinNoise((worldCell.x + seed * 0.13f) * macroNoiseScale, (worldCell.y - seed * 0.17f) * macroNoiseScale);
|
||||
float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.23f) * detailNoiseScale, (worldCell.y + seed * 0.19f) * detailNoiseScale);
|
||||
float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.31f) * ridgeNoiseScale, (worldCell.y + seed * 0.29f) * ridgeNoiseScale) * 2f - 1f);
|
||||
|
||||
float rockValue = macro * 0.62f + detail * 0.18f + ridge * 0.20f + rockBias;
|
||||
if (IsInsideGlobalPass(worldCell))
|
||||
{
|
||||
rockValue -= 0.45f;
|
||||
}
|
||||
|
||||
return rockValue >= wallThreshold;
|
||||
}
|
||||
|
||||
private bool IsInsideGlobalPass(Vector2Int worldCell)
|
||||
{
|
||||
float primary = Mathf.PerlinNoise((worldCell.x + seed * 0.41f) * passNoiseScale, (worldCell.y - seed * 0.43f) * passNoiseScale);
|
||||
float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.17f) * passDetailScale, (worldCell.y + seed * 0.23f) * passDetailScale);
|
||||
float ridged = Mathf.Abs(primary * 2f - 1f);
|
||||
float warped = Mathf.Lerp(ridged, Mathf.Abs(detail * 2f - 1f), 0.35f);
|
||||
return warped <= passThreshold + passFeather * detail;
|
||||
}
|
||||
|
||||
private void BuildEnvironment(Vector2Int coord, GeneratedChunk chunk)
|
||||
{
|
||||
for (int y = 0; y < chunkSize; y++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
if (chunk.WallMask[x, y])
|
||||
{
|
||||
chunk.EnvironmentMask[x, y] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasAdjacentOpenTiles(chunk.WallMask, x, y, 1))
|
||||
{
|
||||
chunk.EnvironmentMask[x, y] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2Int worldCell = ChunkToWorldCell(coord, x, y);
|
||||
float noise = Mathf.PerlinNoise((worldCell.x + seed * 0.53f) * environmentNoiseScale, (worldCell.y - seed * 0.61f) * environmentNoiseScale);
|
||||
chunk.EnvironmentMask[x, y] = noise >= environmentThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasAdjacentOpenTiles(bool[,] wallMask, int x, int y, int radius)
|
||||
{
|
||||
for (int oy = -radius; oy <= radius; oy++)
|
||||
{
|
||||
for (int ox = -radius; ox <= radius; ox++)
|
||||
{
|
||||
int nx = x + ox;
|
||||
int ny = y + oy;
|
||||
if (nx < 0 || ny < 0 || nx >= chunkSize || ny >= chunkSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!wallMask[nx, ny])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RenderChunk(Vector2Int coord)
|
||||
{
|
||||
if (!chunks.TryGetValue(coord, out GeneratedChunk chunk))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorldAutotileProfile activeProfile = profile != null && profile.HasAnyAssignedTiles() ? profile : runtimeFallbackProfile;
|
||||
if (activeProfile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
chunk.Ground.ClearAllTiles();
|
||||
chunk.Walls.ClearAllTiles();
|
||||
chunk.Environment.ClearAllTiles();
|
||||
|
||||
for (int y = 0; y < chunkSize; y++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
Vector3Int localCell = new Vector3Int(x, y, 0);
|
||||
chunk.Ground.SetTile(localCell, activeProfile.baseGroundTile);
|
||||
|
||||
if (chunk.WallMask[x, y])
|
||||
{
|
||||
Vector2Int worldCell = ChunkToWorldCell(coord, x, y);
|
||||
AutoTileShape shape = ResolveWallShape(worldCell);
|
||||
chunk.Walls.SetTile(localCell, activeProfile.GetWallTile(shape));
|
||||
}
|
||||
else if (chunk.EnvironmentMask[x, y])
|
||||
{
|
||||
TileBase tile = PickEnvironmentTile(ChunkToWorldCell(coord, x, y), activeProfile);
|
||||
if (tile != null)
|
||||
{
|
||||
chunk.Environment.SetTile(localCell, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshNeighborBorders(Vector2Int coord)
|
||||
{
|
||||
RenderChunk(coord + Vector2Int.up);
|
||||
RenderChunk(coord + Vector2Int.right);
|
||||
RenderChunk(coord + Vector2Int.down);
|
||||
RenderChunk(coord + Vector2Int.left);
|
||||
}
|
||||
|
||||
private AutoTileShape ResolveWallShape(Vector2Int worldCell)
|
||||
{
|
||||
bool top = HasWallAt(worldCell + Vector2Int.up);
|
||||
bool right = HasWallAt(worldCell + Vector2Int.right);
|
||||
bool bottom = HasWallAt(worldCell + Vector2Int.down);
|
||||
bool left = HasWallAt(worldCell + Vector2Int.left);
|
||||
bool topLeft = HasWallAt(worldCell + new Vector2Int(-1, 1));
|
||||
bool topRight = HasWallAt(worldCell + new Vector2Int(1, 1));
|
||||
bool bottomRight = HasWallAt(worldCell + new Vector2Int(1, -1));
|
||||
bool bottomLeft = HasWallAt(worldCell + new Vector2Int(-1, -1));
|
||||
|
||||
if (!top && !left)
|
||||
{
|
||||
return AutoTileShape.OuterTopLeft;
|
||||
}
|
||||
|
||||
if (!top && !right)
|
||||
{
|
||||
return AutoTileShape.OuterTopRight;
|
||||
}
|
||||
|
||||
if (!bottom && !right)
|
||||
{
|
||||
return AutoTileShape.OuterBottomRight;
|
||||
}
|
||||
|
||||
if (!bottom && !left)
|
||||
{
|
||||
return AutoTileShape.OuterBottomLeft;
|
||||
}
|
||||
|
||||
if (top && left && !topLeft)
|
||||
{
|
||||
return AutoTileShape.InnerTopLeft;
|
||||
}
|
||||
|
||||
if (top && right && !topRight)
|
||||
{
|
||||
return AutoTileShape.InnerTopRight;
|
||||
}
|
||||
|
||||
if (bottom && right && !bottomRight)
|
||||
{
|
||||
return AutoTileShape.InnerBottomRight;
|
||||
}
|
||||
|
||||
if (bottom && left && !bottomLeft)
|
||||
{
|
||||
return AutoTileShape.InnerBottomLeft;
|
||||
}
|
||||
|
||||
if (!top)
|
||||
{
|
||||
return AutoTileShape.Top;
|
||||
}
|
||||
|
||||
if (!right)
|
||||
{
|
||||
return AutoTileShape.Right;
|
||||
}
|
||||
|
||||
if (!bottom)
|
||||
{
|
||||
return AutoTileShape.Bottom;
|
||||
}
|
||||
|
||||
if (!left)
|
||||
{
|
||||
return AutoTileShape.Left;
|
||||
}
|
||||
|
||||
return AutoTileShape.Center;
|
||||
}
|
||||
|
||||
private bool HasWallAt(Vector2Int worldCell)
|
||||
{
|
||||
Vector2Int coord = new Vector2Int(Mathf.FloorToInt(worldCell.x / (float)chunkSize), Mathf.FloorToInt(worldCell.y / (float)chunkSize));
|
||||
if (!chunks.TryGetValue(coord, out GeneratedChunk chunk))
|
||||
{
|
||||
return SampleRock(worldCell);
|
||||
}
|
||||
|
||||
int localX = worldCell.x - coord.x * chunkSize;
|
||||
int localY = worldCell.y - coord.y * chunkSize;
|
||||
if (localX < 0 || localY < 0 || localX >= chunkSize || localY >= chunkSize)
|
||||
{
|
||||
return SampleRock(worldCell);
|
||||
}
|
||||
|
||||
return chunk.WallMask[localX, localY];
|
||||
}
|
||||
|
||||
private TileBase PickEnvironmentTile(Vector2Int worldCell, WorldAutotileProfile activeProfile)
|
||||
{
|
||||
if (activeProfile.environmentTiles == null || activeProfile.environmentTiles.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
float total = 0f;
|
||||
for (int i = 0; i < activeProfile.environmentTiles.Count; i++)
|
||||
{
|
||||
EnvironmentTileEntry entry = activeProfile.environmentTiles[i];
|
||||
if (entry != null && entry.tile != null)
|
||||
{
|
||||
total += Mathf.Max(0.01f, entry.weight);
|
||||
}
|
||||
}
|
||||
|
||||
if (total <= 0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
float selector = Hash01(worldCell.x, worldCell.y, seed + 701);
|
||||
float threshold = selector * total;
|
||||
float cumulative = 0f;
|
||||
|
||||
for (int i = 0; i < activeProfile.environmentTiles.Count; i++)
|
||||
{
|
||||
EnvironmentTileEntry entry = activeProfile.environmentTiles[i];
|
||||
if (entry == null || entry.tile == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cumulative += Mathf.Max(0.01f, entry.weight);
|
||||
if (threshold <= cumulative)
|
||||
{
|
||||
return entry.tile;
|
||||
}
|
||||
}
|
||||
|
||||
return activeProfile.environmentTiles[0].tile;
|
||||
}
|
||||
|
||||
private Vector2Int WorldToChunk(Vector3 position)
|
||||
{
|
||||
return new Vector2Int(
|
||||
Mathf.FloorToInt(position.x / chunkSize),
|
||||
Mathf.FloorToInt(position.y / chunkSize));
|
||||
}
|
||||
|
||||
private Vector2Int ChunkToWorldCell(Vector2Int coord, int localX, int localY)
|
||||
{
|
||||
return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localY);
|
||||
}
|
||||
|
||||
private static int Hash(int x, int y, int seed)
|
||||
{
|
||||
int hash = x;
|
||||
hash = hash * 397 ^ y;
|
||||
hash = hash * 397 ^ seed;
|
||||
hash = (hash << 13) ^ hash;
|
||||
return hash * (hash * hash * 15731 + 789221) + 1376312589;
|
||||
}
|
||||
|
||||
private static float Hash01(int x, int y, int seed)
|
||||
{
|
||||
return (Hash(x, y, seed) & int.MaxValue) / (float)int.MaxValue;
|
||||
}
|
||||
|
||||
private readonly struct GeneratedChunk
|
||||
{
|
||||
public GeneratedChunk(Transform root, Tilemap ground, Tilemap walls, Tilemap environment, bool[,] wallMask, bool[,] environmentMask)
|
||||
{
|
||||
Root = root;
|
||||
Ground = ground;
|
||||
Walls = walls;
|
||||
Environment = environment;
|
||||
WallMask = wallMask;
|
||||
EnvironmentMask = environmentMask;
|
||||
}
|
||||
|
||||
public Transform Root { get; }
|
||||
public Tilemap Ground { get; }
|
||||
public Tilemap Walls { get; }
|
||||
public Tilemap Environment { get; }
|
||||
public bool[,] WallMask { get; }
|
||||
public bool[,] EnvironmentMask { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bacd3a0ac13e6f4a94548426dd89ebb
|
||||
@@ -0,0 +1,350 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
public static class ProceduralWorldArt
|
||||
{
|
||||
private const int TextureSize = 32;
|
||||
private static readonly Dictionary<string, Sprite> SpriteCache = new Dictionary<string, Sprite>();
|
||||
private static readonly Dictionary<string, Tile> TileCache = new Dictionary<string, Tile>();
|
||||
|
||||
public static Sprite CreatePlayerSprite()
|
||||
{
|
||||
const string key = "player_sprite";
|
||||
if (SpriteCache.TryGetValue(key, out Sprite cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
Color transparent = new Color(0f, 0f, 0f, 0f);
|
||||
Texture2D texture = CreateTexture(key, transparent);
|
||||
Color cloak = new Color(0.13f, 0.33f, 0.79f, 1f);
|
||||
Color trim = new Color(0.85f, 0.92f, 1f, 1f);
|
||||
Color face = new Color(0.99f, 0.89f, 0.72f, 1f);
|
||||
|
||||
FillRect(texture, 10, 4, 12, 16, cloak);
|
||||
FillRect(texture, 9, 18, 14, 7, cloak);
|
||||
FillRect(texture, 12, 24, 8, 5, face);
|
||||
FillRect(texture, 11, 23, 10, 1, trim);
|
||||
FillRect(texture, 8, 12, 3, 10, trim);
|
||||
FillRect(texture, 21, 12, 3, 10, trim);
|
||||
FillRect(texture, 12, 0, 3, 4, cloak);
|
||||
FillRect(texture, 17, 0, 3, 4, cloak);
|
||||
texture.Apply();
|
||||
|
||||
Sprite sprite = CreateSprite(texture);
|
||||
SpriteCache[key] = sprite;
|
||||
return sprite;
|
||||
}
|
||||
|
||||
public static TileBase CreateSolidTile(string key, Color color, float noiseStrength = 0.04f)
|
||||
{
|
||||
if (TileCache.TryGetValue(key, out Tile cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
Texture2D texture = CreateTexture(key, color);
|
||||
ApplyNoise(texture, noiseStrength, 31);
|
||||
texture.Apply();
|
||||
Tile tile = CreateTile(texture, key);
|
||||
TileCache[key] = tile;
|
||||
return tile;
|
||||
}
|
||||
|
||||
public static TileBase CreateFeatureTile(string key, Color fill, Color border, bool top, bool right, bool bottom, bool left, InnerCornerMask innerCornerMask = InnerCornerMask.None, bool solidCollider = false)
|
||||
{
|
||||
if (TileCache.TryGetValue(key, out Tile cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
Texture2D texture = CreateTexture(key, fill);
|
||||
ApplyNoise(texture, 0.05f, 71);
|
||||
DrawBorder(texture, top, right, bottom, left, border, 4);
|
||||
CarveInnerCorner(texture, innerCornerMask);
|
||||
texture.Apply();
|
||||
|
||||
Tile tile = CreateTile(texture, key);
|
||||
if (solidCollider)
|
||||
{
|
||||
tile.colliderType = Tile.ColliderType.Grid;
|
||||
}
|
||||
TileCache[key] = tile;
|
||||
return tile;
|
||||
}
|
||||
|
||||
public static TileBase CreateDecorationTile(string key, Color baseColor, Color accentColor, DecorationPattern pattern)
|
||||
{
|
||||
if (TileCache.TryGetValue(key, out Tile cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
Color transparent = new Color(0f, 0f, 0f, 0f);
|
||||
Texture2D texture = CreateTexture(key, transparent);
|
||||
|
||||
switch (pattern)
|
||||
{
|
||||
case DecorationPattern.Bush:
|
||||
DrawDisc(texture, 16, 15, 8, baseColor);
|
||||
DrawDisc(texture, 10, 13, 6, baseColor);
|
||||
DrawDisc(texture, 22, 13, 6, baseColor);
|
||||
DrawDisc(texture, 14, 17, 3, accentColor);
|
||||
DrawDisc(texture, 21, 17, 3, accentColor);
|
||||
break;
|
||||
case DecorationPattern.Flower:
|
||||
DrawDisc(texture, 16, 12, 2, baseColor);
|
||||
DrawDisc(texture, 12, 16, 2, baseColor);
|
||||
DrawDisc(texture, 20, 16, 2, baseColor);
|
||||
DrawDisc(texture, 16, 20, 2, baseColor);
|
||||
DrawDisc(texture, 16, 16, 2, accentColor);
|
||||
DrawLine(texture, 16, 0, 16, 12, new Color(0.2f, 0.5f, 0.2f, 1f));
|
||||
break;
|
||||
default:
|
||||
DrawDisc(texture, 12, 12, 4, baseColor);
|
||||
DrawDisc(texture, 20, 18, 3, accentColor);
|
||||
DrawDisc(texture, 9, 20, 2, accentColor);
|
||||
break;
|
||||
}
|
||||
|
||||
texture.Apply();
|
||||
Tile tile = CreateTile(texture, key);
|
||||
TileCache[key] = tile;
|
||||
return tile;
|
||||
}
|
||||
|
||||
private static Texture2D CreateTexture(string key, Color fillColor)
|
||||
{
|
||||
Texture2D texture = new Texture2D(TextureSize, TextureSize, TextureFormat.RGBA32, false)
|
||||
{
|
||||
filterMode = FilterMode.Point,
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
name = key
|
||||
};
|
||||
|
||||
Color[] pixels = new Color[TextureSize * TextureSize];
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
pixels[i] = fillColor;
|
||||
}
|
||||
|
||||
texture.SetPixels(pixels);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static Tile CreateTile(Texture2D texture, string key)
|
||||
{
|
||||
Sprite sprite = CreateSprite(texture);
|
||||
Tile tile = ScriptableObject.CreateInstance<Tile>();
|
||||
tile.sprite = sprite;
|
||||
tile.name = key;
|
||||
tile.hideFlags = HideFlags.HideAndDontSave;
|
||||
return tile;
|
||||
}
|
||||
|
||||
private static Sprite CreateSprite(Texture2D texture)
|
||||
{
|
||||
Sprite sprite = Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), TextureSize);
|
||||
sprite.name = texture.name;
|
||||
return sprite;
|
||||
}
|
||||
|
||||
private static void ApplyNoise(Texture2D texture, float strength, int offset)
|
||||
{
|
||||
if (strength <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = 0; y < texture.height; y++)
|
||||
{
|
||||
for (int x = 0; x < texture.width; x++)
|
||||
{
|
||||
Color pixel = texture.GetPixel(x, y);
|
||||
if (pixel.a <= 0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float noise = Mathf.PerlinNoise((x + offset) * 0.21f, (y + offset) * 0.21f) - 0.5f;
|
||||
texture.SetPixel(x, y, pixel * (1f + noise * strength * 2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawBorder(Texture2D texture, bool top, bool right, bool bottom, bool left, Color color, int thickness)
|
||||
{
|
||||
if (top)
|
||||
{
|
||||
FillRect(texture, 0, TextureSize - thickness, TextureSize, thickness, color);
|
||||
}
|
||||
|
||||
if (bottom)
|
||||
{
|
||||
FillRect(texture, 0, 0, TextureSize, thickness, color);
|
||||
}
|
||||
|
||||
if (left)
|
||||
{
|
||||
FillRect(texture, 0, 0, thickness, TextureSize, color);
|
||||
}
|
||||
|
||||
if (right)
|
||||
{
|
||||
FillRect(texture, TextureSize - thickness, 0, thickness, TextureSize, color);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CarveInnerCorner(Texture2D texture, InnerCornerMask innerCornerMask)
|
||||
{
|
||||
if (innerCornerMask == InnerCornerMask.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Color transparent = new Color(0f, 0f, 0f, 0f);
|
||||
const int radius = 8;
|
||||
|
||||
if ((innerCornerMask & InnerCornerMask.TopLeft) != 0)
|
||||
{
|
||||
CutCorner(texture, 0, TextureSize - 1, radius, transparent, 1, -1);
|
||||
}
|
||||
|
||||
if ((innerCornerMask & InnerCornerMask.TopRight) != 0)
|
||||
{
|
||||
CutCorner(texture, TextureSize - 1, TextureSize - 1, radius, transparent, -1, -1);
|
||||
}
|
||||
|
||||
if ((innerCornerMask & InnerCornerMask.BottomRight) != 0)
|
||||
{
|
||||
CutCorner(texture, TextureSize - 1, 0, radius, transparent, -1, 1);
|
||||
}
|
||||
|
||||
if ((innerCornerMask & InnerCornerMask.BottomLeft) != 0)
|
||||
{
|
||||
CutCorner(texture, 0, 0, radius, transparent, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CutCorner(Texture2D texture, int originX, int originY, int radius, Color transparent, int dirX, int dirY)
|
||||
{
|
||||
for (int x = 0; x < radius; x++)
|
||||
{
|
||||
for (int y = 0; y < radius; y++)
|
||||
{
|
||||
float dx = x / (float)radius;
|
||||
float dy = y / (float)radius;
|
||||
if (dx * dx + dy * dy > 1f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int px = originX + x * dirX;
|
||||
int py = originY + y * dirY;
|
||||
if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize)
|
||||
{
|
||||
texture.SetPixel(px, py, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawDisc(Texture2D texture, int centerX, int centerY, int radius, Color color)
|
||||
{
|
||||
int sqrRadius = radius * radius;
|
||||
for (int y = -radius; y <= radius; y++)
|
||||
{
|
||||
for (int x = -radius; x <= radius; x++)
|
||||
{
|
||||
if (x * x + y * y > sqrRadius)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int px = centerX + x;
|
||||
int py = centerY + y;
|
||||
if (px >= 0 && px < TextureSize && py >= 0 && py < TextureSize)
|
||||
{
|
||||
texture.SetPixel(px, py, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawLine(Texture2D texture, int x0, int y0, int x1, int y1, Color color)
|
||||
{
|
||||
int dx = Mathf.Abs(x1 - x0);
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int dy = -Mathf.Abs(y1 - y0);
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int error = dx + dy;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (x0 >= 0 && x0 < TextureSize && y0 >= 0 && y0 < TextureSize)
|
||||
{
|
||||
texture.SetPixel(x0, y0, color);
|
||||
}
|
||||
|
||||
if (x0 == x1 && y0 == y1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int twiceError = 2 * error;
|
||||
if (twiceError >= dy)
|
||||
{
|
||||
error += dy;
|
||||
x0 += sx;
|
||||
}
|
||||
|
||||
if (twiceError <= dx)
|
||||
{
|
||||
error += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillRect(Texture2D texture, int x, int y, int width, int height, Color color)
|
||||
{
|
||||
for (int py = y; py < y + height; py++)
|
||||
{
|
||||
if (py < 0 || py >= TextureSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int px = x; px < x + width; px++)
|
||||
{
|
||||
if (px < 0 || px >= TextureSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
texture.SetPixel(px, py, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DecorationPattern
|
||||
{
|
||||
Bush,
|
||||
Flower,
|
||||
Rocks
|
||||
}
|
||||
|
||||
[System.Flags]
|
||||
public enum InnerCornerMask
|
||||
{
|
||||
None = 0,
|
||||
TopLeft = 1,
|
||||
TopRight = 2,
|
||||
BottomRight = 4,
|
||||
BottomLeft = 8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc8c4a69ee2e7a046adf658709613591
|
||||
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
public static class RuntimeWorldProfileFactory
|
||||
{
|
||||
public static WorldAutotileProfile CreateFallbackProfile()
|
||||
{
|
||||
WorldAutotileProfile profile = ScriptableObject.CreateInstance<WorldAutotileProfile>();
|
||||
profile.name = "RuntimeFallbackWorldProfile";
|
||||
|
||||
Color grass = new Color(0.35f, 0.62f, 0.26f, 1f);
|
||||
Color wallFill = new Color(0.48f, 0.37f, 0.22f, 1f);
|
||||
Color wallBorder = new Color(0.29f, 0.21f, 0.12f, 1f);
|
||||
|
||||
profile.baseGroundTile = ProceduralWorldArt.CreateSolidTile("base_grass", grass, 0.06f);
|
||||
profile.wallTiles.center = ProceduralWorldArt.CreateFeatureTile("wall_center", wallFill, wallBorder, false, false, false, false, InnerCornerMask.None, true);
|
||||
profile.wallTiles.top = ProceduralWorldArt.CreateFeatureTile("wall_top", wallFill, wallBorder, true, false, false, false, InnerCornerMask.None, true);
|
||||
profile.wallTiles.right = ProceduralWorldArt.CreateFeatureTile("wall_right", wallFill, wallBorder, false, true, false, false, InnerCornerMask.None, true);
|
||||
profile.wallTiles.bottom = ProceduralWorldArt.CreateFeatureTile("wall_bottom", wallFill, wallBorder, false, false, true, false, InnerCornerMask.None, true);
|
||||
profile.wallTiles.left = ProceduralWorldArt.CreateFeatureTile("wall_left", wallFill, wallBorder, false, false, false, true, InnerCornerMask.None, true);
|
||||
profile.wallTiles.outerTopLeft = ProceduralWorldArt.CreateFeatureTile("wall_outer_tl", wallFill, wallBorder, true, false, false, true, InnerCornerMask.None, true);
|
||||
profile.wallTiles.outerTopRight = ProceduralWorldArt.CreateFeatureTile("wall_outer_tr", wallFill, wallBorder, true, true, false, false, InnerCornerMask.None, true);
|
||||
profile.wallTiles.outerBottomRight = ProceduralWorldArt.CreateFeatureTile("wall_outer_br", wallFill, wallBorder, false, true, true, false, InnerCornerMask.None, true);
|
||||
profile.wallTiles.outerBottomLeft = ProceduralWorldArt.CreateFeatureTile("wall_outer_bl", wallFill, wallBorder, false, false, true, true, InnerCornerMask.None, true);
|
||||
profile.wallTiles.innerTopLeft = ProceduralWorldArt.CreateFeatureTile("wall_inner_tl", wallFill, wallBorder, false, false, false, false, InnerCornerMask.TopLeft, true);
|
||||
profile.wallTiles.innerTopRight = ProceduralWorldArt.CreateFeatureTile("wall_inner_tr", wallFill, wallBorder, false, false, false, false, InnerCornerMask.TopRight, true);
|
||||
profile.wallTiles.innerBottomRight = ProceduralWorldArt.CreateFeatureTile("wall_inner_br", wallFill, wallBorder, false, false, false, false, InnerCornerMask.BottomRight, true);
|
||||
profile.wallTiles.innerBottomLeft = ProceduralWorldArt.CreateFeatureTile("wall_inner_bl", wallFill, wallBorder, false, false, false, false, InnerCornerMask.BottomLeft, true);
|
||||
|
||||
profile.environmentTiles.Add(new EnvironmentTileEntry
|
||||
{
|
||||
id = "Bush",
|
||||
tile = ProceduralWorldArt.CreateDecorationTile("env_bush", new Color(0.15f, 0.45f, 0.18f, 1f), new Color(0.23f, 0.58f, 0.25f, 1f), DecorationPattern.Bush),
|
||||
weight = 1.4f
|
||||
});
|
||||
profile.environmentTiles.Add(new EnvironmentTileEntry
|
||||
{
|
||||
id = "FlowerBlue",
|
||||
tile = ProceduralWorldArt.CreateDecorationTile("env_flower_blue", new Color(0.44f, 0.62f, 0.96f, 1f), new Color(0.98f, 0.91f, 0.52f, 1f), DecorationPattern.Flower),
|
||||
weight = 0.75f
|
||||
});
|
||||
profile.environmentTiles.Add(new EnvironmentTileEntry
|
||||
{
|
||||
id = "Rock",
|
||||
tile = ProceduralWorldArt.CreateDecorationTile("env_rock", new Color(0.48f, 0.48f, 0.52f, 1f), new Color(0.69f, 0.69f, 0.74f, 1f), DecorationPattern.Rocks),
|
||||
weight = 0.9f
|
||||
});
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static List<ChunkTemplate> CreateFallbackTemplates(int size)
|
||||
{
|
||||
List<ChunkTemplate> templates = new List<ChunkTemplate>
|
||||
{
|
||||
CreateCross(size),
|
||||
CreateStraightHorizontal(size),
|
||||
CreateStraightVertical(size),
|
||||
CreateCorner(size, true, true, false, false),
|
||||
CreateCorner(size, false, true, true, false),
|
||||
CreateCorner(size, false, false, true, true),
|
||||
CreateCorner(size, true, false, false, true),
|
||||
CreateTJunction(size, true, true, true, false),
|
||||
CreateTJunction(size, false, true, true, true),
|
||||
CreateTJunction(size, true, false, true, true),
|
||||
CreateTJunction(size, true, true, false, true)
|
||||
};
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
private static ChunkTemplate CreateCross(int size)
|
||||
{
|
||||
ChunkTemplate template = CreateTemplate("Runtime_Cross", size, size, true, true, true, true);
|
||||
DrawRoom(template, 3, 3, size - 6, size - 6);
|
||||
DrawPillar(template, 4, 4);
|
||||
DrawPillar(template, size - 5, 4);
|
||||
DrawPillar(template, 4, size - 5);
|
||||
DrawPillar(template, size - 5, size - 5);
|
||||
ScatterEnvironment(template, 3);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static ChunkTemplate CreateStraightHorizontal(int size)
|
||||
{
|
||||
ChunkTemplate template = CreateTemplate("Runtime_Straight_H", size, size, false, true, false, true);
|
||||
DrawRoom(template, 2, 4, size - 4, size - 8);
|
||||
DrawSideAlcoves(template, horizontal: true);
|
||||
ScatterEnvironment(template, 2);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static ChunkTemplate CreateStraightVertical(int size)
|
||||
{
|
||||
ChunkTemplate template = CreateTemplate("Runtime_Straight_V", size, size, true, false, true, false);
|
||||
DrawRoom(template, 4, 2, size - 8, size - 4);
|
||||
DrawSideAlcoves(template, horizontal: false);
|
||||
ScatterEnvironment(template, 2);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static ChunkTemplate CreateCorner(int size, bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
ChunkTemplate template = CreateTemplate($"Runtime_Corner_{top}_{right}_{bottom}_{left}", size, size, top, right, bottom, left);
|
||||
DrawRoom(template, 3, 3, size - 6, size - 6);
|
||||
if (!top)
|
||||
{
|
||||
DrawWallLine(template, 5, size - 5, size - 6, size - 5);
|
||||
}
|
||||
if (!right)
|
||||
{
|
||||
DrawWallLine(template, size - 5, 5, size - 5, size - 6);
|
||||
}
|
||||
if (!bottom)
|
||||
{
|
||||
DrawWallLine(template, 5, 4, size - 6, 4);
|
||||
}
|
||||
if (!left)
|
||||
{
|
||||
DrawWallLine(template, 4, 5, 4, size - 6);
|
||||
}
|
||||
ScatterEnvironment(template, 2);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static ChunkTemplate CreateTJunction(int size, bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
ChunkTemplate template = CreateTemplate($"Runtime_T_{top}_{right}_{bottom}_{left}", size, size, top, right, bottom, left);
|
||||
DrawRoom(template, 2, 2, size - 4, size - 4);
|
||||
DrawPillar(template, size / 2, size / 2);
|
||||
ScatterEnvironment(template, 3);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static ChunkTemplate CreateTemplate(string name, int width, int height, bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
ChunkTemplate template = ScriptableObject.CreateInstance<ChunkTemplate>();
|
||||
template.name = name;
|
||||
template.Resize(width, height);
|
||||
template.exitTop = top;
|
||||
template.exitRight = right;
|
||||
template.exitBottom = bottom;
|
||||
template.exitLeft = left;
|
||||
template.ApplyBorderWallsFromExits(3);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static void DrawRoom(ChunkTemplate template, int xMin, int yMin, int width, int height)
|
||||
{
|
||||
for (int x = xMin; x < xMin + width; x++)
|
||||
{
|
||||
template.SetWall(x, yMin, true);
|
||||
template.SetWall(x, yMin + height - 1, true);
|
||||
}
|
||||
|
||||
for (int y = yMin; y < yMin + height; y++)
|
||||
{
|
||||
template.SetWall(xMin, y, true);
|
||||
template.SetWall(xMin + width - 1, y, true);
|
||||
}
|
||||
|
||||
template.CarveExit(ChunkExit.Top, 3);
|
||||
template.CarveExit(ChunkExit.Right, 3);
|
||||
template.CarveExit(ChunkExit.Bottom, 3);
|
||||
template.CarveExit(ChunkExit.Left, 3);
|
||||
}
|
||||
|
||||
private static void DrawPillar(ChunkTemplate template, int x, int y)
|
||||
{
|
||||
for (int py = -1; py <= 1; py++)
|
||||
{
|
||||
for (int px = -1; px <= 1; px++)
|
||||
{
|
||||
template.SetWall(x + px, y + py, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawSideAlcoves(ChunkTemplate template, bool horizontal)
|
||||
{
|
||||
if (horizontal)
|
||||
{
|
||||
DrawWallLine(template, template.width / 2 - 1, 5, template.width / 2 - 1, 7);
|
||||
DrawWallLine(template, template.width / 2 + 1, template.height - 8, template.width / 2 + 1, template.height - 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawWallLine(template, 5, template.height / 2 - 1, 7, template.height / 2 - 1);
|
||||
DrawWallLine(template, template.width - 8, template.height / 2 + 1, template.width - 6, template.height / 2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawWallLine(ChunkTemplate template, int x0, int y0, int x1, int y1)
|
||||
{
|
||||
int dx = Math.Sign(x1 - x0);
|
||||
int dy = Math.Sign(y1 - y0);
|
||||
int x = x0;
|
||||
int y = y0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
template.SetWall(x, y, true);
|
||||
if (x == x1 && y == y1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (x != x1)
|
||||
{
|
||||
x += dx;
|
||||
}
|
||||
|
||||
if (y != y1)
|
||||
{
|
||||
y += dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScatterEnvironment(ChunkTemplate template, int spacing)
|
||||
{
|
||||
for (int y = 2; y < template.height - 2; y += spacing + 2)
|
||||
{
|
||||
for (int x = 2; x < template.width - 2; x += spacing + 3)
|
||||
{
|
||||
if (!template.GetWall(x, y))
|
||||
{
|
||||
template.SetEnvironment(x, y, ((x + y) & 1) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5b485257bdc222448c314a7cd173845
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace InfiniteWorld
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Infinite World/World Autotile Profile", fileName = "WorldAutotileProfile")]
|
||||
public class WorldAutotileProfile : ScriptableObject
|
||||
{
|
||||
public TileBase baseGroundTile;
|
||||
public AutoTileDefinition wallTiles = new AutoTileDefinition();
|
||||
public List<EnvironmentTileEntry> environmentTiles = new List<EnvironmentTileEntry>();
|
||||
|
||||
public TileBase GetWallTile(AutoTileShape shape)
|
||||
{
|
||||
return wallTiles != null ? wallTiles.GetTile(shape) : null;
|
||||
}
|
||||
|
||||
public bool HasAnyAssignedTiles()
|
||||
{
|
||||
if (baseGroundTile != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wallTiles != null && wallTiles.HasAnyAssignedTiles())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < environmentTiles.Count; i++)
|
||||
{
|
||||
if (environmentTiles[i] != null && environmentTiles[i].tile != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AutoTileShape
|
||||
{
|
||||
Center,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
Left,
|
||||
OuterTopLeft,
|
||||
OuterTopRight,
|
||||
OuterBottomRight,
|
||||
OuterBottomLeft,
|
||||
InnerTopLeft,
|
||||
InnerTopRight,
|
||||
InnerBottomRight,
|
||||
InnerBottomLeft
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AutoTileDefinition
|
||||
{
|
||||
public TileBase center;
|
||||
public TileBase top;
|
||||
public TileBase right;
|
||||
public TileBase bottom;
|
||||
public TileBase left;
|
||||
public TileBase outerTopLeft;
|
||||
public TileBase outerTopRight;
|
||||
public TileBase outerBottomRight;
|
||||
public TileBase outerBottomLeft;
|
||||
public TileBase innerTopLeft;
|
||||
public TileBase innerTopRight;
|
||||
public TileBase innerBottomRight;
|
||||
public TileBase innerBottomLeft;
|
||||
|
||||
public TileBase GetTile(AutoTileShape shape)
|
||||
{
|
||||
TileBase tile = shape switch
|
||||
{
|
||||
AutoTileShape.Center => center,
|
||||
AutoTileShape.Top => top,
|
||||
AutoTileShape.Right => right,
|
||||
AutoTileShape.Bottom => bottom,
|
||||
AutoTileShape.Left => left,
|
||||
AutoTileShape.OuterTopLeft => outerTopLeft,
|
||||
AutoTileShape.OuterTopRight => outerTopRight,
|
||||
AutoTileShape.OuterBottomRight => outerBottomRight,
|
||||
AutoTileShape.OuterBottomLeft => outerBottomLeft,
|
||||
AutoTileShape.InnerTopLeft => innerTopLeft,
|
||||
AutoTileShape.InnerTopRight => innerTopRight,
|
||||
AutoTileShape.InnerBottomRight => innerBottomRight,
|
||||
AutoTileShape.InnerBottomLeft => innerBottomLeft,
|
||||
_ => center
|
||||
};
|
||||
|
||||
return tile ?? center;
|
||||
}
|
||||
|
||||
public bool HasAnyAssignedTiles()
|
||||
{
|
||||
return center != null ||
|
||||
top != null ||
|
||||
right != null ||
|
||||
bottom != null ||
|
||||
left != null ||
|
||||
outerTopLeft != null ||
|
||||
outerTopRight != null ||
|
||||
outerBottomRight != null ||
|
||||
outerBottomLeft != null ||
|
||||
innerTopLeft != null ||
|
||||
innerTopRight != null ||
|
||||
innerBottomRight != null ||
|
||||
innerBottomLeft != null;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class EnvironmentTileEntry
|
||||
{
|
||||
public string id = "Environment";
|
||||
public TileBase tile;
|
||||
[Min(0f)] public float weight = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e36b055e81273a54c8e736e2ef74fa50
|
||||
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"templatePinStates": [],
|
||||
"dependencyTypeInfos": [
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.AnimationClip",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEditor.Animations.AnimatorController",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.AnimatorOverrideController",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEditor.Audio.AudioMixerController",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.ComputeShader",
|
||||
"defaultInstantiationMode": 1
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Cubemap",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.GameObject",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEditor.LightingDataAsset",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.LightingSettings",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Material",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEditor.MonoScript",
|
||||
"defaultInstantiationMode": 1
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.PhysicsMaterial",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.PhysicsMaterial2D",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Rendering.PostProcessing.PostProcessResources",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Rendering.VolumeProfile",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEditor.SceneAsset",
|
||||
"defaultInstantiationMode": 1
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Shader",
|
||||
"defaultInstantiationMode": 1
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.ShaderVariantCollection",
|
||||
"defaultInstantiationMode": 1
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Texture",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Texture2D",
|
||||
"defaultInstantiationMode": 0
|
||||
},
|
||||
{
|
||||
"userAdded": false,
|
||||
"type": "UnityEngine.Timeline.TimelineAsset",
|
||||
"defaultInstantiationMode": 0
|
||||
}
|
||||
],
|
||||
"defaultDependencyTypeInfo": {
|
||||
"userAdded": false,
|
||||
"type": "<default_scene_template_dependencies>",
|
||||
"defaultInstantiationMode": 1
|
||||
},
|
||||
"newSceneOverride": 0
|
||||
}
|
||||
Reference in New Issue
Block a user