EDIT: As I've found out finger ID gets the same number as the touch's original index value, I made a method resetting the saved finger ID from a discarded touch to -1. A complete answer is posted.
I'm trying to implement 2 finger multitouch control for my simple shooting game, where one finger shot continuously affect the rotation of the player if first held down at the defined area lower at the screen, and the other controls the shooting button which can also be loaded to shoot more powerful bullets.
At first I tried storing the touch position when TouchPhase = Began in a Vector3 array with two places, with the other classes checking if and which one of the values in the array comply with the rule for starting their activity. The issue was that the finger index seems to drop down if an earlier finger is lifted, so if I pressed for example aiming -> shot button and then lifted aiming the aiming button became stuck in loading as it was trying to track touch(1) which actually became (0).
The solution it seemed should be using fingerId, which produces a consistent number for a touch regardless of its index. The thing is - what I'm writing works even worse than before. It activates functions even when I'm pressing at the wrong places, so it seems the if statement complies even when it shouldn't. What's wrong with the code?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TouchControls : MonoBehaviour
{
public static int aimID;
public static int shootID;
// Start is called before the first frame update
void Start()
{
aimID = 0;
shootID = 0;
}
// Update is called once per frame
void Update()
{
for (int i = 0; i < Input.touchCount && i < 2; i++)
{
if (Input.GetTouch(i).phase == TouchPhase.Began)
{
Vector2 pos = Camera.main.ScreenToWorldPoint(Input.GetTouch(i).position);
if (pos.x == Mathf.Clamp(pos.x, -2.5f * SceneScale.ratio, 2.5f * SceneScale.ratio) &&
pos.y <= Camera.main.ScreenToWorldPoint(new Vector3(0, 0, 0)).y + (2 * SceneScale.ratio))
{
aimID = Input.GetTouch(i).fingerId;
}
else if (Vector2.Distance(pos, ShotButton.position) <= ShotButton.scale.x)
{
shootID = Input.GetTouch(i).fingerId;
}
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
public class ShotButton : MonoBehaviour
{
public static bool[] time;
static public Vector3 scale;
static public Vector2 position;
// Start is called before the first frame update
void Start()
{
scale = gameObject.transform.localScale;
position = new Vector2(gameObject.transform.position.x, gameObject.transform.position.y);
time = new bool[2];
time[0] = false;
time[1] = false;
}
// Update is called once per frame
public void FixedUpdate()
{
for (int i = 0; i < Input.touchCount && i < 2; i++)
{
time[i] = false;
if (Input.GetTouch(i).fingerId == TouchControls.shootID)
{
time[i] = true;
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public GameObject player;
public static bool isDead = false;
public float rot;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
public void FixedUpdate()
{
for (int i = 0; i < Input.touchCount && i < 2; i++)
{
if (Input.GetTouch(i).fingerId == TouchControls.aimID)
{
rot = Camera.main.ScreenToWorldPoint(Input.GetTouch(i).position).x * 80 / (2.5f * SceneScale.ratio);
}
}
rot = Mathf.Clamp(rot, -80, 80);
player.transform.rotation = Quaternion.Euler(0, 0, rot);
}
}
As this has received no answers and I haven't found a different method, I'll post what I've found to work — even though there might be other more effective solutions.
Finger ID matches the original index of the touch, which could be 0 or above. So I saved a unique integer type for for each controlling method and initialized it with the value -1. When touch with TouchPhase.Began falls within the boundaries defined for one of the buttons or controlling areas, I give the unique integer type the recorded finger ID value, which is then used by the respective method to find the index at which the touch is during each frame, and when the touch with this finger ID returns TouchPhase.Ended it gives the saved integer the value of -1 once again, which can't be found in any touch and thus it halts the controlling method.
Related
I'm making a script in Unity using C#. I'm trying to use the Update() method to detect once the Camera position is past a certain point and then Instantiate an object into the scene and overwrite the variable "x" to something else so this only happens once.
The problem is I cant overwrite this "x" variable.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour{
public GameObject GroundSprite;
public int x = 1;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (((Camera.main.transform.position.x) < -4) && ( x == 1))
{
Instantiate(GroundSprite, transform.position, Quaternion.identity);
int x = 2;
}
}
}
Please remove int from below code, your value will be overwritten.
// Update is called once per frame
void Update()
{
if (((Camera.main.transform.position.x) < -4) && ( x == 1))
{
Instantiate(GroundSprite, transform.position, Quaternion.identity);
//int x = 2;
x=2;
}
}
I'm wondering how to smoothly zoom in and smoothly zoom out on button press in Unity3d using c#. I've got zooming part down already, but not sure how to make a transition of zooming in and out smooth. As an example, I'd like it to zoom in as smooth as it is in ARMA or DayZ game.
Here's my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class zoomIN : MonoBehaviour {
public Camera cam;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButton (1)) {
cam.fieldOfView = 20;
}
if (Input.GetMouseButtonUp (1)) {
cam.fieldOfView = 60;
}
}
}
I'd appreciate any help!
Thanks and Merry Xmas!
Use coroutine to do this. You can use it to enable the speed or duration of the zooming. Perform a Mathf.Lerp between cam.fieldOfView and the destination( 20 or 60) depending on if the key is pressed or released.
Note: You must change Input.GetMouseButton to Input.GetMouseButtonDown otherwise your first if statement will be running every frame while the right mouse button is held down. I think you want to be true once only.
public Camera cam;
Coroutine zoomCoroutine;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(1))
{
//Stop old coroutine
if (zoomCoroutine != null)
StopCoroutine(zoomCoroutine);
//Start new coroutine and zoom within 1 second
zoomCoroutine = StartCoroutine(lerpFieldOfView(cam, 20, 1f));
}
if (Input.GetMouseButtonUp(1))
{
//Stop old coroutine
if (zoomCoroutine != null)
StopCoroutine(zoomCoroutine);
//Start new coroutine and zoom within 1 second
zoomCoroutine = StartCoroutine(lerpFieldOfView(cam, 60, 1f));
}
}
IEnumerator lerpFieldOfView(Camera targetCamera, float toFOV, float duration)
{
float counter = 0;
float fromFOV = targetCamera.fieldOfView;
while (counter < duration)
{
counter += Time.deltaTime;
float fOVTime = counter / duration;
Debug.Log(fOVTime);
//Change FOV
targetCamera.fieldOfView = Mathf.Lerp(fromFOV, toFOV, fOVTime);
//Wait for a frame
yield return null;
}
}
A simple way to get your smooth zoom animation is by performing the zoom operation over multiple frames. So instead of changing the fieldOfView from 20 to 60 right away, increase the fieldOfView with 5 every frame until you reach your target of 60. (To lengthen the animation you can of course take a smaller number than 5.) So based on the mouse input you can keep a state _zoomedIn and based on that state and on the current fieldOfView you can determine whether you still need to add or substract to the value. Which gives you something like the following code: (not tested)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class zoomIN : MonoBehaviour {
public Camera cam;
private bool _zoomedIn = false;
private int _zoomedInTarget = 60;
private int _zoomedOutTarget = 20;
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown (1))
_zoomedIn = true;
if (Input.GetMouseButtonUp (1)) {
_zoomedIn = false;
}
if (_zoomedIn) {
if (cam.fieldOfView < _zoomedInTarget)
cam.fieldOfView += 5;
} else {
if (cam.fieldOfView > _zoomedOutTarget)
cam.fieldOfView -= 5;
}
}
I want some enemies to spawn in 4 different locations for a survival-type game. The problem is, they all spawn in the same place. Why is this? This is in Unity by the way.
C# script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour {
public int spawnHere = 0;
public int spawnTimer = 0;
public int spawnRate = 1;
public Transform spawner1;
public Transform spawner2;
public Transform spawner3;
public Transform spawner4;
public GameObject melee1;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
spawnTimer = spawnTimer + spawnRate;
spawnHere = Random.Range (1, 5);
if(spawnTimer >= 120) {
if(spawnHere == 1) {
Instantiate (melee1, spawner1);
}
if(spawnHere == 2) {
Instantiate (melee1, spawner2);
}
if(spawnHere == 3) {
Instantiate (melee1, spawner3);
}
if(spawnHere == 4) {
Instantiate (melee1, spawner3);
}
spawnTimer = 0;
}
}
}
Have you connected the spawners correctly in the UI?
Watch this: Unity - Survival Shooter - Spawning Enemies
This worked pretty good for my project.
You should also use Time.deltaTime for timing the spawns. Not every system outputs the same amount of frames / second.
Unity - docs - Time.deltaTime
BTW:
Random.rand(min, max)
Unity - docs - Random.Rand
includes max as a possible value.
In Unity2D, I have made a script to repeat the background sprite just before the camera can see the end.
Here's my code:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof(SpriteRenderer))]
public class Tiling : MonoBehaviour {
public int offsetX = 2; // the offset so that we don't get any weird errors
// these are used for checking if we need to instantiate stuff
public bool hasARightBuddy = false;
public bool hasALeftBuddy = false;
public bool reverseScale = false; // used if the object is not tilable
private float spriteWidth = 0f; // the width of our element
private Camera cam;
private Transform myTransform;
private float localSc;
void Awake () {
cam = Camera.main;
myTransform = transform;
localSc = transform.localScale.x;
}
// Use this for initialization
void Start () {
SpriteRenderer sRenderer = GetComponent<SpriteRenderer>();
spriteWidth = sRenderer.sprite.bounds.size.x * transform.localScale.x;
}
// Update is called once per frame
void Update () {
// does it still need buddies? If not do nothing
if (hasALeftBuddy == false || hasARightBuddy == false) {
// calculate the cameras extend (half the width) of what the camera can see in world coordinates
float camHorizontalExtend = cam.orthographicSize * Screen.width/Screen.height;
// calculate the x position where the camera can see the edge of the sprite (element)
float edgeVisiblePositionRight = (myTransform.position.x + spriteWidth/2) - camHorizontalExtend;
float edgeVisiblePositionLeft = (myTransform.position.x - spriteWidth/2) + camHorizontalExtend;
// checking if we can see the edge of the element and then calling MakeNewBuddy if we can
if (cam.transform.position.x >= edgeVisiblePositionRight - offsetX && hasARightBuddy == false)
{
MakeNewBuddy (1);
hasARightBuddy = true;
}
else if (cam.transform.position.x <= edgeVisiblePositionLeft + offsetX && hasALeftBuddy == false)
{
MakeNewBuddy (-1);
hasALeftBuddy = true;
}
}
}
// a function that creates a buddy on the side required
void MakeNewBuddy (int rightOrLeft) {
// calculating the new position for our new buddy
Vector3 newPosition = new Vector3 (myTransform.position.x + spriteWidth * rightOrLeft, myTransform.position.y, myTransform.position.z);
// instantating our new body and storing him in a variable
Transform newBuddy = Instantiate (myTransform, newPosition, myTransform.rotation) as Transform;
newBuddy.parent = myTransform.parent;
// if not tilable let's reverse the x size on our object to get rid of missmatches
if (reverseScale == true) {
newBuddy.localScale = new Vector3 (localSc*-1 , 1, 1);
}
if (rightOrLeft == 1) { //if this function was called to make a right buddy (1)
newBuddy.GetComponent<Tiling>().hasALeftBuddy = true;
}
else { //else we just made a left buddy, so it has a right copy
newBuddy.GetComponent<Tiling>().hasARightBuddy = true;
}
}
Now, the script is attached to the background sprite and it works fine.
You'll see that there's a bool reverseScale to reverse the image.
This is because if the image is not repeatable, (the end and the start to not match on a pixel level) we can mirror it by reverting (* -1) the x scale.
The strange thing is, if I launch this with reverseScale disabled, eveything works as I said. If I enable reverseScale, it becomes a mess. Infinite loop of overlapping, badly scaled sprites, crashing the game.
What am I missing? Worst case (but still shouldn't happen), that code snippet should make an image that doesn't match, why is it breaking the program?
EDIT:
I found a solution thanks to Enfyve answer. I was flipping the scale of the whole graphic component instead oa single one for come reason. the flipX field should be used instead. Also, only one every two tiles has to be flipped, to avoid missmatches.
SpriteRenderer already contains a property to draw a sprite flipped, use flipX instead.
Recently I'm making a chess game which has animation when the chess move. I used two method, RotateTowards and Translate(). RotateTowards() only run in Update() function. Here is my code:
using UnityEngine;
using System.Collections;
public class OnTouch : MonoBehaviour {
public GameObject cube;
public GameObject sphere;
int clicked;
Quaternion lastRot;
// Use this for initialization
void Start () {
clicked = 0;
}
// Update is called once per frame
void Update () {
if(clicked == 1)
{
var rotation = Quaternion.LookRotation(cube.transform.position - transform.position);
print(rotation);
rotation.x = 0;
rotation.z = 0;
cube.transform.rotation = Quaternion.RotateTowards(cube.transform.rotation, rotation, 100 * Time.deltaTime);
if(cube.transform.rotation = ???? <- No Idea){
clicked = 0;
}
}
}
void OnMouseDown()
{
print("clicked");
clicked = 1;
}
}
I attach that code to all chess tile. So, after the cube stop rotating, I tried to click another tile. But, the previous RotateTowards() method keep running, so it ran both. I've try to make IF logic, but I have no idea for
if(cube.transform.rotation == ??? <-- No idea){
clicked = 0;
}
Any solution? Thank you!
it will never reaches your final rotation, it will get very close though so you can check if the angle between your destination rotation and current rotation is smaller than a small degree then you can say it reached.
float DestAngle = 1.0f; //here we take 1 degree as the near enough angle
float angle = Quaternion.Angle(transform.rotation, target.rotation);//we calculate the angle between current rotaion and destination
if (Mathf.Abs (angle) <= DestAngle ) { //we reached }
if (cube.transform.rotation == rotation)