Need Help Making Collision with Ground Work Correctly

I understand that part, but I keep pointing out I do in fact RTFM as you guys tell me to, and it doesn’t make sense even when I read the instructions. Your point is valid, but is totally unrelated to the error I actually had; it was telling me a semicolon was expected where it clearly doesn’t go, if you go by the examples and read the manual, where it is spelled out for you very clearly how semicolons work; that is what I am protesting doesn’t make sense in particular/more detail.

And yes, I do understand what a Raycast vector does as well, so you are not right assuming I don’t know the basics of what it does; and I did have to change the SceneAvatar based on the fact that the current engine code doesn’t support using SceneAvatar for multi-part models, as was told to me very explicitly earlier, like in another thread.

I understand that might not be the right answer cause it was literally just a guess, but again the original code assumed that you were only using a single piece avatar/model, which is certainly not the case here (it’s just not supported that way to make a multi-part model without a Transform container in the current engine version, or so I believe).

And yes, I would believe that copy-pasting the answer is appropriate if the answer is well-known to actually work, is my final rebuttal; if it looked exactly that way and worked fine one time, shouldn’t it work the same exact way every time? Is that not the very fundamental basics of what computer code is supposed to do?

And you also need to learn to understand the errors detected by the compiler.
There must not be a semicolon in the end preceding else, you put it.
But you don’t need the compiler to know that, it’s basic Pascal programming.

Exactly! But the error I am getting contradicts/goes against that, which is why I posted for help in the first place. If you look closely at the particular snippet of code it is highlighted went wrong, it’s telling me it expected a semicolon but found an else, even though you will actually see I placed the semicolon correctly according to how the examples and manual show it. (Lines 226 and 227; it is correctly ending the block at line 226 and not being placed at line 227 character 3 for no good reason, like it seems to want me to do).

And now guess the heck what? Even when I omit/delete the semicolon, I get the same exact error on the same exact line (227, 3 as the image explains in further detail); it’s like I’m literally screwed over either way I go!

The following is your code formatted with tabs, important for seeing the begin and end blocks clearly. You will find comments inside.

begin
  inherited;
  SandyIdling := 0;
  SandyAirborne := 0;
  MoveAmount := Vector3(0, -5, 0);
  RayCastResult := AvatarRigidBody.PhysicsRayCast(
                   AvatarTransform.Middle,
                   AvatarTransform.Direction
                   );
  if RayCastResult.Hit then
    SandyAirborne := 0;
  begin // this is not required
    LabelFps.Caption := 'FPS: PLACEHOLDER';
    if SandyIdling = 0 then
    begin
      SandyIdling := 1;
      SandyLegs.PlayAnimation('LEGS_IDLE', true);
      SandyTorso.PlayAnimation('TORSO_IDLE', true);
    end
  end; // this is not required, furthermore there is a semicolon which does not go before else
  else // else of what? There is no condition that implies an else
    SandyIdling := 0;
  begin // this is not required
    if SandyAirborne = 0 then
    begin
      SandyAirborne := 1;
      SandyTorso.PlayAnimation('TORSO_AIRBORNE', true);
      SandyLegs.PlayAnimation('LEGS_AIRBORNE', true);
    end // a semicolon should go here
    AvatarTransform.Move(MoveAmount, false, true);
    LabelFps.Caption := 'FPS: ' + Container.Fps.ToString
  end; //this is not required
end;

The begin keyword indicates the beginning of a block but it makes no sense to put a begin inside a begin if there is no condition that justifies the presence of a block. The begin with its end will simply be ignored.

Your else makes no sense again. There is no unexecuted condition that justifies the presence of this else.

Don’t take the compiler’s words literally, your code is confusing and it gets confused as well. Just consider that a few lines before or a few lines after the line number reported by the compiler, there is an error. Sometimes the indication is correct, sometimes it is less precise.

And as for what you write:

I’m not entirely sure.

Oh, now it makes sense; I was getting frustrated because I was interpreting the compiler’s instructions too literally, but again it would be a lot neater/more easier and intuitive if the compiler was able to tell you the actual error directly, rather than just forcing you to use your expertise and figure out where the error actually is.

Okay, now that you indented it for easier legibility, the things you point out are redundant/unnecessary are actually there for a logical reason when you look more closely;

