Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

♟ Simple Chess 2D

UnityLicenseStatus


This started as a Unity 2D chess template — something reusable, data-driven, no hardcoded rules. Then at some point I thought it'd be fun to give it actual polish, got inspired by Balatro's aesthetic, and here we are.

The good news: it's still a template. The visual layer sits completely on top. The underlying architecture — ScriptableObject configs, modular move patterns, event-driven game flow — is untouched. You can strip the visuals and use this as a starting point for your own chess variant without fighting the code.


✨ Features

🎨 Visual

  • Balatro-inspired animated shader background
  • Smooth board flip animation for local 2-player
  • Drag-and-drop piece control with scale-up feedback
  • Highlight system for valid moves, captures, check

⚙️ Gameplay

  • Fully configurable pieces via ScriptableObjects — custom move patterns, no code needed
  • Castling, en passant, pawn promotion (with piece selection UI)
  • Per-team countdown timer
  • Check, checkmate, and stalemate detection

🔧 Technical

  • JSON save/load with multiple named slots
  • Audio manager with sound priority queue
  • Music crossfade between menu / game / game over
  • Pause menu, game over screen, save/load panel

🗂 Project structure

Scripts/
├── Config/
│ └── Configs.cs — ScriptableObjects: BoardConfig, PieceConfig, GameSetupConfig
├── Core/
│ ├── BoardGenerator.cs — spawns cells, CellToWorld/WorldToCell with rotation support
│ ├── Cell.cs — single cell: visuals, highlight, click event
│ ├── Piece.cs — piece component: drag, counter-rotation during board flip
│ └── PieceSpawner.cs — spawns and manages all piece GameObjects
├── Data/
│ └── DataModels.cs — serializable structs: SaveData, MoveRecord, GameState
├── Logic/
│ ├── BoardFlipper.cs — rotates board, pieces counter-rotate to stay upright
│ ├── MoveValidator.cs — move generation from MovePattern, check/mate detection
│ ├── SelectionHandler.cs — click + drag input, executes moves, handles promotion
│ ├── TimerManager.cs — per-team countdown
│ └── TurnManager.cs — turn flow, fires events for move/capture/castling/check
├── Other/
│ └── AnimatorSupport.cs — helper for triggering sounds from Animator
├── Save/
│ └── SaveManager.cs — JSON save slots in Application.persistentDataPath
├── UI/
│ ├── UIManager.cs — all panels: menu, HUD, pause, save/load, game over, promotion
│ └── ShaderController.cs — smooth property transitions on the background shader
├── GameManager.cs — central singleton, wires everything together
└── AudioManager.cs — priority queue for sounds, crossfade music

🎮 Scene hierarchy

GameManager — GameManager + SaveManager + AudioManager
Board — BoardGenerator
Pieces — PieceSpawner
Logic
├── MoveValidator
├── TurnManager
├── SelectionHandler
├── TimerManager
└── BoardFlipper
Canvas — UIManager
├── MainMenuPanel
├── HUDPanel
├── PauseMenuPanel
├── SaveLoadPanel
├── GameOverPanel
└── PromotionPanel
Main Camera

🔩 Configuring pieces

Each piece type is a PieceConfig ScriptableObject (Assets → Create → ChessTemplate → PieceConfig).

Move behavior is entirely data-driven through MovePattern entries:

FieldDescription
dRowRow delta per step. Flipped for team 1 automatically.
dColColumn delta per step.
applyDirectionToColAlso flip dCol for team 1. Use for pawn diagonal attacks.
slideRepeat until blocked — rook, bishop, queen.
canCaptureCan land on an enemy piece.
canMoveEmptyCan land on an empty cell. Set to false for pawn diagonals.
firstMoveOnlyOnly usable on the piece's first move. Pawn double-advance.

Pawn setup (4 patterns):

dRowdColapplyDirectionToColcanCapturecanMoveEmptyfirstMoveOnly
10
20
11
1-1

Special flags:

  • isRoyal — losing this piece triggers check/checkmate. King only.
  • canCastleWith — can castle with the royal piece. Rook only.
  • canPromote — triggers promotion on reaching the last rank. Pawn only.

🏗 Configuring the board

BoardConfig controls the visual side of the board — grid dimensions, cell size, light/dark colors or sprite overrides, highlight colors for selection/moves/captures/check, and border styling.

The board is always centered on the Board GameObject. Move the object to reposition everything.


📐 Starting layouts

GameSetupConfig defines the full starting state:

  • Which BoardConfig to use
  • Team definitions (name, color, index)
  • PiecePlacement list — PieceConfig + row + col + teamIndex
  • Rule toggles: enableCastling, enableEnPassant, enablePromotion

Team 0 starts at row 0 (bottom), team 1 at row 7 (top) for classic 8×8.


⚡ Special moves

CastlingMoveValidator checks that both king and rook are unmoved, the path is clear, and no traversed square is under attack. SelectionHandler.ExecuteCastling moves both pieces.

En passant — the validator tracks the last double pawn move in LastDoublePawnMove. The capturing pawn moves diagonally to an empty cell; RemovePiece clears the captured pawn at enPassantCaptureRow/Col.

Promotion — fires when a pawn with canPromote reaches the last rank. Input locks until the player picks a piece from UIManager.ShowPromotionPanel. No panel assigned → auto-promotes to the first entry in promotionConfigs.


🔄 Board flip

BoardFlipper rotates boardTransform. Each Piece calls transform.rotation = Quaternion.identity in LateUpdate to stay visually upright regardless of what the board is doing. CellToWorld and WorldToCell both factor in transform.rotation, so clicks and drag hit the right cells at any angle.

Two modes — automatic on turn change (autoFlipOnTurn), or via a manual button. flipDuration = 0 skips the animation.


🔊 Audio priority

When multiple events fire in one frame (move + check, for example), only the highest-priority sound plays. Everything goes through RequestSound and resolves in LateUpdate:

PriorityEvent
0Checkmate
1Check
2Capture
3Castling
4Move
5Game start
6Game over
7UI

UI sounds (PlayUIClick, PlayUIOpen) skip the queue and play immediately on a separate source.

Music crossfades between two AudioSources using a coroutine. Call PlayMenuMusic / PlayGameMusic / PlayGameOverMusic from GameManager at the right moments.


💾 Saves

JSON files land in Application.persistentDataPath/saves/.

PlatformPath
Windows%AppData%/../LocalLow/<Company>/<App>/saves/
macOS~/Library/Application Support/<Company>/<App>/saves/
Android / iOSApp sandbox

Each save stores the full board state, move history, current turn, and timer budgets. Multiple named slots supported.

⚠️pieceId in PieceConfig is the JSON key for piece type. Renaming it breaks existing saves.


🎨 Assets

All assets in this project — music, sprites, sound effects, and UI — are either free to use or made by me.

One exception: the animated background shader is taken directly from Balatro. It's included here purely for personal/educational use. If you're building something commercial, swap it out.


🛠 Requirements

  • Unity 2021.3 LTS or newer
  • TextMeshPro — Window → TextMeshPro → Import TMP Essentials
  • No external packages

About

A 2D chess game for Unity, built as a template first and then dressed up with Balatro-style visuals. Pieces and rules are set up through ScriptableObjects, so you can swap things around without touching the code. Has drag-and-drop, board flipping, JSON saves, and sound priorities. Use it as a base for your own thing, or just play it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages