Demo mountains (for beginners)

After following the various steps proposed in the manual (Designing user interface and handling events (press, update) within the state) I wanted to play a bit to get familiar with the engine and the Editor.
A nice thing came out of it (very simple but which can be useful for beginners like me) so I share it for those who want to take a look at it.
I modified the code to get some different behaviors: the plane no longer falls, you can always move it with the cursor keys but I made some changes on its movement with the mouse.
The plane goes straight to the click point instead of making an angle like in the Dragon demo. To get it I change the speed on the two x and y axes.
The plane rotates on its axis taking the same direction as the target.
At both departure and arrival the plane rotates slowly to take the direction of flight.
I have introduced the double click which doubles the flight speed.

I uploaded the code to pastebin. It is only needed to duplicate LabelFps and rename it to LabelRot (it will show the rotation of the plane in radians).

UPDATE: I changed the double click management code. If you have downloaded the first version replace it with this one,

You can see a very short video

For @michalis
I have two questions:
To manage the double click I wanted to use the CGE Timer but I did not understand if once started it is always active. I haven’t found a function to stop it. So I just used CastleGetTickCount64. But in any case I found it difficult to prevent the action from starting on the first click before intercepting the second click and then manage everything as a double click. Any suggestions?
This brings me to the second question. Why does CGE not provide for a double click? Is it a little requested function or maybe it is not easily portable to systems other than Windows?
Adventure games make great use of this feature (run, teleporting, fast switching from one scene to another) , it would be really appreciated if it were available directly.

Nice work Valterb!
I will look at and can learn from your code (as I am bad with Mathematics).
Looks we all benefit from the latest update of the manual about this, so it should encourage Michalis to continue updating the manual with step-by-step explaining in simple examples.
And I had not thought about a double mouse click yet; may be interesting so I vote for a YES.
:wink:

Yes, when I started working with Wintermute (I don’t know if you know it, another engine for creating adventure games) I really benefited from the many examples provided for almost all the methods, procedures and functions of the engine. that I was able to complete 4 full scenes of my game including puzzles etc. I also could have completed the game but then I quit because the engine stopped updating. Now there is CGE which among other things uses my favorite language and has a power that I never thought I could find. I come from Unity which is undoubtedly a fantastic engine but I love 2.5D adventure games with 2D backgrounds and 3D characters. CGE might be the best choice!
If @michalis could really provide us with many basic examples on the functions of CGE we should participate and help him by expanding his demos and making his work less tiring.

Update: if you downloaded the example code, download it again because the first one had an error in the management of the double click. The link has been updated.

I do not know Wintermute but have tried several languages in the past, from Basic to C but sticked to Pascal when I found out SpriteX engine. But it was discontinued and there was no support.
So eventually I found CGE.
The last Months it appeared I was the only one here in the forum asking for help but now it seems there are more people joining in, which is great.

Our interests in game design are mainly the same here, as I also am trying to make an adventure game, but with additional time passing events and actions, like playing a film that progresses upon making choices. I have written a comical science fiction book in the past that I would like to convert to a game.
3D is too difficult for me so I stick with the old “Sierra” approach, but with “Cinemaware” like additions.
:slight_smile:

The problem is that CGE is “fairly new”, not many people know it and the few who use it have a superior preparation and don’t need a lot of support. Some time ago I read in a forum of someone who, speaking of CGE, stated that it was difficult to use and another who replied that with Pascal it could not be otherwise.
But now @michalis has come up with the Editor which, although not easy to use, certainly simplifies things.
I am sure that an engine like CGE will take off but for this to happen we beginners (much more than professionals) need many examples to expand and optimize. I think that many who have tried it have then abandoned because their preparation did not allow them to continue learning.

Cool! I’m very happy that the recent update to the manual was helpful.

More will come in the immediate future :slight_smile:

You can deactivate TCastleTimer using Exists property (that it inherits from TCastleUserInterface).

But the TCastleTimer API is not really comfortable to fire some event once.

It is better to do this yourself, watching time (using e.g. CastleGetTickCount64 or TTimer or adding time passing yourself using MyTime += SecondsPassed in the Update). Your code shows one way to do this, I will show example of how I would do it lower :slight_smile:

No particular reason. Double-click wasn’t requested so far, until your recent inquiry :), and I didn’t need it in my projects either.

I simple implementation of it, that I would recommend now, is this:

