diff --git a/Cargo.toml b/Cargo.toml
index b7f6daba9dd563c5d5548788f7ffe0d2bdc0be24..8c8466f2ad5272483cda92bb7fc41dc71b40ec5b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "micro_games_macros"
-version = "0.5.0"
+version = "0.6.0"
 edition = "2021"
 authors = ["Louis Capitanchik <contact@louiscap.co>"]
 description = "Utility macros to make it easier to build complex systems with Bevy"
diff --git a/README.md b/README.md
index 5ed9ce50a1ca8b2b4a728b16a43e77932cf53436..c01ecb81c39da67f8d08c22222ec20664f34cbec 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,14 @@
 [![docs.rs](https://img.shields.io/docsrs/micro_games_macros?style=for-the-badge)](https://docs.rs/micro_games_macros)
 [![Crates.io](https://img.shields.io/crates/v/micro_games_macros?style=for-the-badge)](https://crates.io/crates/micro_games_macros)
 
-A collection of utility macros for building games
+A collection of utility macros for building games. While this library should theoretically work with
+any version of Bevy that includes the required traits and structs for a given macro, it is worth checking the
+version of bevy listed as a dev dependency in `Cargo.toml`, as that will be the version tested against.
 
-**Current Version Support: 0.5.x -> Bevy 0.14**
+This works because the library does not directly depend on bevy, but instead just generates fully qualified paths
+to derives, traits, and structs that will resolve to the version of Bevy used in your downstream project
+
+**Current Version Support: 0.6.x -> Bevy 0.14**
 
 ## Macros
 
@@ -82,4 +87,21 @@ struct MyComponent;
 
 // ... Other Kayak Setup ...
 
+```
+
+### Tag Finder
+
+Create a system param for checking whether an entity has one of a specified number of tags. The tag finder will also
+create the tag components, which can use either table or sparse storage
+
+```rust
+use micro_games_macros::tag_finder;
+
+#[tag_finder]
+pub struct TagFinder {
+    player: Player,
+    item: Item,
+    #[sparse]
+    hover: Hover,
+}
 ```
\ No newline at end of file
diff --git a/src/fqpath.rs b/src/fqpath.rs
index 6ff2d4e47499b1747e3d48557a9a13f1a5f61ac7..5442aedaeafe42750845b963d72e5cd066dd89e0 100644
--- a/src/fqpath.rs
+++ b/src/fqpath.rs
@@ -20,6 +20,7 @@ fq!(FQVec => ::std::vec::Vec);
 fq!(FQString => ::std::string::String);
 fq!(FQHashMap => ::std::collections::HashMap);
 fq!(FQClone => ::core::clone::Clone);
+fq!(FQCopy => ::core::marker::Copy);
 fq!(FQDebug => ::core::fmt::Debug);
 fq!(FQDisplay => ::core::fmt::Display);
 fq!(FQDefault => ::core::default::Default);
@@ -44,9 +45,13 @@ fq!(BevyWorld => ::bevy::ecs::world::World);
 fq!(BevyEvent => ::bevy::ecs::event::Event);
 fq!(BevyRes => ::bevy::ecs::system::Res);
 fq!(BevyResMut => ::bevy::ecs::system::ResMut);
+fq!(BevyWith => ::bevy::ecs::query::With);
+fq!(BevyQuery => ::bevy::ecs::system::Query);
+fq!(BevyEntity => ::bevy::ecs::entity::Entity);
 fq!(BevyEventReader => ::bevy::ecs::event::EventReader);
 fq!(BevySystemParam => ::bevy::ecs::system::SystemParam);
 fq!(BevyResource => ::bevy::ecs::system::Resource);
+fq!(BevyComponent => ::bevy::ecs::component::Component);
 fq!(BevyTypePath=> ::bevy::reflect::TypePath);
 fq!(BevyTypeUuid => ::bevy::reflect::TypeUuid);
 fq!(BevyDeref => ::bevy::prelude::Deref);
diff --git a/src/lib.rs b/src/lib.rs
index d1e93e3e6843ae058557d2a70e1e61969ac71140..7a1f6cba1f6e04b273f995b819acff5da1dffe2f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -104,6 +104,7 @@ pub(crate) mod json_loader;
 #[cfg(feature = "kayak")]
 pub(crate) mod kayak;
 pub(crate) mod std_traits;
+pub(crate) mod tag_finder;
 
 /// Generate loader and handler implementations for keyed JSON resources. The asset must implement `bevy::asset::Asset`, as well as
 /// `serde::Deserialize` and `serde::Serialize`
@@ -369,3 +370,48 @@ pub fn derive_from_inner(input: TokenStream) -> TokenStream {
 	let input = parse_macro_input!(input as DeriveInput);
 	std_traits::from_inner::derive(input).into()
 }
+
+/// Create a TagFinder type, used for checking what tag components might contain
+///
+/// ## Examples
+///
+/// Spawns a number of entities, and then perform different behaviour based on tags
+///
+/// ```
+/// # use bevy::prelude::*;
+/// # use micro_games_macros::tag_finder;
+///
+/// #[tag_finder]
+/// struct TagFinder {
+///   ally: Ally,
+///   enemy: Enemy,
+/// }
+///
+/// pub fn spawn_entities(mut commands: Commands) {
+///   commands.spawn((Ally, SpatialBundle::default()));
+///   commands.spawn((Enemy, SpatialBundle::default()));
+///   commands.spawn((Enemy, SpatialBundle::default()));
+/// }
+///
+/// pub fn my_checking_system(query: Query<(Entity, &Transform)>, tags: TagFinder) {
+///   for (e, t) in &query {
+///     if tags.is_ally(e) {
+///       println!("Celebrate");
+///     } else if tags.is_enemy(e) {
+///       println!("Run away")
+///     }
+///   }
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn tag_finder(_: TokenStream, input: TokenStream) -> TokenStream {
+	let input = parse_macro_input!(input as DeriveInput);
+	tag_finder::tag_finder(input).into()
+}
+
+/// Marker attribute, used exclusively by other proc macros
+#[proc_macro_attribute]
+#[doc(hidden)]
+pub fn sparse(_: TokenStream, input: TokenStream) -> TokenStream {
+	input
+}
diff --git a/src/tag_finder/components.rs b/src/tag_finder/components.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bab22d65c1e80e0bb6227beafb25c2cc71a43eff
--- /dev/null
+++ b/src/tag_finder/components.rs
@@ -0,0 +1,85 @@
+use crate::fqpath::{
+	BevyComponent, BevyEntity, BevyQuery, BevySystemParam, BevyWith, FQClone, FQCopy, FQDebug,
+	FQDefault,
+};
+use crate::utils::{ident_prefix, only_named_struct, only_struct};
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned};
+use syn::spanned::Spanned;
+use syn::{DeriveInput, Field};
+
+macro_rules! or_return {
+	($expr: expr) => {
+		match $expr {
+			Ok(value) => value,
+			Err(err) => return err,
+		}
+	};
+}
+
+pub fn tag_finder(input: DeriveInput) -> TokenStream {
+	let struct_data = or_return!(only_struct(&input));
+	let _ = or_return!(only_named_struct(struct_data));
+
+	let struct_fields: TokenStream = struct_data.fields.iter().map(map_named_field).collect();
+	let struct_methods: TokenStream = struct_data.fields.iter().map(map_named_method).collect();
+	let tag_types: TokenStream = struct_data.fields.iter().map(map_tag_types).collect();
+
+	let DeriveInput { vis, ident, .. } = input;
+
+	quote! {
+		#tag_types
+
+		#[derive(#BevySystemParam)]
+		#vis struct #ident <'w, 's> {
+			#struct_fields
+		}
+
+		impl <'w, 's> #ident<'w, 's> {
+			#struct_methods
+		}
+	}
+}
+
+fn map_tag_types(field: &Field) -> TokenStream {
+	let Field { ty, attrs, .. } = field;
+
+	let is_sparse = attrs.iter().any(|attr| attr.path().is_ident("sparse"));
+
+	if is_sparse {
+		quote! {
+			#[derive(#FQCopy, #FQClone, #FQDefault, #FQDebug, #BevyComponent)]
+			#[component(storage = "SparseSet")]
+			pub struct #ty;
+		}
+	} else {
+		quote! {
+			#[derive(#FQCopy, #FQClone, #FQDefault, #FQDebug, #BevyComponent)]
+			pub struct #ty;
+		}
+	}
+}
+
+fn map_named_field(field: &Field) -> TokenStream {
+	let Field { ident, ty, .. } = field;
+	quote! {
+		#ident: #BevyQuery<'w, 's, (), #BevyWith<#ty>>,
+	}
+}
+
+fn map_named_method(field: &Field) -> TokenStream {
+	let Field { ident, ty, .. } = field;
+	match ident {
+		None => {
+			quote_spanned! { ty.span() => 	compile_error!("Unable to map field with type {} that does not have a name"); }
+		}
+		Some(ident) => {
+			let name = ident_prefix(ident, "is_");
+			quote! {
+				pub fn #name(&self, entity: #BevyEntity) -> bool {
+					self.#ident.contains(entity)
+				}
+			}
+		}
+	}
+}
diff --git a/src/tag_finder/mod.rs b/src/tag_finder/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..29f71a5807d633cb61ea0df4b6f68032e51a47ee
--- /dev/null
+++ b/src/tag_finder/mod.rs
@@ -0,0 +1,3 @@
+mod components;
+
+pub use components::tag_finder;
diff --git a/src/utils.rs b/src/utils.rs
index bcd173f9e8c8621583cdce176fe6914c35f19ba0..59539ebf40db85c6b1c06f13367af89335ccfb10 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -2,7 +2,10 @@ use proc_macro2::{Ident, TokenStream, TokenTree};
 use quote::{quote, quote_spanned, ToTokens};
 use std::collections::HashMap;
 use std::fmt::Display;
