VR Adding Locomotion: Walk - c#

Using Unity2017.3.1f1 Personal (64 bit) to build a VR app for Android, using Cardboard VR SDK. The purpose of the app is to allow users visualize data in an immersive way.
Currently want to do something similar to the scene view where one can move forward by going where one's looking but with the Cardboard single button i'm just gonna give the ability to move forward.
Built a Canvas where user can select what type of locomotion he/she wants: teleport or walk.
The teleport works fine as you can see here.
When the user selects walk, the following error shows in the Console:
NullReferenceException: Object
reference not set to an instance of an
object
Player.TryWalk () (atAssets/Player.cs:44)
Player.Update () (at Assets/Player.cs:37)
My Player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum InputMode
{
NONE,
TELEPORT,
WALK
}
public class Player : MonoBehaviour {
public static Player instance; //Singleton design pattern: only one instance of the class appears
public InputMode activeMode = InputMode.NONE;
[SerializeField]
private float playerSpeed = 3.0f;
void Awake()
{
if(instance != null)
{
GameObject.Destroy(instance.gameObject);
}
instance = this;
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update ()
{
TryWalk();
}
public void TryWalk()
{
if(Input.GetMouseButton(0) && activeMode == InputMode.WALK)
{
Vector3 forward = Camera.main.transform.forward;
Vector3 newPosition = transform.position + forward * Time.deltaTime * playerSpeed;
transform.position = newPosition;
}
}
}
The Player script was added as component of Player:
When the button Walk is pressed, the Active Mode changes to WALK, as you can see in the next image.
Still, even though this happens, the user is not able to Walk.
What can I do to solve this?

Camera.main; was returning null.
In order to fix it, had to have the camera in my scene tagged MainCamera as you can see in the next image.
See it working here.

Related

Referencing Instantiated objects after their creation in Unity

Hi!
After the discussion with Ruzihm in the comments. I've now created a simple version of my game to better ask the question I'm having.
The question now is, since I'm not able to manually create a connection to the testObject field in the inspector. How do I now tell Unity to use my instantiated objects while the game is running?
And is this a good solution for a RTS game that may have 100s of Units active at a time? The end goal here is to apply this force to a radius around the cursor. Which I was thinking of using Physics.OverlapSphere
Here's the minimal scenario of what I have:
New Unity scene
Attached the InputManager to the main camera.
Created a capsule and a plane.
Added ApplyForce to the Capsule
Created a prefab from the capsule and deleted it from the scene.
In the InputManager I added the ability to press space to Instantiate a capsule with the ApplyForce script attached..
Drag the capsule prefab to the InputManager "objectToGenerate"
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class InputManager : MonoBehaviour
{
public GameObject testObject;
public ApplyForce onSpawnTest;
public GameObject objectToGenerate;
void Start()
{
onSpawnTest = testObject.GetComponent<ApplyForce>();
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(objectToGenerate);
}
if (Input.GetMouseButton(0))
{
onSpawnTest.PushForward();
}
}
}
}
The ApplyForce script that I attach to the Capsule:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class ApplyForce : MonoBehaviour
{
public float moveSpeed;
Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
Debug.Log("A Mite has spawned!");
}
public void PushForward()
{
rb.AddRelativeForce(Vector3.up * moveSpeed * Time.deltaTime);
Debug.Log("A force of: " + moveSpeed + " is being added.");
}
}
}
Well, you are creating your new instances of your object, but your input manager immediately forgets about them (note that you do nothing with the return value). The InputManager only knows about the ApplyForce that was created in its Start (and then interacts with it depending on mouse input) and your ApplyForce script knows nothing about any InputManager. So, it should come as no surprise that only the first instance reacts to the mouse input.
So, something has to be done to your InputManager and/or your ApplyForce. Your InputManager could remember the instances it creates (which isn't enough, because what if for example, a map trigger creates new player controllable units) or it could go looking for units each time.
Your ApplyForce could register with the InputManager when they are created, but then you would need to loop through the units and find out which ones are under the mouse, anyway.
Since you only want to select ones based on what is near or under your cursor and only when input occurs and not like every frame, I would go with the simplest approach, just letting your InputManager find the units when it needs them. Something like below, explanation in comments:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class InputManager : MonoBehaviour
{
public GameObject testObject;
public ApplyForce onSpawnTest;
public GameObject objectToGenerate;
private Camera mainCam;
// which layers to consider for cursor detection
[SerializeField] LayerMask cursorLayerMask;
// how big for cursor detection
[SerializeField] float cursorRadius;
void Awake()
{
// cache main camera
mainCam = Camera.main;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(objectToGenerate);
}
if (Input.GetMouseButton(0))
{
Collider[] colls = FindCollidersUnderCursor();
// check each collider for an applyforce and use it if present
foreach( Collider coll in colls)
{
ApplyForce af = coll.GetComponent<ApplyForce>();
if (af != null)
{
af.PushForward();
}
}
}
}
Collider[] FindCollidersUnderCursor()
{
// find ray represented by cursor position on screen
// and find where it intersects with ground
// This technique is great for if your camera can change
// angle or distance from the playing field.
// It uses mathematical rays and plane, no physics
// calculations needed for this step. Very performant.
Ray cursorRay = mainCam.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(cursorRay, out float cursorDist))
{
Vector3 worldPos = cursorRay.GetPoint(cursorDist);
// Check for triggers inside sphere that match layer mask
return Physics.OverlapSphere(worldPos, cursorRadius,
cursorLayerMask.value, QueryTriggerInteraction.Collide);
}
// if doesn't intersect with ground, return nothing
return new Collider[0];
}
}
}
Of course, this will require that every unit you're interested in manipulating has a trigger collider.

