Good day!
I have few configuration files with information about the game objects. So, when game starts I need to load and read (and sometimes save) lot of information.
For now I use few my custom procedures. But I want the more optimal instrument.
What you use in the you projects? For now I look to the TStringList and other standard Pascal things. Is they fast and effective enough for the load and save game resources? Or you prefer to make only necessary procedures by yourself?
Personally I rely on object serialization/deserialization for saving and loading game states, see Streaming components - Free Pascal wiki and Streaming JSON - Free Pascal wiki . This is used by both Lazarus LCL (save & load form resources) and CGE (save & load design files) so it is battle-tested and effective.
I use also the TStringList and text files (or csv). In my projects I need only some values to store the options, highscores or levels. Read/write on text files is for me very fast and I tried with hundrets of lines. But if you need to join some information from different data files (like tables) I would go with sqlite database. It is easy to use and makes fun to write sql.
Personally I would recommend to define components with published properties and then you can use CGE serialization/deserialization to read them (CastleComponentSerialize
with ComponentLoad
, ComponentSave
). Like
type
TMyGameConfiguration = class(TCastleComponent)
private
FMonsterSpeed: Single;
FPlayerSpeed: Single;
FDefaultMonster: TMonster;
published
property MonsterSpeed: Single read FMonsterSpeed write FMonsterSpeed;
property PlayerSpeed: Single read FPlayerSpeed write FPlayerSpeed;
property DefaultMonster: TMonster read FDefaultMonster write FDefaultMonster;
end;
and load like
MyConfig := ComponentLoad(
'castle-data:/my_config.castle-component', SomeOwner)
as TMyGameConfiguration;
Advantages:
-
You can make it as simple or as complicated as you want That is, TMyGameConfiguration can have just simple properties (strings, floats, integers) but it can also refer to other components. There is documentation how to make components in Custom Components | Manual | Castle Game Engine .
-
You also have ability to make CGE editor aware of them (again see Custom Components | Manual | Castle Game Engine ) and then even allow game designers to configure your objects using CGE editor’s object inspector, so it’s more visual to them. It’s an option – you don’t need to use this option.
-
The serialization / deserialization of CGE components uses FpJsonRtti and it results in relatively “regular” JSON files. So if you want to later edit them by hand, in any text editor, grep them etc. – no a problem, you just have text files.
-
You can save components “on their own” (like in above example
my_config.castle-component
) or attach them to other components (like particular creatures) using TCatleComponent.NonVisualComponents. -
If you want your things to be more than only data, you can also consider using behaviors, Behaviors | Manual | Castle Game Engine , that (by convention) also make some behavior based on their properties.
This also matches how we did game configurations in games using Unity, back when I was working with Unity Exposing parameters by public fields in MonoBehavior, allowing game designers to tweak them in Unity Editor visually → this was powerful and easy to use by game designers. What I described above → is mostly an equivalent to this in Castle Game Engine.
You are right, there are some advantages when using components with published properties. You do not need to know the file structure.
I tried your example:
if event.IsKey(keyC) then
begin
MyConfig:=TMyGameConfiguration.Create(self);
MyConfig:=ComponentLoad('castle-data:/my_config.castle-component',Self) as TMyGameConfiguration;
label2.Caption:=Ord(MyConfig.DefaultMonster).ToString + ' ' + MyConfig.PlayerSpeed.ToString+ ' ' + MyConfig.MonsterSpeed.ToString;
MyConfig.Free;
Exit(true);
end;
- Is it recommended to free the MyConfig after the load, or can I use MyConfig as long as the view is running and I free it when the view is closing?
- Did I understand this right, that it could be possible to make this available in the editor? Some hints to do this?
You can keep the MyConfig
existing as long as you wish, there are no limits here. You can keep it until the view stops.
Or even longer: you can load it in ApplicationInitialize
(if you use the default CGE new project setup, this is a routine inside GameInitialize
unit that is assigned to Application.OnInitialize
) and create MyConfig
there, using the Application
(singleton in CastleWindow
unit) as the owner:
MyConfig:=TMyGameConfiguration.Create(Application);
Thsi way, MyConfig can just “live” throughout the entire application.
You can define a trivial unit like
unit GameConfig;
interface
type
TMyGameConfiguration = class(TCastleComponent)
...
end;
var
MyConfig: TMyGameConfiguration;
implementation
end.
to have the singleton MyConfig
available for all the units (including view) code without any hassle.
See the Custom Components | Manual | Castle Game Engine , esp. initial sections " Example of custom component and its registration", " Running editor with custom components".
The short version, adjusted to example above, is:
-
Add something like
RegisterSerializableComponent(TMyGameConfiguration, 'My Game Configuration');
to the
initialization
section of the unitGameConfig
. -
In
CastleEngineManifest.xml
, addeditor_units="GameConfig"
-
Now open the project in CGE editor - it should propose to rebuild editor, and the new editor will have a new component called “My Game Configuration” using your code from
GameConfig
. -
You can create a new design using this component, using menu item “Design → New Non-Visual Component (Custom Root) → My Game Configuration”. And thus you will have the object inspector to visually editor all published TMyGameConfiguration properties.
(It’s a bit non-standard to edit non-visual components in CGE editor, but it’s actually cool Our “sounds collection” idea from Sound | Manual | Castle Game Engine also uses this. )
-
You can save the file as
my_config.castle-component
and from code load it using your code from above post. In the future, you can just open your project in CGE editor, double-click onmy_config.castle-component
in CGE editor “Files” panel, and edit it.
So, I will try the few above approaches.
I think TCastleComponent
will more useful for game settings.
And for the game maps I will use strings from files with special format - for now it still more easy for me, especially for potential modding without CGE API.
it works, for simple properties. But if I want to write the highscore data, I have to define 60 properties (10 lines with 6 properties each).
Then I tried this:
if event.IsKey(keyH) then
begin
MyConfig:=TMyGameConfiguration.Create(self);
MyHighscore:=TCastleComponent.Create(self);
MyHighscore:=ComponentLoad('castle-data:/highscore1.castle-component',Self) as TCastleComponent;
MyConfig:= MyHighscore.NonVisualComponents[6] as TMyGameConfiguration; {line 7 from the higscore file "highscore1.castle-component" }
label2.Caption:=MyConfig.Monsterspeed1.ToString + ' ' + MyConfig.Monsterspeed2.ToString+ ' ' + MyConfig.Monsterspeed3.ToString + LineEnding +
ord(MyConfig.MonsterType1).ToString + ' ' + ord(MyConfig.MonsterType2).ToString+ ' ' + ord(MyConfig.MonsterType3).ToString + LineEnding +
MyConfig.MonsterStrengh1.ToString + ' ' + MyConfig.MonsterStrengh2.ToString+ ' ' + MyConfig.MonsterStrengh3.ToString;
MyHighscore.Free;
MyConfig.Free;
Exit(true);
end;
It seems to work correct. The values of the line 7 from highscore are shown.
Is there a better solution?
A more elegant way is to use TCollection, which allows you to define complex properties. You can use it to store an array of TMyGameConfiguration.
Basically you create a new TCollection property (HighScores , for example):
property HighScores : TCollection read FHighScores ;
Then you initialize HighScores in your component’s Constructor method (remember to free it in Destructor method):
FHighScores := TCollection.Create(TMyGameConfiguration);
After that you can edit it freely in castle-editor.
Example with Anchors
as a TCollection:
Edit: Forget to mention, TMyGameConfiguration must be a descendant of TCollectionItem.
Indeed, so we have 2 ways of effectively have a “list of X, that can visually configured using CGE editor”.
-
One is to use
NonVisualComponents
which are a list, as @Didi shows.Note that one non-visual component can be within another, so you can have
HighScores: TCastleComponent
and just make a deal with yourself “I will only add there children of type THighScore” or such. -
Another is to define properties of type TCollection as @kagamma shows. It means you can have as many
TCollection
instances as you want and they have clear names, likeHighScores
in the example. I agree this looks cleaner thanNonVisualComponents
I wanted to add we have a TODO in roadmap that will become the 3rd and recommended answer, once implemented: TCastleComponentList<T>
. See the description in that page about my plans and reasons. This is explicitly something like “our TCollection, with some advantages (I explicitly added a note about this at the end to that section)”.