How would I apply slowness effect to player without Coroutines - unity3d

A few days ago I started a new project, a MOBA like test, and it is been going well for the most part. Currently I am having difficulties importing slowness effect or buff/debuff system in to the game. The exact problem is with the timer I need to change the speed value of the character but I can not use ref/out parameters in coroutines. I tried some other things like using invoke or using functions but they just do not do what I want. All feedback is appreciated and thanks in advance
Here is the code:
Coroutine:
public IEnumerator ApplyDebuff(ref float stat, float percentage, float duration)
{
float originalValue = stat;
float timeStamp = Time.time;
while (Time.time < timeStamp + duration)
{
float debuffValue = percentage / 100 * originalValue;
stat -= debuffValue;
yield return null;
}
stat = originalValue;
}
Where I call the coroutine
private void CheckForCollision()
{
Collider[] colliders = Physics.OverlapSphere(position, scale, layerMask);
foreach(Collider c in colliders)
{
c.TryGetComponent<PlayerController>(out PlayerController player);
player.ApplyDebuff(ref player.speed, 70, 5);
player.TakeDamage(50);
Destroy(gameObject);
}
}

You can't use ref in IEnumerator, the Action<float> solves the problem. This method is called Callback.
public IEnumerator ApplyDebuff(float stat, float percentage, float duration, Action<float> OnWhileAction, Action<float> AfterWaitAction)
{
float originalValue = stat;
float timeStamp = Time.time;
while (Time.time < timeStamp + duration)
{
float debufValue = percentage / 100 * originalValue;
OnWhileAction(debufValue);
yield return null;
}
AfterWaitAction(originalValue);
}
The following lambda can execute your command after wait simply.
foreach(Collider c in colliders)
{
/// ...
player.ApplyDebuff(player.speed, 70, 5, debufValue => player.speed-=debufValue, orginalValue => player.speed = orginalValue);
///...
}
For using System library as write using system in top of code;

Related

Smoothly change the player's speed when he leaves the zone

I'm trying to make special zone located on ground that accelerates the player, entering which the player's speed increases. When he gets out of it, the speed smoothly returns to the original. But now, when the player leaves the zone, the speed instantly becomes the original. Don't know how to realize it. Please help:)
My code:
PlayerMovement:
[SerializeField] private Rigidbody _rigidoby;
public bool inZone = false;
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
}
}
SpecialZone:
private Vector3 _cachedVelocity;
private Rigidbody _collisionRigidbody;
[SerializeField] private PlayerMovement _player;
private void OnTriggerStay(Collider other)
{
_player.inZone = true;
if (_collisionRigidbody == null)
{
_collisionRigidbody = other.GetComponent<Rigidbody>();
_cachedVelocity = _collisionRigidbody.velocity;
}
_collisionRigidbody.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 1000;
}
private void OnTriggerExit(Collider other)
{
_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
A simple solution can be to gradually decrease the speed in the update once you exit the trigger like this:
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;)
_collisionRigidbody.velocity -= 0.01;
}
}
Another could be to launch a coroutine when you exit the trigger.
Alternatively, you could use Vector3.Lerp and set the percent of the speed change in the update like this:
Vector3 _startingSpeedBeforeDeceleration = Vector3.zero;
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_startingSpeedBeforeDeceleration = _collisionRigidbody.velocity;
_player.inZone = false;
}
float percent = 0f; //from 0 to 1
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;) {
_collisionRigidbody.velocity = Vector3.Lerp(_startingSpeedBeforeDeceleration, _cachedVelocity, percent);
percent += 0,01f;
}
}
}
Not debugged code. Note that I kept the departing speed in the variable _startingSpeedBeforeDeceleration, that is optional, and depends on the movement behaviour you want.
You can play around with Vector3.Lerp for example changing the first argument _startingSpeedBeforeDeceleration as a fixed value, and put there rigidbody speed every time it changes like this Vector3.Lerp(_collisionRigidbody.velocity, _cachedVelocity, percent);, that way you have a smooth start and smooth end, and a top speed change at the middle of the path. Also the percent change can be handled to set the movement behaviour.

Unity : How can I create and scale up 100 cubes over time one by one with using Vector3.Lerp function

