Willicious 8 Posted March 17, 2023 Hi all, complete beginner to Delphi/Embarcadero here. I'm following this tutorial in an attempt to make a very basic movement-based game. I'm getting a bunch of errors, so I've dialled it back and I'm doing everything a step at a time and checking for errors. Can anyone see what's going wrong here? I've tried so many things to solve this error, but since the error message is extremely vague ("E2008 Incompatible types"), it makes troubleshooting very difficult. I've posted the entire code so far. The error is with the word "then" on the line if Key = 'w' then Movement := 'down'; unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls; type TForm1 = class(TForm) Shape1: TShape; KeyboardInput: TListBox; procedure FormCreate(Sender: TObject); procedure KeyboardInputKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; Movement: String; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin KeyboardInput.SetFocus(); end; procedure TForm1.KeyboardInputKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = 'w' then Movement := 'down'; end; end. Incidentally, if anyone knows of any good online tutors for Delphi/Embarcadero (i.e. live tutoring, NOT video tutorials), I'm interested! Thanks, Will Share this post Link to post
gkobler 38 Posted March 17, 2023 Try the follow if Key = Ord('w') then Movement := 'down'; 1 Share this post Link to post
programmerdelphi2k 237 Posted March 17, 2023 1) pay attention on "param type", in case: "KEY = WORD type", or be, "a number" not a "letter" Byte, Word, SmallInt, Integer, Double, etc.. = numberic value string, char, shortstring, etc.. = character value (number, letter, symbol quoted) 2) if you need that a "control" stay on focus in your form, try use property "ActiveControl" on form (in design-time or runtime) too! no need a coding, at all. Share this post Link to post
Gary 18 Posted March 17, 2023 Interesting, I looked at the video Will is following take a look at 2:54. The tutorial's ListBox OnKeyDown event has an additional parameter var KeyChar : char that the instructor is using therefore the 'w' instead of Ord('w'). My ListBox doesn't have that parameter and neither does Will's. Anybody know what's going on? 2:06 shows Delphi 10.2 Share this post Link to post
Gary 18 Posted March 17, 2023 Here you go Will. The tutorial is a multi platform application, you started a VCL application, the controls have different parameters. 1 Share this post Link to post
Willicious 8 Posted March 17, 2023 Thanks for the replies, everyone! gkobler's reply solved the error, but the keydown doesn't translate to movement. I've now removed the listbox and I'm applying KeyDown code directly to the form. The form now just has a square. I'm trying to make the square move downwards by 10px when 'd' is pressed: type TForm1 = class(TForm) Square: TShape; ActualMovement: TTimer; procedure ActualMovementTimer(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; Movement: String; implementation {$R *.dfm} procedure TForm1.ActualMovementTimer(Sender: TObject); begin if Movement = 'Down' then Square.Top := + 10; end; procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = Ord('w') then Movement := 'Down'; end; end. I figured that Square.Top would do this, but perhaps there is something incorrect about the line if Movement = 'Down' then Square.Top := +10; Share this post Link to post
Lajos Juhász 293 Posted March 17, 2023 1 minute ago, Willicious said: if Movement = 'Down' then Square.Top := +10; If Movement = 'Down' then Square.Top:= Square.Top + 10; Share this post Link to post
Willicious 8 Posted March 17, 2023 Brilliant, thanks Lajos Juhász 👍 Unfortunately, it's not actually making the square move. Maybe I need to get rid of the TTimer...? Share this post Link to post
Willicious 8 Posted March 17, 2023 Got rid of the TTimer altogether, to even further simplify what I'm trying to do. The square is not moving: type TForm1 = class(TForm) Square: TShape; procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; Movement: String; implementation {$R *.dfm} procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = Ord('w') then Square.Top := Square.Top + 10; end; end. Share this post Link to post
Gary 18 Posted March 17, 2023 If you are following the tutorial you should start a new project. Select New / Multi-Device Application not Windows VCL Application. In this case the Windows VCL TListBox component does not have the " var KeyChar : char" parameter that the Multi-Device TListBox component has. Many others could be different as well and that would create serious cause of confusion. 1 Share this post Link to post
Gary 18 Posted March 17, 2023 Follow the tutorial. Setting "Movement" in the event is just the first step, the actual moving of the image is done later with a timer. Share this post Link to post
Lajos Juhász 293 Posted March 17, 2023 For me this is working with VCL. I set the timer to 250ms. unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls; type TDirection = (dinone, diup, didown, dileft, diright); TForm1 = class(TForm) box: TShape; Timer1: TTimer; procedure Timer1Timer(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } fDirection: TDirection; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin case key of ord('W'): fdirection:=diup; ord('A'): fDirection:=diLeft; ord('D'): fDirection:=diRight; ord('X'): fDirection:=diDown; else fDirection:=diNone; end; end; procedure TForm1.Timer1Timer(Sender: TObject); begin case fDirection of diup : box.Top:=box.top-10; didown : box.Top:=box.Top+10; dileft: box.left:=box.Left-10; diright: box.Left:=box.Left+10; end; end; end. 1 Share this post Link to post
Willicious 8 Posted March 17, 2023 @GaryThanks for the suggestion, but tbh I'd like to try doing it as a Windows app. Later, I'll try again following the tutorial as you've suggested, for sure. For now though, the important thing is that I learn to bind keyboard input to movement. Share this post Link to post
Willicious 8 Posted March 17, 2023 (edited) @Lajos JuhászHow would you specify a single line of keyboard input? if key = Ord('w') then ... isn't working for me OK, it works if you make the letter capital, so: if key = Ord('W') then Edited March 17, 2023 by Willicious Share this post Link to post
Willicious 8 Posted March 17, 2023 Thanks so much for your help so far, everyone! Apologies that these are such basic questions! 🤦♂️ Share this post Link to post
programmerdelphi2k 237 Posted March 17, 2023 (edited) if key is a char: if Key in ['W','w'] then.... if Lower(key) = 'w' then... if key is a Word: if char(key) in ['W', 'w'] then... Edited March 17, 2023 by programmerdelphi2k 1 Share this post Link to post
programmerdelphi2k 237 Posted March 17, 2023 (edited) Hello VCL world... type TForm1 = class(TForm) Image1: TImage; ImageBackground: TImage; procedure FormCreate(Sender: TObject); procedure FormKeyPress(Sender: TObject; var Key: Char); procedure FormDestroy(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} var LStep : integer = 5; LHelloArray: array [0 .. 2] of TBitmap; procedure MyLoadHello; begin LHelloArray[0] := TBitmap.Create; LHelloArray[1] := TBitmap.Create; LHelloArray[2] := TBitmap.Create; /// LHelloArray[0].LoadFromFile('..\..\Hello.bmp'); LHelloArray[1].LoadFromFile('..\..\Hello_Left.bmp'); LHelloArray[2].LoadFromFile('..\..\Hello_Right.bmp'); end; procedure MyDestroyHello; begin for var i: integer := 0 to high(LHelloArray) do FreeAndNil(LHelloArray[i]); end; procedure MoveMyHello(const AKey: Char; const AImage: TImage); var ALeft, ATop: integer; begin if (AKey = '') or (AImage = nil) then exit; // ALeft := AImage.Left; ATop := AImage.Top; // case AKey of 'a', 'q', 'z', 'A', 'Q', 'Z': AImage.Picture.Bitmap := LHelloArray[1]; // left 'd', 'e', 'c', 'D', 'E', 'C': AImage.Picture.Bitmap := LHelloArray[2]; // right else AImage.Picture.Bitmap := LHelloArray[0]; // default end; // case AKey of 'a', 'A': ALeft := ALeft - LStep; 'd', 'D': ALeft := ALeft + LStep; 'w', 'W': ATop := ATop - LStep; 's', 'S': ATop := ATop + LStep; 'q', 'Q': begin ALeft := ALeft - LStep; ATop := ATop - LStep; end; 'e', 'E': begin ALeft := ALeft + LStep; ATop := ATop - LStep; end; 'z', 'Z': begin ALeft := ALeft - LStep; ATop := ATop + LStep; end; 'c', 'C': begin ALeft := ALeft + LStep; ATop := ATop + LStep; end; end; // AImage.Left := ALeft; AImage.Top := ATop; end; procedure TForm1.FormCreate(Sender: TObject); begin Self.KeyPreview := true; // all keys processed by form... // ImageBackground.Picture.LoadFromFile('..\..\background.bmp'); // MyLoadHello; // Image1.Stretch := true; Image1.Transparent := true; // to BMPs Image1.Picture.Bitmap := LHelloArray[0]; end; procedure TForm1.FormDestroy(Sender: TObject); begin MyDestroyHello; end; procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char); begin MoveMyHello(Key, Image1); end; end. Edited March 17, 2023 by programmerdelphi2k Share this post Link to post
Remy Lebeau 1396 Posted March 17, 2023 (edited) 4 hours ago, Lajos Juhász said: For me this is working with VCL. I set the timer to 250ms. You forgot to add an OnKeyUp event handler to reset the direction. procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin case Key of Ord('W'): fdirection := diup; Ord('A'): fDirection := diLeft; Ord('D'): fDirection := diRight; Ord('X'): fDirection := diDown; end; end; procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin case Key of Ord('W'), Ord('A'), Ord('D'), Ord('X'): fDirection := diNone; end; end; Edited March 17, 2023 by Remy Lebeau 1 Share this post Link to post
David Schwartz 426 Posted March 25, 2023 (edited) On 3/17/2023 at 8:44 AM, Willicious said: @GaryThanks for the suggestion, but tbh I'd like to try doing it as a Windows app. Later, I'll try again following the tutorial as you've suggested, for sure. I believe the difference here is whether you're using VCL -- which is ONLY Windows -- or FMX -- of which Windows is ONE OF MANY target platforms that can be selected. That is, with Windows, you can use EITHER VCL or FMX. Edited March 25, 2023 by David Schwartz 1 Share this post Link to post
Lars Fosdal 1792 Posted March 26, 2023 https://docwiki.embarcadero.com/Libraries/Alexandria/en/System.SysUtils.CharInSet is the recommended method for checking if a character is in a set. It handles Unicode correctly. Share this post Link to post
Lajos Juhász 293 Posted March 26, 2023 7 hours ago, Lars Fosdal said: It handles Unicode correctly. Can you explain how? The help clearly states CharInSet checks whether a given character is contained within a set of AnsiChar. Share this post Link to post
Lars Fosdal 1792 Posted March 27, 2023 9 hours ago, Lajos Juhász said: Can you explain how? The input char is mapped correctly. For keyboards with national keys (f.x. äöæøå etc in Nordic), it at least works correctly. Not sure how it translates source code to AnsiChar, considering that source code can be UTF-8. procedure TestChar; var ch: char; begin ch := '€'; if ch in ['æ', 'ø', '€'] // gives [dcc32 Warning] : W1050 WideChar reduced to byte char in set expressions. Consider using 'CharInSet' function in 'SysUtils' unit. then DebugOut('Yup'); end; Share this post Link to post
Lajos Juhász 293 Posted March 27, 2023 (edited) You should open the Sysutils and verify the magic. A spoiler alert. There is no black magic it uses the same code as when you write ch in [.... just without a warning. When you use CharInSet you just admit that you're aware what's going on (that you string is converted to AnsiString and char into ansichar). Edited March 27, 2023 by Lajos Juhász Share this post Link to post
Lars Fosdal 1792 Posted March 27, 2023 Interesting. Did they have other plans once upon a time? This means it would be safer with if/then/else structures when testing for Unicode chars. or use var ch: char; begin ch := '€'; if System.WideStrUtils.InOpArray(ch, ['æ', 'ø', '€']) then DebugOut('Yup'); but that is just a sequenctial scan of a widechar array. Share this post Link to post
Fr0sT.Brutal 900 Posted March 27, 2023 (edited) 35 minutes ago, Lars Fosdal said: Interesting. Did they have other plans once upon a time? This means it would be safer with if/then/else structures when testing for Unicode chars. They couldn't. Set is limited to 255 elements, so CharInSet only works for Ansi sets (I'm sure you know that 😄 ) Btw, I wonder how do you check whether a char is a letter of a some language (f.ex. Norwegian, German)? They have not only latin chars but some additional which can't be declared with simple 'a'..'z'. You have to add them explicitly, like 'a'..'z', 'æ'? Edited March 27, 2023 by Fr0sT.Brutal Share this post Link to post