diff --git a/examples/dodge-the-creeps/rust/src/player.rs b/examples/dodge-the-creeps/rust/src/player.rs
index 0d8a1c563..f24265129 100644
--- a/examples/dodge-the-creeps/rust/src/player.rs
+++ b/examples/dodge-the-creeps/rust/src/player.rs
@@ -62,7 +62,7 @@ impl GodotExt for Player {
             .base
             .get_node_as::<AnimatedSprite2D>("AnimatedSprite2D");
 
-        let mut velocity = Vector2::new(0.0, 0.0).inner();
+        let mut velocity = Vector2::new(0.0, 0.0);
 
         // Note: exact=false by default, in Rust we have to provide it explicitly
         let input = Input::singleton();
@@ -80,7 +80,7 @@ impl GodotExt for Player {
         }
 
         if velocity.length() > 0.0 {
-            velocity = velocity.normalize() * self.speed;
+            velocity = velocity.normalized() * self.speed;
 
             let animation;
 
@@ -101,10 +101,10 @@ impl GodotExt for Player {
         }
 
         let change = velocity * delta as f32;
-        let position = self.base.get_global_position().inner() + change;
+        let position = self.base.get_global_position() + change;
         let position = Vector2::new(
-            position.x.max(0.0).min(self.screen_size.inner().x),
-            position.y.max(0.0).min(self.screen_size.inner().y),
+            position.x.max(0.0).min(self.screen_size.x),
+            position.y.max(0.0).min(self.screen_size.y),
         );
         self.base.set_global_position(position);
     }
diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs
index 9f8c83c4a..56e3b8855 100644
--- a/godot-core/src/builtin/mod.rs
+++ b/godot-core/src/builtin/mod.rs
@@ -4,9 +4,36 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-//! Built-in types like `Vector2`, `GodotString` or `Variant`.
+//! Built-in types like `Vector2`, `GodotString` and `Variant`.
+//!
+//! # Background on the design of vector algebra types
+//!
+//! The basic vector algebra types like `Vector2`, `Matrix4` and `Quaternion` are re-implemented
+//! here, with an API similar to that in the Godot engine itself. There are other approaches, but
+//! they all have their disadvantages:
+//!
+//! - We could invoke API methods from the engine. The implementations could be generated, but it
+//!   is slower and prevents inlining.
+//!
+//! - We could re-export types from an existing vector algebra crate, like `glam`. This removes the
+//!   duplication, but it would create a strong dependency on a volatile API outside our control.
+//!   The `gdnative` crate started out this way, using types from `euclid`, but [found it
+//!   impractical](https://github.com/godot-rust/gdnative/issues/594#issue-705061720). Moreover,
+//!   the API would not match Godot's own, which would make porting from GDScript (slightly)
+//!   harder.
+//!
+//! - We could opaquely wrap types from an existing vector algebra crate. This protects users of
+//!   `gdextension` from changes in the wrapped crate. However, direct field access using `.x`,
+//!   `.y`, `.z` is no longer possible. Instead of `v.y += a;` you would have to write
+//!   `v.set_y(v.get_y() + a);`. (A `union` could be used to add these fields in the public API,
+//!   but would make every field access unsafe, which is also not great.)
+//!
+//! - We could re-export types from the [`mint`](https://crates.io/crates/mint) crate, which was
+//!   explicitly designed to solve this problem. However, it falls short because [operator
+//!   overloading would become impossible](https://github.com/kvark/mint/issues/75).
 
 mod macros;
+mod vector_macros;
 
 mod arrays;
 mod color;
@@ -16,8 +43,11 @@ mod string;
 mod string_name;
 mod variant;
 mod vector2;
+mod vector2i;
 mod vector3;
+mod vector3i;
 mod vector4;
+mod vector4i;
 
 pub mod meta;
 
@@ -29,5 +59,8 @@ pub use string::*;
 pub use string_name::*;
 pub use variant::*;
 pub use vector2::*;
+pub use vector2i::*;
 pub use vector3::*;
+pub use vector3i::*;
 pub use vector4::*;
+pub use vector4i::*;
diff --git a/godot-core/src/builtin/vector2.rs b/godot-core/src/builtin/vector2.rs
index 2e3eb6dc9..9506086be 100644
--- a/godot-core/src/builtin/vector2.rs
+++ b/godot-core/src/builtin/vector2.rs
@@ -4,80 +4,114 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  */
 
+use std::fmt;
+
 use godot_ffi as sys;
 use sys::{ffi_methods, GodotFfi};
 
-type Inner = glam::f32::Vec2;
-//type Inner = glam::f64::DVec2;
+use crate::builtin::Vector2i;
 
-#[derive(Default, Copy, Clone, Debug, PartialEq)]
+/// Vector used for 2D math using floating point coordinates.
+///
+/// 2-element structure that can be used to represent positions in 2D space or any other pair of
+/// numeric values.
+///
+/// It uses floating-point coordinates of 32-bit precision, unlike the engine's `float` type which
+/// is always 64-bit. The engine can be compiled with the option `precision=double` to use 64-bit
+/// vectors, but this is not yet supported in the `gdextension` crate.
+///
+/// See [`Vector2i`] for its integer counterpart.
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
 #[repr(C)]
 pub struct Vector2 {
-    inner: Inner,
+    /// The vector's X component.
+    pub x: f32,
+    /// The vector's Y component.
+    pub y: f32,
 }
 
 impl Vector2 {
-    pub fn new(x: f32, y: f32) -> Self {
-        Self {
-            inner: Inner::new(x, y),
-        }
+    /// Vector with all components set to `0.0`.
+    pub const ZERO: Self = Self::splat(0.0);
+
+    /// Vector with all components set to `1.0`.
+    pub const ONE: Self = Self::splat(1.0);
+
+    /// Vector with all components set to `f32::INFINITY`.
+    pub const INF: Self = Self::splat(f32::INFINITY);
+
+    /// Unit vector in -X direction (right in 2D coordinate system).
+    pub const LEFT: Self = Self::new(-1.0, 0.0);
+
+    /// Unit vector in +X direction (right in 2D coordinate system).
+    pub const RIGHT: Self = Self::new(1.0, 0.0);
+
+    /// Unit vector in -Y direction (up in 2D coordinate system).
+    pub const UP: Self = Self::new(0.0, -1.0);
+
+    /// Unit vector in +Y direction (down in 2D coordinate system).
+    pub const DOWN: Self = Self::new(0.0, 1.0);
+
+    /// Constructs a new `Vector2` from the given `x` and `y`.
+    pub const fn new(x: f32, y: f32) -> Self {
+        Self { x, y }
     }
 
-    pub fn from_inner(inner: Inner) -> Self {
-        Self { inner }
+    /// Constructs a new `Vector2` with both components set to `v`.
+    pub const fn splat(v: f32) -> Self {
+        Self::new(v, v)
     }
 
-    /// only for testing
-    pub fn inner(self) -> Inner {
-        self.inner
+    /// Constructs a new `Vector2` from a [`Vector2i`].
+    pub const fn from_vector2i(v: Vector2i) -> Self {
+        Self {
+            x: v.x as f32,
+            y: v.y as f32,
+        }
     }
 
-    // Hacks for example
-    // pub fn length(self) -> f32 {
-    //     self.inner.length()
-    // }
-    // pub fn normalized(self) -> Vector2 {
-    //     Self::from_inner(self.inner.normalize())
-    // }
+    /// Returns the result of rotating this vector by `angle` (in radians).
     pub fn rotated(self, angle: f32) -> Self {
-        Self::from_inner(glam::Affine2::from_angle(angle).transform_vector2(self.inner))
+        Self::from_glam(glam::Affine2::from_angle(angle).transform_vector2(self.to_glam()))
     }
-}
 
-impl GodotFfi for Vector2 {
-    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
-}
+    /// Converts the corresponding `glam` type to `Self`.
+    fn from_glam(v: glam::Vec2) -> Self {
+        Self::new(v.x, v.y)
+    }
 
-impl std::fmt::Display for Vector2 {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.inner.fmt(f)
+    /// Converts `self` to the corresponding `glam` type.
+    fn to_glam(self) -> glam::Vec2 {
+        glam::Vec2::new(self.x, self.y)
     }
 }
 
-// ----------------------------------------------------------------------------------------------------------------------------------------------
+/// Formats the vector like Godot: `(x, y)`.
+impl fmt::Display for Vector2 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({}, {})", self.x, self.y)
+    }
+}
 
-type IInner = glam::IVec2;
+impl_common_vector_fns!(Vector2, f32);
+impl_float_vector_fns!(Vector2, f32);
+impl_vector_operators!(Vector2, f32, (x, y));
+impl_vector_index!(Vector2, f32, (x, y), Vector2Axis, (X, Y));
 
-#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-pub struct Vector2i {
-    inner: IInner,
+impl GodotFfi for Vector2 {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
 }
 
-impl Vector2i {
-    pub fn new(x: i32, y: i32) -> Self {
-        Self {
-            inner: IInner::new(x, y),
-        }
-    }
+/// Enumerates the axes in a [`Vector2`].
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(i32)]
+pub enum Vector2Axis {
+    /// The X axis.
+    X,
+    /// The Y axis.
+    Y,
 }
 
-impl GodotFfi for Vector2i {
+impl GodotFfi for Vector2Axis {
     ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
 }
-
-impl std::fmt::Display for Vector2i {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.inner.fmt(f)
-    }
-}
diff --git a/godot-core/src/builtin/vector2i.rs b/godot-core/src/builtin/vector2i.rs
new file mode 100644
index 000000000..b20a2dd87
--- /dev/null
+++ b/godot-core/src/builtin/vector2i.rs
@@ -0,0 +1,107 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+use std::fmt;
+
+use godot_ffi as sys;
+use sys::{ffi_methods, GodotFfi};
+
+use crate::builtin::Vector2;
+
+/// Vector used for 2D math using integer coordinates.
+///
+/// 2-element structure that can be used to represent positions in 2D space or any other pair of
+/// numeric values.
+///
+/// It uses integer coordinates and is therefore preferable to [`Vector2`] when exact precision is
+/// required. Note that the values are limited to 32 bits, and unlike [`Vector2`] this cannot be
+/// configured with an engine build option. Use `i64` or [`PackedInt64Array`] if 64-bit values are
+/// needed.
+#[derive(Debug, Default, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct Vector2i {
+    /// The vector's X component.
+    pub x: i32,
+    /// The vector's Y component.
+    pub y: i32,
+}
+
+impl Vector2i {
+    /// Vector with all components set to `0`.
+    pub const ZERO: Self = Self::splat(0);
+
+    /// Vector with all components set to `1`.
+    pub const ONE: Self = Self::splat(1);
+
+    /// Unit vector in -X direction (right in 2D coordinate system).
+    pub const LEFT: Self = Self::new(-1, 0);
+
+    /// Unit vector in +X direction (right in 2D coordinate system).
+    pub const RIGHT: Self = Self::new(1, 0);
+
+    /// Unit vector in -Y direction (up in 2D coordinate system).
+    pub const UP: Self = Self::new(0, -1);
+
+    /// Unit vector in +Y direction (down in 2D coordinate system).
+    pub const DOWN: Self = Self::new(0, 1);
+
+    /// Constructs a new `Vector2i` from the given `x` and `y`.
+    pub const fn new(x: i32, y: i32) -> Self {
+        Self { x, y }
+    }
+
+    /// Constructs a new `Vector2i` with both components set to `v`.
+    pub const fn splat(v: i32) -> Self {
+        Self::new(v, v)
+    }
+
+    /// Constructs a new `Vector2i` from a [`Vector2`]. The floating point coordinates will be truncated.
+    pub const fn from_vector2(v: Vector2) -> Self {
+        Self {
+            x: v.x as i32,
+            y: v.y as i32,
+        }
+    }
+
+    /// Converts the corresponding `glam` type to `Self`.
+    fn from_glam(v: glam::IVec2) -> Self {
+        Self::new(v.x, v.y)
+    }
+
+    /// Converts `self` to the corresponding `glam` type.
+    fn to_glam(self) -> glam::IVec2 {
+        glam::IVec2::new(self.x, self.y)
+    }
+}
+
+/// Formats the vector like Godot: `(x, y)`.
+impl fmt::Display for Vector2i {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({}, {})", self.x, self.y)
+    }
+}
+
+impl_common_vector_fns!(Vector2i, i32);
+impl_vector_operators!(Vector2i, i32, (x, y));
+impl_vector_index!(Vector2i, i32, (x, y), Vector2iAxis, (X, Y));
+
+impl GodotFfi for Vector2i {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
+
+/// Enumerates the axes in a [`Vector2i`].
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(i32)]
+pub enum Vector2iAxis {
+    /// The X axis.
+    X,
+    /// The Y axis.
+    Y,
+}
+
+impl GodotFfi for Vector2iAxis {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
diff --git a/godot-core/src/builtin/vector3.rs b/godot-core/src/builtin/vector3.rs
index 0f2b94954..29b84b48d 100644
--- a/godot-core/src/builtin/vector3.rs
+++ b/godot-core/src/builtin/vector3.rs
@@ -4,75 +4,115 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  */
 
+use std::fmt;
+
 use godot_ffi as sys;
 use sys::{ffi_methods, GodotFfi};
 
-type Inner = glam::f32::Vec3;
-// type Inner = glam::f64::DVec3;
-
-#[derive(Default, Copy, Clone, Debug, PartialEq)]
+use crate::builtin::Vector3i;
+
+/// Vector used for 3D math using floating point coordinates.
+///
+/// 3-element structure that can be used to represent positions in 2D space or any other triple of
+/// numeric values.
+///
+/// It uses floating-point coordinates of 32-bit precision, unlike the engine's `float` type which
+/// is always 64-bit. The engine can be compiled with the option `precision=double` to use 64-bit
+/// vectors, but this is not yet supported in the `gdextension` crate.
+///
+/// See [`Vector3i`] for its integer counterpart.
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
 #[repr(C)]
 pub struct Vector3 {
-    inner: Inner,
+    /// The vector's X component.
+    pub x: f32,
+    /// The vector's Y component.
+    pub y: f32,
+    /// The vector's Z component.
+    pub z: f32,
 }
 
 impl Vector3 {
-    pub fn new(x: f32, y: f32, z: f32) -> Self {
-        Self {
-            inner: Inner::new(x, y, z),
-        }
-    }
-}
+    /// Vector with all components set to `0.0`.
+    pub const ZERO: Self = Self::splat(0.0);
 
-impl GodotFfi for Vector3 {
-    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
-}
+    /// Vector with all components set to `1.0`.
+    pub const ONE: Self = Self::splat(1.0);
 
-impl std::fmt::Display for Vector3 {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        //let Inner {x, y, z} = self.inner;
-        //write!(f, "({x}, {y}, {z})")
-        self.inner.fmt(f)
-    }
-}
+    /// Unit vector in -X direction. Can be interpreted as left in an untransformed 3D world.
+    pub const LEFT: Self = Self::new(-1.0, 0.0, 0.0);
 