For example, the first begin you state is not required is because it is actually the block that is executed if the RayCast hits the ground, if that is omitted then there will be no way to do different things depending on how the RayCast hits the ground.

Likewise the end you state is not required is part of the same exact block, and the else you say doesn’t make sense is because it is telling the compiler/code to execute if you don’t touch the ground; without it, again, there would be no way to make the player character do different things depending on whether it touches the ground or not.

The final two begin and end blocks you argue are not required, actually are because they are the blocks determining what happens if you don’t touch the ground. The begin and end blocks, as far as I understand, are used for conditional branches with multiple lines/statements, so that the compiler knows exactly what code to execute based on the condition.

Maybe “Raycast.Hit” is not a valid conditional to check for, or is that not the problem? Again the semicolon I omitted, but it still gives me the same exact error, although I understand now it is repetitive, and I just used the begin and end blocks in the same exact way the manual and tutorials specified, as I explained in the last paragraph.

Unfortunately you are wrong.

if RayCastResult.Hit then
    SandyAirborne := 0;
begin
  // your code
end;

Writing in this way only the line SandyAirborne := 0; is related to RayCastResult.Hit. Everything that follows will run regardless of this condition.
it’s like writing:

if RayCastResult.Hit then
begin
    SandyAirborne := 0;
end;
begin
  // your code
end;

The correct way is:

if RayCastResult.Hit then
begin
  SandyAirborne := 0;
  // all your code
end
else
begin
  // all your code
end;

You will also understand that if you don’t at least learn to use the blocks correctly you won’t get anywhere.

Yeah, obviously I understand the last sentence, but that goes the same for any kind of code, if I don’t understand the basic rules I won’t do anything productive and keep running into errors, but that’s very obvious.

But yeah, now I get what the actual problem was - I was using a statement between the “if” and “begin” branch, so it wasn’t following the correct formatting and messing with the code - begin must always come first, no matter what, and that’s in the manual too.

{
  Copyright 2020-2023 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Main "playing game" view, where most of the game logic takes place. }
unit GameViewPlay;

{.$define CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}

interface

uses Classes,
  CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, CastleCameras,
  CastleTransform, CastleInputs, CastleThirdPersonNavigation, CastleDebugTransform,
  CastleSceneCore,
  GameEnemy;

type
  { Main "playing game" view, where most of the game logic takes place. }
  TViewPlay = class(TCastleView)
  published
    { Components designed using CGE editor.
      These fields will be automatically initialized at Start. }
    LabelFps: TCastleLabel;
    MainViewport: TCastleViewport;
    AvatarTransform: TCastleTransform;
    SandyLegs, SandyHead, SandyTorso, SceneLevel: TCastleScene;
    AvatarRigidBody: TCastleRigidBody;
    CheckboxCameraFollows: TCastleCheckbox;
    CheckboxAimAvatar: TCastleCheckbox;
    CheckboxDebugAvatarColliders: TCastleCheckbox;
    CheckboxImmediatelyFixBlockedCamera: TCastleCheckbox;
    SliderAirRotationControl: TCastleFloatSlider;
    SliderAirMovementControl: TCastleFloatSlider;
    ButtonChangeTransformationAuto,
      ButtonChangeTransformationDirect,
      ButtonChangeTransformationVelocity,
      ButtonChangeTransformationForce: TCastleButton;
  private
    { Enemies behaviors }
    Enemies: TEnemyList;

    DebugAvatar: TDebugTransform;
    { Change things after ThirdPersonNavigation.ChangeTransformation changed. }
    procedure UpdateAfterChangeTransformation;
    procedure ChangeCheckboxCameraFollows(Sender: TObject);
    procedure ChangeCheckboxAimAvatar(Sender: TObject);
    procedure ChangeCheckboxDebugAvatarColliders(Sender: TObject);
    procedure ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
    procedure ChangeAirRotationControl(Sender: TObject);
    procedure ChangeAirMovementControl(Sender: TObject);
    procedure ClickChangeTransformationAuto(Sender: TObject);
    procedure ClickChangeTransformationDirect(Sender: TObject);
    procedure ClickChangeTransformationVelocity(Sender: TObject);
    procedure ClickChangeTransformationForce(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure Stop; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    function Press(const Event: TInputPressRelease): Boolean; override;
  end;

  TMyThirdPersonNavigation = class(TCastleThirdPersonNavigation)
  protected
    procedure SetAnimation(const AnimationNames: array of String); override;
  end;

var
  ViewPlay: TViewPlay;
  MyThirdPersonNavigation: TMyThirdPersonNavigation;
  GroundRayCast: TPhysicsRayCastResult;
  StandOnGround: Boolean;
  SandyInRunning: Boolean;

implementation

uses SysUtils, Math, StrUtils,
  CastleSoundEngine, CastleLog, CastleStringUtils, CastleFilesUtils, CastleUtils,
  GameViewMenu;

{ TViewPlay ----------------------------------------------------------------- }

constructor TViewPlay.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gameviewplay.castle-user-interface';
end;

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of String);
begin

