I am using a translation matrix to move the screen but when the player collides with an object the player will jitter as if it wants to be in 2 places at once. It looks like velocity wants to keep going down while the block pushes it up, how would I go by fixing this?
Video: Here
Camera class:
class Camera
{
public Vector2 Position;
Viewport viewPort;
public Vector2 cameraBounds;
public float wasGround;
public Matrix Transform()
{
var translationMatrix = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0));
return translationMatrix;
}
public Player thing(Player player)
{
cameraBounds.X = player.Position.X - Game1.offset.X;
if (cameraBounds.X > 0)
Position.X = player.Position.X - Game1.offset.X;
else
Position.X = 0;
//Problem
cameraBounds.Y = player.Position.Y - Game1.offset.Y;
if (cameraBounds.Y > 0)
{
Position.Y = player.Position.Y - Game1.offset.Y;
if (player.goingUp == false && (wasGround != player.ground))
Position.Y = player.ground - Game1.offset.Y;
wasGround = player.ground;
}
else
Position.Y = 0;
return player;
}
public Camera(Viewport viewport)
{
viewPort = viewport;
}
}
I tried to fix the problem by adding in player goingUp and ground if statements but that did not help.
I solved it. It is about sequence of operations. Just move method camera.thing() as shown below:
// TODO: Add your update logic here
HandleInput(Keyboard.GetState());
player.Update(gameTime);
// delete from here
Time += (float)gameTime.ElapsedGameTime.TotalSeconds;
foreach (Block b in Blocks)
{
player = b.BlockCollision(player);
}
// place here
camera.thing(player);
Explanation: You have to set camera position after all collisions have done.
what i can see while you stand still on some object, velocity changes. so try to convert camera position to integer. or make velocity exact 0 if is near 0.
cameraBounds.X = cInt(cameraBounds.X)
cameraBounds.y = cInt(cameraBounds.y)
Related
Currently I'm using a normalized rb velocity to get the x and z velocity directions in order to compare it with eulerAngles of the camera in order to tell the camera which way to spin. But I'm doing this inside a bunch of nested if statements and it just feels.... wrong.
For actually following the ball I just have parent child relationships with Player -> RotatorObject -> Camera, so rotating the RotatorObject rotates around the center of the player.
Here is an example of what I'm currently doing.
// Velocity direction of the ball
Vector3 VelNormed = Vector3.Normalize(rb.velocity);
xVelNormed = VelNormed.x;
zVelNormed = VelNormed.z;
// Current CameraRotationObject angle
yEuler = transform.eulerAngles.y;
zEuler = transform.eulerAngles.z;
// If Moving forwards.
if (zVelNormed >= 0.5)
{
// If camera is within hoped for range. (270 is camera = perfectly forwards)
if (yEuler >= 260 && yEuler <= 280)
{
// Move camera to the right.
if (yEuler >= 260 && yEuler <= 270)
{
// Rotate camera right
transform.Rotate(0, (speed * Time.deltaTime) * 1, 0);
}
else if (yEuler <= 280 && yEuler >= 270)
{
// Rotate camera left
transform.Rotate(0, (speed * Time.deltaTime) * -1, 0);
}
else
{
}
}
}
// If Moving backwards.
else if (zVelNormed <= -0.5)
{
//etc.
}
Like I said before, using all of these if statements like this makes me feel like a monster! IE. I'm almost sure I'm doing this the old computer programming for dummies way
First of all the eulerAngles are very rarely what you want to use
When you read the .eulerAngles property, Unity converts the
Quaternion's internal representation of the rotation to Euler angles.
Because, there is more than one way to represent any given rotation
using Euler angles, the values you read back out may be quite
different from the values you assigned. This can cause confusion if
you are trying to gradually increment the values to produce animation.
Why do you actually need to make the camera spin at all? It sounds to me like your main issue actually origins in the camera being somewhere nested as child below the rolling ball.
You should rather extract the camera into scene root level and make it follow the ball via code like e.g.
public class CameraFollow : MonoBehaviour
{
// Interpolation factors for the position and rotation
[Range(0, 1)] public float moveInterpolationFactor = 5f;
[Range(0, 1)] public float rotateInterpolationFactor = 5f;
// The target this object shall follow
[SerializeField] Rigidbody targetObject;
// Positional offset in the movement direction of the target object
// e.g. (0,0, -1) will keep the camera 1 world space unit behind the move direction
public Vector3 positionOffset = new Vector3(0, 0.25f, -1);
private Vector3 targetPosition;
private Quaternion targetRotation;
private void Awake ()
{
if(!targetObject) return;
UpdateTargetValues();
}
private void UpdateTargetValues ()
{
targetPosition = targetObject.position + targetObject.rotation * positionOffset;
targetRotation = Quaternion.LookRotation(targetObject.velocity);
}
private void LateUpdate ()
{
// If there is no target stay where you are
if(!targetObject) return;
// Get the move velocity since we want to follow based
// on the move direction, not the objects orientation
var velocity = targetObject.velocity;
// If not moving simply continue moving to the last set position
// otherwise update the target values
if(velocity.sqrMagnitude > 0)
{
UpdateTargetValues ();
}
transform.position = Vector3.Slerp(transform.position, targetPosition, moveInterpolationFactor * Time.deltaTime);
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotateInterpolationFactor * Time.deltaTime);
}
public void SetTarget(Rigidbody target)
{
targetObject = target;
if(!targetObject) return;
UpdateTargetValues ();
}
}
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Waypoints : MonoBehaviour
{
public GameObject[] waypoints;
public Transform target;
public float moveSpeed = 10f;
public float slowDownSpeed = 3f;
public float reverseSlowDownSpeed = 3f;
public float rotationSpeed = 1f;
private int targetsIndex = 0;
private Vector3 originalPosition;
private GameObject[] players;
public Transform reverseTarget;
private int reverseTargetsIndex = 0;
private Vector3 reverseOriginalPosition;
public bool random = false;
public bool getNextRandom = true;
// Use this for initialization
void Start()
{
waypoints = GameObject.FindGameObjectsWithTag("Blocks");
players = GameObject.FindGameObjectsWithTag("Player");
originalPosition = players[0].transform.localPosition;
}
// Update is called once per frame
void Update()
{
if (random == true)
{
RandomWayPointsAI();
}
else
{
WayPointsAI();
}
}
private void WayPointsAI()
{
if (targetsIndex == waypoints.Length)
targetsIndex = 0;
target = waypoints[targetsIndex].transform;
if (MovePlayer())
targetsIndex++;
}
private void ReverseWayPointsAI()
{
if (reverseTargetsIndex == 0)
reverseTargetsIndex = waypoints.Length - 1;
reverseTarget = waypoints[reverseTargetsIndex].transform;
if (MovePlayer())
reverseTargetsIndex--;
}
void RandomWayPointsAI()
{
if (random == true && getNextRandom)
{
int index = UnityEngine.Random.Range(0, waypoints.Length);
target = waypoints[index].transform;
getNextRandom = false;
}
getNextRandom = MovePlayer();
}
bool MovePlayer()
{
float distance = Vector3.Distance(players[0].transform.position, target.transform.position);
players[0].transform.localRotation = Quaternion.Slerp(players[0].transform.localRotation, Quaternion.LookRotation(target.position - players[0].transform.localPosition), rotationSpeed * Time.deltaTime);
//move towards the player
if (distance < 30)
{
players[0].transform.localPosition += players[0].transform.forward * slowDownSpeed * Time.deltaTime;
}
else
{
players[0].transform.localPosition += players[0].transform.forward * moveSpeed * Time.deltaTime;
}
if (distance < target.transform.localScale.magnitude)
return true;
else
return false;
}
void DrawLinesInScene()
{
// draw lines between each checkpoint //
for (int i = 0; i < waypoints.Length - 1; i++)
{
Debug.DrawLine(waypoints[i].transform.position, waypoints[i + 1].transform.position, Color.blue);
}
// draw a line between the original transform start position
// and the current transform position //
Debug.DrawLine(originalPosition, players[0].transform.position, Color.red);
Debug.DrawLine(reverseOriginalPosition, players[1].transform.position, Color.red);
// draw a line between current transform position and the next waypoint target
// each time reached a waypoint.
if (target != null)
Debug.DrawLine(target.transform.position, players[0].transform.position, Color.green);
if (reverseTarget != null)
Debug.DrawLine(reverseTarget.transform.position, players[1].transform.position, Color.green);
}
void AddColliderToWaypoints()
{
foreach (GameObject go in waypoints)
{
SphereCollider sc = go.AddComponent<SphereCollider>() as SphereCollider;
sc.isTrigger = true;
}
}
}
There are two problems with the script.
If the Move Speed is set to 3 i need to set the Rotation Speed at least to the 10 if i will set the Rotation Speed to 3 or 4 or 5 the rotation will be too wide and it will take much time to the player to get back on track after rotating.
But if it's 10 it seems he is almost rotating on the place so it's not good either.
I want to be able to change the player rotation speed but also to keep him on the waypoints track at any time. I mean that each waypoint the player is reaching to that he will get to it's center. In this case cubes so not only to touch the waypoint but to get to it's center then moving to the next waypoint.
Another problem i see is with the RandomWayPointsAI()
When i change it to use the random method i'm not sure if it's just picking up random positions around the grid or if it's picking random blocks(Cubes).
But it's never getting to a cube it' getting close or between two or sometimes on a cube and same as before also on the random i want to make the random waypoints not just positions but blocks and to get to each random block center.
Center i mean like standing on it.
A glaring problem in the showcased code is the use of local positions and rotations. When moving and rotating characters and objects in the world you should use world space.
Localposition and localrotation are based on the position and rotation of the parent object. Using those to move your object across the world will cause weird problems.
I think the following answer has a pretty accurate explanation of that stuff https://teamtreehouse.com/community/global-space-vs-local-space
I am working on a simple patrol script, and everything works but the characters are randomly rolling through the floor when they turn around. Here is a short clip of them...
Here is my script...
public class Patrol : MonoBehaviour
{
public float speed = 5;
public float directionChangeInterval = 1;
public float maxHeadingChange = 30;
public bool useRootMotion;
CharacterController controller;
float heading;
Vector3 targetRotation;
Vector3 forward
{
get { return transform.TransformDirection(Vector3.forward); }
}
void Awake()
{
controller = GetComponent<CharacterController>();
// Set random initial rotation
heading = Random.Range(0, 360);
transform.eulerAngles = new Vector3(0, heading, 0);
StartCoroutine(NewHeadingRoutine());
}
void Update()
{
transform.eulerAngles = Vector3.Slerp(transform.eulerAngles, targetRotation, Time.deltaTime * directionChangeInterval);
if (useRootMotion)
{
return;
}
else
{
controller.SimpleMove(forward * speed);
}
}
void OnControllerColliderHit(ControllerColliderHit hit)
{
if (hit.gameObject.tag == "Player")
{
// Bounce off the obstacle and change direction
var newDirection = Vector3.Reflect(forward, hit.normal);
transform.rotation = Quaternion.FromToRotation(Vector3.forward, newDirection);
heading = transform.eulerAngles.y;
NewHeading();
}
if (hit.gameObject.tag == "Boundary")
{
// Bounce off the obstacle and change direction
var newDirection = Vector3.Reflect(forward, hit.normal);
transform.rotation = Quaternion.FromToRotation(Vector3.forward, newDirection);
heading = transform.eulerAngles.y;
NewHeading();
}
}
/// Finds a new direction to move towards.
void NewHeading()
{
var floor = transform.eulerAngles.y - maxHeadingChange;
var ceil = transform.eulerAngles.y + maxHeadingChange;
heading = Random.Range(floor, ceil);
targetRotation = new Vector3(0, heading, 0);
}
/// Repeatedly calculates a new direction to move towards.
IEnumerator NewHeadingRoutine()
{
while (true)
{
NewHeading();
yield return new WaitForSeconds(directionChangeInterval);
}
}
}
I have tried adding a rigidbody to the characters and constraining rotation, but that doesnt work. Oddly enough, the character control isnt rotating at all. In the scene view I can see the character collider staying as it should, but the character flips through the mesh on its own.
It looks like it's because they are walking into a corner and being bounced between the two walls constantly which causes them to behave strangely. I would add a method of checking for a series of very quick collisions to detect that they are in a corner or stuck and then adapt accordingly, perhaps with a method to rotate 180 degrees and keep walking or the like.
You can do it like this:
float fTime = 0.1f
float fTimer = 0;
int iCollisionCounter;
if(collision){
if(fTimer > 0) iCollisionCounter++;
if(iCollisionCounter >= 5) //Character is stuck
fTimer = fTime;
}
Void Update(){
fTimer -= time.deltaTime;
}
That means that if there are multiple collisions within 0.1 seconds of each other you can handle it.
Hope this helps!
The idea is to make that when the player is walking to the edge of the terrain he will stop wont be able to continue and fall.
And in my case i want the objects that move forward when they collide with the invisible wall the object will turn lerp back and move to the other side of the invisible walls.
Another problem that might come up later i read about is that if the objects moving too fast to the invisible walls there is a bug that let them move through ? Not sure about it.
This is a screenshot showing the invisible walls. I created a box collider set the Is Trigger to be on and set the 500 600 500 same as the terrain size.
This is the script of the Invisible Walls: The script i attached it to the Terrain:
using UnityEngine;
using System.Collections;
public class InvisibleWalls : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerExit(Collider other)
{
}
}
This is the script that create the space ships clone of them and make them move forward. But when they get to the edge of the terrain they just gone out. And i want them to lkerp/turn back to the other side.
This script is attached to the Spheres GameObject:
using System;
using UnityEngine;
using Random = UnityEngine.Random;
using System.Collections;
using System.Collections.Generic;
public class SphereBuilder : MonoBehaviour
{
public GameObject SpaceShip;
GameObject[] spheres;
public float moveSpeed = 50;
// for tracking properties change
private Vector3 _extents;
private int _sphereCount;
private float _sphereSize;
/// <summary>
/// How far to place spheres randomly.
/// </summary>
public Vector3 Extents;
/// <summary>
/// How many spheres wanted.
/// </summary>
public int SphereCount;
public float SphereSize;
private void Start()
{
spheres = GameObject.FindGameObjectsWithTag("MySphere");
}
private void OnValidate()
{
// prevent wrong values to be entered
Extents = new Vector3(Mathf.Max(0.0f, Extents.x), Mathf.Max(0.0f, Extents.y), Mathf.Max(0.0f, Extents.z));
SphereCount = Mathf.Max(0, SphereCount);
SphereSize = Mathf.Max(0.0f, SphereSize);
}
private void Reset()
{
Extents = new Vector3(250.0f, 20.0f, 250.0f);
SphereCount = 100;
SphereSize = 20.0f;
}
private void Update()
{
UpdateSpheres();
MoveShips ();
}
private void MoveShips()
{
foreach (Transform child in spheres[0].transform)
{
child.transform.position += Vector3.forward * Time.deltaTime * moveSpeed;
}
}
private void UpdateSpheres()
{
if (Extents == _extents && SphereCount == _sphereCount && Mathf.Approximately(SphereSize, _sphereSize))
return;
// cleanup
var spheres = GameObject.FindGameObjectsWithTag("Sphere");
foreach (var t in spheres)
{
if (Application.isEditor)
{
DestroyImmediate(t);
}
else
{
Destroy(t);
}
}
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
for (var i = 0; i < SphereCount; i++)
{
var o = Instantiate(SpaceShip);
o.tag = "Sphere";
o.transform.SetParent(gameObject.transform);
o.transform.localScale = new Vector3(SphereSize, SphereSize, SphereSize);
// get random position
var x = Random.Range(-Extents.x, Extents.x);
var y = Extents.y; // sphere altitude relative to terrain below
var z = Random.Range(-Extents.z, Extents.z);
// now send a ray down terrain to adjust Y according terrain below
var height = 10000.0f; // should be higher than highest terrain altitude
var origin = new Vector3(x, height, z);
var ray = new Ray(origin, Vector3.down);
RaycastHit hit;
var maxDistance = 20000.0f;
var nameToLayer = LayerMask.NameToLayer("Terrain");
var layerMask = 1 << nameToLayer;
if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
{
var distance = hit.distance;
y = height - distance + y; // adjust
}
else
{
Debug.LogWarning("Terrain not hit, using default height !");
}
// place !
o.transform.position = new Vector3(x, y, z);
}
_extents = Extents;
_sphereCount = SphereCount;
_sphereSize = SphereSize;
}
}
And this is a small short video clip showing what happen to the space ships when they get to the terrain edge:
Spaceships video clip
Update what i did so far:
In top of script added:
public Terrain terrain;
private Vector3 boundLower;
private Vector3 boundUpper;
In Start function i added:
private void Start()
{
spheres = GameObject.FindGameObjectsWithTag("MySphere");
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
boundLower = terrain.transform.position - terrain.transform.size / 2;
boundUpper = terrain.transform.position + terrain.transform.size / 2;
}
But getting errors on both lines: size property not exist:
boundLower = terrain.transform.position - terrain.transform.size / 2;
boundUpper = terrain.transform.position + terrain.transform.size / 2;
And changed the MoveShips function to this:
private Vector3 direction = Vector3.forward;
private void MoveShips() {
foreach (var child in spheres) {
var pos = child.transform.position + direction * Time.deltaTime * moveSpeed;
pos.x = Mathf.Clamp(pos.x, boundLower.x, boundUpper.x);
pos.z = Mathf.Clamp(pos.z, boundLower.z, boundUpper.z);
if (pos.x == boundLower.x || pos.x == boundUpper.x) direction.x = - direction.x;
if (pos.z == boundLower.z || pos.z == boundUpper.z) direction.z = - direction.z;
child.transform.position = pos;
}
}
I would suggest modifying MoveShips() changing Vector3.forward to a variable and flipping it when bounds are reached:
private Vector3 direction = Vector3.forward;
private void MoveShips() {
foreach (var child in spheres) {
var pos = child.transform.position + direction * Time.deltaTime * moveSpeed;
pos.x = Mathf.Clamp(pos.x, boundLower.x, boundUpper.x);
pos.z = Mathf.Clamp(pos.z, boundLower.z, boundUpper.z);
if (pos.x == boundLower.x || pos.x == boundUpper.x) direction.x = - direction.x;
if (pos.z == boundLower.z || pos.z == boundUpper.z) direction.z = - direction.z;
child.transform.position = pos;
}
}
This will remove unnecessary dependence on object collision engine for such a simple thing. Note, how this is making all ships to change direction when furthest reaches the bound. If you want them to move separately, you will need to move this logic to a separate script and attach it to a ship prefab.
And the bounds (boundLower and boundUpper) can be set either as script variables in editor or calculated from terrain, like:
boundLower = terrain.transform.position - terrain.TerrainData.size / 2;
boundUpper = terrain.transform.position + terrain.TerrainData.size / 2;
I would also suggest to move this:
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
out of Update() into Start() unless you do something really funky with it in the process.
Lets start working though your problems one by one :
Question : The objects do not collide, why?
Answer : Objects do not collide from with-in the collider, only from the outside.
What you need in your case is 4 box collider, one at each edge of the map
Question : Another problem that might come up later i read about is that if the objects moving too fast to the invisible walls there is a bug that let them move through ? Not sure about it.
This is only a problem with objects moving at bullet-like speeds, you could edit the rigidbody to have detection mode : "continuous" or continuous-dynamic which will avoid this issue
The scripts, i do not think you would need them in this case, your original idea was good, just the implementation with a single collider over the whole terrain, instead of 4 seperate "wall" colliders, was the issue. As for the rest of the logic, i did not try decipher through that, thus i can not comment on it.
i'm moving game object with this simple function which move body depends on camera view. The only problem that the camera X rotation isnt 0 and body tries to move into the ground but not clear forward and it makes movement slowly
void VerySimpleMove()
{
if (_controllerBody.isGrounded)
{
_moveDirection.x = _mj.GetAxis("Horizontal");
_moveDirection.y = 0;
_moveDirection.z = _mj.GetAxis("Vertical");
_moveDirection = _camera.TransformDirection(_moveDirection);
if (Mathf.Abs(_moveDirection.x) > 0 || Mathf.Abs(_moveDirection.y) > 0)
{
_body.rotation = Quaternion.LookRotation(_moveDirection);
}
if (_jumpButton)
{
_jumpButton = false;
_moveDirection.y = _jumpHeight;
}
}
_moveDirection.y -= _gravity * Time.deltaTime;
_controllerBody.Move(_moveDirection * Time.deltaTime);
}
How is it possible to rotate _moveDirection by _camera.eulerAngle.x to make it real forward?
If I understand correctly, you need to make sure that _moveDirection is always parallel to XZ plane.
One way to do it is instead of using _camera.TransformDirection, to calculate the projection of camera forward vector on XZ plane, then use LookRotation in that direction, like this:
var forward = _camera.transform.forward; // Get camera forward vector
forward.y = 0; // Project it on XZ plane
_moveDirection = Quaternion.LookRotation(forward) * _moveDirection; // Rotate
Note that simply rotating the object by camera X angle as it was suggested won't always help here, since camera Z angle can cause the same effect.
So final code would be:
void VerySimpleMove()
{
if (_controllerBody.isGrounded)
{
_moveDirection.x = _mj.GetAxis("Horizontal");
_moveDirection.y = 0;
_moveDirection.z = _mj.GetAxis("Vertical");
var forward = _camera.transform.forward; // Get camera forward vector
forward.y = 0; // Project it on XZ plane
_moveDirection = Quaternion.LookRotation(forward) * _moveDirection; // Rotate
if (Mathf.Abs(_moveDirection.x) > 0 || Mathf.Abs(_moveDirection.y) > 0)
{
_body.rotation = Quaternion.LookRotation(_moveDirection);
}
if (_jumpButton)
{
_jumpButton = false;
_moveDirection.y = _jumpHeight;
}
}
_moveDirection.y -= _gravity * Time.deltaTime;
_controllerBody.Move(_moveDirection * Time.deltaTime);
}