-// ----------------------------------------------------------------------------------------------------------------------------------------------
+    /// Unit vector in +X direction. Can be interpreted as right in an untransformed 3D world.
+    pub const RIGHT: Self = Self::new(1.0, 0.0, 0.0);
 
-type IInner = glam::IVec3;
+    /// Unit vector in -Y direction. Typically interpreted as down in a 3D world.
+    pub const UP: Self = Self::new(0.0, -1.0, 0.0);
 
-#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-pub struct Vector3i {
-    inner: IInner,
-}
+    /// Unit vector in +Y direction. Typically interpreted as up in a 3D world.
+    pub const DOWN: Self = Self::new(0.0, 1.0, 0.0);
+
+    /// Unit vector in -Z direction. Can be interpreted as "into the screen" in an untransformed 3D world.
+    pub const FORWARD: Self = Self::new(0.0, 0.0, -1.0);
+
+    /// Unit vector in +Z direction. Can be interpreted as "out of the screen" in an untransformed 3D world.
+    pub const BACK: Self = Self::new(0.0, 0.0, 1.0);
 
-impl Vector3i {
-    pub fn new(x: i32, y: i32, z: i32) -> Self {
+    /// Returns a `Vector3` with the given components.
+    pub const fn new(x: f32, y: f32, z: f32) -> Self {
+        Self { x, y, z }
+    }
+
+    /// Returns a new `Vector3` with all components set to `v`.
+    pub const fn splat(v: f32) -> Self {
+        Self::new(v, v, v)
+    }
+
+    /// Constructs a new `Vector3` from a [`Vector3i`].
+    pub const fn from_vector3i(v: Vector3i) -> Self {
         Self {
-            inner: IInner::new(x, y, z),
+            x: v.x as f32,
+            y: v.y as f32,
+            z: v.z as f32,
         }
     }
-}
 
-impl GodotFfi for Vector3i {
-    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+    /// Converts the corresponding `glam` type to `Self`.
+    fn from_glam(v: glam::Vec3) -> Self {
+        Self::new(v.x, v.y, v.z)
+    }
+
+    /// Converts `self` to the corresponding `glam` type.
+    fn to_glam(self) -> glam::Vec3 {
+        glam::Vec3::new(self.x, self.y, self.z)
+    }
 }
 
-impl std::fmt::Display for Vector3i {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.inner.fmt(f)
+/// Formats the vector like Godot: `(x, y, z)`.
+impl fmt::Display for Vector3 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({}, {}, {})", self.x, self.y, self.z)
     }
 }
 
-// ----------------------------------------------------------------------------------------------------------------------------------------------
+impl_common_vector_fns!(Vector3, f32);
+impl_float_vector_fns!(Vector3, f32);
+impl_vector_operators!(Vector3, f32, (x, y, z));
+impl_vector_index!(Vector3, f32, (x, y, z), Vector3Axis, (X, Y, Z));
 
-// TODO auto-generate this, alongside all the other builtin type's enums
+impl GodotFfi for Vector3 {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
 
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+/// Enumerates the axes in a [`Vector3`].
+// TODO auto-generate this, alongside all the other builtin type's enums
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
 #[repr(i32)]
 pub enum Vector3Axis {
+    /// The X axis.
     X,
+    /// The Y axis.
     Y,
+    /// The Z axis.
     Z,
 }
 
diff --git a/godot-core/src/builtin/vector3i.rs b/godot-core/src/builtin/vector3i.rs
new file mode 100644
index 000000000..f42036412
--- /dev/null
+++ b/godot-core/src/builtin/vector3i.rs
@@ -0,0 +1,118 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+use std::fmt;
+
+use godot_ffi as sys;
+use sys::{ffi_methods, GodotFfi};
+
+use crate::builtin::Vector3;
+
+/// Vector used for 3D math using integer coordinates.
+///
+/// 3-element structure that can be used to represent positions in 3D space or any other triple of
+/// numeric values.
+///
+/// It uses integer coordinates and is therefore preferable to [`Vector3`] when exact precision is
+/// required. Note that the values are limited to 32 bits, and unlike [`Vector3`] this cannot be
+/// configured with an engine build option. Use `i64` or [`PackedInt64Array`] if 64-bit values are
+/// needed.
+#[derive(Debug, Default, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct Vector3i {
+    /// The vector's X component.
+    pub x: i32,
+    /// The vector's Y component.
+    pub y: i32,
+    /// The vector's Z component.
+    pub z: i32,
+}
+
+impl Vector3i {
+    /// Vector with all components set to `0`.
+    pub const ZERO: Self = Self::splat(0);
+
+    /// Vector with all components set to `1`.
+    pub const ONE: Self = Self::splat(1);
+
+    /// Unit vector in -X direction.
+    pub const LEFT: Self = Self::new(-1, 0, 0);
+
+    /// Unit vector in +X direction.
+    pub const RIGHT: Self = Self::new(1, 0, 0);
+
+    /// Unit vector in -Y direction.
+    pub const UP: Self = Self::new(0, -1, 0);
+
+    /// Unit vector in +Y direction.
+    pub const DOWN: Self = Self::new(0, 1, 0);
+
+    /// Unit vector in -Z direction.
+    pub const FORWARD: Self = Self::new(0, 0, -1);
+
+    /// Unit vector in +Z direction.
+    pub const BACK: Self = Self::new(0, 0, 1);
+
+    /// Returns a `Vector3i` with the given components.
+    pub const fn new(x: i32, y: i32, z: i32) -> Self {
+        Self { x, y, z }
+    }
+
+    /// Constructs a new `Vector3i` with all components set to `v`.
+    pub const fn splat(v: i32) -> Self {
+        Self::new(v, v, v)
+    }
+
+    /// Constructs a new `Vector3i` from a [`Vector3`]. The floating point coordinates will be truncated.
+    pub const fn from_vector3(v: Vector3) -> Self {
+        Self {
+            x: v.x as i32,
+            y: v.y as i32,
+            z: v.z as i32,
+        }
+    }
+
+    /// Converts the corresponding `glam` type to `Self`.
+    fn from_glam(v: glam::IVec3) -> Self {
+        Self::new(v.x, v.y, v.z)
+    }
+
+    /// Converts `self` to the corresponding `glam` type.
+    fn to_glam(self) -> glam::IVec3 {
+        glam::IVec3::new(self.x, self.y, self.z)
+    }
+}
+
+/// Formats the vector like Godot: `(x, y, z)`.
+impl fmt::Display for Vector3i {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({}, {}, {})", self.x, self.y, self.z)
+    }
+}
+
+impl_common_vector_fns!(Vector3i, i32);
+impl_vector_operators!(Vector3i, i32, (x, y, z));
+impl_vector_index!(Vector3i, i32, (x, y, z), Vector3iAxis, (X, Y, Z));
+
+impl GodotFfi for Vector3i {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
+
+/// Enumerates the axes in a [`Vector3i`].
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(i32)]
+pub enum Vector3iAxis {
+    /// The X axis.
+    X,
+    /// The Y axis.
+    Y,
+    /// The Z axis.
+    Z,
+}
+
+impl GodotFfi for Vector3iAxis {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
diff --git a/godot-core/src/builtin/vector4.rs b/godot-core/src/builtin/vector4.rs
index 22e856ff8..9a8274010 100644
--- a/godot-core/src/builtin/vector4.rs
+++ b/godot-core/src/builtin/vector4.rs
@@ -4,58 +4,106 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  */
 
+use std::fmt;
+
 use godot_ffi as sys;
 use sys::{ffi_methods, GodotFfi};
 
-type Inner = glam::f32::Vec4;
-//type Inner = glam::f64::DVec4;
+use crate::builtin::Vector4i;
 
-#[derive(Default, Copy, Clone, Debug, PartialEq)]
+/// Vector used for 4D math using floating point coordinates.
+///
+/// 4-element structure that can be used to represent any quadruplet of numeric values.
+///
+/// It uses floating-point coordinates of 32-bit precision, unlike the engine's `float` type which
+/// is always 64-bit. The engine can be compiled with the option `precision=double` to use 64-bit
+/// vectors, but this is not yet supported in the `gdextension` crate.
+///
+/// See [`Vector4i`] for its integer counterpart.
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
 #[repr(C)]
 pub struct Vector4 {
-    inner: Inner,
+    /// The vector's X component.
+    pub x: f32,
+    /// The vector's Y component.
+    pub y: f32,
+    /// The vector's Z component.
+    pub z: f32,
+    /// The vector's W component.
+    pub w: f32,
 }
 
+impl_vector_operators!(Vector4, f32, (x, y, z, w));
+impl_vector_index!(Vector4, f32, (x, y, z, w), Vector4Axis, (X, Y, Z, W));
+impl_common_vector_fns!(Vector4, f32);
+impl_float_vector_fns!(Vector4, f32);
+
 impl Vector4 {
-    pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
+    /// Returns a `Vector4` with the given components.
+    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
+        Self { x, y, z, w }
+    }
+
+    /// Returns a new `Vector4` with all components set to `v`.
+    pub const fn splat(v: f32) -> Self {
+        Self::new(v, v, v, v)
+    }
+
+    /// Constructs a new `Vector3` from a [`Vector3i`].
+    pub const fn from_vector4i(v: Vector4i) -> Self {
         Self {
-            inner: Inner::new(x, y, z, w),
+            x: v.x as f32,
+            y: v.y as f32,
+            z: v.z as f32,
+            w: v.w as f32,
         }
     }
-}
 
