In-Game Calendar — Chris Burns
Asset Store Discord GitHub
In-Game Calendar wall calendar prop in the demo scene

The WallCalendar prop in the included demo scene — month illustration on top, day grid with red X marks crossing off past days, and the SunController driving directional lighting.

Overview

In-Game Calendar gives your game world a living, functional sense of time. A single GameCalendar scene singleton drives everything — either mirroring the player's real system clock or advancing at a configurable in-game speed. The WallCalendar prop reads from it every day tick and updates a 3D wall calendar mesh: the correct month illustration is shown, days that have passed are crossed off in red, and today's cell is highlighted.

When the month rolls over, the CalendarPageTurn component plays a skinned mesh page-turn animation, snapping a texture of the completed calendar before the new month loads in. A SunController demo component drives a directional light as a sun with seasonal arc variation.

Requirements

RequirementDetail
Unity Version2022.3 LTS or higher
TextMeshProRequired — install via Package Manager if not present
Render PipelineURP, HDRP, and Built-in all supported
Scripting BackendMono and IL2CPP both supported
PlatformsAll Unity-supported platforms
📝
If TextMeshPro is not in your project, go to Window → Package Manager → TextMeshPro and install it before importing this asset. When prompted, also import the TMP Essential Resources.

Package Contents

InGameCalendar/ ├── Animation/ │ ├── PageTurn.anim — skinned mesh page-turn animation clip │ └── WallCalendar.controller — animator controller with PageTurn trigger ├── Audio/ │ └── WallCalendarPageTurn.wav — paper page-turn sound effect ├── Demo/ │ ├── DemoScene.unity — fully configured demo scene │ └── Scripts/ │ └── SunController.cs — directional light sun driver ├── Fonts/ │ ├── GochiHand-Regular SDF.asset — handwritten font for X marks │ └── Roboto-VariableFont SDF.asset — day number labels ├── Materials/ │ ├── Calendar.mat │ ├── Calendar-Landscapes.mat │ ├── CalendarDaysMondayStart.mat │ └── CalendarDaysSundayStart.mat ├── Models/ │ └── WallCalendar.fbx — optimised wall calendar mesh ├── Prefabs/ │ ├── WallCalendar.prefab — ready-to-use calendar (default art) │ ├── WallCalendar-Landscapes.prefab │ ├── DayMarker.prefab — single day cell prefab │ └── DaysOfTheWeek.prefab — day-of-week column headers ├── Scripts/ │ ├── GameCalendar.cs │ ├── WallCalendar.cs │ ├── DayMarker.cs │ ├── CalendarPageTurn.cs │ └── Editor/ │ └── WallCalendarEditor.cs └── Textures/ ├── CalendarImages.png — default 4×4 atlas ├── InGameCalendar-Cars.png — cars theme atlas ├── InGameCalendar-Landscapes.png — landscapes theme atlas ├── WallCalendarMondayStartRT.renderTexture └── WallCalendarSundayStartRT.renderTexture

Quick Start

Demo scene open in the Unity Editor

The included demo scene open in Unity, showing the WallCalendar prop, GameCalendar singleton, and SunController all wired up and ready to run.

Option A — Use the Demo Scene

Open InGameCalendar/Demo/DemoScene and press Play. Everything is already connected.

Option B — Add to your own scene

1
Add the GameCalendar Create an empty GameObject (e.g. _GameSystems) and add the GameCalendar component via Add Component → In Game Calendar → Game Calendar. Configure the starting date and time speed in the Inspector.
2
Drop in the prefab Drag InGameCalendar/Prefabs/WallCalendar.prefab (or WallCalendar-Landscapes.prefab) into your scene. Position and rotate it on your wall surface.
3
Press Play The WallCalendar auto-finds the GameCalendar singleton. The grid, X marks, and month label update immediately. No further setup required for a basic configuration.
4
(Optional) Add the page-turn animator Add a CalendarPageTurn component to the same GameObject (or any object in the scene). Assign the animated page Renderer and RenderTexture references. See the CalendarPageTurn section for full setup.
💡
Right-click the WallCalendar component header and select Force Refresh (Editor) to preview the calendar visuals without entering Play Mode.

GameCalendar Singleton

Scene singleton that tracks in-game date and time. Add once to a persistent GameObject. All other components find it automatically via GameCalendar.Instance.

Namespace: ChrisBurns.InGameCalendar

GameCalendar component Inspector

The GameCalendar Inspector — toggle between Real-World Clock and In-Game mode, set the starting date, and control how fast in-game time advances.

Inspector Properties