-use syn::{spanned::Spanned, Attribute, DataStruct, Field, Meta, MetaNameValue, Visibility};
+use syn::{
+	spanned::Spanned, Attribute, Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Meta,
+	MetaNameValue, Visibility,
+};
 
 /// Convert a list of characters to snake case
 pub fn snake_case(chars: impl Iterator<Item = char>) -> String {
@@ -160,6 +163,30 @@ pub fn module_wrapped(
 	}
 }
 
+pub fn only_struct<'a>(input: &'a DeriveInput) -> Result<&'a DataStruct, TokenStream> {
+	match &input.data {
+		Data::Struct(at) => Ok(at),
+		Data::Enum(en) => Err(
+			quote_spanned!(en.enum_token.span() => compile_error!("Unsupported for Enum, only for Struct");),
+		),
+		Data::Union(un) => Err(
+			quote_spanned!(un.union_token.span() => compile_error!("Unsupported for Union, only for Struct");),
+		),
+	}
+}
+
+pub fn only_named_struct(data: &DataStruct) -> Result<&FieldsNamed, TokenStream> {
+	match &data.fields {
+		Fields::Named(named) => Ok(named),
+		Fields::Unnamed(uf) => Err(
+			quote_spanned!(uf.paren_token.span => compile_error!("Unsupported for tuple structs, must use named structs");),
+		),
+		Fields::Unit => Err(
+			quote_spanned!(data.semi_token.span() => compile_error!("Unsupported for unit structs, must use named structs");),
+		),
+	}
+}
+
 #[cfg(test)]
 mod tests {
 	use crate::utils::{snake_case, unquote};