Monday 3 September 2012

Coordinates in mixed 3D/2D space

WizardBlast seems to be a legit 3D space game when you look at it, but when you start thinking about the in-game logic it turns out to be 2D limited. 

In fact WizardBlast is a 3D rendered 2D game, because everything happens on 2D platform (maze). It is the representation of the maze that gets to full 3D.

In such mixed environment some of the variables regarding position of an in-game entity need to be 2D (x,y), some need to be 3D (x,y,z), and some need both. Also the 3D y component doesn't necessarily need to be equal to the 2D y component.

When I started coding WizardBlast I used Vector3 and Vector2 to store coordinates, and then worked on one of them when needed. It all made the code quite messy and difficult to read/modify/refactor.

That is why I created a new class to store complex position vectors that work in an unificated way. At any time I am able to get both 2D and 3D components and any modification made to one them affects the other.

I think it's a piece of code worth sharing. Not because it's difficult to write, but because it's not always obvious that you need such code :-)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace WizardBlast.Entities
{
 public class Position
 {
  public static float ZeroFieldXLocation = -2.5f;
  public static float ZeroFieldZLocation = -2.5f;
  public static float Rift = .55f;
  public static float InvRift = 1 / Rift;

  private float z;
  private float x;
  private int ix;
  private int iz;

  public float Z { get { return z; } set { z = value; iz = FieldZIndex(value); } }
  public float Y { get; set; }
  public float X { get { return x; } set { x = value; ix = FieldXIndex(value); } }

  public int IX { get { return ix; } set { ix = value; x = FieldXPosition(value); } }
  public int IZ { get { return iz; } set { iz = value; z = FieldZPosition(value); } }

  public Position(Vector3 position)
  {
   Z = position.Z;
   Y = position.Y;
   X = position.X;
  }

  public Position(Position position)
  {
   IZ = position.IZ;
   Y = position.Y;
   IX = position.IX;
  }

  public Position(int x, int z)
  {
   IZ = z;
   IX = x;
   Y = 0f;
  }

  public Position(float x, float z)
  {
   X = x;
   Z = z;
   Y = 0f;
  }

  public static Position operator +(Position p1, Position p2)
  {
   var result = new Position(p1);
   result.X = p1.X + p2.X;
   result.Y = p1.Y + p2.Y;
   result.Z = p1.Z + p2.Z;
   return result;
  }

  public static bool operator ==(Position p1, Position p2)
  {
   return p1.ix == p2.ix && p1.iz == p2.iz;
  }

  public static bool operator !=(Position p1, Position p2)
  {
   return p1.ix != p2.ix || p1.iz != p2.iz;
  }

  //Always override GetHashCode(),Equals when overloading ==
  public override bool Equals(object o)
  {
   return this == (Position)o;
  }
  public override int GetHashCode()
  {
   return ix ^ iz;
  }

  public static Position operator +(Position p1, Vector3 p2)
  {
   var result = new Position(p1);
   result.X = p1.X + p2.X;
   result.Y = p1.Y + p2.Y;
   result.Z = p1.Z + p2.Z;
   return result;
  }

  public static Position operator +(Position p1, Vector2 p2)
  {
   var result = new Position(p1);
   result.X = p1.X + p2.X;
   result.Z = p1.Z + p2.Y;
   return result;
  }

  public static Position operator -(Position p1, Position p2)
  {
   var result = new Position(p1);
   result.X = p1.X - p2.X;
   result.Y = p1.Y - p2.Y;
   result.Z = p1.Z - p2.Z;
   return result;
  }

  public static Position operator *(Position p1, float m)
  {
   var result = new Position(p1);
   result.X = p1.X * m;
   result.Y = p1.Y * m;
   result.Z = p1.Z * m;
   return result;
  }

  public static implicit operator Vector3(Position pos)
  {
   return new Vector3(pos.X, pos.Y, pos.Z);
  }

  public static implicit operator Vector2(Position pos)
  {
   return new Vector2(pos.X, pos.Z);
  }

  public static explicit operator Position(Vector3 vec)
  {
   return new Position(vec);
  }

  public static explicit operator Position(Vector2 vec)
  {
   return new Position((int)vec.X, (int)vec.Y);
  }

  private float FieldXPosition(int index)
  {
   return ZeroFieldXLocation + index * Rift;
  }

  private float FieldZPosition(int index)
  {
   return ZeroFieldZLocation + index * Rift;
  }

  private int FieldXIndex(float position)
  {
   return Convert.ToInt32((position - ZeroFieldXLocation) * InvRift);
  }

  private int FieldZIndex(float position)
  {
   return Convert.ToInt32((position - ZeroFieldZLocation) * InvRift);
  }
 }
}


2 comments:

  1. You might want to make it a struct rather than a class to avoid potentially loads of heap allocations.

    ReplyDelete