03 Custom Type Event: Automated Code Generation
๐ Overviewโ
In real games, passing a single float for damage is rarely enough. You often need to bundle data: Who attacked? Was it a crit? What damage type? Where did it hit? This demo demonstrates how to create events for custom C# classes and leverage the automatic code generation system to maintain type safety.
- How to create events with custom data classes
- How the system auto-generates
GameEvent<T>for your types - How to pass complex data structures through events
- How one event payload can drive multiple feedback systems
๐ฌ Demo Sceneโ
Assets/TinyGiants/GameEventSystem/Demo/03_CustomTypeEvent/03_CustomTypeEvent.unity
Scene Compositionโ
UI Layer (Canvas):
- ๐ฎ Three Attack Buttons - Located at the bottom of the screen
- "Raise (Physical Damage)" โ Triggers
CustomEventRaiser.DealPhysicalDamage() - "Raise (Fire Damage)" โ Triggers
CustomEventRaiser.DealFireDamage() - "Raise (Critical Strike)" โ Triggers
CustomEventRaiser.DealCriticalStrike()
- "Raise (Physical Damage)" โ Triggers
Game Logic Layer (Demo Scripts):
-
๐ค CustomTypeEventRaiser - GameObject with the raiser script
- Holds references to 3 events:
GameEvent<DamageInfo>for Physical, Fire, and Critical attacks - Constructs
DamageInfoobjects with different properties and raises corresponding events
- Holds references to 3 events:
-
๐ฅ CustomTypeEventReceiver - GameObject with the receiver script
- Listens to all 3 damage events through visual binding in Game Event Editor
- Parses the
DamageInfopayload to trigger appropriate visual and physics feedback
Visual Feedback Layer (Demo Objects):
- ๐ฏ Capsule - The damage target (dummy)
- Has Rigidbody for physics knockback
- Has Renderer for color flash effects
- ๐ฅ Particle Effects - Fire hit VFX spawned at impact points
- ๐ฌ Floating Text - Damage numbers displayed above the capsule
- ๐ Plane - Ground surface for scene context
๐ฎ How to Interactโ
Step 1: Enter Play Modeโ
Press the Play button in Unity.
Step 2: Test Different Attack Typesโ
Click "Raise (Physical Damage)":
- โช White color flash on capsule
- ๐ฌ Floating text shows "10" in white
- ๐ฏ Small knockback force applied
- ๐ Console logs:
[Combat Log] Dealt 10 (Physical) damage. Crit: False, Attacker: Player01
Click "Raise (Fire Damage)":
- ๐ Orange color flash on capsule
- ๐ฌ Floating text shows randomized damage (15-25) in orange
- ๐ฅ Fire particle effect spawns at the hit point
- ๐ฏ Standard knockback force applied
- ๐ Console logs fire damage details with attacker "Player02"
Click "Raise (Critical Strike)":
- ๐ฃ Purple color flash on capsule
- ๐ฌ Larger floating text shows high damage (50-80) with "!" suffix
- ๐น Camera shake effect for dramatic impact
- ๐ฏ Strong knockback force applied
- ๐ Console logs critical strike details with attacker "Player03"
๐๏ธ Scene Architectureโ
The Custom Data Structureโ
The DamageInfo class bundles all combat-related data into a single packet:
[Serializable]
public class DamageInfo
{
public int amount; // Damage value
public bool isCritical; // Critical hit flag
public DamageType type; // Physical, Fire, or Void
public Vector3 hitPoint; // Impact position for VFX spawning
public string attacker; // Name of damage source
}
Why Bundle Data?
- โ One event call passes all necessary information
- โ Easier to extend (add new properties without changing event signatures)
- โ Type-safe serialization and validation
- โ Clear data contract between sender and receiver
Event Definitionsโ
Open the Game Event Editor window to see the 3 damage events:

Events in Database:
| Event Name | Type | Purpose |
|---|---|---|
OnPhysicalDamage | GameEvent<DamageInfo> | Standard physical attacks |
OnFireDamage | GameEvent<DamageInfo> | Fire-based magical damage |
OnCriticalStrike | GameEvent<DamageInfo> | High-impact critical hits |
Notice the Behavior Column:
All three events show (DamageInfo) as the type indicator. These GameEvent<DamageInfo> classes were automatically generated by the plugin when you created the eventsโno manual coding required!
When you create an event with a custom type in the Game Event Creator, the plugin automatically:
- Generates the
GameEvent<YourType>class - Creates corresponding listener interfaces
- Ensures type safety in Inspector dropdowns and method binding
Sender Setup (CustomTypeEventRaiser)โ
Select the CustomTypeEventRaiser GameObject in the Hierarchy:

Configuration Details:
GameEvent Section:
Physical Damage EventโOnPhysicalDamageFire Damage EventโOnFireDamageCritical Strike EventโOnCriticalStrike
Settings Section:
Hit Targetโ Capsule (Transform) - Used to calculate random hit points
Type Safety in Action:
- The dropdown only shows
GameEvent<DamageInfo>assets - You cannot assign a
GameEvent<string>orGameEvent<Vector3>to these slots - This prevents runtime type mismatch errors
Receiver Setup (CustomTypeEventReceiver)โ
Select the CustomTypeEventReceiver GameObject in the Hierarchy:

Reference Configuration:
Floating Text Prefabโ DamageFloatingText (GameObject)Hit Particle Prefabโ FireHitVFX (ParticleSystem)Target Rendererโ Capsule (Mesh Renderer)Target Rigidbodyโ Capsule (Rigidbody)
Behavior Binding:
All three damage events are bound to the same receiver method through the Behavior Window:
| Event | Bound Method | Signature |
|---|---|---|
OnPhysicalDamage | OnDamageReceived | void (DamageInfo info) |
OnFireDamage | OnDamageReceived | void (DamageInfo info) |
OnCriticalStrike | OnDamageReceived | void (DamageInfo info) |
Smart Routing:
The single receiver method intelligently routes feedback based on the DamageInfo propertiesโchecking type for fire particles, isCritical for camera shake, etc.
๐ป Code Breakdownโ
๐ค CustomTypeEventRaiser.cs (Sender)โ
using UnityEngine;
using TinyGiants.GameEventSystem.Runtime;
public class CustomEventRaiser : MonoBehaviour
{
[Header("GameEvent")]
// Notice: GameEvent<DamageInfo> was AUTO-GENERATED by the plugin
[GameEventDropdown] public GameEvent<DamageInfo> physicalDamageEvent;
[GameEventDropdown] public GameEvent<DamageInfo> fireDamageEvent;
[GameEventDropdown] public GameEvent<DamageInfo> criticalStrikeEvent;
[Header("Settings")]
public Transform hitTarget;
/// <summary>
/// Simulates a standard physical attack from "Player01".
/// Sends fixed damage with Physical type.
/// </summary>
public void DealPhysicalDamage()
{
SendDamage(physicalDamageEvent, 10f, false, DamageType.Physical, "Player01");
}
/// <summary>
/// Simulates a fire spell from "Player02".
/// Demonstrates randomized damage generation (15-25).
/// </summary>
public void DealFireDamage()
{
float dmg = Random.Range(15f, 25f);
SendDamage(fireDamageEvent, dmg, false, DamageType.Fire, "Player02");
}
/// <summary>
/// Simulates a critical strike from "Player03".
/// Sets isCritical flag to trigger special effects (camera shake, larger text).
/// </summary>
public void DealCriticalStrike()
{
float dmg = Random.Range(50f, 80f);
SendDamage(criticalStrikeEvent, dmg, true, DamageType.Void, "Player03");
}
/// <summary>
/// Constructs the DamageInfo packet and raises the event.
/// </summary>
private void SendDamage(GameEvent<DamageInfo> gameEvent, float baseDamage,
bool isCrit, DamageType type, string attacker)
{
if (gameEvent == null) return;
// Calculate random hit point to simulate impact variation
Vector3 randomPoint = hitTarget != null
? hitTarget.position + Random.insideUnitSphere * 0.5f
: Vector3.zero;
// Construct the data packet
DamageInfo info = new DamageInfo(
Mathf.RoundToInt(baseDamage),
isCrit,
type,
randomPoint,
attacker
);
// Raise the event with the complex object
gameEvent.Raise(info);
Debug.Log($"[Combat Log] Dealt {info.amount} ({info.type}) damage. " +
$"Crit: {info.isCritical}, Attacker: {info.attacker}");
}
}
Key Points:
- ๐ฏ Custom Type Support -
GameEvent<DamageInfo>handles complex objects - ๐๏ธ Data Construction - Build the packet with all relevant properties
- ๐ฆ Single Call -
.Raise(info)passes the entire data structure - ๐ Decoupling - No knowledge of what visual effects will be triggered
๐ฅ CustomTypeEventReceiver.cs (Listener)โ
using UnityEngine;
using TMPro;
using System.Collections;
public class CustomTypeEventReceiver : MonoBehaviour
{
[Header("Reference")]
[SerializeField] private GameObject floatingTextPrefab;
[SerializeField] private ParticleSystem hitParticlePrefab;
[SerializeField] private Renderer targetRenderer;
[SerializeField] private Rigidbody targetRigidbody;
private Camera _mainCamera;
/// <summary>
/// Listener method for GameEvent<DamageInfo>.
/// Parses the complex data to trigger multiple feedback systems.
/// </summary>
public void OnDamageReceived(DamageInfo info)
{
// 1. Visual: Color flash based on damage type
Color effectColor = GetColorByType(info.type);
StartCoroutine(FlashColorRoutine(effectColor));
// 2. UI: Floating damage text
if (floatingTextPrefab != null)
{
ShowFloatingText(info, effectColor);
}
// 3. VFX: Fire particles for fire damage
if (info.type == DamageType.Fire && hitParticlePrefab != null)
{
Vector3 centerToHitDir = (info.hitPoint - transform.position).normalized;
Vector3 spawnPos = info.hitPoint + (centerToHitDir * 0.2f);
var vfxInstance = Instantiate(hitParticlePrefab, spawnPos,
Quaternion.LookRotation(centerToHitDir));
var main = vfxInstance.main;
main.startColor = effectColor;
vfxInstance.Play();
Destroy(vfxInstance.gameObject, 2.0f);
}
// 4. Physics: Knockback force (stronger for crits)
if (targetRigidbody != null)
{
Vector3 forceDir = (info.hitPoint - transform.position).normalized * -1f;
float forceStrength = info.isCritical ? 5f : 2f;
targetRigidbody.AddForce(forceDir * forceStrength + Vector3.up * 2f,
ForceMode.Impulse);
targetRigidbody.AddTorque(Random.insideUnitSphere * forceStrength,
ForceMode.Impulse);
}
// 5. Camera: Screen shake for critical hits
if (info.isCritical)
{
StartCoroutine(ShakeCameraRoutine(0.2f, 0.4f));
}
}
private void ShowFloatingText(DamageInfo info, Color color)
{
GameObject go = Instantiate(floatingTextPrefab, info.hitPoint + Vector3.up,
Quaternion.identity);
var tmp = go.GetComponent<TextMeshPro>();
if (tmp != null)
{
// Critical hits get "!" suffix and larger font
tmp.text = info.isCritical ? $"{info.amount}!" : info.amount.ToString();
tmp.color = color;
tmp.fontSize = info.isCritical ? 10 : 6;
}
if (Camera.main)
go.transform.rotation = Camera.main.transform.rotation;
StartCoroutine(AnimateText(go.transform));
}
private IEnumerator FlashColorRoutine(Color color)
{
if (targetRenderer != null)
{
Color original = targetRenderer.material.color;
targetRenderer.material.color = color * 1.5f;
yield return new WaitForSeconds(0.1f);
targetRenderer.material.color = original;
}
}
private IEnumerator ShakeCameraRoutine(float duration, float magnitude)
{
if (_mainCamera == null) yield break;
Vector3 originalPos = _mainCamera.transform.position;
float elapsed = 0.0f;
while (elapsed < duration)
{
float x = Random.Range(-1f, 1f) * magnitude;
float y = Random.Range(-1f, 1f) * magnitude;
_mainCamera.transform.position = originalPos + new Vector3(x, y, 0);
elapsed += Time.deltaTime;
yield return null;
}
_mainCamera.transform.position = originalPos;
}
private Color GetColorByType(DamageType type)
{
switch (type)
{
case DamageType.Physical: return Color.white;
case DamageType.Fire: return new Color(1f, 0.5f, 0f);
case DamageType.Void: return new Color(0.8f, 0f, 1f);
default: return Color.grey;
}
}
}
Key Points:
- ๐ฏ Property-Based Routing - Check
info.typeandinfo.isCriticalto decide actions - ๐จ Multiple Feedback Systems - Color flash, floating text, VFX, physics, camera shake
- ๐ Spatial Data Usage -
info.hitPointdetermines VFX spawn location - ๐ Decoupling - No knowledge of which button or raiser triggered the event
๐ Key Takeawaysโ
| Concept | Implementation |
|---|---|
| ๐ฏ Custom Types | GameEvent<YourClass> supports any serializable C# class |
| ๐ญ Auto-Generation | Plugin generates event classes automaticallyโno manual coding |
| ๐ฆ Data Bundling | Pass complex objects with multiple properties in one call |
| ๐ Smart Routing | Single receiver method can handle different logic paths based on data |
| ๐จ Rich Feedback | One event payload drives multiple coordinated systems |
Custom type events are perfect for complex game systems like combat, dialogue, or inventory. Instead of firing 5 separate events (OnDamage, OnDamageType, OnCritical, etc.), you fire one event with all the data, keeping your event system clean and efficient!
๐ฏ What's Next?โ
You've mastered custom data types. Now let's explore how to add custom sender information to track event sources.
Next Chapter: Learn about sender tracking in 04 Custom Sender Event
๐ Related Documentationโ
- Game Event Creator - How to create events with custom types
- Code Generation - Understanding the automatic code generation system
- API Reference - Generic event API for custom types