screen_36

For our Global Game Jam entry Spider vs Dragon, we wanted to use a funky grid. We settled on the inside of a torus, and to make things a bit more interesting, we distorted the torus by adjusting the small radius by a sine wave.

The basic idea is to implement a custom map that maps rect points to the torus, using the pretty straightforward equations on Wikipedia. We also augmented the map with extra methods that give the appropriate up vectors, and the central circle. To generate the grid, we put a plane at each grid point using positions obtained from the map, and orient the plane using a look-at vector (constructed from the central circle point and current point) and the up vector. (This is of course not very efficient – in ‘n non-prototype you will generate a mesh instead. The same information will be used, though).

The camera, as it races through the grid, uses the same calculations to position and orient itself.

normal_torus2

Grid with no distortion.

deformed_torus

Torus with extensive distortion.

Map

using Gamelogic.Grids;
using UnityEngine;

public class TorusMap : IMap3D
{
   private const float DeformationAmount = 16;
   private const float DeformationsPerCycle = 5;
   private const float FixedBigRadius = 100;
   private const float FixedSmallRadius = 30;

   private readonly float bigSectorCount = 20;
   private readonly float smallSectorCount = 20;

   public TorusMap(int smallSectorCount, int bigSectorCount)
   {
      this.smallSectorCount = smallSectorCount;
      this.bigSectorCount = bigSectorCount;
   }

   public float SmallRadius(RectPoint point)
   {
      var offset = ScaleOffset(point);

      return FixedSmallRadius + DeformationAmount * offset;
   }

   public float ScaleOffset(RectPoint point)
   {
      return Mathf.Sin(DeformationsPerCycle * 2 * Mathf.PI * point.Y / bigSectorCount);
   }

   public Vector3 Up(RectPoint point)
   {
      return (CentralLine(point) - this[point]).normalized;
   }

   public Vector3 this[RectPoint point]
   {
      get
      {
         float smallAngle = Mathf.PI * 2 * point.X / smallSectorCount;
         float bigAngle = Mathf.PI * 2 * point.Y / bigSectorCount;

         float x = (FixedBigRadius + SmallRadius(point) * Mathf.Cos(smallAngle)) * Mathf.Cos(bigAngle);
         float z = (FixedBigRadius + SmallRadius(point) * Mathf.Cos(smallAngle)) * Mathf.Sin(bigAngle);
         float y = SmallRadius(point) * Mathf.Sin(smallAngle);

         return new Vector3(x, y, z);
      }
   }

   public float GetCameraRollDegrees(RectPoint point)
   {
      return Mathf.PI * 2 * point.X / smallSectorCount;
   }

   public Vector3 CentralLine(RectPoint point)
   {
      float bigAngle = Mathf.PI * 2 * point.Y / bigSectorCount;

      float x = FixedBigRadius * Mathf.Cos(bigAngle);
      float z = FixedBigRadius * Mathf.Sin(bigAngle);

      return new Vector3(x, 0, z);
   }

   public Vector3 CentralLine(Vector2 point)
   {
      float bigAngle = Mathf.PI * 2 * point.y / bigSectorCount;

      float x = FixedBigRadius * Mathf.Cos(bigAngle);
      float z = FixedBigRadius * Mathf.Sin(bigAngle);

      return new Vector3(x, 0, z);
   }

   public RectPoint this[Vector3 point]
   {
      get { throw new System.NotImplementedException(); }
   }

   public IMap To2D()
   {
      throw new System.NotImplementedException();
   }
}

Main

public void BuildGrid()
{
   grid = RectGrid.WrappedParallelogram(SmallSectorCount, bigSectorCount);
   obstacles = grid.CloneStructure();
   map = new TorusMap(SmallSectorCount, bigSectorCount);

   foreach (var point in grid)
   {
      var tile = Instantiate(tilePrefab);

      tile.transform.parent = gridRoot.transform;
      tile.transform.localPosition = map[point];

      var up = map.CentralLine(point) - map[point];

      tile.transform.LookAt(map[point + RectPoint.North * 10], map.Up(point));
      tile.transform.localScale = 1.5f * Vector3.one*(2 + 0.5f*map.ScaleOffset(point));
      tile.SetOverlayOn(false);

      grid[point] = tile;
   }
}