useRealWorldClock
bool
When enabled, the game clock mirrors the player's system date and time exactly. Starting date and time speed settings are ignored.
startYear
int
The year the in-game clock begins at. (In-game mode only.)
startMonth
CalendarMonth enum
The month the in-game clock begins at. (In-game mode only.)
startDay
int
Day of the month the in-game clock begins at. (In-game mode only.)
startHour
float (0–23)
Hour of the day the clock begins at. (In-game mode only.)
gameMinutesPerRealSecond
float
How many in-game minutes pass per real-world second.
1 = real time (1 day = 24 real hours)
60 = 1 game hour per real second (~24s per day)
1440 = 1 full game day per real second
Default: 60

Public API

MemberTypeDescription
Instancestatic propertyScene singleton reference
Yearint (read-only)Current in-game year
Monthint 1–12 (read-only)Current in-game month
Dayint 1–31 (read-only)Current in-game day
Hourfloat 0–24 (read-only)Current in-game hour (fractional)
DayOfWeekSystem.DayOfWeek (read-only)Current day of the week
GameMinutesPerRealSecondfloat (read-only)Current time-scale setting
ClockInterfaceIGameClock (read-only)Interface view of this clock (implements IScalableClock and IMonthChangedNotifier) for systems that shouldn't depend on GameCalendar directly
JumpToDate(month, day = 1, hour = 8f)methodInstantly jump to a date. In-game mode only.
SetCurrentHour(float)methodSet time of day without changing the date. In-game mode only.
SetTime(string)methodSet time from a "H:MM" or "HH:MM" string.
// Example: jump to July 4th at 9am
GameCalendar.Instance.JumpToDate(CalendarMonth.July, 4, 9f);

// Example: set time of day
GameCalendar.Instance.SetTime("14:30");

// Example: read current date
int day   = GameCalendar.Instance.Day;
int month = GameCalendar.Instance.Month;

Events

OnDayChanged
Action
Fired once whenever the in-game day rolls over. Subscribe here to drive any system that needs to update daily.
OnMonthChanged
Action
Fired once whenever the in-game month rolls over.
void Start()
{
    GameCalendar.Instance.OnDayChanged  += HandleNewDay;
    GameCalendar.Instance.OnMonthChanged += HandleNewMonth;
}

void HandleNewDay()  { /* spawn daily events, refresh quest states, etc. */ }
void HandleNewMonth() { /* change season, update shop inventory, etc. */ }

WallCalendar Prop Controller

Reads from GameCalendar on the OnDayChanged event and updates all prop visuals: the month illustration texture, month name label, day-of-week column headers, and the 35-cell (5 rows × 7 columns) grid of DayMarker cells. Can alternatively sync to the Cozy: Stylized Weather time system — see Cozy Weather Integration.

Namespace: ChrisBurns

WallCalendar component Inspector

The WallCalendar Inspector — assign the GameCalendar reference, top image Renderer, month label, grid container, and configure colour settings for past, present, and future day cells.

Inspector Properties

gameCalendar
GameCalendar
Reference to the scene GameCalendar. Leave null to auto-find via FindFirstObjectByType on Awake.
topImageRenderer
Renderer
The Renderer whose material UV offset is updated each month to show the correct atlas illustration.
topImageTexProperty
string
Material texture property name. Use _BaseMap for URP/HDRP or _MainTex for Built-in.
monthLabelTMP
TMP_Text
TextMeshPro text component that displays the month name (e.g. "August").
dayOfWeekLabelPrefab
GameObject
Prefab with a TMP_Text component. Seven copies are instantiated automatically as column headers (M T W T F S S or S M T W T F S).
weekStartsOnSunday
bool
When true the grid starts on Sunday. When false (default) the grid starts on Monday.
dayMarkerPrefab
DayMarker
Prefab for each day cell. Assign the DayMarker.prefab from the Prefabs folder. Cells are auto-instantiated via Populate Grid.
gridContainer
Transform
Parent transform for the 35 instantiated grid cells. Should have a GridLayoutGroup for automatic cell positioning. Defaults to this transform if left null.
showDateNumbers
bool
When true each cell's dateLabel TMP_Text shows the day number.
pastDayColour
Color
Colour applied to the X mark on days that have passed. Default: red.
todayColour
Color
Background tint applied to the current day's cell. Default: orange.
futureDayColour
Color
Tint applied to future-day and spacer cell backgrounds. Past-day cell backgrounds also use this colour — only the X mark itself gets pastDayColour. Default: transparent.
dayOfWeekContainer
Transform
Parent transform for the 7 instantiated day-of-week header labels. Defaults to this transform if left null.
atlasColumns / atlasRows
int
Layout of the month illustration texture atlas. Default: 4 × 4.
coverCellIndex
int
Atlas cell index used for the calendar cover page (shown before any month). Default: 12.
useCozyWeather
bool
When enabled the calendar reads date and time from the Cozy Weather Sphere instead of GameCalendar. Requires the Cozy Weather package and the COZY_WEATHER scripting define — see Cozy Weather Integration. Default: false.
📝
Months that would need a sixth row (e.g. a 31-day month starting in the last column) wrap their final days into the unused leading cells of the first row, so the grid never exceeds 35 cells.

Events

OnBeforeMonthRefresh
Action<int>
Fired when the month changes, just before visuals update. Receives the old month number (1–12). CalendarPageTurn subscribes here to snapshot the live RenderTexture while it still shows the completed month.

Scene Setup (from scratch)

WallCalendar GameObject hierarchy in the Unity Editor

The WallCalendar GameObject hierarchy — the GridContainer holds the GridLayoutGroup that positions 35 DayMarker cells, with a separate row above for the day-of-week column headers.

The included prefab is already fully configured. If you need to set up a WallCalendar manually:

1
Add the WallCalendar mesh — Place the WallCalendar.fbx model in your scene and assign materials from the Materials folder.
2
Add a WallCalendar component — Assign the topImageRenderer to the top-panel mesh Renderer. Assign a TMP_Text to monthLabelTMP.
3
Set up the grid container — Create a child Canvas (World Space), add a Panel with a GridLayoutGroup. Assign this panel as gridContainer.
4
Assign the DayMarker prefab — Drag DayMarker.prefab into the dayMarkerPrefab slot. Click Populate Grid in the Inspector to instantiate 35 cells.
5
Assign the DaysOfTheWeek prefab — Set up a header row above the grid. Assign to dayOfWeekLabelPrefab and click Populate Day Labels.
⚠️
The Populate Grid button destroys existing cell children before recreating them. If you have customised individual cells, back them up first.

DayMarker Cell Component

Placed on the root of each day-cell prefab. Controlled entirely by WallCalendar — you do not need to call any methods on it directly.

Namespace: ChrisBurns

DayMarker prefab and cell states

The DayMarker prefab Inspector — assign the X mark object, cell image, and date label. Each cell is automatically set to one of three states: past day (red X), today (highlighted), or future day (neutral).

Inspector Fields

xObject
GameObject
Child GameObject holding the X graphic (a TMP_Text with an "×" character). Activated for past days only.
markerRenderer
Renderer
For 3D / world-space setups — tinted via MaterialPropertyBlock. Leave null for pure UI setups.
markerImage
UI.Image
For canvas / UI setups — Image component tinted for today's highlight. Takes priority over markerRenderer when set.
dateLabel
TMP_Text
Displays the day number (e.g. "14"). Cleared for spacer cells before and after the active days of the month.

CalendarPageTurn Animation

Listens for WallCalendar.OnBeforeMonthRefresh, snapshots the live RenderTexture, and triggers a skinned mesh page-turn animation.

Namespace: ChrisBurns

Page-turn animation mid-flip

The page-turn animation mid-flip — the skinned mesh page peels back showing the previous month's illustration on the front and the incoming month's live RenderTexture on the reverse.

💡
The page-turn is entirely optional. The WallCalendar prop works without it — months simply update instantly.

Setup

1
Add the component — Add CalendarPageTurn to any GameObject. Assign the WallCalendar reference — if left null it is auto-found by searching this GameObject, then its parents, its children, and finally the whole scene.
2
Assign the animated page — Assign the page GameObject to animatedPage and its Animator to pageAnimator. Point animatedPageRenderer to the SkinnedMeshRenderer on the page mesh, and set illustrationSlot and gridSlot to the correct material slot indices.
3
Assign the RenderTexture — Set liveCalendarRT to the included WallCalendarMondayStartRT or WallCalendarSundayStartRT asset.
4
Add Animation Event — At the end of the PageTurn clip, add an Animation Event calling OnPageTurnComplete(). This hides the page and restores the live RT.
5
(Optional) Sound — Assign an AudioSource and the included WallCalendarPageTurn.wav clip. Plays automatically when the page turn triggers, or call PlayPageTurnSound() from an Animation Event for precise timing.
📝
By default Suppress On Start skips the animation and sound on the first month change after the game starts, so the calendar jumps straight to the correct date (e.g. when loading a save). Disable it to animate every month change.

Inspector Properties

wallCalendar
WallCalendar
The WallCalendar to listen to. Auto-found if left null (this GameObject → parents → children → scene-wide).
pageAnimator
Animator
Animator on the animated page. Must have a Trigger parameter named PageTurn.
animatedPage
GameObject
The animated page object. Hidden on Awake; shown while the page-turn plays, then hidden again by OnPageTurnComplete().
animatedPageRenderer
Renderer
The SkinnedMeshRenderer on the animated page object.
illustrationSlot
int
Material slot index for the month illustration texture. The previous month's atlas UV is set here before the animation plays. Default: 0.
gridSlot
int
Material slot for the days-grid render texture. Shows the live calendar grid on the back of the page mid-turn. Default: 1.
illustrationTexProperty / gridTexProperty / lowerPanelTexProperty
string
Material texture property names. Default _BaseMap (URP/HDRP) — use _MainTex for Built-in.
atlasColumns / atlasRows
int
Must match the atlas settings on WallCalendar. Default: 4 × 4.
liveCalendarRT
RenderTexture
The live RenderTexture the calendar camera renders to.
lowerPanelRenderer
Renderer
The main lower panel on the calendar mesh. Frozen on a snapshot during the animation; restored to the live RT when complete.
audioSource / pageTurnSound
AudioSource / AudioClip
Optional. When both are assigned the page-turn sound plays via PlayOneShot.
suppressOnStart
bool
Skips the animation and sound on the first month change after the game starts, so the calendar jumps straight to the correct date. Default: true.

SunController Demo

Drives a scene Directional Light as a sun, reading GameCalendar.Hour and Month every Update. Requires a Light component on the same GameObject.

Namespace: ChrisBurns.InGameCalendar.Demo

northernHemisphere
bool
True = sun arcs through the south at noon. False (default) = arcs through the north. Set to false for Australian settings.
summerPeakMonth
int 1–12
Month with the highest noon sun elevation. Default 1 (January) for southern hemisphere summer.
summerNoonElevation
float (degrees)
Maximum sun elevation at noon. Default 72°.
winterNoonElevation
float (degrees)
Minimum sun elevation at noon, six months after summerPeakMonth. Default 28°.
dayIntensity
float
Intensity of the directional light at full noon. Default 1.2.
duskFadeAngle
float (degrees)
Degrees either side of the horizon over which light intensity fades. Higher = softer dusk and dawn. Default 8°.
sunColour
Gradient
Light colour evaluated by normalised elevation. Time 0 = horizon (warm amber). Time 1 = noon (neutral white). Leave empty to keep the light's existing colour.

Texture Atlas

The top illustration panel uses a 4×4 UV-sliced texture atlas (16 cells total). Months map to cells 0–11; cells 12–15 are available for cover, back, and spare pages.

4x4 texture atlas showing all 12 month illustrations

The included CalendarImages.png — a 4×4 texture atlas containing all 12 month illustrations (cells 0–11) plus spare cells for cover, back, and blank pages. Swap this texture to use your own artwork.

RowCol 0Col 1Col 2Col 3
0 (top)JanuaryFebruaryMarchApril
1MayJuneJulyAugust
2SeptemberOctoberNovemberDecember
3Cover (cell 12)Back (cell 13)Blank (cell 14)Spare (cell 15)
📐
Three atlas variants are included: CalendarImages (default), InGameCalendar-Cars, and InGameCalendar-Landscapes. Swap by changing the texture on the Calendar.mat material.

Custom Art

To replace the month illustrations with your own artwork:

1
Create your atlas — Make a square image (e.g. 2048×2048) divided into a 4×4 grid. Place your 12 month images in cells 0–11, left-to-right top-to-bottom.
2
Import to Unity — Import as a standard 2D Texture. Set Wrap Mode to Clamp. No sprite sheet slicing needed.
3
Assign to the material — Drag your texture onto Calendar.mat's Base Map slot.
⚠️
If your atlas has a different number of rows or columns, update Atlas Columns and Atlas Rows on the WallCalendar component to match (and on CalendarPageTurn if you use the page-turn animation).

Week Start Day

Toggle the Week Starts On Sunday checkbox on the WallCalendar component. The grid columns and day-of-week headers update automatically — no scene rebuild needed.

Two RenderTexture + Material pairs are included: WallCalendarMondayStartRT and WallCalendarSundayStartRT. Use whichever matches your setting.

Cozy Weather Integration

WallCalendar can drive its date directly from Cozy: Stylized Weather (DistantLands) instead of GameCalendar — useful if Cozy is already your project's source of truth for time of day and seasons.