-impl GodotFfi for Vector4 {
-    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
-}
+    /// Zero vector, a vector with all components set to `0.0`.
+    pub const ZERO: Self = Self::splat(0.0);
 
-impl std::fmt::Display for Vector4 {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.inner.fmt(f)
-    }
-}
+    /// One vector, a vector with all components set to `1.0`.
+    pub const ONE: Self = Self::splat(1.0);
 
-type IInner = glam::IVec4;
+    /// Infinity vector, a vector with all components set to `f32::INFINITY`.
+    pub const INF: Self = Self::splat(f32::INFINITY);
 
-#[derive(Default, Copy, Clone, Debug)]
-#[repr(C)]
-pub struct Vector4i {
-    inner: IInner,
+    /// Converts the corresponding `glam` type to `Self`.
+    fn from_glam(v: glam::Vec4) -> Self {
+        Self::new(v.x, v.y, v.z, v.w)
+    }
+
+    /// Converts `self` to the corresponding `glam` type.
+    fn to_glam(self) -> glam::Vec4 {
+        glam::Vec4::new(self.x, self.y, self.z, self.w)
+    }
 }
 
-impl Vector4i {
-    pub fn new(x: i32, y: i32, z: i32, w: i32) -> Self {
-        Self {
-            inner: IInner::new(x, y, z, w),
-        }
+/// Formats the vector like Godot: `(x, y, z, w)`.
+impl fmt::Display for Vector4 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w)
     }
 }
 
