After months of building web applications with Rails, I decided to try something completely different: game development with Unity. I thought it would be a world apart from web development, but I was surprised by how many concepts translated between the two.
In this post, I'll share what I learned building my first Unity game and how Rails experience actually helped me understand game development patterns.
Why Unity After Rails?
As a Rails developer, I was comfortable with:
- Object-oriented programming
- Component-based architecture (Rails models, concerns)
- MVC patterns
- Working with frameworks and conventions
Unity appealed to me because:
- Visual development - Drag and drop interface
- C# language - Similar to Ruby in many ways
- Component system - Felt familiar from Rails concerns
- Large community - Lots of learning resources
The Mental Model Shift
Rails: Request/Response Cycle
# User makes request -> Controller processes -> Model queries database -> View renders -> Response
def show
@user = User.find(params[:id]) # Model
render :show # View
end
Unity: Game Loop
// Every frame: Input -> Update -> Physics -> Render -> Repeat
void Update() {
HandleInput(); // Check for player input
UpdateGameState(); // Update object positions, health, etc.
CheckCollisions(); // Physics and interactions
// Rendering happens automatically
}
The biggest shift was from event-driven (Rails responds to requests) to frame-driven (Unity runs continuously).
My First Game: A Simple Platformer
I decided to build a 2D platformer where a character can:
- Move left and right
- Jump on platforms
- Collect coins
- Avoid enemies
Simple goals, but they taught me fundamental Unity concepts.
Unity Concepts That Felt Familiar
1. GameObjects ≈ ActiveRecord Models
Rails Model:
class User < ApplicationRecord
has_many :posts
has_many :comments
validates :email, presence: true
def full_name
"#{first_name} #{last_name}"
end
end
Unity GameObject:
// Player GameObject with components
public class Player : MonoBehaviour
{
public float speed = 5f;
public float jumpForce = 400f;
private Rigidbody2D rb;
private bool isGrounded;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
Move();
Jump();
}
}
Similarities:
- Both represent entities in your application
- Both can have properties and behaviors
- Both interact with other objects
Differences:
- GameObjects exist in 3D space
- GameObjects run every frame
- GameObjects have visual representations
2. Components ≈ Rails Concerns
Rails Concern:
module Trackable
extend ActiveSupport::Concern
included do
after_create :track_creation
after_update :track_update
end
def track_activity(action)
# Track user activity
end
end
class User < ApplicationRecord
include Trackable
end
Unity Component:
// Reusable component for any GameObject
public class Health : MonoBehaviour
{
public int maxHealth = 100;
private int currentHealth;
void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
if (currentHealth <= 0) {
Die();
}
}
void Die()
{
Destroy(gameObject);
}
}
// Attach to any GameObject that needs health
// Player, Enemy, Destructible objects, etc.
Similarities:
- Reusable functionality
- Can be mixed into different classes/objects
- Encapsulate specific behaviors
3. Prefabs ≈ Rails Partials
Rails Partial:
<!-- _user_card.html.erb -->
<div class="user-card">
<h3><%= user.name %></h3>
<p><%= user.email %></p>
<% if user.avatar.present? %>
<%= image_tag user.avatar %>
<% end %>
</div>
<!-- Usage -->
<%= render 'user_card', user: @user %>
Unity Prefab:
// Create a Coin prefab with:
// - Sprite Renderer (visual)
// - Collider (for collection)
// - Coin script (behavior)
public class Coin : MonoBehaviour
{
public int value = 10;
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player")) {
GameManager.Instance.AddScore(value);
Destroy(gameObject);
}
}
}
// Instantiate coins throughout the level
Instantiate(coinPrefab, spawnPosition, Quaternion.identity);
Similarities:
- Reusable templates
- Consistent behavior across instances
- Easy to update all instances
Building the Player Controller
Here's how I built player movement, thinking like a Rails developer:
1. Start with the Interface
public class PlayerController : MonoBehaviour
{
[Header("Movement Settings")]
public float moveSpeed = 5f;
public float jumpForce = 400f;
[Header("Ground Detection")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayerMask;
// Similar to Rails strong parameters - expose what you need
private Rigidbody2D rb;
private bool isGrounded;
private float horizontalInput;
}
2. Separate Concerns
void Update()
{
HandleInput(); // Like controller actions
CheckGrounded(); // Like model validation
Move(); // Like service objects
Jump(); // Like service objects
}
void HandleInput()
{
horizontalInput = Input.GetAxisRaw("Horizontal");
if (Input.GetButtonDown("Jump") && isGrounded) {
Jump();
}
}
void Move()
{
rb.velocity = new Vector2(horizontalInput * moveSpeed, rb.velocity.y);
// Flip sprite based on direction (like Rails helper methods)
if (horizontalInput > 0) {
transform.localScale = new Vector3(1, 1, 1);
} else if (horizontalInput < 0) {
transform.localScale = new Vector3(-1, 1, 1);
}
}
void Jump()
{
rb.AddForce(Vector2.up * jumpForce);
}
void CheckGrounded()
{
isGrounded = Physics2D.OverlapCircle(groundCheck.position,
groundCheckRadius,
groundLayerMask);
}
3. Add Validation and Error Handling
void Start()
{
// Like Rails model validations
rb = GetComponent<Rigidbody2D>();
if (rb == null) {
Debug.LogError("PlayerController requires Rigidbody2D component!");
}
if (groundCheck == null) {
Debug.LogError("GroundCheck transform not assigned!");
}
}
// Like Rails rescue blocks
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy")) {
TakeDamage(10);
}
}
void TakeDamage(int damage)
{
// Like Rails error handling
try {
health -= damage;
if (health <= 0) {
GameOver();
}
} catch (System.Exception e) {
Debug.LogError($"Error taking damage: {e.Message}");
}
}
Game Manager Pattern (Like ApplicationController)
Just like Rails has ApplicationController, Unity games often have a GameManager:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
[Header("Game State")]
public int score = 0;
public int lives = 3;
public bool isGamePaused = false;
[Header("UI References")]
public Text scoreText;
public Text livesText;
public GameObject gameOverPanel;
void Awake()
{
// Singleton pattern (like Rails application instance)
if (Instance != null && Instance != this) {
Destroy(this);
} else {
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
void Start()
{
UpdateUI();
}
// Like controller actions
public void AddScore(int points)
{
score += points;
UpdateUI();
// Like Rails callbacks
if (score > 1000) {
UnlockAchievement("High Score");
}
}
public void LoseLife()
{
lives--;
UpdateUI();
if (lives <= 0) {
GameOver();
}
}
void UpdateUI()
{
// Like Rails view updates
scoreText.text = "Score: " + score;
livesText.text = "Lives: " + lives;
}
void GameOver()
{
isGamePaused = true;
gameOverPanel.SetActive(true);
Time.timeScale = 0f; // Pause game
}
}
Collectibles System (Like Rails Resources)
Creating a coin collection system felt like building a Rails resource:
// Like a Rails model
[System.Serializable]
public class Collectible
{
public string name;
public int value;
public Sprite sprite;
public AudioClip collectSound;
}
// Like a Rails controller
public class CollectibleController : MonoBehaviour
{
public Collectible collectibleData;
void Start()
{
// Like Rails view rendering
GetComponent<SpriteRenderer>().sprite = collectibleData.sprite;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player")) {
Collect(other.gameObject);
}
}
void Collect(GameObject player)
{
// Like Rails controller action
GameManager.Instance.AddScore(collectibleData.value);
// Like Rails after_action callback
PlayCollectEffect();
// Like Rails destroy action
Destroy(gameObject);
}
void PlayCollectEffect()
{
// Like Rails flash messages
if (collectibleData.collectSound != null) {
AudioSource.PlayClipAtPoint(collectibleData.collectSound, transform.position);
}
}
}
Enemy AI (Like Rails Services)
Creating enemy behavior felt like writing Rails service objects:
public class EnemyAI : MonoBehaviour
{
[Header("AI Settings")]
public float moveSpeed = 2f;
public float detectionRange = 5f;
public float attackRange = 1.5f;
private Transform player;
private Rigidbody2D rb;
private enum EnemyState { Patrol, Chase, Attack }
private EnemyState currentState = EnemyState.Patrol;
void Start()
{
rb = GetComponent<Rigidbody2D>();
player = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update()
{
// Like Rails service object with different actions
switch (currentState) {
case EnemyState.Patrol:
Patrol();
break;
case EnemyState.Chase:
ChasePlayer();
break;
case EnemyState.Attack:
AttackPlayer();
break;
}
CheckPlayerDistance();
}
void Patrol()
{
// Basic back-and-forth movement
rb.velocity = new Vector2(moveSpeed, rb.velocity.y);
}
void ChasePlayer()
{
Vector2 direction = (player.position - transform.position).normalized;
rb.velocity = new Vector2(direction.x * moveSpeed, rb.velocity.y);
}
void AttackPlayer()
{
// Attack logic here
Debug.Log("Attacking player!");
}
void CheckPlayerDistance()
{
float distanceToPlayer = Vector2.Distance(transform.position, player.position);
// Like Rails conditional logic
if (distanceToPlayer <= attackRange) {
currentState = EnemyState.Attack;
} else if (distanceToPlayer <= detectionRange) {
currentState = EnemyState.Chase;
} else {
currentState = EnemyState.Patrol;
}
}
}
Scene Management (Like Rails Routing)
Unity's scene system felt similar to Rails routing:
public class SceneManager : MonoBehaviour
{
// Like Rails routes
public void LoadMainMenu()
{
UnityEngine.SceneManagement.SceneManager.LoadScene("MainMenu");
}
public void LoadLevel(int levelNumber)
{
UnityEngine.SceneManagement.SceneManager.LoadScene($"Level{levelNumber}");
}
public void RestartCurrentLevel()
{
Scene currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
UnityEngine.SceneManagement.SceneManager.LoadScene(currentScene.name);
}
// Like Rails redirect_to with parameters
public void LoadLevelWithScore(int levelNumber, int currentScore)
{
PlayerPrefs.SetInt("CarryOverScore", currentScore);
LoadLevel(levelNumber);
}
}
What Surprised Me
1. Performance Matters More
In Rails, I could be somewhat careless with database queries and fix them later. In Unity, every frame counts:
// Bad - creates garbage every frame
void Update()
{
Vector3 playerPosition = new Vector3(player.x, player.y, 0f);
}
// Good - reuse variables
private Vector3 playerPosition;
void Update()
{
playerPosition.Set(player.x, player.y, 0f);
}
2. Visual Debugging is Amazing
Unity's inspector lets you see and modify values in real-time while the game runs. It's like having rails console
but visual and live.
3. Component Composition is Powerful
Instead of inheritance (like Rails STI), Unity favors composition. You build complex behaviors by combining simple components.
4. State Management is Critical
Games have much more complex state than web apps. You need to track player position, health, inventory, level progress, etc., all updating every frame.
Common Mistakes I Made
1. Overusing Update()
// Bad - checking every frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) {
Jump();
}
}
// Better - use events or coroutines for less frequent checks
2. Not Using Object Pooling
// Bad - creates/destroys objects constantly
Instantiate(bulletPrefab, firePosition, Quaternion.identity);
// Better - reuse objects from a pool
BulletPool.Instance.GetBullet();
3. Tight Coupling
// Bad - direct references everywhere
public class Enemy : MonoBehaviour
{
public Player player;
public GameManager gameManager;
public UIManager uiManager;
}
// Better - use events or singletons
public class Enemy : MonoBehaviour
{
void Die()
{
EventManager.TriggerEvent("EnemyDied", this);
}
}
Key Takeaways
What Transferred Well from Rails:
- Object-oriented thinking
- Separation of concerns
- Component-based architecture
- Debugging and testing mindset
- Reading documentation and community resources
What Was Different:
- Performance optimization mindset
- Frame-based instead of request-based thinking
- Visual and spatial reasoning
- Real-time system constraints
- Much more complex state management
Skills I Developed:
- Understanding game loops and frame rates
- Working with physics and collisions
- Managing complex object hierarchies
- Optimizing for performance
- Visual design and user experience
Next Steps in Game Development
After completing my first platformer, I want to explore:
- Multiplayer networking (feels like Rails API development)
- Save/load systems (like Rails database persistence)
- Procedural generation (like Rails factories for content)
- Mobile deployment (like Rails deployment to Heroku)
For Rails Developers Considering Unity
You'll feel comfortable with:
- C# syntax (similar to Ruby)
- Component patterns
- Documentation and community
- Problem-solving approaches
You'll need to learn:
- Game-specific concepts (physics, rendering, audio)
- Performance optimization techniques
- Visual design principles
- Real-time programming patterns
Start with:
- Simple 2D games
- Unity's official tutorials
- Small, achievable projects
- Prototype first, polish later
Conclusion
Building my first Unity game as a Rails developer was challenging but rewarding. Many fundamental programming concepts transferred well, but the real-time, visual nature of games required new ways of thinking.
The biggest lesson: good software design principles apply everywhere. Whether you're building a web app or a game, clean code