Coding Traps

1. Changing a temporary record value

This code is a mistake:

// DO NOT USE THIS, IT WILL NOT COMPILE
Scene.Translation.Z := Scene.Translation.Z + 0.1;

As Scene.Translation is an advanced record (TVector3), using Scene.Translation may return just a temporary copy of it (just as if you would call a method Scene.GetTranslation). So the code would change the Z value only in a temporary copy of the record value, which is pointless — and therefore the compiler doesn’t allow such assignment.

Warning
In engine versions before 2022-02-10 (7.0-alpha.1, and 7.0-alpha.snapshot before that date), the above code would even compile, despite being wrong. We have improved vector definition on the engine side to prevent the compilation of the incorrect code.

The solution is to only modify the record fields (like Z of a TVector3) when you know that it is a simple field, or variable in your code. To apply this solution above, you should make a temporary variable and change it:

var
  V: TVector3;
begin
  V := Scene.Translation;
  V.Z := V.Z + 0.1;
  Scene.Translation := V;
end;

You can also rework it to assign whole vector, like this:

Scene.Translation := Scene.Translation + Vector3(0, 0, 0.1);

Another possible solution is to use TCastleTransform.TranslationPersistent. Most CGE vector properties in components have a counterpart XxxPersistent that allows to serialize and display vectors in editor, and it also has properties to get/set vector components that avoid the trap outlined here. So this is valid:

Scene.TranslationPersistent.Z := Scene.TranslationPersistent.Z + 0.1;

We don’t advise using TranslationPersistent though — because it means you have an additional way of dealing with vectors, using TCastleVector3Persistent. It is simpler to use TVector3 for vectors everywhere.

Similar situation happens when using any methods that change the record. For example this is a mistake too:

// DO NOT USE THIS, IT IS DEPRECATED
Scene.Translation.Init(1, 2, 3);

While it may work now, it may stop working in the future — if Scene.Translation will be a property using a "getter" function, then this code could set a temporary value, which will have no effect on the underlying translation vector.

Instead assign vectors like this:

Scene.Translation := Vector3(1, 2, 3);

The problem is inherent to using properties with records. C# has this problem too with its structs, in Unity too. We cannot easily avoid doing it in CGE — we need records (for things like vectors, records are both more efficient and more comfortable to pass around than classes) and we need properties of them (because setters often have to do some useful things).


To improve this documentation just edit this page and create a pull request to cge-www repository.