5
uses SysUtils, GL, GLU, GLExt, CastleWindow, SandBoxMap, CastleFilesUtils,
6
CastleWindowModes, SandBoxPlayer, CastleStringUtils, Math, CastleUtils,
7
CastleGLUtils, SandBoxGame, CastleKeysMouse, CastleMessages, CastleGLImages;
10
Window: TCastleWindowDemo;
13
ViewMoveX, ViewMoveY: Single;
14
ViewFollowsPlayer: boolean = true;
18
Map := TMap.CreateFromFile(ProgramDataPath + 'maps' + PathDelim + '1.map');
19
Player := TPlayer.Create;
20
Player.Teleport(Map.PlayerStartX, Map.PlayerStartY, dirSouth);
23
procedure Draw(Window: TCastleWindowBase);
25
RealViewMoveX, RealViewMoveY: Integer;
27
procedure DrawImageOnTile(X, Y: Cardinal; GLImage: TGLImage;
28
const SpecialMoveX: Integer = 0;
29
const SpecialMoveY: Integer = 0);
33
PosX := X * BaseWidth;
35
PosX += BaseWidth div 2;
36
PosX += RealViewMoveX + SpecialMoveX;
37
PosY := Y * (BaseHeight div 2);
38
PosY += RealViewMoveY + SpecialMoveY;
40
if (PosX >= 0) and (PosY >= 0) then
42
glRasterPos2i(PosX, PosY);
45
{ Instead of glRasterPos2i(PosX, PosY) we use following trick from
46
[http://www.opengl.org/resources/features/KilgardTechniques/oglpitfall/].
47
We don't use this trick always --- possibly normal glRasterPos2i may
48
be a little faster when it's enough. }
50
glBitmap(0, 0, 0, 0, PosX, PosY, nil);
59
BaseFitX, BaseFitY: Cardinal;
60
X1, X2, Y1, Y2: Integer;
62
glClear(GL_COLOR_BUFFER_BIT);
63
glEnable(GL_ALPHA_TEST);
64
glAlphaFunc(GL_GREATER, 0.5);
66
BaseFitX := Ceil(ScreenWidth / BaseWidth) + 1;
67
BaseFitY := Ceil(2 * ScreenHeight / BaseHeight) + 1;
69
if ViewFollowsPlayer then
71
{ Ignore ViewMoveX/Y, calculate RealView such that the player
73
RealViewMoveX := Player.XPixel;
74
RealViewMoveY := Player.YPixel;
77
RealViewMoveX -= Round(Player.MovingSmallMoveX);
78
RealViewMoveY -= Round(Player.MovingSmallMoveY);
82
RealViewMoveX := Round(ViewMoveX);
83
RealViewMoveY := Round(ViewMoveY);
86
{ First: this is what would be seen if RealViewMoveX/Y is zero. }
88
X2 := Integer(BaseFitX) - 2;
90
Y2 := Integer(BaseFitY) - 2;
91
{ Now translate taking RealViewMoveX/Y into account. }
92
X1 -= Ceil(RealViewMoveX / BaseWidth);
93
X2 -= Floor(RealViewMoveX / BaseWidth);
94
Y1 -= Ceil(2 * RealViewMoveY / BaseHeight);
95
Y2 -= Floor(2 * RealViewMoveY / BaseHeight);
96
{ Eventually correct to be inside 0..Map.Width/Height - 1 range }
97
Clamp(X1, 0, Map.Width - 1);
98
Clamp(X2, 0, Map.Width - 1);
99
Clamp(Y1, 0, Map.Height - 1);
100
Clamp(Y2, 0, Map.Height - 1);
105
MapTile := Map.Items[X, Y];
106
DrawImageOnTile(X, Y, MapTile.BaseTile.GLImage);
109
{ TODO: shitty code, should draw only the part that fits within the window.
110
We should auto-check width/height of bonus tile, to know when to draw it.
111
Even better, we should record this on the map --- which tile is visible
113
for Y := Map.Height - 1 downto 0 do
115
{ The order of drawing is important. Player must be drawn
116
on top of some objects and below some others. }
119
if Player.Moving then
120
DrawImageOnTile(Player.X, Player.Y, Player.GLImage[Player.Direction],
121
Round(Player.MovingSmallMoveX),
122
Round(Player.MovingSmallMoveY)) else
123
DrawImageOnTile(Player.X, Player.Y, Player.GLImage[Player.Direction]);
126
for X := 0 to Map.Width - 1 do
128
MapTile := Map.Items[X, Y];
129
if MapTile.BonusTile <> nil then
130
DrawImageOnTile(X, Y, MapTile.BonusTile.GLImage);
134
glDisable(GL_ALPHA_TEST);
136
{ Tests: middle lines:
137
GLVerticalLine(Window.Width / 2, 0, Window.Height);
138
GLHorizontalLine(0, Window.Width, Window.Height / 2); }
141
procedure Press(Window: TCastleWindowBase; const Event: TInputPressRelease);
143
NewViewMoveX, NewViewMoveY: Integer;
145
procedure EditBaseTile;
150
C := MessageChar(Window, 'Enter the character code of new base tile, ' +
151
'or Escape to cancel', AllChars - [#0], '', taMiddle);
152
if C <> CharEscape then
154
BaseTile := Map.BaseTiles[C];
155
if BaseTile = nil then
156
MessageOK(Window, Format('The character "%s" is not a code ' +
157
'for any base tile', [C]), taMiddle) else
158
Map.Items[Player.X, Player.Y].BaseTile := BaseTile;
162
procedure EditBonusTile;
164
BonusTile: TBonusTile;
167
C := MessageChar(Window, 'Enter the character code of new bonus tile, ' +
168
'or "_" to clear or Escape to cancel', AllChars - [#0], '', taMiddle);
169
if C <> CharEscape then
172
Map.Items[Player.X, Player.Y].BonusTile := nil else
174
BonusTile := Map.BonusTiles[C];
175
if BonusTile = nil then
176
MessageOK(Window, Format('The character "%s" is not a code ' +
177
'for any bonus tile', [C]), taMiddle) else
178
Map.Items[Player.X, Player.Y].BonusTile := BonusTile;
183
procedure ShowFieldInfo;
185
function TileDescr(Tile: TTile): string;
188
Result := '<none>' else
189
Result := Format('"%s" (filename "%s")',
190
[Tile.CharCode, Tile.RelativeFileName]);
194
MessageOK(Window, Format(
195
'Position: %d, %d' +nl+
198
[ Player.X, Player.Y,
199
TileDescr(Map.Items[Player.X, Player.Y].BaseTile),
200
TileDescr(Map.Items[Player.X, Player.Y].BonusTile) ]), taLeft);
206
if Event.EventType = itKey then
208
case Event.KeyCharacter of
210
ViewFollowsPlayer := not ViewFollowsPlayer;
211
if not ViewFollowsPlayer then
213
{ Set ViewMoveX/Y initial values such that the player is still
214
in the middle. This is less confusing for user. }
215
ViewMoveToCenterPosition(Player.X, Player.Y,
216
NewViewMoveX, NewViewMoveY);
217
ViewMoveX := NewViewMoveX;
218
ViewMoveY := NewViewMoveY;
225
if MessageInputQuery(Window, 'Save map as name' +
226
' (don''t specify here initial path and .map extension)',
227
FileName, taLeft) then
228
Map.SaveToFile(ProgramDataPath + 'maps' + PathDelim +
232
CharEscape: Quit := true;
237
procedure Idle(Window: TCastleWindowBase);
239
ViewMoveChangeSpeed = 10.0 * 50.0;
241
if not ViewFollowsPlayer then
243
if Window.Pressed[K_Up] then ViewMoveY -= ViewMoveChangeSpeed * Window.Fps.IdleSpeed;
244
if Window.Pressed[K_Down] then ViewMoveY += ViewMoveChangeSpeed * Window.Fps.IdleSpeed;
245
if Window.Pressed[K_Right] then ViewMoveX -= ViewMoveChangeSpeed * Window.Fps.IdleSpeed;
246
if Window.Pressed[K_Left] then ViewMoveX += ViewMoveChangeSpeed * Window.Fps.IdleSpeed;
249
{ At first I placed the commands below in KeyDown, as they work
250
like KeyDown: non-continuously. However, thanks to smooth scrolling
251
of the screen, user is easily fooled and thinks that they work
252
continuously. So he keeps pressing them. So we should check them
254
if Window.Pressed[K_Up] then Player.Move(dirNorth);
255
if Window.Pressed[K_Down] then Player.Move(dirSouth);
256
if Window.Pressed[K_Left] then Player.Move(dirWest);
257
if Window.Pressed[K_Right] then Player.Move(dirEast);
259
if Window.Pressed[K_Numpad_7] then Player.Move(dirNorthWest);
260
if Window.Pressed[K_Numpad_9] then Player.Move(dirNorthEast);
261
if Window.Pressed[K_Numpad_1] then Player.Move(dirSouthWest);
262
if Window.Pressed[K_Numpad_3] then Player.Move(dirSouthEast);
263
if Window.Pressed[K_Numpad_4] then Player.Move(dirWest);
264
if Window.Pressed[K_Numpad_6] then Player.Move(dirEast);
265
if Window.Pressed[K_Numpad_2] then Player.Move(dirSouth);
266
if Window.Pressed[K_Numpad_8] then Player.Move(dirNorth);
269
GameTime += Window.Fps.IdleSpeed;
278
SavedMode := TGLMode.CreateReset(Window, 0, true, @Draw, @Resize2D, nil);
280
Window.AutoRedisplay := true;
281
Window.OnPress := @Press;
282
Window.OnIdle := @Idle;
286
if not Application.ProcessMessage(true, true) then Quit := true;
289
finally FreeAndNil(SavedMode); end;
293
Window := TCastleWindowDemo.Create(Application);
295
Window.Caption := 'The Sandbox';
296
Window.ResizeAllowed := raOnlyAtOpen;
297
Window.OnResize := @Resize2D;
298
Window.SetDemoOptions(K_F11, CharEscape, true);
301
ScreenWidth := Window.Width;
302
ScreenHeight := Window.Height;