function TStateMain.Press(const Event: TInputPressRelease): Boolean;
const
  MaxDistanceForDoubleClick = 10;
  MaxTimeForDoubleClick = 0.2;
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  if Event.IsMouseButton(buttonLeft) then
  begin
    if HasLastMousePressPosition and
       (PointsDistance(Event.Position, LastMousePressPosition) < MaxDistanceForDoubleClick) and
       (LastMousePressTime.ElapsedTime < MaxTimeForDoubleClick) then
    begin
      WritelnLog('Detected double-click!');
    end;

    HasLastMousePressPosition := true;
    LastMousePressPosition := Event.Position;
    LastMousePressTime := Timer;

    Exit(true); // event was handled
  end;
end;

Adjust the constants (MaxDistanceForDoubleClick, MaxTimeForDoubleClick) as needed. They seem OK after a quick test :slight_smile:

A complete unit doing this is on Simple code detecting double-click in CGE · GitHub .

I fully agree with you both @valterb and @Carring that we need more upgrades to the manual. I’m working on them right now.

I’m kind of angry at myself that I didn’t do it sooner :slight_smile: I knew that I should work on manual more, but I kept postponing this task, and doing “cool features in engine” instead of documenting properly how to use the existing features. But that’s going to change :slight_smile: The manual will be updated to show the latest and most advised “workflows” for using the engine (which often incorporate using the editor). The recently written chapter Designing user interface and handling events (press, update) within the state | Manual | Castle Game Engine is just the beginning.

Certainly, more examples will also appear.

My immediate plans for the manual:

They are also various videos plannned :slight_smile:

My suggestion, from one among many users, is just that … CGE is already very powerful, it is important at this point for it to acquire many more users. This is possible if users are able to use it by creating even only small but complete games.

Great! Thanks.

I tested your code for the double click, it works correctly obviously :slightly_smiling_face: but I can’t find the way to prevent the event triggered by the single click from not happening before checking if it is a double click.

Ah right. For this, I would make sure (in 2 places – Update and Press) that once HasLastMousePressPosition is set to true, it is processed as either a single-click or double-click, and never just ignored. I tested and updated the code on Simple code detecting double-click in CGE · GitHub .

I have tried the code and as it is it works very well.
The problem occurs when it is adapted into larger code. Maybe I’m doing something wrong.
For example:

if Event.IsMouseButton(buttonLeft) then
  begin
    if HasLastMousePressPosition and
       (PointsDistance(Event.Position, LastMousePressPosition) < MaxDistanceForDoubleClick) and
       (LastMousePressTime.ElapsedTime < MaxTimeForDoubleClick) then
    begin
      // WritelnLog('Detected double-click!');
      // I double the speed of the plane and run the code. That is fine.
      // [ My code.... ]
      HasLastMousePressPosition := false;
    end else
    begin
      // Make single-click, if we have an unprocessed press event that wasn't double-click (because of too far distance).
      if HasLastMousePressPosition then
        // WritelnLog('Detected single-click! (because next press too far)');
        // I assign the base speed. The code is not executed.
      // [ Same code with different speed...  ]
      HasLastMousePressPosition := true;
      LastMousePressPosition := Event.Position;
      LastMousePressTime := Timer;
    end;

    Exit(true); // event was handled
  end;
end;

I published the code on pastebin.

TestDblClick - Pastebin.com

For simplicity I have duplicated the routine in single and double clicks, only the speed changes.
If you try it you will see that the single click is not executed.
Maybe I’m missing something?

“Fairly new” is relative. I think Michalis is already working about 12 years on it now? :slight_smile:
I fully agree with you we need more examples before expanding (though I want the editor to be expanded also, for instance the “event” tab should do something). :wink:
And the www thing, so we can play our games in a browser!
:star_struck:

1 Like

Well, don’t be angry with yourself. Both examples and cool features are important.
But when more people start using CGE because there is more useful documented stuff and tutorials, this place will be more crowded also and people will come with tips and needs so we can help each other with programming questions. They will come with improvement ideas and you can focus on that and the cool stuff, because what already is, is ready and understandable.
:wink:

1 Like

@michalis Reading Docs Microsoft:
Typically, a single click initiates a user interface action and a double-click extends the action … an action tied to the Click or MouseClick event is performed before the action tied to the DoubleClick or MouseDoubleClick event … a double click raises the click event twice …
It actually makes sense.
According to MS, a workaround is required if we do not want a single click to be performed before a double click.
One of these would be to intercept the double click instead of the single click and if it does not occur execute the code of the single click (as in your example).
I worked on it a bit and something came out. It definitely needs to be optimized (maybe using a Timer instead of CastleGetTickCount64) but it seems to work fine.
These are the main steps:

const
  { Interval between two clicks }
  DblClick: Single = 250;
  { Maximum distance between two clicks }
  MaxDistanceForDoubleClick = 10; 

procedure CallSingleClick;
procedure MovePlane;
procedure ResetPlane(IsDblClick: Boolean);

procedure TStateMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
var
  CheckDblClick: QWord;
begin
  ...
  { Reset the first click if the double click has not occurred and run single click }
  if ((FirstClick > 0) and (imgFlying <> true)) then
  begin
    CheckDblClick := CastleGetTickCount64;
    if ((CheckDblClick - FirstClick) > DblClick) then
      CallSingleClick;
  ... 
end;

function TStateMain.Press(const Event: TInputPressRelease): Boolean;
begin
  if Event.IsMouseButton(buttonLeft) then
  begin
    ...
    { Intercepts the double click if it occurs }
    if FirstClick = 0 then
    begin
      FirstClick := CastleGetTickCount64;
      LastMousePressPosition := Event.Position;
    end
    else if (PointsDistance(Event.Position, LastMousePressPosition) < MaxDistanceForDoubleClick) then
    begin
        ResetPlane(true);
        MovePlane;
    end;
    Exit(true);
  end;
end;

procedure TStateMain.CallSingleClick;
begin
  ResetPlane(false);
  MovePlane;
end;

Now the plane starts at reduced speed with a single click and at double speed, without going through the single click, with the double click.

I uploaded the code to pastebin, when you have some time can you take a look and suggest improvements?
TestDblClick2
Regarding your Simple code detecting double-click example code in CGE · GitHub, did you manage to figure out if and where I went wrong in applying it to my code?

In the code I proposed, as well as your version, we just handle interpreting “what is a double-click” ourselves, independently from WinAPI detection of these events. The choice is ours whether “double-click should be an extension of the similar action at single-click (or something completely different)”, and whether something should happen when you perform the first click (when we just don’t know whether it will be a double-click or remain a single-click) is ours :slight_smile:

In my example, Simple code detecting double-click in CGE · GitHub I call

WritelnLog('Detected single-click!...')

only when it is known that it is not a double-click. But one could easily change it (and simplify the code): you can just do something as soon as if Event.IsMouseButton(buttonLeft) then is true, and then you would react on mouse press (well, not exactly “click”, but I guess close to it) regardless of whether this is part of double-click or not.

Looking at your earlier version, TestDblClick - Pastebin.com , you didn’t apply my Update code. You have

  if HasLastMousePressPosition and
     (LastMousePressTime.ElapsedTime >= MaxTimeForDoubleClick) then
  begin
    // Single-click handled, so forget about previous press event and do not store this press event.
    HasLastMousePressPosition := false;
  end;

so you essentially ignore it if user makes a mouse press, and then waits (doing nothing) for some time. In my code I have

procedure TStateMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
  inherited;
  // Make single-click, if we have an unprocessed press event that cannot become double-click anymore.
  if HasLastMousePressPosition and
     (LastMousePressTime.ElapsedTime >= MaxTimeForDoubleClick) then
  begin
    WritelnLog('Detected single-click! (waiting too long for double click)');
    // Single-click handled, so forget about previous press event and do not store this press event.
    HasLastMousePressPosition := false;
  end;
end;

So there are 2 ways how WritelnLog('Detected single-click!...') can occur. One from Press, one from Update. In real-life code, you should call something like CallSingleClick from both these cases, and there implement the same logic.

In many ways, this is similar to my code, so we aim at the same thing :slight_smile: You use CastleGetTickCount64, you assume value = 0 means “no event yet”, where I instead use TTimerResult and explicit HasLastMousePressPosition:Boolean – this is just a matter of taste and both our approaches should work.

On the first glance, you miss 2 things:

  • I think you will miss the single click that occurs very fast (within DblClick time) but is distant (outside of MaxDistanceForDoubleClick). This occurs if I click, move mouse very fast, then click again. It should be 2 single-clicks. That is the point of mine code “Make single-click, if we have an unprocessed press event that wasn’t double-click (because of too far distance).”. In your code, you just ignore the situation when mouse click occurred, and it was fast enough (within DblClick time) but with large distance.

  • (Minor) you assume that Update will be executed on time to clear the FirstClick flag if it passed the required time for double-click (Update will clear the FirstClick in this case by calling CallSingleClick which calls ResetPlane which calls FirstClick := 0;). But in theory, Update could happen more seldom than Press, so Press should also check the time, to make sure that next click should cause double-click.

These are things I tried to address in my version :slight_smile: