0%

构建Vector2类型


在大多数游戏引擎中,都会实现Vector类型,包括但不限于Vector2、Vector3等等。
出于好奇,加之想练习值类型的应用,我打算自己实现Vector2类型。


C#的值类型和引用类型

在C#里,数据类型有两种:值类型和引用类型。
具体来讲,值类型的数据是指储存在栈里的。每一个值类型直接包含值,修改一个已经有值的值类型,会创建一个新的值类型。
引用类型的数据是储存在堆里的。每一个已初始化的引用类型都包含一个指向数据的地址,相当于是C语言里的指针。若是有两个引用类型引用同一个对象,修改其中一个数据,都会使另一个数据发生变化。
常见的值类型有:

  • 整形类型;
  • 浮点类型;
  • 布尔类型

常见的引用类型有:

  • 字符串类型;
  • class

Vector2类型的构建

可以把一个二维向量看作是一个值,那么我想要实现的Vector2类型,便是类似于整形类型的值类型。

基础

依据高中数学,我们可以很快写出如下代码:

1
2
3
4
5
6
7
8
9
10
11
public struct Vector2
{
public Vector2(float x, float y)
{
X = x;
Y = y;
}

public float X { get; }
public float Y { get; }
}

通过一个构造函数来初始化坐标。

重写ToString()

现在的Vector2类型不过只是储存了数据,还无法输出到控制台,或者说无法变为一个有格式的字符串。
我们对一个二维向量,一般用$(x, y)$来表示坐标。
可以通过重写ToString()方法来实现其格式输出:

1
2
3
4
5
6
public struct Vector2
{
......

public override string ToString() => $"({X},{Y})";
}

这样,便可以调用Console.WriteLine()打印一个Vector2类型的变量。

基本属性

向量有几个基本属性:

  • 模;
  • 单位向量

为类型添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
public struct Vector2
{
......

public float Length { get => (float)Math.Sqrt(X * X + Y * Y); }
public static Vector2 Up { get => new Vector2(0, 1f); }
public static Vector2 Down { get => new Vector2(0, -1f); }
public static Vector2 Left { get => new Vector2(-1f, 0); }
public static Vector2 Right { get => new Vector2(1f, 0); }
public static Vector2 Zero { get => new Vector2(0, 0); }
}

这些属性在某些情况下很有用。

比较

对于向量,我们一般只去判断是否相等。这里相等的条件不是数学意义上的相等,而是指坐标的严格相等。
可以考虑重载Equals()方法,不过在重载之前,可以考虑用元组来自建一个比较方法。
添加如下代码:

1
2
3
4
5
6
public struct Vector2
{
......

public bool Equals(Vector2 vector2) => (X, Y).Equals((vector2.X, vector2.Y));
}

这里使用元组的Equals()方法来比较,极大简化了代码量。
但在实际中,我们更常用==!=来判断两者相等与否,这就需要重载运算符来实现。

1
2
3
4
5
6
7
public struct Vector2
{
......

public static bool operator ==(Vector2 a, Vector2 b) => a.Equals(b);
public static bool operator !=(Vector2 a, Vector2 b) => !(a == b);
}

利用我们刚刚写好的Equals()可以方便的写出重载运算符。
这时编译器可能会提示,要求重载Equals()方法,所以我们不妨将其加上。

1
2
3
4
5
6
7
public struct Vector2
{
......

public override bool Equals(object obj) => Equals((Vector2)obj);
public override int GetHashCode() => (X, Y).GetHashCode();
}

要重载Equals()方法,必须同时重载GetHashCode()方法。用元组可以轻松地重载后者。

计算

向量可以实现一些基本运算。可以通过重载+``-``*``/来实现。
添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
public struct Vector2
{
......

public static Vector2 operator +(Vector2 a, Vector2 b) => new Vector2(a.X + b.X, a.Y + b.Y);
public static Vector2 operator -(Vector2 a, Vector2 b) => new Vector2(a.X - b.X, a.Y - b.Y);
public static Vector2 operator *(Vector2 a, Vector2 b) => new Vector2(a.X * b.X, a.Y * b.Y);
public static Vector2 operator *(Vector2 a, float b) => new Vector2(a.X * b, a.Y * b);
public static Vector2 operator /(Vector2 a, Vector2 b) => new Vector2(a.X / b.X, a.Y / b.Y);
public static Vector2 operator /(Vector2 a, float b) => new Vector2(a.X / b, a.Y / b);
}

这样便实现了向量的基本运算。

归一化

获取向量所代表的方向,需要将其端点移至原点,并将其模变作$1$。
为了实现这个功能,先将两个坐标用坐标中较小的一个的绝对值做除法,然后除以其模。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public struct Vector2
{
......

public static Vector2 Normalize(Vector2 vector)
{
Vector2 v = vector;
v /= Math.Max(Math.Abs(v.X), Math.Abs(v.Y));
v /= v.Length;
return v;
}
}

整体代码

全部代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public struct Vector2
{
public Vector2(float x, float y)
{
X = x;
Y = y;
}

public float X { get; }
public float Y { get; }
public float Length { get => (float)Math.Sqrt(X * X + Y * Y); }
public static Vector2 Up { get => new Vector2(0, 1f); }
public static Vector2 Down { get => new Vector2(0, -1f); }
public static Vector2 Left { get => new Vector2(-1f, 0); }
public static Vector2 Right { get => new Vector2(1f, 0); }
public static Vector2 Zero { get => new Vector2(0, 0); }

public override bool Equals(object obj) => Equals((Vector2)obj);
public bool Equals(Vector2 vector2) => (X, Y).Equals((vector2.X, vector2.Y));
public static bool Equals(Vector2 a, Vector2 b) => a.Equals(b);
public override string ToString() => $"({X},{Y})";
public override int GetHashCode() => (X, Y).GetHashCode();

public static bool operator ==(Vector2 a, Vector2 b) => a.Equals(b);
public static bool operator !=(Vector2 a, Vector2 b) => !(a == b);
public static Vector2 operator +(Vector2 a, Vector2 b) => new Vector2(a.X + b.X, a.Y + b.Y);
public static Vector2 operator -(Vector2 a, Vector2 b) => new Vector2(a.X - b.X, a.Y - b.Y);
public static Vector2 operator *(Vector2 a, Vector2 b) => new Vector2(a.X * b.X, a.Y * b.Y);
public static Vector2 operator *(Vector2 a, float b) => new Vector2(a.X * b, a.Y * b);
public static Vector2 operator /(Vector2 a, Vector2 b) => new Vector2(a.X / b.X, a.Y / b.Y);
public static Vector2 operator /(Vector2 a, float b) => new Vector2(a.X / b, a.Y / b);

public static Vector2 Normalize(Vector2 vector)
{
Vector2 v = vector;
v /= Math.Max(Math.Abs(v.X), Math.Abs(v.Y));
v /= v.Length;
return v;
}
}

可以通过加入更多有用的方法和属性,来使这个类型更加完善。


-------------End-------------