How To Add Mobile Touch Controllers In Unity

I am working on a game, and till now, i have allowed a user to play the game by moving up and down using the up and down arrow keys on the computer keyboard.
But now i want to make the user be able to do that on a mobile phone by touching up or down, or maybe swiping up or down to move in the game.
I am using c# for this.
This is the current code assigned to the player object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float playerSpeed;
private Rigidbody2D rb;
private Vector2 playerDirection;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
float directionY = Input.GetAxisRaw("Vertical");
playerDirection = new Vector2(0, directionY).normalized;
}
void FixedUpdate()
{
rb.velocity = new Vector2(0, playerDirection.y * playerSpeed);
}
}
What do i need to do to achieve this?
You should try to make buttons with trasparency
You have plenty of possibilities here !
But there might have better solutions than others ;)
You could make a UI representing up and down arrow that respond with wathever behaviour you'd like when pressed ORyou could dig a little the Input documentation, espacially what's relative to touches.
From there you can get touch position in screen space an make whatever you want with it !
The choice is lead by the type of game or application you're trying to make. Hope that helped ;)

Unity c# - unable to Spawn Prefabs on a NavMesh

I am trying to make a zombie wave game and current have a Prefab for my enemies. If I have the prefab be in the scene when I hit run, they are attached to the NavMesh and track the player perfectly. I want to achieve this but with the enemy being spawned from an empty GameObject so I can get the waves spawning in. I have achieved them Spawning but they have the error,
"SetDestination" can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.AI.NavMeshAgent:SetDestination(Vector3)
EnemyAI:Update() (at Assets/Scripts/EnemyAI.cs:25)
Here is my EnemyAI Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyAI : MonoBehaviour
{
public float lookRadius = 10f;
Transform target;
NavMeshAgent agent;
public GameObject Player;
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
float distance = Vector3.Distance(Player.transform.position, transform.position);
if (distance <= lookRadius)
{
agent.SetDestination(Player.transform.position);
}
}
}
And my spawning script, which is attached to an empty game object,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawning : MonoBehaviour
{
public GameObject prefab;
public int CountofCubes;
private IEnumerator coroutine;
public float spawnRate;
IEnumerator Start()
{
while (true)
{
for (int i = 0; i < CountofCubes; i++)
{
Instantiate(prefab, new Vector3(Random.Range(-25.0f, 25.0f), 0.5f, Random.Range(-25.0f, 25.0f)), Quaternion.identity);
}
yield return new WaitForSeconds(spawnRate);
}
}
}
Any help would be great thanks!
I had the same issue and I don't have an explanation but only a workaround:
In the Start fucntion, I added:
navAgent = GetComponent<NavMeshAgent>();
navAgent.enabled = false;
// Invoke is used as a workaround for enabling NavMeshAgent on NavMeshSurface
Invoke("EnableNavMeshAgent", 0.025f);
And the EnableNavMeshAgent function is just :
private void EnableNavMeshAgent ()
{
navAgent.enabled = true;
}
If I set the invoke delay to a value less than 0.025 second the error keep going but for 0.025 I only have it twice and the behaviour is the one I wanted after that.
Some reasons this might happen:
The agent is not on level with any navmesh i.e. can be too far above or below. You want it to be "close to the NavMesh surface". Use raycasting on the floor to position the agent or add a rigidbody and drop it from above the floor. After you do this you might need to disable and enable the agent.
Moving the transform yourself rather than using Wrap function. There's property where you can check if the simulated and actual position are in sync.
Corrupted navmesh so you might need to re-bake it.
It is essentially trying to tell you your agent is not on the mesh so it cannot find a path. Try playing with where you're placing the agent.
I kind of remember running into a similar problem a while back and problem was I forgot to bake the NavMesh in Window > AI > Navigation. Just in case.

Still pausing/resuming and start a new game is not working good. Can'r figure what should I do?

First a screenshot of my Hierarchy :
Everything is inside a one scene name The :
The game start when Main Game is disabled. Inside Main Game sit all the game objects. The Main Menu and Game Manager are enabled.
When running the game first time, When the game start there is short animation of the player for 5 seconds. The player start from some rotating degrees on Z. Z = 50 when x and y both are 0. Then the player is rotating slowly over the Z to 0.
It's like the player is sleeping and awake up.
I'm using post processing stack by unity.
And here is the first problem. While the player is rotating on the Z and post processing effect is working if I press the Escape key it will bring me back to the Main Menu but then if I press the Escape key again it will start the game over again from the begin.
But if I'm waiting in my game for the player to finish rotating on the Z and the post processing effect is finished and then pressing on Escape it will bring the main menu and second time will resume the game from the same point.
I can't figure out why when the player is rotating and the post process is working the escape key make it start the game over again from the being ?
This is a screenshot of the game when start and after finish the rotating and the process stack :
Another problem I noticed now. After the game start using the post process and the player rotating finished if I press Escape it will go to main menu and escape again will be back to the game but for example in the second screenshot the conversation is not continue. It will return to the same point in the game but things not seems to continue like the conversation.
On the Back to main menu object I have a script attached to it :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BackToMainMenu : MonoBehaviour
{
private bool _isInMainMenu = false;
public GameObject mainGame;
public GameObject mainMenu;
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (!_isInMainMenu)
{
// -- Code to freeze the game
mainGame.SetActive(false);
mainMenu.SetActive(true);
}
else
{
// -- Code to unfreeze the game
mainGame.SetActive(true);
mainMenu.SetActive(false);
}
_isInMainMenu = !_isInMainMenu;
}
}
}
On the Main Menu object under Main Menu I have attached this script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public GameObject mainGame;
public GameObject mainMenu;
public void PlayGame()
{
mainGame.SetActive(true);
mainMenu.SetActive(false);
}
public void QuitGame()
{
Application.Quit();
}
}
On the PLAY button I'm using this script method PlayGame from the MainMenu script.
In the Main Game object on the Player object attached to thew Player I have some scripts the controller :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 10.0f;
// Update is called once per frame
void Update()
{
float translatioin = Input.GetAxis("Vertical") * speed;
float straffe = Input.GetAxis("Horizontal") * speed;
translatioin *= Time.deltaTime;
straffe *= Time.deltaTime;
transform.Translate(straffe, 0, translatioin);
}
}
Player Lock Manager :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerLockManager : MonoBehaviour
{
public PlayerCameraMouseLook playerCameraMouseLook;
public PlayerController playerController;
// Start is called before the first frame update
public void PlayerLockState(bool LockPlayer, bool LockPlayerCamera)
{
if (LockPlayer == true)
{
playerController.enabled = false;
}
else
{
playerController.enabled = true;
}
if (LockPlayerCamera == true)
{
playerCameraMouseLook.enabled = false;
}
else
{
playerCameraMouseLook.enabled = true;
}
}
}
And Mouse Lock State :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseLockState : MonoBehaviour
{
public bool lockState = true;
private void Start()
{
LockState(lockState);
}
private void Update()
{
}
public void LockState(bool lockState)
{
if (lockState == false)
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
else
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
}
}
Under the Player as child I have the Player Camera object and attached to the Player Camera also some scripts :
Player Camera Mouse Look :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCameraMouseLook : MonoBehaviour
{
public float sensitivity = 5.0f;
public float smoothing = 2.0f;
private GameObject player;
private Vector2 mouseLook;
private Vector2 smoothV;
// Use this for initialization
void Start()
{
player = this.transform.parent.gameObject;
}
// Update is called once per frame
void Update()
{
var md = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));
md = Vector2.Scale(md, new Vector2(sensitivity * smoothing, sensitivity * smoothing));
smoothV.x = Mathf.Lerp(smoothV.x, md.x, 1f / smoothing);
smoothV.y = Mathf.Lerp(smoothV.y, md.y, 1f / smoothing);
mouseLook += smoothV;
mouseLook.y = Mathf.Clamp(mouseLook.y, -90f, 90f);
transform.localRotation = Quaternion.AngleAxis(-mouseLook.y, Vector3.right);
player.transform.localRotation = Quaternion.AngleAxis(mouseLook.x, Vector3.up);
}
}
And Depth Of Field script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.PostProcessing;
public class DepthOfField : MonoBehaviour
{
public GameObject player;
public PostProcessingProfile postProcessingProfile;
public bool dephOfFieldFinished = false;
public PlayerLockManager playerLockManager;
private Animator playerAnimator;
private float clipLength;
// Start is called before the first frame update
void Start()
{
playerAnimator = player.GetComponent<Animator>();
AnimationClip[] clips = playerAnimator.runtimeAnimatorController.animationClips;
foreach (AnimationClip clip in clips)
{
clipLength = clip.length;
}
var depthOfField = postProcessingProfile.depthOfField.settings;
depthOfField.focalLength = 300;
StartCoroutine(changeValueOverTime(depthOfField.focalLength, 1, clipLength));
postProcessingProfile.depthOfField.settings = depthOfField;
}
IEnumerator changeValueOverTime(float fromVal, float toVal, float duration)
{
playerLockManager.PlayerLockState(true, true);
float counter = 0f;
while (counter < duration)
{
var dof = postProcessingProfile.depthOfField.settings;
if (Time.timeScale == 0)
counter += Time.unscaledDeltaTime;
else
counter += Time.deltaTime;
float val = Mathf.Lerp(fromVal, toVal, counter / duration);
dof.focalLength = val;
postProcessingProfile.depthOfField.settings = dof;
yield return null;
}
playerAnimator.enabled = false;
dephOfFieldFinished = true;
}
}
I have under Main Game also a object name Openning Scene and attached to it :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OpeningCutscene : MonoBehaviour
{
public NaviConversations naviConversation;
public DepthOfField dephOfField;
// Update is called once per frame
void Update()
{
if (dephOfField.dephOfFieldFinished == true)
{
naviConversation.PlayNaviConversation(0);
dephOfField.dephOfFieldFinished = false;
}
}
}
It's a bit long but everything is connected.
The game start with the main menu.
When clicking the PLAY button a new game start.
When a new game start the script Depth Of Field is in action using the post processing. Also I'm locking the player so the player will not be able to move either the mouse or the player it self.
When the depth of field script finished his work a conversation between Navi and the Player start. When a conversation is in action the player can move the mouse 360 degrees but cant move the player to any direction.
When the conversation ended the player can also move to any direction.
Problems :
When start a new game while the depth of field script is in work pressing escape will bring the main menu but pressing escape again will not resume the depth of field script but will start it all over again.
When waiting the conversation to end if not pressing escape until the conversation ended then when moving around and pressing escape it will bring the main menu and again will resume the game from the same point.
The problems is when the game is doing something like the depth of field it will start the game over again instead resuming or when in conversation in the middle the conversation will never continue.
The ides with the escape key is once to get to main menu and second to resume the game.
The PLAY button is what should only start a new game and not the escape key.
It's a bit long but everything is connected.
I can't see which objects you hooked up to mainGame and mainMenu in BackToMainMenu from your hierarchy, so I have some conjecture in here.
Sounds like you want Escape key to pause, and unpause, as well as bring up a menu, you want Play button in the menu to restart your game.
However, in both MainMenu and BackToMainMenu you use the same code:
mainGame.SetActive(true);
mainMenu.SetActive(false);
This just turns the objects on and off in the hierarchy. This means the first time you do one of these things, all objects turned on under the gameobject referenced by mainGame will run their Awake and Start methods, but not the second time. Also depending which objects are active (like I said I can't fully see what component is on what object and which objects are referenced by which serialized field) you might be able to introduce a state error on BackToMainMenu._isInMainMenu because that field isn't changed when you hit play. Here is a fantastic image showing execution timeline in Unity:
Monobehavior Timeline
In summary:
The second time you run the game DepthOfField won't call Start a second time. Instead try OnEnable.
The conversation and game logic is not shown in your post, but likely it also is initializing in Start and won't run a second time.
A bit of trickiness around Coroutines is likely to blame here. When DepthOfField starts it also starts a coroutine. That coroutine keeps going even if you set the gameobject inactive. So you run the game once, that coroutine starts, you turn the object off when you hit escape, the coroutine finishes, you hit play again, but DepthOfField.dephOfFieldFinished == true and your playerAnimator is disabled and won't enable again.
Also 5. This kind of behavior can be tricky and is usually dependent on what else you have going on in your scene. Since you have one scene with everything at once, you need to watch out for putting stuff in Awake and Start since this will only run once. Instead you can try a number of things, my favorite is usually to set up a singleton or static class that works as a state machine for the whole scene. It will call custom Initialize functions in your behaviors instead of using Awake and Start.
Keep references to your coroutines by doing things like:
private Coroutine depthOfFieldRoutineRef;
private OnEnable()
{
if (depthOfFieldRoutineRef != null)
{
StopCoroutine(depthOfFieldRoutineRef);
}
depthOfFieldRoutineRef =
StartCoroutine(changeValueOverTime
(depthOfField.focalLength, 1, clipLength));
// Don't forget to set depthOfFieldRoutineRef to null again at the end of routine!
}
For simple pausing behavior you can also try setting Time.timescale = 0; and back to 1 when you want play to resume.
Less a question than a set of problems, hopefully this set of solutions helps!
Tutorial on Awake and Start
Docs on Coroutines
Docs on Time.timescale

Unable to fix monobehaviour scripts problem in Unity

I was just getting started with Unity and using it's Standard Assets library. I used its rollerball object, but both its Ball and BallUserControl scripts gave me the following error:
No Monobehaviour scripts in the file, or the names do not match the file name.
I didn't change any scripts, and I have no other objects or scripts in my environment. The class names match the file names. I am sure I am missing something obvious (being so new). What are some possible errors?
I am running the latest version of unity on windows 10.
Here's the BallUserControl script, as requested:
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
namespace UnityStandardAssets.Vehicles.Ball
{
public class BallUserControl : MonoBehaviour
{
private Ball ball; // Reference to the ball controller.
private Vector3 move;
// the world-relative desired move direction, calculated from the camForward and user input.
private Transform cam; // A reference to the main camera in the scenes transform
private Vector3 camForward; // The current forward direction of the camera
private bool jump; // whether the jump button is currently pressed
private void Awake()
{
// Set up the reference.
ball = GetComponent<Ball>();
// get the transform of the main camera
if (Camera.main != null)
{
cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Ball needs a Camera tagged \"MainCamera\", for camera-relative controls.");
// we use world-relative controls in this case, which may not be what the user wants, but hey, we warned them!
}
}
private void Update()
{
// Get the axis and jump input.
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
jump = CrossPlatformInputManager.GetButton("Jump");
// calculate move direction
if (cam != null)
{
// calculate camera relative direction to move:
camForward = Vector3.Scale(cam.forward, new Vector3(1, 0, 1)).normalized;
move = (v*camForward + h*cam.right).normalized;
}
else
{
// we use world-relative directions in the case of no main camera
move = (v*Vector3.forward + h*Vector3.right).normalized;
}
}
private void FixedUpdate()
{
// Call the Move function of the ball controller
ball.Move(move, jump);
jump = false;
}
}
}
If monobeviour files are missing or not showing in the visual studio and creates error, Go to Edit/Preferences/External Tools/ select visual studio version and
Regenerate project files. It worked for me.

Categories

Resources