Let me correct my first answer: the camera in ROXX is slightly tilted back so the viewplane on y == 0 is a trapezoid, not rectangle. But the same behavior is going on. Here is the PlaneOfActionManager and the functions to wrap.
STATE.WorldSizePercentageAboveViewport is equal to 0.10f, so 10% on either side “overscan.”
The entire game takes place “flat,” in Y == 0, so Z is going “up screen” and X is left and right.
I’ll dig up the starfield code in a second. Next post.
/*
The following license supersedes all notices in the source code.
*/
/*
Copyright (c) 2015 Kurt Dekker/PLBM Games All rights reserved.
http://www.twitter.com/kurtdekker
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Neither the name of the Kurt Dekker/PLBM Games nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using UnityEngine;
using System.Collections;
public class PlaneOfActionManager : MonoBehaviour
{
// right AT the corners of the screen
static Vector3 v3UL, v3UR, v3LL, v3LR;
// trapezoidal limits of the world
public static Vector3 o3UL, o3UR, o3LL, o3LR;
GameObject goUL, goUR, goLL, goLR;
Plane p;
public static Vector3 GenerateRandomEdgePosition()
{
Vector3 pos;
pos = o3UL;
switch( Random.Range(0,4))
{
default :
case 0 :
// across top edge of world
pos = Vector3.Lerp(
PlaneOfActionManager.o3UL,
PlaneOfActionManager.o3UR,
Random.Range( 0.0f, 1.0f));
break;
case 1 :
// across right edge of world
pos = Vector3.Lerp(
PlaneOfActionManager.o3UR,
PlaneOfActionManager.o3LR,
Random.Range( 0.0f, 1.0f));
break;
case 2:
// across bottom edge of world
pos = Vector3.Lerp(
PlaneOfActionManager.o3LL,
PlaneOfActionManager.o3LR,
Random.Range( 0.0f, 1.0f));
break;
case 3 :
// across right edge of world
pos = Vector3.Lerp(
PlaneOfActionManager.o3UL,
PlaneOfActionManager.o3LL,
Random.Range( 0.0f, 1.0f));
break;
}
return pos;
}
public static Vector3 v3Center()
{
return Vector3.Lerp(
Vector3.Lerp( v3UL, v3UR, 0.5f),
Vector3.Lerp( v3LL, v3LR, 0.5f),
0.5f
);
}
void CastToPlane( Vector3 viewport, out Vector3 hitpoint)
{
float distance;
// cast to the actual mathematical y = 0 plane
p.Raycast( Camera.main.ViewportPointToRay(
new Vector3( 0.5f, viewport.y, 0)),
out distance);
// distance now must be adjusted down by our FOV angle,
// since the distance to the plane at edges is different (fan of fov)
// this is pure cosine in the Y direction, relying on the p.Raycast
// above being (0,viewport.y,0)... it is not right for X != 0 (corners)
distance *= Mathf.Cos( (Mathf.PI * 0.5f * Camera.main.fieldOfView) / 180.0f);
// Now distance is axial to the camera's direction, let's project!
hitpoint = Camera.main.ViewportToWorldPoint(
new Vector3( viewport.x, viewport.y, distance));
// fuck it, I'm hammering it in... close enough for gummint work!
hitpoint.y = 0;
}
void Start ()
{
p = new Plane( Vector3.up, Vector3.zero);
LateUpdate();
}
// size of trapezoidal play area
static float w1; // world width at top edge of screen
static float w2; // world width at bottom edge of screen
static float h; // world height screen top to bottom
void LateUpdate()
{
if (goUL == null) { goUL = new GameObject(); goUL.name = "goUL"; }
if (goUR == null) { goUR = new GameObject(); goUR.name = "goUR"; }
if (goLL == null) { goLL = new GameObject(); goLL.name = "goLL"; }
if (goLR == null) { goLR = new GameObject(); goLR.name = "goLR"; }
CastToPlane( new Vector3( 0, 1, 0), out v3UL);
goUL.transform.position = v3UL;
CastToPlane( new Vector3( 1, 1, 0), out v3UR);
goUR.transform.position = v3UR;
CastToPlane( new Vector3( 0, 0, 0), out v3LL);
goLL.transform.position = v3LL;
CastToPlane( new Vector3( 1, 0, 0), out v3LR);
goLR.transform.position = v3LR;
o3UL = v3UL;
o3UR = v3UR;
o3LL = v3LL;
o3LR = v3LR;
w1 = Mathf.Abs ( o3UL.x - o3UR.x);
w2 = Mathf.Abs ( o3LL.x - o3LR.x);
h = Mathf.Abs ( o3UL.z - o3LL.z);
o3UL.x -= w1 * STATE.WorldSizePercentageAboveViewport;
o3UL.z += h * STATE.WorldSizePercentageAboveViewport;
o3UR.x += w1 * STATE.WorldSizePercentageAboveViewport;
o3UR.z += h * STATE.WorldSizePercentageAboveViewport;
o3LL.x -= w2 * STATE.WorldSizePercentageAboveViewport;
o3LL.z -= h * STATE.WorldSizePercentageAboveViewport;
o3LR.x += w2 * STATE.WorldSizePercentageAboveViewport;
o3LR.z -= h * STATE.WorldSizePercentageAboveViewport;
w1 = Mathf.Abs ( o3UL.x - o3UR.x);
w2 = Mathf.Abs ( o3LL.x - o3LR.x);
h = Mathf.Abs ( o3UL.z - o3LL.z);
goUL.transform.position = o3UL;
goUR.transform.position = o3UR;
goLL.transform.position = o3LL;
goLR.transform.position = o3LR;
}
public static bool TrapezoidalWrapAround( GameObject go)
{
bool weWrapped;
weWrapped = false;
if (go.transform.position.y != 0)
{
go.transform.position += Vector3.down * go.transform.position.y;
}
if (go.transform.position.z > o3UL.z)
{
go.transform.position += Vector3.back * h;
weWrapped = true;
}
if (go.transform.position.z < o3LL.z)
{
go.transform.position += Vector3.forward * h;
weWrapped = true;
}
// where we are vertically within our available window (normalized)
float myh = (go.transform.position.z - o3LL.z) / h;
// how wide the window is at this point
float myw = w1 * myh + w2 * (1.0f - myh);
if (go.transform.position.x < o3UL.x + (o3LL.x - o3UL.x) *
(go.transform.position.z - o3UL.z) / (o3LL.z - o3UL.z))
{
go.transform.position += Vector3.right * myw;
weWrapped = true;
}
if (go.transform.position.x > o3UR.x + (o3LR.x - o3UR.x) *
(go.transform.position.z - o3UR.z) / (o3LR.z - o3UR.z))
{
go.transform.position += Vector3.left * myw;
weWrapped = true;
}
return weWrapped;
}
public static bool CheckIsVisible( GameObject go, float extraDistance)
{
if (go.transform.position.z > v3UL.z + extraDistance)
{
return false;
}
if (go.transform.position.z < v3LL.z - extraDistance)
{
return false;
}
if (go.transform.position.x < v3UL.x + (v3LL.x - v3UL.x) *
(go.transform.position.z - v3UL.z) / (v3LL.z - v3UL.z) - extraDistance)
{
return false;
}
if (go.transform.position.x > v3UR.x + (v3LR.x - v3UR.x) *
(go.transform.position.z - v3UR.z) / (v3LR.z - v3UR.z) + extraDistance)
{
return false;
}
return true;
}
}
The reason for the CheckIsVisible() function is that things like spaceships and spaceships shots do NOT wrap around because that would be kinda deadly.