1
Install Cozy — Import the Cozy: Stylized Weather asset and add a Cozy Weather Sphere to your scene as usual.
2
Add the scripting define — Go to Edit → Project Settings → Player → Other Settings → Scripting Define Symbols and add COZY_WEATHER. This compiles in the integration code.
3
Enable it on the calendar — Tick Use Cozy Weather on the WallCalendar component. A GameCalendar is no longer required in the scene for the wall calendar itself.

How it works

In Cozy mode the calendar subscribes to Cozy's onNewDay event and reads the month, day, and year from the Cozy time module each refresh. The month is resolved from Cozy's own month name (so custom month-profile lengths are respected); if the name doesn't match a standard English month name, it falls back to the year-percentage. OnBeforeMonthRefresh still fires on month rollover, so the CalendarPageTurn animation works unchanged.

⚠️
Without the COZY_WEATHER define, the Use Cozy Weather checkbox has no effect — the integration code is compiled out and the calendar falls back to GameCalendar.
📝
Cozy mode only affects the wall calendar. Components that read from GameCalendar directly (e.g. the SunController demo) still need a GameCalendar in the scene — or simply use Cozy's own sun instead. The edit-mode Force Refresh (Editor) preview also requires a GameCalendar; in a Cozy-only scene, preview via Play Mode.

Configuring Time Speed

The gameMinutesPerRealSecond field on GameCalendar controls how fast in-game time passes.

ValueEffect
1Real time — 1 game day = 24 real hours
601 game hour per real second — 1 day ≈ 24 seconds (default)
7201 game day every ~2 real minutes
14401 full game day per real second
GameCalendar.Instance.gameMinutesPerRealSecond = 120f; // 2x speed

Scripting Integration

Subscribing to day and month changes

using ChrisBurns.InGameCalendar;

public class QuestSystem : MonoBehaviour
{
    void Start()
    {
        GameCalendar.Instance.OnDayChanged   += OnNewDay;
        GameCalendar.Instance.OnMonthChanged += OnNewMonth;
    }

    void OnDestroy()
    {
        // Always unsubscribe to prevent memory leaks
        if (GameCalendar.Instance != null)
        {
            GameCalendar.Instance.OnDayChanged   -= OnNewDay;
            GameCalendar.Instance.OnMonthChanged -= OnNewMonth;
        }
    }

    void OnNewDay()
    {
        int day   = GameCalendar.Instance.Day;
        int month = GameCalendar.Instance.Month;
        Debug.Log($"New day: {day}/{month}");
    }

    void OnNewMonth()
    {
        // Rotate stock, change season, etc.
    }
}

Forcing a calendar refresh from code

// After changing the date manually, force the wall calendar to refresh visuals.
// Note: WallCalendar lives in the ChrisBurns namespace (add: using ChrisBurns;)
FindFirstObjectByType<WallCalendar>().Refresh();

FAQ

The calendar shows the wrong month illustration

Check that Texture Property Name on WallCalendar matches your pipeline — _BaseMap for URP/HDRP, _MainTex for Built-in. Also verify Atlas Columns/Rows match your texture.

The day grid isn't positioned correctly

The grid container needs a GridLayoutGroup with a cell size and spacing that fits your mesh. Check the prefab's Grid Canvas settings for reference values.

The page-turn animation doesn't play

Ensure the Animator has a Trigger parameter named exactly PageTurn. Confirm the Animation Event at the end of the clip calls OnPageTurnComplete on the CalendarPageTurn component, not WallCalendar. Note also that with Suppress On Start enabled (the default), the very first month change after entering Play Mode is intentionally not animated.

Can I use this without the wall calendar mesh?

Yes — GameCalendar is completely decoupled. Use it as a standalone game clock and subscribe to its events to drive any other system.

Does this work with a custom time system?

Partially. GameCalendar.ClockInterface exposes the clock through the IGameClock, IScalableClock, and IMonthChangedNotifier interfaces, so your own systems can consume in-game time without depending on GameCalendar directly. WallCalendar itself accepts two time sources: a GameCalendar reference, or Cozy: Stylized Weather via the built-in Cozy integration — it does not accept an arbitrary custom IGameClock implementation.

Can I have multiple wall calendars in one scene?

Yes. Each WallCalendar subscribes to the same GameCalendar singleton independently. All instances update in sync.

Changelog

v1.0.1 — Asset Store Review Fixes

v1.0.0 — Initial Release

In-Game Calendar — © Chris Burns  |  Asset Store  |  Discord  |  chrisburns.com.au