How can I create and scale up 100 cubes over time one by one with using Vector3.Lerp function. I need to be dependent on the duration variable. I need an efficient way.
Though your question is pretty vague:
Use a Coroutine like e.g.
// Adjust these in the Inspector
[SerilaizeField] private Vector3 targetScale = Vector3.one;
[SerilaizeField] private Vector3 startScale = Vector3.zero;
[SerilaizeField] private float durationPerCube = 1f;
[SerilaizeField] private Transform[] cubes;
private IEnumerator ScaleUpRoutine()
{
if(cubes.Length == 0) yield break;
foreach(var cube in cubes)
{
var timePassed = 0f;
while(timePassed < durationPerCube)
{
var factor = timePassed / duration;
// optional easing-in and -out
//factor = Mathf.SmoothStep(0, 1, factor);
cube.localScale = Vector3.Lerp(startScale, targetScale, factor);
timePassed += Time.deltaTime;
yield return null;
}
// just to be sure set a hard value
cube.localScale = targetScale;
}
}
You start it whenever you want via StartCoroutine e.g.
private void Start()
{
StartCoroutine(ScaleUpRoutine);
}
If you rather wanted it based on overall duration you could do
// Adjust this in the Inspector
[SerilaizeField] private float overallDuration = 2f;
private IEnumerator ScaleUpRoutine()
{
if(cubes.Length == 0) yield break;
var durationPerCube = overallDuration / cubes.Length;
...
Keep in mind though that yield return null; currently waits for at least one frame per cube so if your overallDuration is very small (< 100 frames) it might not be accurate. In this case you might want to add a StopWatch in order to only yield return a frame after a certain real-time threshold like
...
private IEnumerator ScaleUpRoutine()
{
if(cubes.Length == 0) yield break;
var stopWatch = new StopWatch();
stopWatch.Restart();
...
while(timePassed < durationPerCube)
{
var factor = timePassed / duration;
// optional easing-in and -out
//factor = Mathf.SmoothStep(0, 1, factor);
cube.localScale = Vector3.Lerp(startScale, targetScale, factor);
timePassed += Time.deltaTime;
if(stopWatch.ElapsedMilliseconds >= maxMillisecondPerFrame)
{
yield return null;
stopWatch.Restart();
}
}
...
For the creating part: You shouldn't.
Depending on your needs I would either
Use a hidden object that already holds 100 cubes in the scene. Only display it when needed
Extend the same idea a bit further and use pooling instead

How to get width difference between the canvas and a scaled image and do an efficient PingPong effect in Unity3D

I am trying to get the width difference between the canvas and a image which is scaled by CanvasScaler in order to create translation between the image and his border.
Illustration:
How get the size of the red arrow?
[EDIT 1]
The code snippet bellow give me a possible correct result
var dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
But It seems to be incorrect:
public class Background : Monobehaviour
{
private float dist;
private float _percentage;
private float _currentLerpTime;`
private readonly Dictionary<LerpDirection, Func<Vector3>> _lerpDirectionActions;
public float lerpTime;
void Awake()
{
var image = GetComponent<Image>();
var canvasRectTransform = GetComponentInParent<RectTransform>();
dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
_lerpDirectionActions = new Dictionary<LerpDirection, Func<Vector3>>
{
[LerpDirection.Left] = LerpToLeft,
[LerpDirection.Right] = LerpToRight
};
}
private Vector3 Lerp()
{
return Vector3.Lerp(
transform.position,
_lerpDirectionActions[lerpDirection].Invoke(), // will call LerpToRight or LerpToLeft
_percentage
);
}
private float LerpX => Lerp().x;
private Vector3 LerpToRight()
{
return new Vector3(transform.position.x - dist, transform.position.y);
}
private Vector3 LerpToLeft()
{
return new Vector3(transform.position.x + dist , transform.position.y);
}
void Update()
{
_currentLerpTime += Time.deltaTime;
_percentage = _currentLerpTime / lerpTime;
var localPositionX = tranform.position.x;
var mustGoRight = localPositionX <= 0 && lerpDirection == LerpDirection.Right;
var mustGoLeft = localPositionX >= dist && lerpDirection == LerpDirection.Left;
if (mustGoLeft || mustGoRight)
{
direction = direction.Invert(); // invert direction
Reset();
}
tranform.position = new Vector3(LerpX, tranform.position.y)
}
}
The Background script is applied to the Background GameObject.
_lerpDirectionActions[lerpDirection].Invoke()
This code above will invoke the right function for lerping on left or on right.
Illustration:
The translation change his direction but not when the canvas is on the border on the image.
The value you are searching for would be
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
Your script looks quite complicated to be honest.
Why not simply put it all into one single Coroutine using Mathf.PingPong which does exactly what you are currently controlling with your direction flags and actions
PingPongs the value t, so that it is never larger than length and never smaller than 0.
The returned value will move back and forth between 0 and length.
public Canvas canvas;
public Image image;
// Time in seconds to finish the movement from one extreme to the other
public float duration = 1;
private void Start()
{
StartCoroutine (LerpForthAndBack());
}
private IEnumerator LerpForthAndBack()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
var maxPosition = Vector3.right * difference;
var minPosition = Vector3.left * difference;
// Hugh? :O
// -> This is actually totally fine in a Coroutine
// as long as you yield somewhere within it
while(true)
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
// "Pause" the routine, render the frame and
// and continue from here in the next frame
yield return null;
}
}
ofcourse you could also do the same still in Update
private Vector3 minPosition;
private Vector3 maxPosition;
private void Start()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
maxPosition = Vector3.right * difference;
minPosition = Vector3.left * difference;
}
private void Update()
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
}

Coroutine still running even if game is paused and time scale set to 0

As the title pointed out, for some reason when my game is paused my co-routine still runs. I even went as far as to put the time scale condition in a while condition so that the while doesnt run if it paused but to no avail. I've added my code in it's entirety and hope that someone will be able to assist.
using UnityEngine;
using System.Collections;
using Chronos;
public class ObjectSpawn : BaseBehaviour //MonoBehaviour
{
public float minTime = 3f;
public float maxTime = 9f;
public float minX = -65.5f;
public float maxX = -5.5f;
public float topY = -5.5f;
public float z = 0.0f;
public int count = 50;
public GameObject prefab;
public bool doSpawn = true;
public float fallGrav =1.0f;
int first = 1;
void Start()
{
Clock clock = Timekeeper.instance.Clock("MovingOneWayPlatforms");
StartCoroutine(Spawner());
}
IEnumerator Spawner()
{
while (first == 1) {
yield return time.WaitForSeconds(8.0f);
first = 0;
}
while (doSpawn && count > 0 /*&& time.timeScale != 0 */)
{
Renderer renderer = GetComponent<Renderer>();
float min = renderer.bounds.min.x;
float max = renderer.bounds.max.x;
Vector3 v12 = new Vector3(Random.Range(minX, maxX), this.gameObject.transform.position.y, 0f);
prefab.GetComponent<Rigidbody2D>().gravityScale = fallGrav;
prefab = Instantiate(prefab, v12, Random.rotation);
count--;
// yield return new WaitForSeconds(Random.Range(minTime, maxTime));
yield return time.WaitForSeconds(Random.Range(minTime, maxTime));
Destroy(prefab, 6);
}
}
}
Try to uncomment your 2nd while statement, I think that is your problem.
I am new and still not used to Chronos.
Maybe I'm wrong but my guess is this line.
Destroy(prefab, 6);
In my understanding, Destroy's delay should not affected by chronos.
You better use new Coroutine to destroy it.
like this
StartCoroutine(DestroyRoutine(prefab))
IEnumurator DestroyRoutine(GameObject gameobject)
{
yield return time.WaitForSeconds(6);
Destroy(gameObject)
}

Lifting platforms is not working as it should

I would like to make some lifting platforms in my game, so if the platform went down, the characters can't go over it. I have written a script for it, but for some reason the "lifting up" is not working as intended. It won't go back to its starting place, but it will go a bit below. And for some reason it won't go smoothly to the place where it should, just "teleport" there and done. I thougt multiplying Time.deltaTime with a const will help, but it is the same.
Here is my code, any help would be appreciated:
public class LiftingPlatform : MonoBehaviour {
private Transform lift;
private bool isCanBeLifted;
private float timeToLift;
public float timeNeededToLift = 5f;
private Vector3 startPos;
private Vector3 downPos;
private Vector3 shouldPos;
private bool isDown;
public GameObject[] collidingWalls;
// Use this for initialization
void Start () {
lift = transform;
isCanBeLifted = true;
timeToLift = 0f;
isDown = false;
startPos = transform.position;
downPos = new Vector3(startPos.x, startPos.y - 5f, startPos.z);
}
// Update is called once per frame
void Update () {
timeToLift += Time.deltaTime;
if (timeToLift >= timeNeededToLift) {
if (isCanBeLifted) {
if (isDown) {
shouldPos = Vector3.Lerp(startPos, downPos, Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = true;
}
else if (!isDown) {
shouldPos = Vector3.Lerp(downPos, new Vector3(startPos.x, startPos.y, startPos.z), Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = false;
}
}
timeToLift = 0;
}
if (!isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(true);
}
}
else if (isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = false;
}
}
void OnTriggerExit(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = true;
}
}
}
These lifting platforms are a child of another Platforms object.
It doesn't look like you are updating the object's position every frame. You are only checking if the total time passed is greater than the time needed to lift, and then updating the position to a value that is dependent on the delta time (using the Vector3.Lerp function).
What I would do is in the update step, if timeToLift is greater then timeNeededToLift, subtract the latter from the former and invert the value of isDown. Then, in your Vector3.Lerp, make the third argument (timeToLift / timeNeededToLift) instead of (Time.deltaTime * 10). Can you try that and see if it works?
The third argument for Vector3.Lerp is the "blending factor" between the two vectors, 0 is the first vector, 1 is the second, and 0.5 is in between. If the total time is greater than the time needed to lift, but the delta time is not greater than 1, it will get the position of the platform using a blending factor of less than 1, resulting in a platform that didn't move fully.