Sistema de Puntuació Multi-Mètrica
Un model de puntuació dinàmic que rastreja qualsevol nombre de mètriques amb nom a través d'un únic servei, retornant instantànies immutables que les regles de joc no han de posseir mai.
Per a què serveix aquest sistema
La majoria de jocs necessiten més d'un número per descriure el rendiment del jugador. Un shooter rastreja kills, precisió i temps. Un plataformes rastreja monedes, vides perdudes i millor temps per volta. Un joc de ritme rastreja notes encertades, combo i una puntuació final ponderada. Cada projecte reinventa el mateix cablejat: un manager estàtic, un munt de floats públics i un HUD que els llegeix directament.
L'agregat Score de Serenity reemplaça aquest cablejat amb un servei propi. Les mètriques es registren per nom a l'inici i es muten mitjançant operacions tipades. El servei mai sap el que kills o precisió signifiquen per al joc — només sap com sumar, restar, multiplicar, dividir, assignar i restablir un valor amb nom, i retornar el resultat com una instantània immutable.
El problema a Unity
Els singletons de puntuació es trenquen en el moment que un joc necessita dos contextos de puntuació — una puntuació per nivell i una puntuació total de la partida, per exemple — o quan una característica com els multiplicadors de combo necessita aplicar-se en diverses mètriques alhora. Els floats públics en un MonoBehaviour fan que els HUDs escriguin directament en l'estat de joc, cosa que converteix qualsevol refactor en una cerca i substitució manual per cada escena i cada script que mai va cridar `ScoreManager.Instance.kills++`.
La persistència és l'altre punt de fallada. Desar una puntuació màxima implica accedir a la forma que tingui el singleton en aquell moment. No hi ha cap contracte estable, no hi ha un moment clar de captura i no hi ha manera de comparar dues sessions sense duplicar la lògica de lectura a la pantalla de desat i a la de classificació.
Com ho aborda Serenity
Serenity exposa la puntuació a través d'IScoreService. Cada mètrica es registra amb una ScoreKey — un identificador de cadena immutable — i un ScoreMetricKind que descriu com ha d'interpretar-se el valor: Integer, Float, Percentage o Time. Un cop registrada, la mètrica pot modificar-se mitjançant Add, Subtract, Multiply, Divide, Set o ResetKey, i tot el model pot netejar-se amb ResetAll. Cap operació retorna res; el servei posseeix l'estat.
El contracte d'instantània és el punt de disseny central. Cridar GetSnapshot retorna un ScoreSnapshot: un struct immutable que conté un diccionari de només lectura amb cada ScoreValue actual indexat per ScoreKey. Els HUDs, les pantalles de classificació i la capa de persistència reben la mateixa instantània. ScoreRecord captura el millor, pitjor, promig i nombre de sessions d'una mètrica al llarg del temps, amb IScoreRepository com a port de persistència perquè l'estratègia d'emmagatzematge mai filtri al domini.
Les mètriques ja no s'han de registrar des de codi. Un asset UnityScoreSettings llista files Key/Kind a través de l'assistent Score Settings (Tools ▸ Serenity ▸ Create ▸ Score ▸ Score Settings), i la tasca InstallScore de la Pipeline d'Inicialització registra cada fila declarada a l'arrencada — de manera additiva a, mai en substitució de, les crides a IScoreService.Register del teu propi codi. Score arrenca ara com a part del pipeline estàndard en lloc d'existir només quan un test el construeix directament.
Dues peces tanquen el forat entre una Transform en moviment i una partida puntuada. UnityTransformDistanceScoreMeter alimenta una mètrica declarada amb la distància recorreguda per una Transform — una màscara per eix decideix què compta, Score Per Unit escala la recompensa, i una salvaguarda Max Delta Per Frame torna a sembrar l'origen en lloc de pagar un teletransport o un respawn. AddScoreSignal defineix un parell Key/Delta com a senyal entrant sense codi, de manera que qualsevol disparador tipus SignalEmitterComponent pot atorgar una quantitat fixa a una mètrica sense una línia de glue de gameplay.
Score i Classificacions es troben en una garantia d'ordre d'arrencada i un component. InstallScore sempre registra les mètriques abans que corri InstallLeaderboard, així que una classificació mai demana una ScoreKey que encara no existeix, i UnityLeaderboardScoreSubmitter embolcalla el flux d'enviar la puntuació actual en una única crida SubmitNow() sense paràmetres — sense construir un UnityLeaderboardScoreBridge a mà des del clic d'un botó o un SignalReactionComponent.
Com encaixa a Serenity
Score viu al namespace Serenity.Score i segueix la mateixa estructura per capes que la resta d'agregats de Serenity. La capa de Domini defineix els value objects ScoreKey, ScoreValue i ScoreSnapshot, l'entitat ScoreRuntime, l'enum ScoreMetricKind i el tipus de registre ScoreRecord. La capa d'Aplicació exposa IScoreService i el port de persistència IScoreRepository. La capa d'Infraestructura proporciona UnityScoreService i UnityScoreRepository com a implementacions concretes. ScoreInstaller i UnityScoreInstaller registren-ho tot a través de la pipeline d'inicialització.
Score coopera amb l'agregat Game Session, que dispara ResetAll a l'inici de cada sessió i crida GetSnapshot al final perquè el resultat pugui persistir-se a través d'IScoreRepository. L'Event Dispatcher porta la instantània a qualsevol component HUD que s'hagi subscrit als senyals de canvi de puntuació, de manera que el HUD mai manté una referència al servei.
Combo no té pàgina pròpia a la landing, així que el seu contracte viu aquí: IComboService (AddCombo, BreakCombo, GetSnapshot) rastreja un únic comptador de kills consecutives de la sessió, i el glue que el crida despatxa ComboIncreasedSignal i ComboBrokenSignal. El servei no aplica regles pròpies sobre quan puja o es trenca el combo — aquest criteri pertany a la capa de joc que el consumeix — però el comptador que produeix és exactament el que alimenta un multiplicador de puntuació: el glue del joc sol cridar Multiply en la ScoreKey corresponent usant el combo actual, escalant els punts que val una kill sense que Score i Combo s'acoblin directament entre si.
Flux de treball pràctic
- Defineix les ScoreKeys com a constants o una classe estàtica perquè tots els sistemes facin referència als mateixos identificadors.
- Registra cada mètrica a l'inici de la sessió cridant IScoreService.Register amb el ScoreMetricKind corresponent, o declara files Key/Kind en un asset UnityScoreSettings i deixa que InstallScore les registri a l'arrencada.
- Dispara Add, Subtract, Multiply, Divide o Set des de la lògica de joc en resposta a events de gameplay — o col·loca un UnityTransformDistanceScoreMeter en un objecte en moviment, o connecta AddScoreSignal a un disparador, per puntuar sense codi.
- Crida IComboService.AddCombo() en una kill consecutiva i BreakCombo() en rebre dany o expirar el temps, despatxant ComboIncreasedSignal / ComboBrokenSignal al costat de cada crida perquè el HUD i Score reaccionin.
- Deixa que el glue del combo cridi Multiply en la ScoreKey objectiu usant CurrentCombo quan hi hagi un multiplicador de combo actiu.
- Subscriu-te a l'event de canvi de puntuació a través de l'Event Dispatcher per actualitzar el HUD sense fer polling.
- Crida GetSnapshot al final d'una sessió o punt de control i passa-ho a IScoreRepository.Save per persistir-lo, o crida UnityLeaderboardScoreSubmitter.SubmitNow() per desar la instantània i enviar-la a una taula en un sol pas.
Què inclou
- Interfície de servei IScoreService amb Add, Subtract, Multiply, Divide, Set, ResetKey i ResetAll
- Claus de mètrica amb nom mitjançant el value object ScoreKey — qualsevol identificador de cadena, immutable i hashable
- Enum de tipus de mètrica ScoreMetricKind: Integer, Float, Percentage i Time
- ScoreValue que combina un valor float amb el seu tipus en un únic struct immutable
- ScoreSnapshot: diccionari immutable de només lectura de totes les mètriques actuals, segur per passar entre sistemes
- ScoreRecord per a persistència: millor, pitjor, promig i nombre de sessions per clau de mètrica
- Port de persistència IScoreRepository amb Save, TryLoadLatest, LoadAll i Clear
- UnityScoreInstaller registra el servei a través de la pipeline d'inicialització estàndard de Serenity, connectat mitjançant la tasca de pipeline InstallScore
- Mètriques declaratives mitjançant UnityScoreSettings (files Key/Kind) i l'assistent Score Settings — sense crides a Register escrites a mà
- Component d'escena UnityTransformDistanceScoreMeter: màscara per eix, escalat Score Per Unit i salvaguarda de teletransport/respawn per a puntuació per distància
- AddScoreSignal per atorgar punts sense codi, amb delta constant, des de qualsevol disparador que emeti senyals
- Agregat Combo: IComboService amb AddCombo, BreakCombo i GetSnapshot; ComboIncreasedSignal i ComboBrokenSignal com a superfície pública d'events; el comptador de combo sol alimentar un multiplicador de puntuació via Multiply
Quan fer-lo servir
- Jocs que rastreja dues o més dimensions de puntuació independents com kills, temps, precisió o combo.
- Projectes que necessiten un contracte d'instantània estable perquè HUDs, fitxers de desat i classificacions llegeixin les mateixes dades.
- Bases de codi que volen que el sistema de combo o sessió enviï multiplicadors a la puntuació sense acoblar-se directament a un singleton.
- Dissenyadors que volen puntuació per distància o disparada per senyals configurada en un component o un asset en lloc d'escrita en codi.
- Qualsevol projecte que hagi superat un score manager estàtic i necessiti un servei reemplaçable i testejable darrere d'una interfície.
Sistemes relacionats
Utilitza Serenity quan vulguis que la puntuació sigui un servei de domini propi — un que parla en claus amb nom i instantànies immutables, roman aliè a les regles de joc, arrenca automàticament al costat del comptador de combo que el multiplica i s'integra nètament amb el cicle de vida de la sessió, la classificació i el HUD a través de l'Event Dispatcher.
English
Español
Català