Short and simple, I just don’t get why this works the way it does:
using UnityEngine;
using System.Collections.Generic;
// this struct holds data for a single 'shake' source
public struct Shake
{
public float AmountX;
public float AmountY;
public float Duration;
private float percentageComplete;
public float PercentageComplete { get { return percentageComplete; } }
public Shake(float _amountX, float _amountY, float _duration)
{
AmountX = _amountX;
AmountY = _amountY;
Duration = _duration;
percentageComplete = 0f;
Debug.Log("wtf");
}
public void Update()
{
Debug.Log("a " + percentageComplete);
percentageComplete += Time.deltaTime;
Debug.Log("b " + percentageComplete);
AmountX = Mathf.Lerp(AmountX, 0f, percentageComplete);
AmountY = Mathf.Lerp(AmountY, 0f, percentageComplete);
}
}
// This class uses that data to implement the shakes on a game camera
public class CameraShake : MonoBehaviour
{
// components
private Transform myTransform;
// vars
private List<Shake> shakes;
private float totalShakeX;
private float totalShakeY;
private void Awake()
{
myTransform = transform;
shakes = new List<Shake>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
AddShake(new Shake(.1f, .1f, 1f));
}
totalShakeX = 0f;
totalShakeY = 0f;
for (int i = 0; i < shakes.Count; i++)
{
shakes[i].Update();
Debug.Log("c " + shakes[i].PercentageComplete);
totalShakeX += shakes[i].AmountX;
totalShakeY += shakes[i].AmountY;
if (shakes[i].PercentageComplete >= 1f)
{
shakes.RemoveAt(i);
i--;
}
}
myTransform.position += new Vector3(totalShakeX, 0f, totalShakeY);
}
public void AddShake(Shake shake)
{
shakes.Add(shake);
}
}
When I run this code (using Unity3D game engine) the struct Shake does not log the correct values.
Percentage complete logs these values (every frame):
a 0
b 0.001 (or hereabouts, its Time.deltaTime, the maount of time it took to process the previous frame)
c 0
What gives? Why is percentageComplete resetting to 0 every frame?
If I change Shake into a class instead of a struct it works fine.
C# does not allow a property access to combine a read and a write, which means an access to a property of a structure type will be considered a read-only access. Unfortunately, C# provides no means of indicating whether a struct method will modify
this. Consequently, an attempt to invoke a struct method which modifiesthison a read-only struct will compile fine, but it won’t actually work.To avoid this problem, if your struct needs to define a method like
Updatewhich modifies a struct in-place, you should change it to be something like:The compiler would then be able to squawk, correctly, if you were to attempt something like:
Shake.Update(ref shakes[i]);whenshakesis aList<Shake>, but would allow a construct likevar temp = shakes[i]; Shake.Update(ref temp); shakes[i] = temp;, or would allow the first construct (and it would work), ifshakeshad been aShake[]rather than aList<Shake>.Value types have different semantics from reference types. Sometimes these different semantics can be useful. The semantic differences are more apparent with value types that allow convenient piece-wise mutation than with those that do not, and some people who believe all types should behave identically denounce them as evil for that reason, but I disagree with such thinking. A struct which exposes all its fields, and which has no methods that mutate
this, will behave like every other struct meeting that description (save for the precise names and types of its fields). Such behavior is unlike that of any reference type, but that’s what makes it useful. By contrast, while structs with private fields may be coded so as to “pretend” to be immutable, and to eliminate the semantic advantages of value types, mutable storage locations of non-trivial struct types will always hold mutable struct instances, regardless of whether the types pretend to be immutable. IMHO, it’s better for value types to advertise themselves as what they are, than to pretend to behave as something they’re not.