Appearance
Appearance
You can now track your key performance indicators right from the LiveOps dashboard, no setup required. Head to the LiveOps dashboard metrics page to check it out.
Client-only patch releases can now be tagged with a Client Patch Version
identifier and the Client Compatibility settings in the Metaplay LiveOps dashboard has been extended to support specifying client patch version requirements per client platform. This is useful for the ability to release non-critical fixes to clients without requiring a server update or forcing a client update, while maintaining the ability to force clients to update to a specific patch version per platform at a later time.
The "LiveOps Events" page now has a visual timeline that you can use to organize and view all your events in. This first rollout introduces the concept of groups, rows and colors that can be used to organize your events. We will be working on more editing and navigation features for the next release and feedback is already welcome!
Metaplay now supports authenticating users by their Steam accounts on server login, see Implementing Steam Authentication for details!
OrderedDictionary -> MetaDictionary: The class OrderedDictionary
has been renamed to MetaDictionary
in preparation for supporting .NET9. This rename causes no change in functionality and any serialized data continues to be compatible. The reason for this is that .NET9 introduces System.Collections.Generic.OrderedDictionary
which technically could co-exist with Metaplay.Core.OrderedDictionary
but would necessitate using the namespace-qualified name of the class and would create the potential of accidentally using the system implementation where the Metaplay implementation was intended. See Migration Guide For Self-Service Customers for guidance on migrating your project.
New MetaConfigId Reference Type: We've introduced MetaConfigId
for optimizing the server memory footprint of games with large game configs and complicated experiments setups. MetaConfigId
, which remains a simple config ID in memory, can be used in place of MetaRef
, which resolves the referenced item into memory. For more information, see Using MetaConfigId Instead of MetaRef.
[BigQueryAnalyticsFormat(BigQueryAnalyticsFormatMode.ExtractDictionaryElements)]
can be used to format dictionary-like objects as if dictionary elements were object fields.SubscribeToAsync(..., bool allowMultiple = false)
to opt-in to allowing multiple subscriptions between two entities.EntityAskAsync(...)
calls and [EntityAskHandler]
definitions. You can opt in per request-response type pair by deriving them from EntityAskRequest<TResponse>
and EntityAskResponse
instead of plain MetaMessage
.IEntityClientContext
now has EarlyUpdate
. EarlyUpdate()
is called for every entity context before Update()
is called.insertAll
streaming API, with up to 2 TiB of data ingestion per month available for free.bigquery.tables.updateData
permissions.DynamicEnum
now supports declaring the enum values dynamically on initialization via a static method on the DynamicEnum
derived class identified by DynamicEnumFactoryAttribute
.GameConfigBuildUtil.PublishGameConfigArchiveToServerAsync()
now also supports setting the name and description.Type.HasCustomAttribute()
allows checking an attribute presence without allocation, unlike Type.GetCustomAttribute() != null
.MetaplayCoreOptions
for tagging the current ClientPatchVersion
and the associated logic for refusing unsupported client patch versions on client login based on the current client patch version requirements in ClientCompatibilitySettings
.ClientPatchVersionTooOld
and the associated client-side MetaplayConnection terminal error state. The default MetaplayClient
implementation maps this error into the existing ConnectionLostReason.ClientVersionTooOld
which is also used for the case when the client logic version is too old.MP_OD_01
and MP_OD_02
for renaming OrderedDictionary
to MetaDictionary
.RandomPCG
now has an overload for NextLong()
with a exclusive max parameter.MetaDuration.Second
, MetaDuration.Minute
, MetaDuration.Hour
and MetaDuration.Day
.IAdminApiServices
interface.TryReassignPlayer
API to the league integration, which allows a player to be reassigned to a new division within the same season.DefaultEnvironmentConfigProvider
's various path name properties are now correctly virtual, allowing you to override them in a derived class (which automatically will become the active provider thanks to the integration registry system).EntityAskAsync
and SubscribeToAsync
, will now throw EntityCrashedError
instead of UnexpectedEntityAskError
if entity crashes while processing some preceding message.SubscribeToAsync
will now throw EntityUnreachableError
if target entity cannot be reached.UnsubscribeFromAsync
will no longer throw TimeoutError
if unsubscription cannot be guaranteed by the time limit and instead returns false
.LiveOpsTimelineManager
, to reduce load and responsibilities from GlobalStateManager
which previously managed them. This is mostly an SDK-internal change and likely does not affect you. If you send messages directly to GlobalStateManager
for managing LiveOps Events, you now need to send those messages to LiveOpsTimelineManager
instead.PlayerAddMail
, PlayerDeleteMail
and PlayerForceDeleteMail
now use client/server action base classes rather than setting ModelActionExecuteFlags
via attribute to make it easier to understand whether they are client or server actions.game_entity_asks_total
and game_entity_ask_errors_total
metrics have their message
label values changed from <MessageType>
to Ask-<MessageType>
for EntityAsk
s.logger
or adminApi
parameters.SharedApiBridge
and ServerApiBridge
to encapsulate the game-specific helpers from GameAdminApiController
to make them available in controllers deriving from other controllers, notably from MetaplayWebhookController
. The helper methods are available directly on GameAdminApiController
but must be invoked via ApiBridge.HelperMethod()
in other controllers.PlayerLoginEvent.SessionLengthApprox
. This is expected to reduce SessionLengthApprox
by about 20 seconds.MetaplayClient.Connection.Reconnect()
.EntityImportEntityIdRemapper
will now remap EntityIds that are not part of the current import entity set or are not persisted in the database to EntityId.None
.stdin
stream even if --Environment:EnableKeyboardInput=false
is not given.IMetaplayLifecycleDelegate.OnSessionLost()
is now called also when connection is closed with MetaplayConnection.Close()
.Metaplay.Core.Json.JsonSerialization
now supports deserializing base classes' properties with private setters. This is needed for building game configs from JSON data, because some SDK-defined base classes have properties with private setters yet expect those to be settable via deserialization.MModal
.OverviewListItem.asLink()
option to the initialization API to make it possible to inject links into the overview lists.getGameServerHelloSubscriptionOptions()
subscription. This provides access to the /hello
endpoint of the game server that contains immutable dashboard configuration, like the current environment name and feature flags. The same data is now removed from coreStore
.MInputSingleSelectDropdown
component for better type safety.Experiment Detail
page has been updated and is now organized into three main tabs: Details
Tab: Contains the experiment's details defined in the game configs.Audience & Targeting
Tab: Contains details on the experiment's audience and targeting criteria.Audit Log
Tab: Displays a log of all changes made to the experiment.Experiment Detail
page overview card has been updated and details of the experiment's target audience have been moved to the Audience & Targeting
tab.coreStore.hello
has been removed and replaced by the new useStaticInfos()
composable in @metaplay/gameServerApi
. This composable provides access to the same underlying data, but in a more consistent and easier-to-use way.coreStore.getEnvironmentFamily()
has been removed as it was a simple getter for helloData.environmentFamily
. Use the new useStaticInfos()
composable instead.replace
option has been removed from the addUiComponent()
API's layout options as it was ambiguous and rarely used. Call removeUiComponent()
followed by addUiComponent()
to achieve the same effect.okButtonDisabled
prop for disabling the OK
button in modal components (MActionModal
, MActionModalButton
) has been removed. To disable the OK
button, you must now provide a message to the new okButtonDisabledTooltip
prop explaining why it’s disabled. This message will be displayed as a tooltip when users hover over the button.ConfigContentCard
experiment search functionality now allows searching by both the experiment name and experiment ID.View Audit Log Event
page.DashboardHeaderColorInHex
colors.Settings View
has been updated and is now organized into two main tabs: Common Settings
Tab: Contains frequently used system settings.Advanced
Tab: Contains advanced controls to help you manage your system.MInputNumber
number component did not work.MTabLayout
component where the dropdown menu was hidden in mobile view.MTabLayout
where the correct tab would not be selected when navigating backwards to a previously viewed page.index
value on item-card
slot of MetaListCard
, which was incorrect.MInputSingleFile*
components where they could have a value that wasn't visible in the UI.LeagueSeasonRankCard
that caused it to only access divisions from the lowest rank.Metaplay SDK has deprecated support for Unity 2021.3 as it has reached end-of-life and is no longer supported by Unity. The lowest supported Unity version is now 2022.3. While this release still works with Unity 2021.3, we plan to remove support for it in R32, tentatively scheduled for March 2025.
Still using 2021.3?
If you cannot reasonably upgrade to 2022.3 or later in the near future, please let us know.
Please apply the following changes to your project to ensure compatibility with the latest Metaplay SDK.
Backward-Incompatible Changes
Bump your game's MetaplayCoreOptions.supportedLogicVersions
to force a synchronized update of your game client and server.
Helm chart v0.7.0 is recommended for cloud deployments and minimum required version is now v0.6.3.
Migration Steps:
Update your CI pipelines for all your environments to use the latest version of the Helm chart.
When using GitHub Action scripts, make the following change:
jobs:
...
build-and-deploy-server:
...
with:
- helm-chart-version: "0.x.y"
+ helm-chart-version: "0.7.0"
With other CI systems, apply the following change (or the equivalent in your CI system):
- export HELM_CHART_VERSION="0.x.y"
+ export HELM_CHART_VERSION="0.7.0"
Roll out the change to each environment by running the CI job to build and deploy a new version. You can do this for each environment separately, whenever is a good time for you.
Premium SDK Update Support
If your support contract includes Metaplay-provided SDK updates, all the following steps have already been applied to your project. You can skip this migration guide!
This guide offers step-by-step instructions for migrating your project to the latest version of the Metaplay SDK. You can skip the migrations steps for features you are not using in your project.
The following core SDK changes affect all Metaplay projects:
Metaplay's built-in database schema has changed, and you need to apply the migration steps to your project.
Migration Steps:
Generate the database schema migration code with:
# Install or update the EFCore tool:
Backend/Server$ dotnet tool install -g dotnet-ef
# Then, generate the migration code:
Backend/Server$ dotnet ef migrations add MetaplayRelease31
Then, add the generated files to your project's source control. For example, using Git:
Backend/Server$ git add .
Backend/Server$ git commit -m "Database schema migrations"
The migration steps will be automatically applied when you deploy the updated game server into an environment.
The following LiveOps Dashboard changes affect projects that have a game-specific dashboard project:
You should ensure that you have Node version 22.11.0 (the latest 22.x version at the time of writing) installed. To check the current version, run node --version
. If you are using nvm
, you can update Node with:
# Install Node 22.11.0 with Node Version Manager (nvm).
nvm install 22.11.0
# Use the new version.
nvm use 22.11.0
Update the Node version in your project's package.json
file to reflect the new requirement:
"engines": {
"node": "20.x"
"node": ">=20"
}
Migration Steps:
git clean -fdx ':(glob)**/node_modules/*'
from the root of your repository. This clears any currently installed dependencies.git clean -fdx ':(glob)**/dist/*'
from the root of your repository. This clears any previously built files.pnpm-lock.yaml
file from the root of your repository. This clears the cached dependency versions.As usual, we have updated the underlying dependencies and configurations of the LiveOps Dashboard. This causes changes to several configuration files, which you will need to update in your dashboard project. We use the MetaplaySDK/Frontend/DefaultDashboard
folder as the source of truth for these files.
Migration Steps:
Update the package.json
to update your project's dependencies.
package.json
file directly from the MetaplaySDK/Frontend/DefaultDashboard
directory. Next, restore the name property inside the file to that of your project. This is typically of the form "name": "<projectName>-dashboard"
.Copy and overwrite the following files:
vite.config.ts
vue-compat.d.ts
index.html
Update the compilerOptions
in your tsconfig.json
file to match the configuration in MetaplaySDK/Frontend/DefaultDashboard
. Remove the target
, module
, and moduleResolution
options as they are no longer required.
{
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Run pnpm install
in your 'Dashboard' folder. This recreates all of the above files and folders with the correct dependencies.
As a part of continued code review and polish of the dashboard codebase, we have done a few breaking changes to internal APIs that you may be using in your custom components.
coreStore.hello
to the useStaticInfos()
ComposableOur /hello
endpoint is the first thing that the dashboard fetches to access its initial configuration, such as feature flags. We have reorganised and refactored how this data is fetched and stored in the dashboard to make it more consistent and easier to use.
You need to update any possible call sites from coreStore.hello
to the new useStaticInfos()
composable.
Migration Steps:
Import and access the useStaticInfos()
composable.
import { useStaticInfos } from '@metaplay/gameServerApi'
const staticInfos = useStaticInfos()
Update coreStore.hello
callsites to use the new composable.
const environmentName = coreStore.hello.environment
const environmentName = staticInfos.environmentInfo.environmentName
Remove references to the now unnecessary coreStore
.
import { useCoreStore } from '@metaplay/core'
const coreStore = useCoreStore()
Here is the updated schema for the data returned by useStaticInfos()
for quick reference:
type StaticInfos = {
projectInfo: {
projectName: string;
};
environmentInfo: {
environmentName: string;
environmentFamily: "Local" | "Development" | "Staging" | "Production";
isProductionEnvironment: boolean;
grafanaUri: string | null;
kubernetesNamespace: string | null;
};
gameServerInfo: {
buildNumber: string;
commitId: string;
};
liveOpsDashboardInfo: {
playerDeletionDefaultDelay: string;
authConfig: {
type: string;
allowAssumeRoles: boolean;
rolePrefix: string | null;
logoutUri: string | null;
};
};
featureFlags: {
pushNotifications: boolean;
guilds: boolean;
asyncMatchmaker: boolean;
web3: boolean;
playerLeagues: boolean;
localization: boolean;
liveOpsEvents: boolean;
gameTimeSkip: boolean;
googlePlayInAppPurchaseRefunds: boolean;
removeIapSubscriptions: boolean;
};
}
coreStore.getEnvironmentFamily()
to useStaticInfos()
This function was a simple getter for coreStore.hello.environmentFamily
and has been removed in favour of directly accessing the data via the new useStaticInfos()
composable that now has the same info at environmentInfo.environmentFamily
.
Migration Steps:
Import and access the useStaticInfos()
composable.
import { useStaticInfos } from '@metaplay/gameServerApi'
const staticInfos = useStaticInfos()
Replace coreStore.getEnvironmentFamily()
with staticInfos.environmentInfo.environmentFamily
.
const environmentFamily = coreStore.getEnvironmentFamily()
const environmentFamily = staticInfos.environmentInfo.environmentFamily
or
import { getEnvironmentFamily() EnvironmentFamily } from '@metaplay/core'
import { useStaticInfos } from '@metaplay/gameServerApi'
getEnvironmentFamily() === EnvironmentFamily.Local
staticInfos.environmentInfo.environmentFamily === 'Local'
Remove references to the now unnecessary coreStore
.
import { useCoreStore } from '@metaplay/core'
const coreStore = useCoreStore()
addUiComponent()
Calls That Use the replace
Optionreplace
has been removed from the addUiComponent()
API's layout options as it was ambiguous and rarely used. Call removeUiComponent()
followed by addUiComponent()
to achieve the same effect.
Migration Steps:
Call removeUiComponent()
first and remove the replace
option from the addUiComponent()
call.
initializationApi.removeUiComponent('OverviewView', 'GlobalIncidents')
initializationApi.addUiComponent(
'OverviewView',
{ uniqueId: 'CustomGlobalIncidents', vueComponent: async () => await import('./MyOwnIncidentsCard.vue') },
{ position: 'replace', targetId: 'GlobalIncidents' }
)
For custom components targeting the Settings View
, update your code to ensure they are assigned to either the System/Common
or System/Advanced
tab.
Migration Steps:
Replace any references to System/Details
with one of the following:
initializationApi.addUiComponent('System/Details',
initializationApi.addUiComponent('System/Common',
// Or
initializationApi.addUiComponent('System/Advanced',
The okButtonDisabled
prop for disabling the OK
button in modal components (MActionModal
, MActionModalButton
) has been replaced by okButtonDisabledTooltip
prop.
Migration Steps:
okButtonDisabled
with okButtonDisabledTooltip
in your dashboard project.MActionModal(
- :ok-button-disabled
+ :ok-button-disabled-tooltip="Reason why this button is disabled"
)
MActionModalButton(
- :ok-button-disabled
+ :ok-button-disabled-tooltip="Reason why this button is disabled"
)
MActionModalButton(
- :ok-button-disabled="!customForm.valid"
+ :ok-button-disabled-tooltip="!customForm.valid ? 'Reason why this button is disabled' : undefined"
)
If you have customized the role-to-permission table in your Options.*.yaml
files, you need to add two additional permissions to the table.
The following new dashboard admin permissions have been introduced:
api.players.update_logic_version
- Controls who can force update an individual player's logic version.api.metrics.view
- Controls who can view metrics data.Migration Steps:
Specify which roles should have access to these permissions in your Backend/Server/Config/Options.*.yaml
(whichever file you use to configure the access). You can modify the roles as needed.
AdminApi:
Permissions:
...
api.players.update_logic_version: [ game-admin, customer-support-senior, customer-support-agent ]
api.metrics.view: [ game-admin ]
The following migrations apply to games that have implemented custom ASP.NET controllers. These are generally classes inheriting GameAdminApiController
.
The Metaplay base controller class constructors no longer need to be passed any arguments.
Migration Steps:
Remove the now-unnecessary constructor arguments and call to base class constructor:
-public MyController(ILogger<MyController> logger, IActorRef adminApi) : base(logger, adminApi)
+public MyController()
Optionally, you can now completely remove any fully empty controller constructors:
-public MyController()
-{
-}
The following changes to Prometheus Metrics may affect your alerting and monitoring stacks:
message
Label Formatgame_entity_asks_total
and game_entity_ask_errors_total
message
label format has changed to clarify the difference of EntityAsks and EntitySubscribes. Asks now produce labels message=Ask-<MessageType>
instead of message=<MessageType>
. Subscribes continue to produce message=Subscribe-<TargetEntityKind>-<MessageType>
. If your metric processing depends on the exact message
labels, you need to update your parsing logic.
Migration steps:
game_entity_asks_total{message="FooMessage"}
, convert the query to game_entity_asks_total{message="Ask-FooMessage"}
.game_entity_ask_errors_total{message="FooMessage"}
, convert the query to game_entity_ask_errors_total{message="Ask-FooMessage"}
.The following changes require integration updates only if your project uses a custom MetaplayClient
implementation and therefore handles client connection error states manually.
TerminalError.ClientPatchVersionTooOld
For information on client patch versions see Client Patches.
When clients of unsupported patch versions attempt to connect to the server the connection attempt will result in an error state of TerminalError.ClientPatchVersionTooOld
. This should be handled by the game by directing the player to update to the latest released version, similarly to how the error state of TerminalError.LogicVersionMismatch
is handled when the advertised supported logic version is greater than the client's.
Migration steps:
TerminalError.ClientPatchVersionTooOld
. Use the existing implementation for handling TerminalError.LogicVersionMismatch
.These changes affect you in case you happen to use any of the APIs changed. You can build your project to get a list of any incompatibilities instead of going through the list one item at a time.
AnalyticsLabel
NamespaceThe AnalyticsLabel
type has been moved from the server-only assembly and Metaplay.Cloud.Analytics
namespace to the shared assembly and Metaplay.Core.Analytics
namespace.
Migration Steps:
AnalyticsLabel
not being found, add statement using Metaplay.Core.Analytics;
.PlayerClientContext.Update()
CallManually calling PlayerClientContext.Update()
is deprecated and is handled automatically in ClientStore.UpdateLogic
.
Migration Steps:
PlayerClientContext.Update()
call being obsolete, remove the call.OrderedDictionary
to MetaDictionary
To eliminate the name conflict with System.Collections.Generic.OrderedDictionary
introduced in .NET 9 the class Metaplay.Core.OrderedDictionary
has been renamed to Metaplay.Core.MetaDictionary
. For SDK update convenience, the SDK in R31 still contains a migration assistance implementation of Metaplay.Core.OrderedDictionary
. This means that your existing code using OrderedDictionary
should continue to compile and work, but we strongly recommend updating all uses of it to use MetaDictionary
instead, in preparation for updating to .NET 9. The migration assistance helper class OrderedDictionary
is marked as Obsolete
and any references to the class will yield compiler warnings that you can use to verify that all references have been update.
Note that this change is reflected in the signature of various API entrypoints in the MetaplaySDK code, and this will cause build errors for any integration code that expects the old signature using OrderedDictionary
parameters.
Migration Steps:
Rename all occurrences of OrderedDictionary
to MetaDictionary
both in your server and client code.
Update any uses of ToOrderedDictionary()
to use ToMetaDictionary()
instead.
To aid in the renaming, the Metaplay SDK contains a Roslyn code analyzer and an associated code fix that can be used for conveniently carrying out the renaming in your code. The relevant code analyzer diagnostics are MP_OD_01
(use of the OrderedDictionary<,>
type) and MP_OD_02
(invocations of the ToOrderedDictionary()
method).
Visual Studio 2022 supports batch fixing of analyzer issues via navigating to an occurrence of the issue and clicking on "Fix all occurrences".
The codefixes can also be applied from command line using the dotnet format
tool, the invocation is:
dotnet format <workspace> analyzers --diagnostics MP_OD_01 MP_OD_02
The batch execution of code fixes happens in a context of a workspace (solution or project). To carry out the renaming both in server and (Unity) client code you will need to run the operation on both your server solution file and the generated Unity solution file.
RandomPCG.NextLong()
OverloadA new overload of RandomPCG.NextLong(long maxExclusive)
with an exclusive max parameter was added in this release. If you have modified the SDK or implemented a custom conflicting overload, check the compatibility of those methods.
Migration Steps:
BigQuery analytics sink now support custom Analytics Context classes, and all custom analytics class types are checked at server launch time to be BigQuery-compatible. The check is performed regardless if BigQuery sink is enabled or not.
Migration Steps:
Unknown analytics event field format in <SomeContextType>
, either change the type of the field to become formattable or ignore the field with [BigQueryAnalyticsFormat(BigQueryAnalyticsFormatMode.Ignore)]
.