-impl GodotFfi for Vector4i {
+impl GodotFfi for Vector4 {
     ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
 }
 
-impl std::fmt::Display for Vector4i {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        self.inner.fmt(f)
-    }
+/// Enumerates the axes in a [`Vector4`].
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(i32)]
+pub enum Vector4Axis {
+    /// The X axis.
+    X,
+    /// The Y axis.
+    Y,
+    /// The Z axis.
+    Z,
+    /// The W axis.
+    W,
+}
+
+impl GodotFfi for Vector4Axis {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
 }
diff --git a/godot-core/src/builtin/vector4i.rs b/godot-core/src/builtin/vector4i.rs
new file mode 100644
index 000000000..d8c341736
--- /dev/null
+++ b/godot-core/src/builtin/vector4i.rs
@@ -0,0 +1,105 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+use std::fmt;
+
+use godot_ffi as sys;
+use sys::{ffi_methods, GodotFfi};
+
+use crate::builtin::Vector4;
+
+/// Vector used for 4D math using integer coordinates.
+///
+/// 4-element structure that can be used to represent 4D grid coordinates or sets of integers.
+///
+/// It uses integer coordinates and is therefore preferable to [`Vector4`] when exact precision is
+/// required. Note that the values are limited to 32 bits, and unlike [`Vector4`] this cannot be
+/// configured with an engine build option. Use `i64` or [`PackedInt64Array`] if 64-bit values are
+/// needed.
+#[derive(Debug, Default, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct Vector4i {
+    /// The vector's X component.
+    pub x: i32,
+    /// The vector's Y component.
+    pub y: i32,
+    /// The vector's Z component.
+    pub z: i32,
+    /// The vector's W component.
+    pub w: i32,
+}
+
+impl_vector_operators!(Vector4i, i32, (x, y, z, w));
+impl_vector_index!(Vector4i, i32, (x, y, z, w), Vector4iAxis, (X, Y, Z, W));
+impl_common_vector_fns!(Vector4i, i32);
+
+impl Vector4i {
+    /// Returns a `Vector4i` with the given components.
+    pub const fn new(x: i32, y: i32, z: i32, w: i32) -> Self {
+        Self { x, y, z, w }
+    }
+
+    /// Constructs a new `Vector4i` with all components set to `v`.
+    pub const fn splat(v: i32) -> Self {
+        Self::new(v, v, v, v)
+    }
+
+    /// Constructs a new `Vector4i` from a [`Vector4`]. The floating point coordinates will be
+    /// truncated.
+    pub const fn from_vector3(v: Vector4) -> Self {
+        Self {
+            x: v.x as i32,
+            y: v.y as i32,
+            z: v.z as i32,
+            w: v.w as i32,
+        }
+    }
+
+    /// Zero vector, a vector with all components set to `0`.
+    pub const ZERO: Self = Self::splat(0);
+
+    /// One vector, a vector with all components set to `1`.
+    pub const ONE: Self = Self::splat(1);
+
+    /// Converts the corresponding `glam` type to `Self`.
+    fn from_glam(v: glam::IVec4) -> Self {
+        Self::new(v.x, v.y, v.z, v.w)
+    }
+
+    /// Converts `self` to the corresponding `glam` type.
+    fn to_glam(self) -> glam::IVec4 {
+        glam::IVec4::new(self.x, self.y, self.z, self.w)
+    }
+}
+
+/// Formats the vector like Godot: `(x, y, z, w)`.
+impl fmt::Display for Vector4i {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w)
+    }
+}
+
+impl GodotFfi for Vector4i {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
+
+/// Enumerates the axes in a [`Vector4i`].
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[repr(i32)]
+pub enum Vector4iAxis {
+    /// The X axis.
+    X,
+    /// The Y axis.
+    Y,
+    /// The Z axis.
+    Z,
+    /// The W axis.
+    W,
+}
+
+impl GodotFfi for Vector4iAxis {
+    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
+}
diff --git a/godot-core/src/builtin/vector_macros.rs b/godot-core/src/builtin/vector_macros.rs
new file mode 100644
index 000000000..addfde689
--- /dev/null
+++ b/godot-core/src/builtin/vector_macros.rs
@@ -0,0 +1,271 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#![macro_use]
+
+/// Implements a single unary operator for a vector type. Only used for `Neg` at the moment.
+macro_rules! impl_vector_unary_operator {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of each individual component, for example `i32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the operator trait, for example `Neg`.
+        $Operator:ident,
+        // Name of the function on the operator trait, for example `neg`.
+        $func:ident$(,)?
+    ) => {
+        impl std::ops::$Operator for $Vector {
+            type Output = Self;
+            fn $func(mut self) -> Self::Output {
+                $(
+                    self.$components = self.$components.$func();
+                )*
+                self
+            }
+        }
+    }
+}
+
+/// Implements a component-wise single infix binary operator between two vectors.
+macro_rules! impl_vector_vector_binary_operator {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of each individual component, for example `i32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the operator trait, for example `Add`.
+        $Operator:ident,
+        // Name of the function on the operator trait, for example `add`.
+        $func:ident$(,)?
+    ) => {
+        impl std::ops::$Operator for $Vector {
+            type Output = Self;
+            fn $func(mut self, rhs: $Vector) -> Self::Output {
+                $(
+                    self.$components = self.$components.$func(rhs.$components);
+                )*
+                self
+            }
+        }
+    }
+}
+
+/// Implements a component-wise single infix binary operator between a vector on the left and a
+/// scalar on the right-hand side.
+macro_rules! impl_vector_scalar_binary_operator {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of each individual component, for example `i32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the operator trait, for example `Add`.
+        $Operator:ident,
+        // Name of the function on the operator trait, for example `add`.
+        $func:ident$(,)?
+    ) => {
+        impl std::ops::$Operator<$Scalar> for $Vector {
+            type Output = Self;
+            fn $func(mut self, rhs: $Scalar) -> Self::Output {
+                $(
+                    self.$components = self.$components.$func(rhs);
+                )*
+                self
+            }
+        }
+    }
+}
+
+/// Implements a component-wise single infix binary operator between a scalar on the left and a
+/// vector on the right-hand side.
+macro_rules! impl_scalar_vector_binary_operator {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of each individual component, for example `i32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the operator trait, for example `Add`.
+        $Operator:ident,
+        // Name of the function on the operator trait, for example `add`.
+        $func:ident$(,)?
+    ) => {
+        impl std::ops::$Operator<$Vector> for $Scalar {
+            type Output = $Vector;
+            fn $func(self, mut rhs: $Vector) -> Self::Output {
+                $(
+                    rhs.$components = rhs.$components.$func(self);
+                )*
+                rhs
+            }
+        }
+    }
+}
+
+/// Implements a single arithmetic assignment operator for a vector type, with a vector on the
+/// right-hand side.
+macro_rules! impl_vector_vector_assign_operator {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of each individual component, for example `i32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the operator trait, for example `AddAssign`.
+        $Operator:ident,
+        // Name of the function on the operator trait, for example `add_assign`.
+        $func:ident$(,)?
+    ) => {
+        impl std::ops::$Operator for $Vector {
+            fn $func(&mut self, rhs: $Vector) {
+                $(
+                    self.$components.$func(rhs.$components);
+                )*
+            }
+        }
+    }
+}
+
+/// Implements a single arithmetic assignment operator for a vector type, with a scalar on the
+/// right-hand side.
+macro_rules! impl_vector_scalar_assign_operator {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of each individual component, for example `i32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the operator trait, for example `AddAssign`.
+        $Operator:ident,
+        // Name of the function on the operator trait, for example `add_assign`.
+        $func:ident$(,)?
+    ) => {
+        impl std::ops::$Operator<$Scalar> for $Vector {
+            fn $func(&mut self, rhs: $Scalar) {
+                $(
+                    self.$components.$func(rhs);
+                )*
+            }
+        }
+    }
+}
+
+/// Implements all common arithmetic operators on a built-in vector type.
+macro_rules! impl_vector_operators {
+    (
+        // Name of the vector type to be implemented, for example `Vector2`.
+        $Vector:ty,
+        // Type of each individual component, for example `f32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*)$(,)?
+    ) => {
+        impl_vector_unary_operator!($Vector, $Scalar, ($($components),*), Neg, neg);
+        impl_vector_vector_binary_operator!($Vector, $Scalar, ($($components),*), Add, add);
+        impl_vector_vector_binary_operator!($Vector, $Scalar, ($($components),*), Sub, sub);
+        impl_vector_vector_binary_operator!($Vector, $Scalar, ($($components),*), Mul, mul);
+        impl_vector_scalar_binary_operator!($Vector, $Scalar, ($($components),*), Mul, mul);
+        impl_scalar_vector_binary_operator!($Vector, $Scalar, ($($components),*), Mul, mul);
+        impl_vector_vector_binary_operator!($Vector, $Scalar, ($($components),*), Div, div);
+        impl_vector_scalar_binary_operator!($Vector, $Scalar, ($($components),*), Div, div);
+        impl_vector_vector_assign_operator!($Vector, $Scalar, ($($components),*), AddAssign, add_assign);
+        impl_vector_vector_assign_operator!($Vector, $Scalar, ($($components),*), SubAssign, sub_assign);
+        impl_vector_vector_assign_operator!($Vector, $Scalar, ($($components),*), MulAssign, mul_assign);
+        impl_vector_scalar_assign_operator!($Vector, $Scalar, ($($components),*), MulAssign, mul_assign);
+        impl_vector_vector_assign_operator!($Vector, $Scalar, ($($components),*), DivAssign, div_assign);
+        impl_vector_scalar_assign_operator!($Vector, $Scalar, ($($components),*), DivAssign, div_assign);
+    }
+}
+
+/// Implements `Index` and `IndexMut` for a vector type, using an enum to indicate the desired axis.
+macro_rules! impl_vector_index {
+    (
+        // Name of the vector type to be implemented, for example `Vector2`.
+        $Vector:ty,
+        // Type of each individual component, for example `f32`.
+        $Scalar:ty,
+        // Names of the components, with parentheses, for example `(x, y)`.
+        ($($components:ident),*),
+        // Name of the enum type for the axes, for example `Vector2Axis`.
+        $AxisEnum:ty,
+        // Names of the enum variants, with parenthes, for example `(X, Y)`.
+        ($($AxisVariants:ident),*)
+    ) => {
+        impl std::ops::Index<$AxisEnum> for $Vector {
+            type Output = $Scalar;
+            fn index(&self, axis: $AxisEnum) -> &$Scalar {
+                match axis {
+                    $(<$AxisEnum>::$AxisVariants => &self.$components),*
+                }
+            }
+        }
+
+        impl std::ops::IndexMut<$AxisEnum> for $Vector {
+            fn index_mut(&mut self, axis: $AxisEnum) -> &mut $Scalar {
+                match axis {
+                    $(<$AxisEnum>::$AxisVariants => &mut self.$components),*
+                }
+            }
+        }
+    }
+}
+
+/// Implements functions on vector types which make sense for both floating-point and integer
+/// vectors.
+macro_rules! impl_common_vector_fns {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of target component, for example `f32`.
+        $Scalar:ty
+    ) => {
+        impl $Vector {
+            /// Returns a new vector with all components in absolute values (i.e. positive or
+            /// zero).
+            #[inline]
+            pub fn abs(self) -> Self {
+                Self::from_glam(self.to_glam().abs())
+            }
+        }
+    };
+}
+
+/// Implements common constants and methods for floating-point type vectors. Works for any vector
+/// type that has `to_glam` and `from_glam` functions.
+macro_rules! impl_float_vector_fns {
+    (
+        // Name of the vector type.
+        $Vector:ty,
+        // Type of target component, for example `f32`.
+        $Scalar:ty
+    ) => {
+        impl $Vector {
+            /// Returns the length (magnitude) of this vector.
+            #[inline]
+            pub fn length(self) -> $Scalar {
+                self.to_glam().length()
+            }
+
+            /// Returns the vector scaled to unit length. Equivalent to `self / self.length()`. See
+            /// also `is_normalized()`.
+            ///
+            /// If the vector is zero, the result is also zero.
+            #[inline]
+            pub fn normalized(self) -> Self {
+                Self::from_glam(self.to_glam().normalize_or_zero())
+            }
+        }
+    };
+}