- Home /
space wrap 3D
Howdy,
I have written this script to wrap objects around in space à la Asteroids, but in 3D.
It works fine (don't have to worry about them wrapping to the exactly correct position, or having to have a duplicate of the object (one wrapping out, one wrapping in) etc, since it's in 3D, and the boundary is invisible in world space).
However, my code definitely doesn't follow the DRY principle. I was wondering if anyone might help me with a function which would replace the repetitions.
it would really just need to take in two parameters. One for in which axis the boundary is being exceeded. And one for whether the boundary is being exceeded in the positive or the negative.
(The Portal() function just marks where the asteroid goes through the 'boundary' with a portal animation (on the gameObject 'wrapWarp'))
public class PositionWrap : MonoBehaviour
{
Vector3 asteroidSize;
float asteroidDiameter;
public float boundaryWidth = 100;
public GameObject wrapWarp;
public GameObject wrapWarpClone;
void Start()
{
asteroidDiameter = transform.localScale.x;
asteroidSize = transform.localScale;
}
void Update()
{
Vector3 asteroidPos = transform.position;
if (transform.position.x < -boundaryWidth)
{
Portal(Vector3.right);
asteroidPos.x += boundaryWidth * 2 - asteroidDiameter;
}
if (transform.position.x > boundaryWidth)
{
Portal(Vector3.left);
asteroidPos.x -= boundaryWidth * 2 - asteroidDiameter;
}
if (transform.position.y < -boundaryWidth)
{
Portal(Vector3.up);
asteroidPos.y += boundaryWidth * 2 - asteroidDiameter;
}
if (transform.position.y > boundaryWidth)
{
Portal(Vector3.down);
asteroidPos.y -= boundaryWidth * 2 - asteroidDiameter;
}
if (transform.position.z < -boundaryWidth)
{
Portal(Vector3.forward);
asteroidPos.z += boundaryWidth * 2 - asteroidDiameter;
}
if (transform.position.z > boundaryWidth)
{
Portal(Vector3.back);
asteroidPos.z -= boundaryWidth * 2 - asteroidDiameter;
}
transform.position = new Vector3(asteroidPos.x, asteroidPos.y, asteroidPos.z);
}
void Portal (Vector3 facing)
{
wrapWarpClone = (GameObject) Instantiate(wrapWarp, transform.position, Quaternion.LookRotation(facing));
wrapWarpClone.transform.localScale = asteroidSize;
}
}
Answer by Bunny83 · Oct 03, 2015 at 01:28 PM
I don't see any repetition here except that you should replace "transform.position.XXXX" with "asteroidPos.XXXX". You also might want to put an "else if" for the opposite side since you can't wrap left and right at the same time.
You also could add another member variable to combine "boundaryWidth * 2 - asteroidDiameter" into one value in Start.
You could also use your "direction" vector for the translation
public class PositionWrap : MonoBehaviour
{
Vector3 asteroidSize;
float asteroidDiameter;
public float boundaryWidth = 100;
public GameObject wrapWarp;
public GameObject wrapWarpClone;
private float wrapDistance;
void Start()
{
asteroidDiameter = transform.localScale.x;
asteroidSize = transform.localScale;
wrapDistance = boundaryWidth * 2f - asteroidDiameter;
}
void Update()
{
Vector3 asteroidPos = transform.position;
if (asteroidPos.x < -boundaryWidth)
{
Portal(Vector3.right);
}
else if (asteroidPos.x > boundaryWidth)
{
Portal(Vector3.left);
}
if (asteroidPos.y < -boundaryWidth)
{
Portal(Vector3.up);
}
else if (asteroidPos.y > boundaryWidth)
{
Portal(Vector3.down);
}
if (asteroidPos.z < -boundaryWidth)
{
Portal(Vector3.forward);
}
else if (asteroidPos.z > boundaryWidth)
{
Portal(Vector3.back);
}
}
void Portal(Vector3 facing)
{
wrapWarpClone = (GameObject) Instantiate(wrapWarp, transform.position, Quaternion.LookRotation(facing));
wrapWarpClone.transform.localScale = asteroidSize;
transform.position += facing * wrapDistance;
// maybe create another "portal" on the "exit"?
}
I would probably rename the "Portal" method to "Wrap" or something like that.
ps: I think i will never understand why people do things like:
transform.position = new Vector3(asteroidPos.x, asteroidPos.y, asteroidPos.z);
asteroidPos is already a Vector3. A Vector3 is a value type so each time you pass it around you already work with a copy.
While it is possible to "combine" those 6 cases into a for loop i really really don't recommend something like that as it's less efficient and cryptic.
void Update()
{
Vector3 asteroidPos = transform.position;
for(int i = 0; i < 6; i++)
{
Vector3 dir = Vector3.zero;
float sign = (i<3)?1f:-1f
dir[i%3] = -sign;
if (sign*asteroidPos[i%3] > boundaryWidth)
{
Portal(dir);
}
}
}
This should be seen as "anti sample" because even the code is way shorter it's way less efficient. Also this only works when your game area is a cube. For a box (with different dimensions) it's getting more complicated.
The indexer of Vector3 might get handy in some cases, but it's less efficient compared to accessing the components directly. That indexer just has a switch case and does access each component based on the given index. That's unnecessary overhead. But performance isn't the main issue. Code should be easy to understand. This hacky for loop solution is hard to read and hard to understand.
You really should stick with each case seperately.
edit
Had an error in my logic ^^ You can't use $$anonymous$$athf.Abs. I fixed it (i guess ^^).
Thanks for your help.
I guess because the way I wrote the script, when I first replaced "transform.position.XXXX with asteroidPos.XXXX in the if condition it didn't work.. I thought it needed a Vector3 for where it went "in" to the "boundary" and another separate one for where it came "out".
I thought I might be able to use the "direction" vector for the portal in the translation, and put the translation into the function.. thanks heaps.
I have, since posting the question, created another portal on the "exit" (basically a reverse animation of the portal on the "entry")
Your answer
Follow this Question
Related Questions
Distribute terrain in zones 3 Answers
Multiple Cars not working 1 Answer
Optional parameter, editor raises error "Unexpected symbol `=' " 0 Answers