-
Notifications
You must be signed in to change notification settings - Fork 0
Recipe ‐ AR Slime Demo (Android)
A simple demo project which utilizes Niantic Lightship SDK
for automatic surface detection
and navigation mesh
creation.
You have to grant camera access on your smartphone for this demo to work.
Simply hold your smartphone and point it on a surface, preferably with a wall or corner for better detection results. After the surface has been created you can see blue squares. Touch with your finger on your smartphone on a square to send the agent (Slime) to the desired position.
Features
- Create a navigation mesh on most surfaces
- Navigate a friendly slime
- Change the skin of you slime via the settings menu
Accounts
- Free Niantic Lightship Developer Account - Sign up here
Unity Version
-
2022.3.15f1
-
Android Build Support
- OpenJDK
- Android SDK & NDK Tools
Asset Packs
- Niantic Lightship (ARDK3.1) - https://lightship.dev/
- Models
- Unity Asset Store Package Kawaii Slimes
IDE
- Rider 2023.x.x (or any newer version)
- Please note that you may use any other IDE you are comfortable with
- Sign in into your
Niantic Lightship Account
and create aNew Project
- Click on
Projects
and create aNew Project
- Change the project title from
Untitled
toAR Slime Demo
- Copy and save the
API Key
for later use temporarily in a text editor of your choice
If anything changes in the future you might want to consider following the official Niantic Lightship Documentation.
- Create a new Unity
2022.3.15f1
project with the3D (Core)
template selected and name itAR Slime Demo
, uncheckUnity Cloud
and click on theCreate Project
button
- In your Unity project, open the
Package Manager
by selectingWindow > Package Manager
- From the plus menu on the left hand side in the Package Manager, select
Add package from git URL...
- Enter the git URL https://github.com/niantic-lightship/ardk-upm.git
- Click
Yes
to activate the newInput System Package
for AR Foundation 5.0 (if prompted)
- From the plus menu on the left hand side in the Package Manager, select
- Close the package manager
- In the top menu of Unity, select
Lightship > XR Plug-in Management
to navigate to theXR Plug-in Management
menu - In the
XR Plug-in Management
menu, select the platformAndroid
, then check the checkbox labelledNiantic Lightship SDK+ Google ARCore
- Please note that in Unity version
2022.3.10f1
or above, you might see a benign error in the console at this point
- Please note that in Unity version
- Close
Project Settings
- Open the
Lightship
settings by selectingLightship > Settings
- In the
Project Settings
window which just opened, paste your previously savedAPI Key
into theAPI Key
field underCredentials
- Close
Project Settings
- Open the
Build Settings
window by selectingFile > Build Settings
- Select Android, then click
Switch Platform
- After the progress bar finishes, click
Player Settings
- Player Settings for Android
-
Other Settings > Rendering
- Uncheck AutoGraphics API
-
Other Settings > Rendering
- RemoveVulkan
from the Graphics API list -
Other Settings > Minimum API Level
- Set the Minimum API Level toAndroid 7.0 'Nougat' (API Level 24)
-
Other Settings > Scripting Backend
- SelectIL2CPP
from the drop-down, then enable bothARMv7
andARM64
-
- Close
Project Settings
- Rename the scene
SampleScene
toMainScene
- Open the
MainScene
- Delete the
Main Camera
game object from theHierarchy
panel - Right click in the
Hierarchy
panel and selectXR > AR Session
- Set the position to
0, 0, 0
- Set the position to
- Right click in the Hierarchy panel and select
XR > XR Origin (Mobile AR)
- Set the position to
0, 0, 0
- Set the position to
- Save the scene by pressing
Ctrl + S
on your keyboard
- Navigate to
File > Build Settings
- Press
Add Open Scenes
button to add the current scene to the build settings - Close
Build Settings
- Create folder
Shaders
Add shader InvisibleMeshWithShadows
Shader "Unlit/InvisibleMeshWithShadows"
{
Properties
{
_Shadowetensity ("Shadow Intensity", Range (0, 1)) = 0.6
}
SubShader
{
Tags {"Queue"="AlphaTest" }
Pass
{
Tags {"LightMode" = "ForwardBase" }
Cull Back
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
uniform float _Shadowetensity;
struct v2f
{
float4 pos : SV_POSITION;
LIGHTING_COORDS(0,1)
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos (v.vertex);
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
fixed4 frag(v2f i) : COLOR
{
float attenuation = LIGHT_ATTENUATION(i);
return fixed4(0,0,0,(1-attenuation)*_Shadowetensity);
}
ENDCG
}
}
Fallback "VertexLit"
}
Add shader Silhouette
Shader "Custom/Silhouette" {
Properties {
_Color ("Main Color", Color) = (1.0,1.0,1.0,1.0)
_MainTex ("Base (RGB)", 2D) = "white" { }
}
SubShader {
Pass {
Name "GHOST"
Cull Back
ZWrite Off
ZTest GEqual
//if you want to remove the over draw for overlapping items add this stencil buffer.
Stencil {
Ref 1
Comp GEqual
Pass Invert
}
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 pos : POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.color = float4(1,1,1,1);
return o;
}
sampler2D _MainTex;
half4 frag(v2f i) :COLOR {
//can make these uniforms if you want it controllable.
//setting color to 0 will make them a back silhouette, above mixes there colors.
//setting alpha to 0 will remove the effect 1 will be a solid mix.
float _AlphaAmount = 0.4;
float _ColorAmount = 0.05;
float4 c = tex2D(_MainTex, i.uv)*_ColorAmount;
c.a=_AlphaAmount;
return c;
}
ENDCG
}
Pass {
Name "BASE"
ZWrite On
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
//not sure why we need this to be x2 but it works otherwise the texture is too dark some sort of colorspace thing
Material {
Diffuse (2,2,2,1)
}
Lighting On
SetTexture [_MainTex] {
constantColor [_Color]
Combine texture * constant
}
}
}
Fallback "Diffuse"
}
- Create folder
Materials
Add material InvisibleMeshWithShadowsMat
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: InvisibleMeshWithShadowsMat
m_Shader: {fileID: 4800000, guid: e9a4c4e1418b842fcbd3ef02ec087daa, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Shadowetensity: 0.6
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []
Add material Silhouette
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Silhouette
m_Shader: {fileID: 4800000, guid: 768fecefaff8747a19bf3daa524caab7, type: 3}
m_ValidKeywords: []
m_InvalidKeywords:
- _ALPHAPREMULTIPLY_ON
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 10
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 3
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 0
m_Colors:
- _Color: {r: 0, g: 0.8310709, b: 1, a: 0.3529412}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []
- Create an empty game object with the name
InvisibleMeshPrefab
and set the position to0, 0, 0
- Add the component
Mesh Filter
- Add the component
Mesh Renderer
- Under Materials set
InvisibleMeshWithShadowsMat
- Under Lighting set
Cast Shadows
toOff
- Under Materials set
- Add the component
Mesh Collider
- Add the component
- Create folder
Prefabs
- Create a prefab from
InvisibleMeshPrefab
and add it into thePrefabs
folder - Remove the original object from the scene
- Create an empty game object under the
Camera Offset
object with the nameMeshing
- Add the component
ARMeshManager
- Assign the previously created
InvisibleMeshPrefab
intoMesh Prefab
- Assign the previously created
- Create an empty game object with the name
LightshipNavMeshManager
and set the position to0, 0, 0
- Add the component
Lightship Nav Mesh Manager
- Assign the
Main Camera
toCamera
- Set the
Layer Mask
toDefault
- Assign the
- Add the component
Lightship Nav Mesh Renderer
which is used for visualising the navigation mesh (For development purposes only)- Assign the
LightshipNavMeshManager
toLightship Nav Mesh Manager
- Assign the Material
Silhouette
toMaterial
- Assign the
- Add the component
- Save the scene by pressing
Ctrl + S
on your keyboard
- Create a new folder called
Scripts
- Create a new Script
NavMeshAgentNavigator
inScripts
folder- Add the following content to the newly created script
// NavMeshAgentNavigator.cs
using System.Collections;
using System.Collections.Generic;
using Niantic.Lightship.AR.NavigationMesh;
using UnityEngine;
public class NavMeshAgentNavigator : MonoBehaviour
{
[Header("Agent Settings")]
public float walkingSpeed = 3.0f;
public float jumpDistance = 1;
public int jumpPenalty = 2;
public PathFindingBehaviour pathFindingBehaviour = PathFindingBehaviour.InterSurfacePreferResults;
[Header("Scene References")]
public LightshipNavMeshManager lightshipNavMeshManager;
private enum AgentNavigationState
{
Paused,
Idle,
HasPath
}
private AgentNavigationState state = AgentNavigationState.Idle;
private Path path = new(null, Path.Status.PathInvalid);
private Coroutine actorMoveCoroutine;
private Coroutine actorJumpCoroutine;
private AgentConfiguration agentConfig;
private LightshipNavMesh lightshipNavMesh;
private bool agentReady;
private IEnumerator Start()
{
agentConfig = new AgentConfiguration(jumpPenalty, jumpDistance, pathFindingBehaviour);
yield return new WaitForSeconds(0.1f);
lightshipNavMesh = lightshipNavMeshManager.LightshipNavMesh;
agentReady = true;
}
private void Update()
{
if (!agentReady)
return;
switch (state)
{
case AgentNavigationState.Paused:
break;
case AgentNavigationState.Idle:
StayOnNavMesh();
break;
case AgentNavigationState.HasPath:
break;
}
}
private void StopMoving()
{
if (actorMoveCoroutine != null)
StopCoroutine(actorMoveCoroutine);
}
public void SetDestination(Vector3 destination)
{
StopMoving();
if (lightshipNavMesh == null)
return;
lightshipNavMesh.FindNearestFreePosition(transform.position, out Vector3 startOnBoard);
bool result = lightshipNavMesh.CalculatePath(startOnBoard, destination, agentConfig, out path);
if (!result)
state = AgentNavigationState.Idle;
else
{
state = AgentNavigationState.HasPath;
actorMoveCoroutine = StartCoroutine(Move(transform, path.Waypoints));
}
}
private void StayOnNavMesh()
{
if (lightshipNavMesh == null || lightshipNavMesh.Area == 0)
return;
if (lightshipNavMesh.IsOnNavMesh(transform.position, 0.2f))
return;
List<Waypoint> pathToNavMesh = new();
lightshipNavMesh.FindNearestFreePosition(transform.position, out Vector3 nearestPosition);
pathToNavMesh.Add(new Waypoint
(
transform.position,
Waypoint.MovementType.Walk,
Utils.PositionToTile(transform.position, lightshipNavMesh.Settings.TileSize)
));
pathToNavMesh.Add(new Waypoint
(
nearestPosition,
Waypoint.MovementType.SurfaceEntry,
Utils.PositionToTile(nearestPosition, lightshipNavMesh.Settings.TileSize)
));
path = new Path(pathToNavMesh, Path.Status.PathComplete);
actorMoveCoroutine = StartCoroutine(Move(transform, path.Waypoints));
state = AgentNavigationState.HasPath;
}
private IEnumerator Move(Transform actor, IList<Waypoint> path)
{
Vector3 startPosition = actor.position;
Quaternion startRotation = actor.rotation;
float interval = 0.0f;
int destIdx = 0;
while (destIdx < path.Count)
{
Vector3 destination = path[destIdx].WorldPosition;
//make sure the destination is on the mesh
//LightshipNavMesh is an average height so can be under/over the mesh
//the nav agent should always stand on the mesh, so using a ray cast to lift them up/down as they move.
Vector3 from = destination + Vector3.up;
Vector3 dir = Vector3.down;
RaycastHit hit;
if (Physics.Raycast(from, dir, out hit, 100, lightshipNavMesh.Settings.LayerMask))
{
destination = hit.point;
}
//do i need to jump or walk to the target point
if (path[destIdx].Type == Waypoint.MovementType.SurfaceEntry)
{
yield return new WaitForSeconds(0.5f);
actorJumpCoroutine = StartCoroutine
(
Jump(actor, actor.position, destination)
);
yield return actorJumpCoroutine;
actorJumpCoroutine = null;
startPosition = actor.position;
startRotation = actor.rotation;
}
else
{
//move on step towards target waypoint
interval += Time.deltaTime * walkingSpeed;
actor.position = Vector3.Lerp(startPosition, destination, interval);
}
//face the direction we are moving
Vector3 lookRotationTarget = (destination - transform.position);
//ignore up/down we dont want the creature leaning forward/backward.
lookRotationTarget.y = 0.0f;
lookRotationTarget = lookRotationTarget.normalized;
//check for bad rotation
if (lookRotationTarget != Vector3.zero)
transform.rotation = Quaternion.Lerp(startRotation, Quaternion.LookRotation(lookRotationTarget),
interval);
//have we reached our target position, if so go to the next waypoint
if (Vector3.Distance(actor.position, destination) < 0.01f)
{
startPosition = actor.position;
startRotation = actor.rotation;
interval = 0;
destIdx++;
}
yield return null;
}
actorMoveCoroutine = null;
state = AgentNavigationState.Idle;
}
private IEnumerator Jump(Transform actor, Vector3 from, Vector3 to, float speed = 2.0f)
{
float interval = 0.0f;
Quaternion startRotation = actor.rotation;
float height = Mathf.Max(0.1f, Mathf.Abs(to.y - from.y));
while (interval < 1.0f)
{
interval += Time.deltaTime * speed;
Vector3 rotation = to - from;
rotation = Vector3.ProjectOnPlane(rotation, Vector3.up).normalized;
if (rotation != Vector3.zero)
transform.rotation = Quaternion.Lerp(startRotation, Quaternion.LookRotation(rotation), interval);
Vector3 p = Vector3.Lerp(from, to, interval);
actor.position = new Vector3
(
p.x,
-4.0f * height * interval * interval +
4.0f * height * interval +
Mathf.Lerp(from.y, to.y, interval),
p.z
);
yield return null;
}
actor.position = to;
}
}
- Create a new Script
InputManager
inScripts
folder- Add the following content to the newly created script
// InputManager.cs
using UnityEngine;
public class InputManager : MonoBehaviour
{
[Header("Scene References")]
public NavMeshAgentNavigator navMeshAgentNavigator;
private void Update()
{
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Ended)
{
// Create ray from the camera and passing through the touch position
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
HandleTouchInput(ray);
}
if (Input.GetMouseButtonUp(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
HandleTouchInput(ray);
}
}
private void HandleTouchInput(Ray ray)
{
RaycastHit hit;
if (Physics.Raycast(ray, out hit) && !UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
{
navMeshAgentNavigator.SetDestination(hit.point);
}
}
}
- Create an empty game object with the name
NavMeshAgent
and set the position to0, 0, 2
- Add the component
NavMeshAgentNavigator
- Assign the
LightshipNavMeshManager
toLightship Nav Mesh Manager
- Assign the
- Add the component
- Create a
Cube
as a child ofNavMeshAgent
with the nameMesh
and set thescale
to0.1, 0.1, 0.1
- Remove the
Box Collider
component
- Remove the
- Create a new material with the name
Nose
inMaterials
folder- Change to color to
#FF4949
- Change to color to
- Create a
Cube
as a child ofMesh
with the nameNose
, set theposition
to0, 0, 0.5
and thescale
to0.2, 0.2, 0.3
- Remove the
Box Collider
component - Assign the
Nose
material
- Remove the
- Create an empty game object with the name
- Managers -
and set theposition
to0, 0, 0
- Add the component
Input Manager
- Assign the
NavMeshAgent
toNavMeshAgentNavigator
- Assign the
- Add the component
- Create a
Cube
with the nameFloor
, set theposition
to0, -1, 1.5
and thescale
to5, 0.1, 5
- In the
Hierarchy
right click and click onUI > Canvas
to create an new UI Canvas and the EventSystem - Rearrange the game objects in the
Hierarchy
for a better overview
- Change the
XR Origin
rotation
to14.5, 0, 0
to be able to see the floor easier - Press
Play
in Unity and check out the demo- The NavMeshAgent should automatically place themself on the NavMesh and if you click on a random point on the screen / the NavMesh the NavMeshAgent should automatically move to the nearest possible point
- Save the scene by pressing
Ctrl + S
on your keyboard - You might see some z-fighting if you want to fix this, just change the
Size
of theBox Collider
of theFloor
to1, 1.01, 1
- Save the scene by pressing
Ctrl + S
on your keyboard - You can now deploy it to your smartphone (Android) and try it out
- Be sure to disable or remove the
Floor
game object before deployment otherwise it might cover the screen
- Be sure to disable or remove the
- You are now good to go and continue on your own or if you want to have a different character as well as UI interactions continue with the next section
- Add the
Kawaii Slimes
asset pack to the project - Create folder
Plugins
- Move the folder
Kawaii Slimes
intoPlugins
- Copy Prefabs from
Kawaii Slimes Plugin
- Navigate to the
Plugins/Kawaii Slimes/Prefabs
folder in the Project panel. - Select all prefab files within the
Plugins/Kawaii Slimes/Prefabs
folder - Right-click on the selected prefabs and choose Copy
- Navigate to the
Prefabs
folder in the Project panel and Paste the Prefabs here
- Navigate to the
- Add
Animators
to EachSlime Prefab
- In the
Prefabs
folder, select all the Kawaii Slime prefabs - In the
Inspector
panel, click on theAdd Component
button - Search for
Animator
and select it to add an Animator component to the prefabs - Now set the
Controller
toSlime
and theAvatar
toSlime_AnimAvatar
- In the
- In the
Hierarchy
panel, select theCanvas
game object- Right-click on
Canvas
and navigate toUI > Raw Image
to create a new Raw Image under the Canvas - With the
Raw Image
selected, go to theInspector
panel - Rename the
Raw Image
toUI Menu Open Button
- In the
Rect Transform
component, click on theAnchor Preset
button and select thetop-right
anchor preset to set the Raw Image's anchor to the top right corner - Position the
Raw Image
in theTop Right Corner
- With the
Raw Image
still selected in theHierarchy
panel, click on theAdd Component
button in theInspector
panel - Search for
Button
and select it to add aButton component
to theRaw Image
- If you have a
Icon
that you want to use for the Button you can set it as theTexture
in the Raw Image
- Right-click on
- In the
Hierarchy
panel, right-click on the Canvas game object and navigate toUI > Image
to create a new image- Rename the newly created image to
UI Menu Container
- Rename the newly created image to
- Right-click on the UI Menu Container image in the
Hierarchy
and go toUI > Image
to add another image as a child - Rename this child image to Menu Background
- Adjust the alpha value of the
UI Menu Container
to 130 for semi-transparency and set the color toblack
- Select the
Menu Background
in theHierarchy
and in theInspector
panel, set the color to adark gray
- Set Anchor for
UI Menu Container
andMenu Background
toStretch
- For the
UI Menu Container
setLeft
,Right
,Top
, andBottom
fields all to0
under the Rect Transform component
- For the
Menu Background
setLeft
,Right
,Top
, andBottom
fields all to100
under the Rect Transform component
- Select the
Menu Background
in theHierarchy
panel - Right-click on it and navigate to
UI > Text - TextMeshPro
to create a new Text Mesh Pro text object (move the created TextMesh Pro Folder to the Plugins Folder) - Rename this new object to
Menu Title
- Right-click on the
Menu Background
again and go toUI > Dropdown - TextMeshPro
- Rename this new object to
Menu Dropdown
- Right-click on the
Menu Background
and selectUI > Raw Image
- Rename this new
Raw Image
toSlime Preview
- Right-click on the
Menu Background
and chooseUI > Button
- Rename the new button to
Continue Button
- Customize these
UI elements
to how you want them to look
- Select the
Continue Button
in theHierarchy
panel - In the
Inspector
panel, scroll down to the Button component where you'll find theOnClick()
event section - Click the
+
button to add a new event - Drag the
UI Menu Container
from theHierarchy panel
and drop it into the object field of the newly created event - In the function dropdown (No Function selected by default), navigate to
GameObject > SetActive
- Leave the checkbox next to the function set to
false
- Select the
UI Menu Open Button
in theHierarchy
panel and do the same as you did for the Continue Button but set the checkbox to true - In the Unity Project panel, right-click in the Assets directory - select
Create > Folder
and name this new folderTextures
- Navigate into the
Textures
folder you just created - Right-click within the folder, select
Create > Render Texture
- A new render texture will be created in the
Textures
folder rename it toSlime Preview Texture
- Assign this
Texture
to theSlime Preview Image
in theMenu Background
- Select the
UI Menu Container
in theHierarchy
panel - Right-click on it and choose
Create Empty
to create a new empty GameObject - With the new empty GameObject selected, in the
Inspector
panel, right-click on theRect Transform
component and selectRemove Component
to remove it - Rename this GameObject to
Preview Slime Box
- With
Preview Slime Box
selected, right-click on it and choose3D Object > Cube
to create a new cube - Rename this cube to
Wall
and adjust its position and scale to serve as a wall - Create another cube following the same steps and rename it to
Floor
- Right-click on
Preview Slime Box
and chooseCamera
to add a new camera to this GameObject - Rename the camera as needed
- Assign the
Render Texture
(created previously in the Textures folder) to theTarget Texture
field of the camera - Also remove the
Audio Listener
from the created Camera - The
Camera
should look at the0,0,0
local position so that when a new object is created within thePreview Slime Box
it is in the center of theRender Texture
- In the
Project panel
open theMaterials
folder, right-click and navigate toCreate > Material
to create a new material - Rename the newly created material to a descriptive name, such as
Slime Preview Background
- Select the new material to view its properties in the
Inspector
panel in the Shader dropdown menu, selectUnlit > Color
- Set the color of the material to match the background color of your menu
- In the
Hierarchy
panel, locate and select the-Managers-
GameObject - In the
Inspector
panel, click on theAdd Component
button - In the search bar that appears, type
CharacterManager
and create a new Script to add it to the-Managers-
GameObject
// CharacterManager.cs
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class CharacterManager : MonoBehaviour {
public TMP_Dropdown characterSelectDropdown;
public GameObject slimePreviewBox;
public GameObject navMeshAgentParent;
public List<GameObject> characterPrefabs = new();
private GameObject agentMesh;
private GameObject previewSlimeMesh;
private void Start() {
SetDropdownOptions();
// Add listener for the Dropdown value changes
characterSelectDropdown.onValueChanged.AddListener(delegate { DropdownValueChanged(); });
// Set Current selection
DropdownValueChanged();
}
private void DropdownValueChanged() {
// Get the current index
int index = characterSelectDropdown.value;
// Destroy old Objects
if (agentMesh)
Destroy(agentMesh);
if (previewSlimeMesh)
Destroy(previewSlimeMesh);
// Instantiate new Objects
previewSlimeMesh = Instantiate(characterPrefabs[index], slimePreviewBox.transform);
agentMesh = Instantiate(characterPrefabs[index], navMeshAgentParent.transform);
// Set Position and disable shadows
previewSlimeMesh.transform.localPosition = Vector3.zero;
previewSlimeMesh.GetComponentInChildren<SkinnedMeshRenderer>().receiveShadows = false;
// Set Position and scale
agentMesh.transform.localPosition = Vector3.zero;
agentMesh.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
private void SetDropdownOptions() {
// Clear current options
characterSelectDropdown.ClearOptions();
// Check if the list is not empty
if (characterPrefabs != null && characterPrefabs.Count > 0) {
// Create a list to hold the names
List<string> options = new List<string>();
// Loop through the GameObjects and add their names to the list
foreach (GameObject gameObject in characterPrefabs) {
options.Add(gameObject.name.Replace('_', ' '));
}
// Add the list of names as options
characterSelectDropdown.AddOptions(options);
}
}
}
- With the
Character Manager
component now added, you will see various fields in theInspector
panel - Assign all the necessary objects to the script and it should look like this in the end
- Save the scene by pressing
Ctrl + S
on your keyboard
- Open the
Build Settings
window by selectingFile > Build Settings
- Click
Build
and select adestination folder
for the apk file - Install the apk file to your Android device
Now you have a project which will automatically detect surfaces and create navigation meshes on them which can be used by any type of NavMesh Agent
.
You can download the build demo on itch.io.
- In addition to the core mechanics you could add the possibility of throwing objects like food to the position where the slime should go and let the slime “consume” the object
- Introduce a hunger level and adjust the color of the slime depending on it
- Change the color of the tile where the slime is currently positioned
- Make use of the different animations already provided with the slime
- Add post processing to the scene to make it more pretty
- Add particle effects to the slime to make it more pretty