I'm working on a VR project and I'm struggling to do something that should not be that hard, but I really am bad at math :x
What I want to do, is have "physical" buttons on the world that I can push with my finger to press/unpress it, this part works really well and the feeling is perfect, BUT :
Currently, to "push" the button, I'm using the z delta position of my controller, so I can't push button from the top for example, because I would need the y delta.
I'm trying to be able to have buttons facing any direction, and be able to push them using the right axis of my controller, but can't get my head around it :x
I'd need to be able to push the button depending on it's position/rotation.
I don't know if it's really understandable (probably not), maybe I just missed something really obvious.
Here is my code for my button, the enum part is probably useless, I'm just trying things lol
using System;
using UnityEngine;
public class JVRButton : MonoBehaviour, IJVRFingerInteract
{
[SerializeField] private Vector3 pressedPosition;
[SerializeField] private Vector3 defaultPosition;
[SerializeField] private Vector3 unpressPosition;
[SerializeField] private LinearDragNormal direction;
public bool Pressed { get; private set; }
public event Action<bool> OnStateChange;
private bool _changedState;
private Transform _transform;
private Vector3 _tmp;
private float _delay;
private float _delta;
private void Awake()
{
_transform = transform;
_transform.localPosition = Pressed ? pressedPosition : defaultPosition;
}
private void Update()
{
_delay += Time.deltaTime;
if (_delay < 0.1f) return;
// "Spring" effect
_transform.localPosition = Vector3.MoveTowards(_transform.localPosition, Pressed ? pressedPosition : defaultPosition, Time.deltaTime / 2);
}
public void JVRFingerInteract(JVRFinger jvrFinger)
{
Vector3 test = Quaternion.FromToRotation(jvrFinger.transform.forward, _transform.forward).eulerAngles;
Debug.Log(test);
switch(direction)
{
case LinearDragNormal.XPositive:
_delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.z, 0);
break;
case LinearDragNormal.XNegative:
_delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.z, 0);
break;
case LinearDragNormal.YPositive:
_delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.y, 0);
break;
case LinearDragNormal.YNegative:
_delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.y, 0);
break;
case LinearDragNormal.ZPositive:
_delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.x, 0);
break;
case LinearDragNormal.ZNegative:
_delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.x, 0);
break;
default:
throw new ArgumentOutOfRangeException();
}
MoveButton(Pressed ? unpressPosition : pressedPosition);
}
private void MoveButton(Vector3 position)
{
if (_changedState && _delay < 0.5f) return;
_delay = 0;
_changedState = false;
_tmp = _transform.localPosition;
_tmp = Vector3.MoveTowards(_tmp, position, _delta);
if (_tmp.x < position.x) _tmp.x = position.x;
if (_tmp.y < position.y) _tmp.y = position.y;
if (_tmp.z < position.z) _tmp.z = position.z;
_transform.localPosition = _tmp;
if (_transform.localPosition == pressedPosition)
{
Pressed = true;
_changedState = true;
OnStateChange?.Invoke(Pressed);
}
else if (_transform.localPosition == unpressPosition)
{
Pressed = false;
_changedState = true;
OnStateChange?.Invoke(Pressed);
}
}
}
public enum LinearDragNormal
{
XPositive,
XNegative,
YPositive,
YNegative,
ZPositive,
ZNegative
}
JVRFingerInteract is called every frame my finger is touching the button, I'm simply doing a overlapsphere in my Finger to get interactable objects.
The pushing axis of the button is the button's local Z axis, and positive Z points out of the surface of the button.
Ok, I found the solution by trying, I don't understand HOW and WHY it works, but it does...
_delta = (_forward.x * jvrPos.z + _forward.y * -jvrPos.y + _forward.z * -jvrPos.x);
if (_delta < 0) _delta = 0;
_forward being the button forward, and jvrPos being the local delta of my controller/finger
Assuming the surface of your button faces the button's local up direction:
Then you can use a dot product between the button's up in world space and the delta in world space to determine how much the finger is moving against that direction:
Vector3 worldFingerDelta;
Transform buttonTransform;
float deltaInButtonDown = Vector3.Dot(worldFingerDelta,
-buttonTransform.up);
// when deltainButtonDown is positive, the finger is moving in the
// same direction as "pushing the button"
// When it is negative, the finger is moving in the opposite direction.
// The magnitude of deltaInButtonDown is how much the finger is
// moving in that direction.
If a different local direction is pointing out of the surface of the button, such as its negative forward, you would just use the corresponding local vector in world space:
float deltaInButtonDown = Vector3.Dot(worldFingerDelta,
buttonTransform.forward); // negatives cancel out
Also, if the surface the button is on is moving in world space, you'll want to use Vector3.Dot(worldFingerDelta - worldButtonSurfaceDelta, buttonTransform.forward); in order to allow for things like the button pressing itself by moving into a still finger.
About your answer....
_delta = (_forward.x * jvrPos.z + _forward.y * -jvrPos.y
+ _forward.z * -jvrPos.x);
if (_delta < 0) _delta = 0;
_forward being the button forward, and jvrPos being the local delta of my controller/finger
As far as the vector math goes, you're doing a dot product between the button's forward and the local delta after some kind of inversion/reflection transformation is applied to it. So another way to write your answer is this:
new Vector3 vec = new Vector3(jvrPos.z, -jvrPos.y, -jvrPos.x);
_delta = Vector3.Dot(vec, buttonTransform.forward);
As we discovered in the comments of your answer, that transformation, based on the world rotation of the parent of the controller, turns your local delta into the negative of the world delta. So by taking the negative of what you have for the vector and making that negative in the Dot call, another way to write your answer is this:
new Vector3 calculatedWorldFingerDelta = new Vector3(-jvrPos.z, jvrPos.y, jvrPos.x);
_delta = Vector3.Dot(-calculatedWorldFingerDelta, buttonTransform.forward);
And since the dot product of A and -B equals the dot product of -A and B, your answer is equivalent to:
new Vector3 calculatedWorldFingerDelta = new Vector3(-jvrPos.z, jvrPos.y, jvrPos.x);
_delta = Vector3.Dot(calculatedWorldFingerDelta, -buttonTransform.forward);
Which, if calculatedWorldFingerDelta equals worldFingerDelta, is the same formula above for a button with the positive z direction pointing out of the surface of the button.
I would use worldFingerDelta directly because if the world rotation or position of the parent of the finger changes, then the transformation you're using now won't be guaranteed to give you calculatedWorldFingerDelta that equals worldFingerDelta, and your answer won't work anymore.
Related
I've made an Isometric character controller so it can move and head on an isometric based perspective. The problem is that when I try to move using 2 keys (W + D, A + S...) and release those keys, the player tends to face the very last key that was released, so it is difficult to head a diagonal direction. This makes the character flip its rotation in the very last moment the keys are released.
I've been thinking of using a kind of sleep or coroutine checking if two keys were pressed and released in a short period of time just not rotate it.
There exist any not too rustic solution for this?
Here is my code (I just copied it from here: https://www.studica.com/blog/isometric-camera-unity)
private void SetIsometricDirections() {
vertical = Camera.main.transform.forward;
vertical.y = 0;
vertical = Vector3.Normalize(vertical);
horizontal = Quaternion.Euler(new Vector3(0, 90, 0)) * vertical;
}
private void Move() {
Vector3 horizontalMovement = horizontal * moveSpeed * Time.deltaTime * Input.GetAxis("HorizontalKey");
Vector3 verticalMovement = vertical * moveSpeed * Time.deltaTime * Input.GetAxis("VerticalKey");
Vector3 heading = Vector3.Normalize(horizontalMovement + verticalMovement);
Heading(heading);
transform.position += (horizontalMovement + verticalMovement);
}
private void Heading(Vector3 heading) {
transform.forward = heading;
}
The heading trick it's in the "Heading" method, obviously.
I finally found a solution.
I'll post it just in case any one else needs it in the future.
I calculate the current rotation based on the movement.
private void SetRotation() {
currentRotation = Quaternion.LookRotation(movement);
}
Then, I created a second Quaternion to store the last rotation if is not the same than the current. I also use a counter to store the time a rotation has been faced before stopping the movement or changing the direction.
private void CheckSameRotation() {
if (lastRotation != currentRotation || movement == Vector3.zero) {
sameRotationTime = 0;
lastRotation = currentRotation;
}
else {
sameRotationTime += Time.deltaTime;
}
}
Then, I used a bool to check if the time is high enough to make the rotation happen.
private void TimeAtSameRotation() {
canRotate = (sameRotationTime < 0.015f) ? false : true;
}
And then, I finally rotate the object if the movement is not zero and the condition "canRotate" is true.
private void Rotate() {
if (movement != Vector3.zero && canRotate) {
transform.rotation = currentRotation;
}
}
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.
I am trying to animate a platform prefab that is spawned (the platform object itself is within an empty because its position changes) DOWN when a Runner object collides with it (onCollisonEnter) and UP on collision exit.
I have followed answers given to my old question here verbatim- Unity, C# - cannot make object move down from current y position ONCE on collision enter? but can't get animations using the Animator to work despite having this in my animator as directed:
One person who answered suggested I use Lerp to animate the platform prefab entirely using code. I have researched Lerp but as I need 2 separate states for Down/Idle Down (to make platform stay down) and the same for up/idle up I do not know how to do this.
How can I achieve this effect using Lerp programmatically? Is it possible to achieve the effect I am going for?
ERROR:
EDIT:
how i spawn (dequeue then enqueue) my platforms:
nextPosition += new Vector3 (
Random.Range (minGap.x, maxGap.x) + scale.x,
Random.Range (minGap.y, maxGap.y),
Random.Range (minGap.z, maxGap.z));
Transform o = objectQueue.Dequeue();
o.localScale = scale;
o.localPosition = position;
//o.localEulerAngles = rotation;
o.gameObject.SetActive (true);
int materialIndex = Random.Range(0, materials.Length);
o.GetComponent<Renderer>().material = materials[materialIndex];
o.GetComponent<Collider> ().material = noFrictionMaterial;
platform newScript = new o.GetComponent<platform>(); //getting an error here when I tried to implement your code
objectQueue.Enqueue (o);
if(nextPosition.y < minY){
nextPosition.y = minY + maxGap.y;
}
else if(nextPosition.y > maxY){
nextPosition.y = maxY - maxGap.y;
}
This is what the vectors should ALWAYS be and what I set them at originally in start():
up = new Vector3 (transform.position.x, transform.position.y, transform.position.z);
down = new Vector3 (transform.position.x, transform.position.y-2f, transform.position.z);
Since the platforms aren't actually spawning, are you sure that's the error? Can you help me?
This should work for you:
using UnityEngine;
using System.Collections;
public class Collision : MonoBehaviour {
public Vector3 up, down;
public bool isUp = false;
public float speed = 5f;
float startTime,
length;
bool isMoving = false;
void Start () {
//initialise the platform to a position
if (isUp) transform.position = up;
else transform.position = down;
length = Vector3.Distance(up, down);
}
void Update()
{
if (isUp)
{
//move Down
transform.position = Vector3.Lerp(up, down, ((Time.time - startTime) * speed) / length);
}
else
{
//move Up
transform.position = Vector3.Lerp(down, up, ((Time.time - startTime) * speed) / length);
}
}
//move down
void OnCollisionEnter(Collision col)
{
if (!isMoving)
{
startTime = Time.time;
isMoving = true;
}
}
//move up
void OnCollisionExit(Collision col)
{
if (!isMoving)
{
startTime = Time.time;
isMoving = true;
}
}
}
The thing you have to watch out for is making sure that the "runner" stays on the platform until it's on the bottom so that it can move up again. You could add a check so that it moves back up regardless with another bool it it doesn't work for you. Hope it helps :)
EDIT
What lerp (short for linear interpolation) does is pick points between two vectors based on the progression of the time component (0 to 1). So in order for the platform to go up and down it needs a starting point and an end point, in this case it's up or down (up to the upper position down for the other).
If you want to create a new platform without causing an error you need to set these values after creating it so that everything works correctly (if you leap from any position it will jump aggressively in the first step of the lerp). For example:
//after instantiating the new platform object
scriptName newObject = newGameobject.GetComponent<scriptName>();
newObject.up = (up Vector);
newObject.down = (down Vector);
UPDATE
I managed to figure the answer out myself, what was happening was that my movement function was only registering the players original static position instead of the dynamic coordinates as the player moved around the screen. What I did was create a 'God' class to publicly store the players current X and Y coordinates, and then reference the 'god' class coordinates in my enemy's movement function.
Now, not only does the enemy detect when the player is within it's sights, it will follow the player changing directions accordingly, until it can't 'see' them anymore :D
If anyone wants to see the updated code just ask :)
I'm trying to make a program that contains player and enemy sprites, but I'm having a little trouble with the code for the 'Enemy'. Most of the coding problems on here deal with XNA, which I'm not using, so I was wondering if I could have a little help.
Basically, when the player moves within a certain distance, the movement function for the enemy should activate and it will move towards the players location as long as they're in the 'detection zone'. The enemy's sprite also has a walk animation for each direction, and it should flip through frames depending on whether it's currently moving or not.
The movement function is within a timer, so that it'll update with every tick. I have two separate functions for updating its location, one to physically change it's coordinates in the Form based on the data received from the movement function, and another to update the current frame from my sprite sheet.
The problem is that the enemy doesn't move whatsoever, if I get rid of it's detection and the code to follow the player, and instead just move it across the screen, it works perfectly fine. But unfortunately that's not what I'm trying to achieve.
The code works perfectly fine for my player sprite (albeit, it's controlled by the user and the keyboard keys), so I'm not sure what I'm doing wrong.
I believe the problems lies within the Movement function, but I'm not sure what that problem is exactly.
Here's my code for the relevant functions:
public void Movement() //This function is in a timer, and determines which direction the sprite will move and face
{
//The sprite rectangle is the position of the player sprite, and detection is the area where the enemy would 'see' the player if they entered it
sprite = new Rectangle(charX - 1, charY + 1, charWidth * 2 + 2, charHeight * 2 + 2);
Detection = new Rectangle(curX - 25, curY - 25, Enemy.Width * 6, Enemy.Height * 6);
if (Detection.IntersectsWith(sprite)) //Checks to see if the player is within range
{
//Moves the enemy based on the players position and changes direction accordingly
if (curX > charX)
{
curDirection = RIGHT;
dX = -1;
dY = 0;
numUpdates = 0;
curAnimation = MOVING;
}
else if (curX < charX)
{
curDirection = LEFT;
dX = 1;
dY = 0;
numUpdates = 0;
curAnimation = MOVING;
}
if (curY > charY)
{
curDirection = UP;
dY = -1;
dX = 0;
numUpdates = 0;
curAnimation = MOVING;
}
else if (curY < charY)
{
curDirection = DOWN;
dY = 1;
dX = 0;
numUpdates = 0;
curAnimation = MOVING;
}
}
//If not within range, don't move the sprite
else
{
dY = 0;
dX = 0;
curAnimation = FINISHED;
}
}
These are the updating functions that move the enemy and change the current frame
public void Follow() //Physcially moves the enemy across the form
{
int xMod = 0;
int yMod = 0;
if (dX != 0)
xMod = dX / Math.Abs(dX) * 2;
if (dY != 0)
yMod = dY / Math.Abs(dY) * 2;
//Moves the sprite across the x and y axis
curX += xMod;
dX += -xMod;
curY += yMod;
dY += -yMod;
}
public void UpdateFrame(int direction = DOWN)
{
numUpdates++;
switch (curAnimation)
{
//Stops the animation if the sprite stops moving
case FINISHED:
curFrame = 0;
break;
//Moves to the next frame when movement is found
case MOVING:
curFrame = (curFrame + 1) % 3;
Follow();
//check if done animation
if (dX == 0 && dY == 0) curAnimation = FINISHED;
break;
}
}
If you need more code just ask, any help is appreciated :)
I'm wanting my arrow to point the way I'm targeting in my game.
At the moment, my prefab instantiates in the center of the screen point to the left. When I move my arrow, the point still keeps to the left. How can I get it so that the arrow at least points to the center of my screen, or ideally, has a way of moving, depending on the direction the player is aiming.
For example, if my player is aiming to the top left of the screen, the arrow point would face that way and fire off in that direction.
Here is the code i have so far:
public Object _projectilePrefab;
private bool _pressed = false;
public GameObject _flystickPosition;
private GameObject _currentProjectile;
private Vector3 _firePoint;
private float _startTime;
public float sensitivityX = 15F;
public float sensitivityY = 15F;
int x = 0;
int y = 0;
// Use this for initialization
void Start()
{
_currentProjectile = (GameObject)Instantiate(_projectilePrefab, _flystickPosition.transform.position, _flystickPosition.transform.rotation);
_firePoint = Camera.mainCamera.transform.position - new Vector3(1, 1, 0);
_startTime = (Time.time * 10.0f);
//_serialData = GetComponent<ReadSerial>();
//_udpData = GetComponent<HV_ReadUDPData>();
}
// Update is called once per frame
void Update()
{
_firePoint = Camera.mainCamera.transform.position - new Vector3(1, 1, 0);
float _moveBananaY = transform.localEulerAngles.y + Input.GetAxisRaw("LeftRight") * sensitivityX;
float _moveBananaX = transform.localEulerAngles.x + Input.GetAxisRaw("UpDown") * sensitivityY;
transform.localEulerAngles = new Vector3(_moveBananaX, _moveBananaY, 0);
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
y--;
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
y++;
}
if (Input.GetKeyDown(KeyCode.UpArrow))
{
x--;
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
x++;
}
float xPercent = 0;
float yPercent = 0;
xPercent = x / 25.0f;
yPercent = y / 25.0f;
_flystickPosition.transform.parent.localEulerAngles = new Vector3(xPercent * 90, yPercent * 90, 0.0f);
float smooth = Time.time - _startTime;
_currentProjectile.transform.position = Vector3.Lerp(lerpFrom, _flystickPosition.transform.position, smooth);
if (Input.GetKeyDown(KeyCode.Space))
{
_currentProjectile.GetComponent<Rigidbody>().velocity = _flystickPosition.transform.parent.forward.normalized * 20;
_currentProjectile = null;
_currentProjectile = (GameObject)Instantiate(_projectilePrefab, _firePoint, transform.rotation);
lerpFrom = _currentProjectile.transform.position;
_startTime = Time.time;
}
}
All of this code is found within my FireProjectile class. It is attached to my camera.
Well if I understand you correctly, your game is 2D. Its not important though. I would offer two solutions for your issue:
You can get your pointing direction by using simple vector math. Vector3(to - from). So your arrow pointing direction is : Target.transform.position - Player.transform.position. Now when it is pointing the right direction you can move it like:
Arrow.transform.Translate(Vector3.forward * Time.deltaTime);
Also if your arrow must point to target, but stay in different position than player, just raycast from Player to Target, get raycast point and theres your arrow "to" Vector3.
If arrow always mimic user rotation, you can simply apply rotation to arrow Arrow.transform.rotation = Player.transform.rotation. If rotations don't match (i.e. your object is rotated), just add empty gameobject to mimic rotations and put arrow as child of it. now you can rotate it as you will.