end;

procedure TViewPlay.Start;
var
  SoldierScene: TCastleScene;
  Enemy: TEnemy;
  I: Integer;
begin
  inherited;
  MyThirdPersonNavigation := TMyThirdPersonNavigation.Create(FreeAtStop);
  { Create TEnemy instances, add them to Enemies list }
  Enemies := TEnemyList.Create(true);
  for I := 1 to 4 do
  begin
    SoldierScene := DesignedComponent('SceneSoldier' + IntToStr(I)) as TCastleScene;
    { Below using nil as Owner of TEnemy, as the Enemies list already "owns"
      instances of this class, i.e. it will free them. }
    Enemy := TEnemy.Create(nil);
    SoldierScene.AddBehavior(Enemy);
    Enemies.Add(Enemy);
  end;

  { synchronize state -> UI }
  SliderAirRotationControl.Value := MyThirdPersonNavigation.AirRotationControl;
  SliderAirMovementControl.Value := MyThirdPersonNavigation.AirMovementControl;
  UpdateAfterChangeTransformation;

  CheckboxCameraFollows.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxCameraFollows;
  CheckboxAimAvatar.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxAimAvatar;
  CheckboxDebugAvatarColliders.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxDebugAvatarColliders;
  CheckboxImmediatelyFixBlockedCamera.OnChange := {$ifdef FPC}@{$endif} ChangeCheckboxImmediatelyFixBlockedCamera;
  SliderAirRotationControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirRotationControl;
  SliderAirMovementControl.OnChange := {$ifdef FPC}@{$endif} ChangeAirMovementControl;
  ButtonChangeTransformationAuto.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationAuto;
  ButtonChangeTransformationDirect.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationDirect;
  ButtonChangeTransformationVelocity.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationVelocity;
  ButtonChangeTransformationForce.OnClick := {$ifdef FPC}@{$endif} ClickChangeTransformationForce;

  {$ifndef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  { Hide UI to test ChangeTransformation = ctForce, it is not finished now,
    not really useful for normal usage. }
  ButtonChangeTransformationForce.Exists := false;
  {$endif}

  { This configures SceneAvatar.Middle point, used for shooting.
    In case of old physics (ChangeTransformation = ctDirect) this is also the center
    of SceneAvatar.CollisionSphereRadius. }
  // SceneAvatar.MiddleHeight := 0.9;

  { Configure some parameters of old simple physics,
    these only matter when SceneAvatar.Gravity = true.
    Don't use these deprecated things if you don't plan to use ChangeTransformation = ctDirect! }
  // SceneAvatar.GrowSpeed := 10.0;
  // SceneAvatar.FallSpeed := 10.0;
  { When avatar collides as sphere it can climb stairs,
    because legs can temporarily collide with objects. }
  // SceneAvatar.CollisionSphereRadius := 0.5;

  { Visualize SceneAvatar bounding box, sphere, middle point, direction etc. }
  DebugAvatar := TDebugTransform.Create(FreeAtStop);
  // DebugAvatar.Parent := SceneAvatar;

  { Configure MyThirdPersonNavigation keys (for now, we don't expose doing this in CGE editor). }
  MyThirdPersonNavigation.Input_LeftStrafe.Assign(keyQ);
  MyThirdPersonNavigation.Input_RightStrafe.Assign(keyE);
  MyThirdPersonNavigation.MouseLook := true; // TODO: assigning it from editor doesn't make mouse hidden in mouse look
  MyThirdPersonNavigation.Init;
  MyThirdPersonNavigation.AvatarHierarchy := AvatarTransform;
  SandyLegs.AutoAnimation := 'LEGS_IDLE';
  SandyTorso.AutoAnimation := 'TORSO_IDLE';
end;

procedure TViewPlay.Stop;
begin
  FreeAndNil(Enemies);
  inherited;
end;

procedure TViewPlay.Update(const SecondsPassed: Single; var HandleInput: Boolean);

  // Test: use this to make AimAvatar only when *holding* right mouse button.
  (*
  procedure UpdateAimAvatar;
  begin
    if buttonRight in Container.MousePressed then
      ThirdPersonNavigation.AimAvatar := aaHorizontal
    else
      ThirdPersonNavigation.AimAvatar := aaNone;

    { In this case CheckboxAimAvatar only serves to visualize whether
      the right mouse button is pressed now. }
    CheckboxAimAvatar.Checked := ThirdPersonNavigation.AimAvatar <> aaNone;
  end;
  *)

var
  SandyAnims: array of String;
  RayCastResult: TPhysicsRayCastResult;
  MoveAmount: TVector3;
  SandyAirborne: Integer;
  SandyIdling: Integer;
begin
  inherited;
  { This virtual method is executed every frame (many times per second). }
  SandyIdling := 0;
  SandyAirborne := 0;
  MoveAmount := Vector3(0, -5, 0);
  RayCastResult := AvatarRigidBody.PhysicsRayCast(
  AvatarTransform.Middle,
  AvatarTransform.Direction
  );
  if RayCastResult.Hit then
  begin
  SandyAirborne := 0;
  LabelFps.Caption := 'FPS: PLACEHOLDER';
  if SandyIdling = 0 then
  begin
  SandyIdling := 1;
  SandyLegs.PlayAnimation('LEGS_IDLE', true);
  SandyTorso.PlayAnimation('TORSO_IDLE', true);
  end
  end
  else
  SandyIdling := 0;
  begin
  if SandyAirborne = 0 then
  begin
  SandyAirborne := 1;
  SandyTorso.PlayAnimation('TORSO_AIRBORNE', true);
  SandyLegs.PlayAnimation('LEGS_AIRBORNE', true);
  end;
  AvatarTransform.Move(MoveAmount, false, true);
  LabelFps.Caption := 'FPS: ' + Container.Fps.ToString
  end;
  // UpdateAimAvatar;
end;

function TViewPlay.Press(const Event: TInputPressRelease): Boolean;
var
  HitByAvatar: TCastleTransform;
  HitEnemy: TEnemy;
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { This virtual method is executed when user presses
    a key, a mouse button, or touches a touch-screen.

    Note that each UI control has also events like OnPress and OnClick.
    These events can be used to handle the "press", if it should do something
    specific when used in that UI control.
    The TViewPlay.Press method should be used to handle keys
    not handled in children controls.
  }

  if Event.IsMouseButton(buttonLeft) then
  begin
    SoundEngine.Play(SoundEngine.SoundFromName('shoot_sound'));
  end;

  if Event.IsMouseButton(buttonRight) then
  begin
    MyThirdPersonNavigation.MouseLook := not MyThirdPersonNavigation.MouseLook;
    Exit(true);
  end;

  if Event.IsKey(keyF5) then
  begin
    Container.SaveScreenToDefaultFile;
    Exit(true);
  end;

  if Event.IsKey(keyEscape) then
  begin
    Container.View := ViewMenu;
    Exit(true);
  end;

  if (Event.IsKey(keyArrowUp) and SandyInRunning = false) then
  begin
    SandyInRunning := true;
    SandyLegs.PlayAnimation('LEGS_RUN', true);
    SandyTorso.PlayAnimation('TORSO_RUN', true);
  end;
end;

function TViewPlay.Release(const Event: TInputPressRelease): Boolean;
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { This virtual method is executed when user releases
    a key, a mouse button, or touches a touch-screen.

    Note that each UI control has also events like OnPress and OnClick.
    These events can be used to handle the "press", if it should do something
    specific when used in that UI control.
    The TViewPlay.Release method should be used to handle keys
    not handled in children controls.
  }

  if (Event.IsKey(keyArrowUp) and SandyInRunning = true) then
  begin
    SandyInRunning := false;
    SandyLegs.PlayAnimation('LEGS_IDLE', true);
    SandyTorso.PlayAnimation('TORSO_IDLE', true);
  end;
end;

procedure TViewPlay.ChangeCheckboxCameraFollows(Sender: TObject);
begin
  MyThirdPersonNavigation.CameraFollows := CheckboxCameraFollows.Checked;
end;

procedure TViewPlay.ChangeCheckboxAimAvatar(Sender: TObject);
begin
  if CheckboxAimAvatar.Checked then
    MyThirdPersonNavigation.AimAvatar := aaHorizontal
  else
    MyThirdPersonNavigation.AimAvatar := aaNone;

  { The 3rd option, aaFlying, doesn't make sense for this case,
    when avatar walks on the ground and has Gravity = true. }
end;

procedure TViewPlay.ChangeCheckboxDebugAvatarColliders(Sender: TObject);
begin
  DebugAvatar.Exists := CheckboxDebugAvatarColliders.Checked;
end;

procedure TViewPlay.ChangeCheckboxImmediatelyFixBlockedCamera(Sender: TObject);
begin
  MyThirdPersonNavigation.ImmediatelyFixBlockedCamera := CheckboxImmediatelyFixBlockedCamera.Checked;
end;

procedure TViewPlay.ChangeAirRotationControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirRotationControl := SliderAirRotationControl.Value;
end;

procedure TViewPlay.ChangeAirMovementControl(Sender: TObject);
begin
  MyThirdPersonNavigation.AirMovementControl := SliderAirMovementControl.Value;
end;

procedure TViewPlay.UpdateAfterChangeTransformation;
begin
  ButtonChangeTransformationAuto.Pressed := MyThirdPersonNavigation.ChangeTransformation = ctAuto;
  ButtonChangeTransformationDirect.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctDirect;
  ButtonChangeTransformationVelocity.Pressed := MyThirdPersonNavigation.ChangeTransformation =  ctVelocity;
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ButtonChangeTransformationForce.Pressed := ThirdPersonNavigation.ChangeTransformation = ctForce;
  {$endif}

  { ctDirect requires to set up gravity without physics engine,
    using deprecated TCastleTransform.Gravity.
    See https://castle-engine.io/physics#_old_system_for_collisions_and_gravity }
  AvatarRigidBody.Exists := MyThirdPersonNavigation.ChangeTransformation <> ctDirect;

  { Gravity means that object tries to maintain a constant height
    (SceneAvatar.PreferredHeight) above the ground.
    GrowSpeed means that object raises properly (makes walking up the stairs work).
    FallSpeed means that object falls properly (makes walking down the stairs,
    falling down pit etc. work). }
  SandyTorso.Gravity := not AvatarRigidBody.Exists;
  SandyLegs.Gravity := not AvatarRigidBody.Exists;
  SandyHead.Gravity := not AvatarRigidBody.Exists;
end;

procedure TViewPlay.ClickChangeTransformationAuto(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctAuto;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationDirect(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctDirect;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationVelocity(Sender: TObject);
begin
  MyThirdPersonNavigation.ChangeTransformation := ctVelocity;
  UpdateAfterChangeTransformation;
end;

procedure TViewPlay.ClickChangeTransformationForce(Sender: TObject);
begin
  {$ifdef CASTLE_UNFINISHED_CHANGE_TRANSFORMATION_BY_FORCE}
  ThirdPersonNavigation.ChangeTransformation := ctForce;
  {$endif}
  UpdateAfterChangeTransformation;
end;

end.

Now my code looks like this, and I am getting errors like this about the Key Release function, even though I copy pasted the directions over from Key Press, assuming it would work the same for Key Release, but apparently not.

Does anyone have any suggestions on particular examples I can look up to figure out how the Key Release function is supposed to be programmed? I tried looking in the tutorials Michaelis gave me, as well as looked up examples given in the game engine, like the platformers game and what not, but couldn’t find the Release function used at all, like at all period, no mention or usage of it.

On top of this, the API page doesn’t actually give you any details under the section title, even though it does for the Key Press function. The API page in question for proof:

Proof that the Release function of TInputPressRelease doesn’t actually have an explanation in the API page, even though the Press function does.

I added docs for Release, thanks.

Looking at your code, at least one error stands out: you didn’t declare the Release method in your class. See Modern Object Pascal Introduction for Programmers | Castle Game Engine ,

type
  TMyClass = class
    MyInt: Integer; // this is a field
    property MyIntProperty: Integer read MyInt write MyInt; // this is a property
    procedure MyMethod; // this is a method
  end;

procedure TMyClass.MyMethod;
begin
  WriteLn(MyInt + 10);
end;

^ the MyMethod needs to be declared in the class...end.

Okay, now that makes a lot more sense. I know in general you are supposed to declare methods first before using them, just like variables but Release in particular just wasn’t properly documented as far as I could tell, so I wasn’t sure what was wrong cause of that. That is very helpful though… thank you so much!

Now it compiles correctly. That’s awesome and thank you so much!

Unfortunately, the Raycast test doesn’t actually work because it never actually touches the ground and so Sandy is stuck in the airborne pose while on the ground; what do you guys think the proper coordinates for the begin and length of the raycast should be, given that the AvatarTransform.Middle and Direction don’t work?

I genuinely thought it was good the way it was, because starting at the middle and pointing and ending in the direction of the character makes sense when you draw/plot it out on paper, but what do you guys think the proper coordinates should be?

I feel like I’m talking to a wall.
I asked you a few posts ago

The answer is in my question.
And once again, take an hour to figure out how to visualize the ray. It was explained to you how to do it.
You will use this code many times in your work, why wouldn’t you want to?

Yeah, obviously I understand that’s how the vector works, and I acknowledged it then too; like I know already you start at a particular point and point in a particular direction with a particular length, but what I actually need help with is not the concept, but rather the details; given that we know that it doesn’t work to start in the middle and point in the direction of the player, then what is the appropriate starting point and direction?

Again, I know what a vector is conceptually, but I just need help choosing the right starting point and direction, because I legitimately thought if you started in the middle of the character, and pointed the ray in the direction of the player, the ray would touch the ground. Maybe actually showing you a picture would help, as opposed to having you just mentally visualize it?

Like this:

I thought that’s how the vector would work, but apparently it doesn’t. What I am asking then, is what are the proper details of the vector coordinates I should use?

Again, I know what a vector actually is conceptually, I just need help determining the appropriate starting point and length.

And no, you’re not talking to a wall because I am pointing out I understand what you are saying, but it’s like you only read small parts of my post and not the whole thing.

Again, I know basics like how to organize blocks of code should be done by reading the manual, because no one is going to hold your hand for very basic things like that and that’s understandable, but if it’s something that doesn’t have examples in the manual or example posts, that’s different and that’s been my point the entire time.

In my case, the problem that isn’t explained anywhere in the examples is how to determine what the start and direction of a ray is, given that I am using a model that is multi-part and not just single-part like the examples use.

I really don’t know how to explain it to you any other way.
The direction is vector3.down, i.e. Vector3(0, -1, 0). In another thread I explained how to calculate a direction, but you haven’t even read it or have already forgotten it. In any case you don’t need this now because you have to cast it downwards.
The length of the ray depends on where you start. If you start from the feet, it will have to be very short, maybe about 0.1? If you start from the center of the character it will have to be half of the character + 0.1. This is because your ray needs to be just beyond the character’s feet if you need to check if they are on the ground.
But if you listened to me and visualized the ray leaving its infinite length, you would see for yourself where it starts from and its direction so that you can modify it according to your needs.

Now the further details make sense. Again, I know what a vector is from general life experience, but I didn’t understand for example how to set the ray length to half the character, so that kind of detail is what I got stuck on.

I still don’t understand the exact code in this case unfortunately; is it something along the lines of whatever the code is to get the Transform’s height, divded by 2? I would assume that is the most straightforward answer, but want to double check instead of just making guesses, again like you told me a good coder should do.

As a starting point, the feet are usually used, which normally represent the pivot in a 3D character. It is nothing other than the transform.position in Unity, while in CGE it is perhaps defined with transform.Translation?
To start from the center of the character you need to know its height, perhaps via capsule collider and divide it by two. If the character’s pivot is in the feet, you need to take this position and modify the y value by adding or subtracting the value obtained above (I don’t know how Transform.Middle works). This gives you the starting point for the ray. But why so much work for nothing?
The first thing you need to know is where your character’s pivot is, which I believe is in the feet. And once again, how do you know? Show ray starting from transform.translation(?) and the mystery is revealed.
If it starts from the feet use a length of very little, if it starts from the center, half the length of the character plus very little.