How to get Unity 2D RTS unit selection to work? - c#

I am making a 2d RTS style game in which you have the ability to select units in game with a click and drag selection box. When ever a unit is inside of this selection box and the left mouse button is let go the unit will be added to a "selected" list and their animation will be changed from the idle animation to the selected idle animation. After the selection box is draw again all the units in the selected list will be removed and their animations set back to the idle position, then all the units inside the new selection box will be added to the list and set to the selected idle animation. The problem is that if any of these units in the new selected box had been previously selected they will just switch to the idle animation and stay there instead going to the selected animation. I tested this without animations and it worked, it would stay as the selected sprite when selected again instead of switching to the idle sprite and staying there. I am pretty new at C# so this could very well be a very simple problem to fix.
Thanks
Here is the code:
ControllableUnits ControlUnitSelected;
public List<ControllableUnits> selectedControllableUnits;
private Vector3 mousePosition;
public void Awake()
{
selectedControllableUnits = new List<ControllableUnits>();
selectionAreaTransform.gameObject.SetActive(false);
ControlUnitSelected = this.gameObject.GetComponent<ControllableUnits>();
}
public void Update()
{
//Transforming Selection Area
if (Input.GetMouseButton(0))
{
Vector3 currentMousePosition = UnitControlClass.GetMouseWorldPosition();
Vector3 lowerLeft = new Vector3(
Mathf.Min(mousePosition.x, currentMousePosition.x),
Mathf.Min(mousePosition.y, currentMousePosition.y)
);
Vector3 upperRight = new Vector3(
Mathf.Max(mousePosition.x, currentMousePosition.x),
Mathf.Max(mousePosition.y, currentMousePosition.y)
);
selectionAreaTransform.position = lowerLeft;
selectionAreaTransform.localScale = upperRight - lowerLeft;
}
if (Input.GetMouseButtonDown(0))
{
mousePosition = UnitControlClass.GetMouseWorldPosition();
selectionAreaTransform.gameObject.SetActive(true);
}
//Creating the selection process
if (Input.GetMouseButtonUp(0))
{
Collider2D[] collider2DArray = Physics2D.OverlapAreaAll(mousePosition, UnitControlClass.GetMouseWorldPosition());
foreach (ControllableUnits control in selectedControllableUnits)
{
//if(ControlUnitSelected != null)
control.animPeasant.Play("Peasant_Idle");
control.isSelected = false;
}
selectedControllableUnits.Clear();
foreach (Collider2D collider2D in collider2DArray)
{
ControllableUnits controllableUnit = collider2D.GetComponent<ControllableUnits>();
if (controllableUnit != null)
{
controllableUnit.animPeasant.Play("Peasant_Idle_Selected");
controllableUnit.isSelected = true;
selectedControllableUnits.Add(controllableUnit);
}
}
selectionAreaTransform.gameObject.SetActive(false);
}
}

I guess the problem is when you call Play many times, Unity tries to merge these animations that results a random state finally.
It can be solved by giving the exact playback time.
controllableUnit.animPeasant.Play("Peasant_Idle_Selected", 0, 0f);

Related

Detecting a click on a specific tile in a tilemap

I'm trying to detect when the mouse clicks on a specific tile on a tile map. The game I'm making is a overhead 2d style, and I want to trigger code when a specific resource node (tile) is clicked on. I've tried to use RaycastHit2D:
public class DetectClickOnTile : MonoBehaviour
{
Vector2 worldPoint;
RaycastHit2D hit;
// Update is called once per frame
void Update()
{
worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetMouseButtonDown(0))
{
hit = Physics2D.Raycast(worldPoint, Vector2.down);
if (hit.collider != null)
{
Debug.Log("click on " + hit.collider.name);
Debug.Log(hit.point);
}
}
}
But it only detects collision with the tilemap itself and not any of the individual tiles. Does anyone know why that is? For reference this is the map im trying to detect collision on, only the black tiles are filled in with placeholders, the rest of the tilemap is left blank for now.
Afaik you can get the tile at the given world hit point using something like e.g.
// Reference this vis the Inspector
[SerializeField] TileMap tileMap;
...
hit = Physics2D.Raycast(worldPoint, Vector2.down);
if (hit.collider != null)
{
Debug.Log("click on " + hit.collider.name);
Debug.Log(hit.point);
var tpos = tileMap.WorldToCell(hit.point);
// Try to get a tile from cell position
var tile = tileMap.GetTile(tpos);
...
}
However, I'm pretty sure your Raycast is not what you want to use but rather directly try and get the tile from the worldPoint and if you got one you hit one ;)
Your Raycast goes down meaning you cast from the click point to the bottom of the screen which doesn't sound like what you are trying to achieve.
So probably rather something like
if (Input.GetMouseButtonDown(0))
{
worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
var tpos = tileMap.WorldToCell(worldPoint);
// Try to get a tile from cell position
var tile = tileMap.GetTile(tpos);
if(tile)
{
...
}
}

Pan Tool Algorithm being very jumpy

Looking for a bit of help to see how I can make this algorithm a little better. So I have some code for a pan tool that moves the camera based on the user dragging the mouse to move the camera around. However it is really jumpy unless it is done in really short drags. I.e. the objects in the scene appear to vibrate, which is more likely to be the camera and canvas 'vibrating' rather than the objects themselves.
The code works by toggling a dragging boolean using the SystemEvent methods OnPointerDown and OnPointerUp and assigns a MouseStart Vector3 in world coordinates and also a CameraStart Vector3.
public void OnPointerDown(PointerEventData EventData)
{
if (!dragging)
{
dragging = true;
MouseStart = new Vector3(Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y, 0f);
CameraStart = TheCamera.transform.position;
}
}
public void OnPointerUp(PointerEventData EventData)
{
if (dragging)
{
dragging = false;
}
}
Then in the update loop while the dragging variable is true, xChange and yChange float values are determined based on the current mouse position compared to the original mouse position, the camera position is then adjusted according to these. My thought process was that because it is relative to a fixed MouseStart (because it is only changed in the single frame where the pointer is clicked and dragging = 0) that if I were to drag and then say keep the mouse still, there would be no change in coordinates as it'd be repeatedly putting the Camera in the same position. The full code looks like this:
private bool dragging;
private string CurrentTool;
private ButtonController[] DrawingTools;
public Camera TheCamera;
public Vector3 MouseStart;
public Vector3 CameraStart;
public float sensitivity;
// Use this for initialization
void Start () {
TheCamera = FindObjectOfType<Camera>();
DrawingTools = FindObjectsOfType<ButtonController>();
}
// Update is called once per frame
void Update () {
for (int i = 0; i < DrawingTools.Length; i++)
{
if (DrawingTools[i].Pressed)
{
CurrentTool = DrawingTools[i].gameObject.name;
}
}
if (dragging && CurrentTool == "PanTool Button")
{
float xChange;
float yChange;
Vector3 MousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (MousePosition.x > MouseStart.x)
{
xChange = -Mathf.Abs(MousePosition.x - MouseStart.x);
}
else
{
xChange = Mathf.Abs(MousePosition.x - MouseStart.x);
}
if (MousePosition.y > MouseStart.y)
{
yChange = -Mathf.Abs(MousePosition.y - MouseStart.y);
}
else
{
yChange = Mathf.Abs(MousePosition.y - MouseStart.y);
}
TheCamera.transform.position = new Vector3(CameraStart.x + xChange*sensitivity, CameraStart.y + yChange*sensitivity, CameraStart.z);
}
}
public void OnPointerDown(PointerEventData EventData)
{
if (!dragging)
{
dragging = true;
MouseStart = new Vector3(Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y, 0f);
CameraStart = TheCamera.transform.position;
}
}
public void OnPointerUp(PointerEventData EventData)
{
if (dragging)
{
dragging = false;
}
}
Any help is appreciated, thanks.
EDIT: Just to clarify this is a 2d environment
This is happening because the Camera from which you are determining the world position of the mouse is being updated every frame according to the world position of the mouse, which causes a feedback loop (and therefore noise + jitter).
You can reduce noise from the feedback loop by smoothing the Camera's movement over time (effectively a low pass), or try to remove the feedback loop entirely by altering your calculations so the camera position and target position (mouse) don't rely on each other - although I'm not sure how to go about that if it's actually possible for your intent.
Check out Vector3.SmoothDamp.
Gradually changes a vector towards a desired goal over time.
The vector is smoothed by some spring-damper like function, which will
never overshoot. The most common use is for smoothing a follow camera.

Disable/Toggle visualization of tracked planes in ARCore unity

I have been looking on the code for ARCore Unity for a while and I want to do one simple task that is, to have a toggle button so user can place an object in the scene while knowing where to place it while the tracked planes are visible and once the user places the object, he is given the option of just visually disabling the tracked planes so it looks more realistic. I was able to do this in Android Studio by doing something like this in the main HelloArActivity.java:
if (planeToggle) {
mPlaneRenderer.drawPlanes(mSession.getAllPlanes(), frame.getPose(), projmtx);
}
this was really simple. I made a bool named planeToggle and just placed the mPlaneRenderer.drawPlanes function in an if condition. When the bool is true it displays the planes and when its false, it does not...
However, with Unity I am confused. I did something like this in the HelloARController.cs :
I made a button to togglePlanes.
Set an event listener to it to toggle a boolean variable and did something like this :
for (int i = 0; i < m_newPlanes.Count; i++)
{
// Instantiate a plane visualization prefab and set it to track the new plane. The transform is set to
// the origin with an identity rotation since the mesh for our prefab is updated in Unity World
// coordinates.
GameObject planeObject = Instantiate(m_trackedPlanePrefab, Vector3.zero, Quaternion.identity,
transform);
planeObject.GetComponent<TrackedPlaneVisualizer>().SetTrackedPlane(m_newPlanes[i]);
m_planeColors[0].a = 0;
// Apply a random color and grid rotation.
planeObject.GetComponent<Renderer>().material.SetColor("_GridColor", m_planeColors[0]);
planeObject.GetComponent<Renderer>().material.SetFloat("_UvRotation", Random.Range(0.0f, 360.0f));
if (togglePlanes == false){ // my code
planeObject.SetActive(false); // my code
} //
}
Nothing happens when I press the toggle button.
The other option I had was to make changes in the TrackedPlaneVisualizer.cs where I did something like this :
for (int i = 0; i < planePolygonCount; ++i)
{
Vector3 v = m_meshVertices[i];
// Vector from plane center to current point
Vector3 d = v - planeCenter;
float scale = 1.0f - Mathf.Min((FEATHER_LENGTH / d.magnitude), FEATHER_SCALE);
m_meshVertices.Add(scale * d + planeCenter);
if (togglePlanesbool == true) // my code
{
m_meshColors.Add(new Color(0.0f, 0.0f, 0.0f, 1.0f)); // my code
}
else
{
m_meshColors.Add(new Color(0.0f, 0.0f, 0.0f, 0.0f)); // my code
}
}
This did work. But I am experiencing delays in toggling and sometimes if two different planes have been rendered they start toggling between themselves(if one is enabled, other gets disabled). So I guess this is also not the option to go for....Anyone who can help?
Note that I am a beginner in Unity.
The sample isn't really designed to hide and show the planes, so you have to add a couple things.
First, there is no collection of the GameObjects that represent the ARCore planes. The easiest way to do this is to add a tag to the game objects:
In the Unity editor, find the TrackedPlaneVisualizer prefab and select it. Then in the property inspector, drop down the Tag dropdown and add a tag named plane.
Next, in the Toggle handler method, you need to find all the game objects with the "plane" tag. Then get both the Renderer and TrackedPlaneVisualizer components and enable or disable them based on the toggle. You need to do both components; the Renderer draws the plane, and the TrackedPlaneVisualizer re-enables the Renderer (ideally it would honor the Renderer's state).
public void OnTogglePlanes(bool flag) {
showPlanes = flag;
foreach (GameObject plane in GameObject.FindGameObjectsWithTag ("plane")) {
Renderer r = plane.GetComponent<Renderer> ();
TrackedPlaneVisualizer t = plane.GetComponent<TrackedPlaneVisualizer>();
r.enabled = flag;
t.enabled = flag;
}
}
You can also do a similar thing where the GameObject is instantiated, so new planes honor the toggle.

Fade in/out between scenes is not working in Unity - Google Cardboard plugin

I'm developing an application in Unity with the Google CardbBoard Plugin, and I tried to fade in/out the screen when passing between scenes, I've worked with this example drawing a texture in the GUI object:
GUI.color = new Color (GUI.color.r, GUI.color.g, GUI.color.b, alpha);
Texture2D myTex;
myTex = new Texture2D (1, 1);
myTex.SetPixel (0, 0, fadeColor);
myTex.Apply ();
GUI.DrawTexture (new Rect (0, 0, Screen.width, Screen.height), myTex);
if (isFadeIn)
alpha = Mathf.Lerp (alpha, -0.1f, fadeDamp * Time.deltaTime);
else
alpha = Mathf.Lerp (alpha, 1.1f, fadeDamp * Time.deltaTime);
if (alpha >= 1 && !isFadeIn) {
Application.LoadLevel (fadeScene);
DontDestroyOnLoad(gameObject);
} else if (alpha <= 0 && isFadeIn) {
Destroy(gameObject);
}
The code I worked with is from this page: Video Tutorial, Example downloads, and it worked fine in a Unity game without the Cardboard plugin, but in my current project the same way to use this code is not working. The only difference is the use of the Cardboard plugin.
Is there any specific Cardboard object I must use instead of GUI or another way to draw a texture?
As per the Google Cardboard docs, You need to have GUI elements exist in 3D space infront of the camera so they are replicated in each eye.
I'll share my solution of how I did it. Note that What I've done is have a single instance of the Cardboard Player Prefab spawn when my game starts and persist throughout all my levels via DontDestoryOnLoad(), rather than have a seperate instance in each level.
This allows for settings to be carried over to each loaded level and Fade out and Fade in the screen.
I accomplished a screen fader by creating a World Space Canvas that is parented to the Cardboard prefab's "Head" object so it follows gaze, And put a Black Sprite image that covers the entire Canvas which blocks the players view when the Black Sprite is visible.
This script attached to my Player Prefab allows me to first fade out the screen (call FadeOut()), Load a new level (set LevelToLoad to the level index you want to load), then Fade in the screen after the new level is loaded.
By default it uses the Async way of loading levels, To allow for loading Bars, But you can set UseAsync to false to load levels via Application.LoadLevel()
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class LoadOperations: MonoBehaviour {
public Image myImage;
// Use this for initialization
public bool UseAsync;
private AsyncOperation async = null;
public int LevelToLoad;
public float FadeoutTime;
public float fadeSpeed = 1.5f;
private bool fadeout;
private bool fadein;
public void FadeOut(){
fadein= false;
fadeout = true;
Debug.Log("Fading Out");
}
public void FadeIn(){
fadeout = false;
fadein = true;
Debug.Log("Fading In");
}
void Update(){
if(async != null){
Debug.Log(async.progress);
//When the Async is finished, the level is done loading, fade in the screen
if(async.progress >= 1.0){
async = null;
FadeIn();
}
}
//Fade Out the screen to black
if(fadeout){
myImage.color = Color.Lerp(myImage.color, Color.black, fadeSpeed * Time.deltaTime);
//Once the Black image is visible enough, Start loading the next level
if(myImage.color.a >= 0.999){
StartCoroutine("LoadALevel");
fadeout = false;
}
}
if(fadein){
myImage.color = Color.Lerp(myImage.color, new Color(0,0,0,0), fadeSpeed * Time.deltaTime);
if(myImage.color.a <= 0.01){
fadein = false;
}
}
}
public void LoadLevel(int index){
if(UseAsync){
LevelToLoad= index;
}else{
Application.LoadLevel(index);
}
}
public IEnumerator LoadALevel() {
async = Application.LoadLevelAsync(LevelToLoad);
yield return async;
}
}
The GUI, GUILayout and Graphics do not work in VR. No 2d direct to screen will work properly.
You should render in 3d, easiest thing to do is to put a sphere around the camera (or even better, two spheres around each eye) and animate their opacity.

Snap gameObject to grid?

I'm making a 2D TD game and currently I have made using quill18 tutorials a simple building system. However I'm trying to have it snap on my tiles as I mouse over them.
This is my script applied to the building object
void Update(){
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = 0;
Vector2 mp = mousePos;
this.transform.position = mousePos;
Collider2D col = GetComponent<Collider2D> ();
if (GameManager.instance.player.CanAffordCurrentBuilding()
&& !col.IsTouchingLayers(LayerMask.GetMask("NonBuildingLayer"))
&& col.IsTouchingLayers(LayerMask.GetMask("BuildingLayer"))
&& !col.IsTouchingLayers(LayerMask.GetMask("BlockingLayer"))) {
SpriteRenderer[] sprites = GetComponentsInChildren<SpriteRenderer>();
foreach(SpriteRenderer sr in sprites)
sr .color = Color.green;
canPlace = true;
}
else {
SpriteRenderer[] sprites = GetComponentsInChildren<SpriteRenderer>();
foreach(SpriteRenderer sr in sprites)
sr .color = Color.red;
canPlace = false;
}
if (Input.GetMouseButtonDown (0) && canPlace) {
SpriteRenderer[] sprites = GetComponentsInChildren<SpriteRenderer>();
foreach(SpriteRenderer sr in sprites)
sr .color = Color.white;
Destroy(GetComponent<Rigidbody2D>() );
Destroy(this);
GameManager.instance.player.BuildingWasPlaced();
}
What this does is that if touching layer is "BuildingLayer" I will be able to place the object and the object is always following my mouse until I place it. However I want it to only follow my mouse when the touching layer is not BuildingLayer. And when the touching layer is BuildingLayer I want it to snap onto the tile that are closest to the mouse that is building layer. And continuously change snapping tile when I move around the mouse.
How could I accomplish something like this?
I have a list called grid that contains all the building tiles in the game if that helps.
I'm thinking what you could do is add trigger collision boxes to your buildings.
If you are going to drag one, make that one you are dragging have a slightly bigger trigger collision box.
Than check if there is a trigger collision on the object your dragging, if so, snap to a position relative to the object the collision is with. You'd need to check whether the collision is on the bottom, left, top or right.

Categories