Doom RPG-like game and questions about it

I personally do it this way: code/map/gamemap.pas · grandmaster · EugeneLoza / Vinculike · GitLab

Or you can couple it with minimap feature as in code/map/gamemap.pas · grandmaster · EugeneLoza / Vinculike · GitLab - this way if “monster is in currently visible area” then player can shoot it.

Unfortunately both methods are overoptimized and suffer from my lack of knowledge on discrete math (they seem to be as efficient as they can be, but contain some “not well grounded logic” inside which could otherwise result in cleaner and more understandable code). But what they do in practice: get player position, make a step forward, check if nothing collides, make another step forward. Just optimizing things for “different kinds of forward direction”.

This is an ancient implementation of raycasting I’ve made unit1.pas · master · EugeneLoza / Project-Helena · GitLab - it should be more straightforward in the logic, but despite a simpler algorithm it’s by far less readable code :slight_smile:

@michalis I played The Castle, a cool game, and I thought the way the enemies attack was what I needed, but after looking at the code I didn’t understand where it was implemented, can you give me a hint?

Depends on what you need. Note that The Castle is an old game and it uses now-obsolete (deprecated and removed) TWalkAttackCreature class.

You can find its attack definition here castle-game/data/creatures/ghost/resource.xml at master · castle-engine/castle-game · GitHub - it just plays an animation. The rest is handled inside the TWalkAttackCreature. You can find the latest version here castle-engine/src/deprecated_units/castlecreatures.pas at 3944fc7817b8eb9844a5a8d4e39b8389d3932724 · castle-engine/castle-engine · GitHub e.g. attack specifically happens here castle-engine/src/deprecated_units/castlecreatures.pas at 3944fc7817b8eb9844a5a8d4e39b8389d3932724 · castle-engine/castle-engine · GitHub ---- I see this unit was completely removed recently in favor of new “behavior”.

1 Like

What I need in general is to direct rays, or whatever, in four directions, either from the player or from enemies; when the player’s camera hits them, the enemy attacks, and I was planning to do the sequence of steps with a bool.


All the ways you suggested to me - they are, as they are called, made “done wisely”. Considering that I don’t have that much programming experience at the moment, I need to do this as I described, at least I assume that it can be done somehow. Based on this, the only thing I can do is this:

Yes, just arrange collisions and use them through WorldBoundingBox.Contains(); but this option is very stupid and bulky.

Although I came up with a slightly less stupid idea - just make enemies shoot back, but obviously they won’t pay attention to the player at all

Oh, I see you’ve posted your project above, sorry I’ve missed a few previous messages. So, looking at it - you don’t seem to have a “separate logic layer” in your game, but going more collider-raycast style. Sorry, my advice above therefore wasn’t really useful :slight_smile:

So, what you can do then is indeed make a raycast. And you are doing it perfectly correctly in

if (MainViewport.TransformUnderMouse <> nil) and
   (MainViewport.TransformUnderMouse.FindBehavior(TEnemy) <> nil) then
begin
  HitEnemy := MainViewport.TransformUnderMouse.FindBehavior(TEnemy) as TEnemy;
  HitEnemy.Hurt;    
...

Now if you want to tell if the enemy can see the player, you just do a RayCollision := MainViewport.Items.WorldRay(Player.Position, Direction); where Direction=Player.Translation - Enemy.Translation. I do believe that Direction should automatically be normalized, so no need to normalize it manually, just make sure it’s not zero.

Note, that this ray may go to a “weird” point of the object. E.g. depending on scene organization sometimes those represent the point on the ground, which can in turn make some problems (e.g. if you have grass or some objects on the floor). So it may be better looking at bounding box of the enemy (see Castle Game Engine: CastleTransform: Class TCastleTransform ) - namely something like Enemy.BoundingBox.Center + Vector3(0, Enemy.BoundingBox.SizeY / 2, 0); would be the top of the bounding box which is most likely what you need.

This will tell if the Player can see the enemy and in turn if the enemy can see the Player. Now, I’m not 100% sure how you want to handle this information.

For sure that will allow you to tell if the enemy can see the Player and therefore if it can shoot the Player on its turn. Just do something like that if CanSeePlayer then Shoot where CanSeePlayer := MainViewport.Items.WorldRay(Player.Position, Self.Position - PlayerPosition)[0] = Self;

What should happen when the Player presses “fire”? Not sure. E.g. if there are multiple enemies on the screen - Clicking them would allow to shoot them. If you want to attach this behavior to the key then maybe Player should shoot the nearest visible enemy or the enemy who is closer to the center of the screen. Both should check angle between camera vector and vector to the enemy: e.g. using this function Castle Game Engine: CastleVectors : so obviously angle should be less than FOV angle (usually something like Pi/4) to make sure the enemy is on-screen. Next check raycast. And finally distance. Do this for all enemies and that should work.

I don’t understand how to address the enemies
So I have a Enemy in editor

In the code they are registered as in the generated “3D FPS game” example
изображение

How do I use them in an example like this?


HitEnemy is not working in this example

Yep, I’m trying to do as I did the shooting, as you can see it works for some reason. And here I will have about 8-9 enemies, they will be spread out on the map and not move. And here if there will be a beam coming from the player - they will attack and the variable in gameenemy will switch, well as their health is reduced to zero, I think you saw how I did it.

I realize you now discuss other things in this thread (I will catch up with it once I’m back from Pascal conference next week!), I just wanted to quickly note that blending (treatment of transparent shapes) in Castle Game Engine got significantly improved. I announced it all on Big renderer improvements: Correct and automatic blending sorting, more powerful batching (now cross-scene), easier and more reliable occlusion culling and occlusion sorting – Castle Game Engine .

If you ever find needing to display multiple shapes with blending, it should now work correctly. The automatic behavior is better, and you can also customize it better. Everything is explained in that news post, and in docs: Blending (Rendering Partially-Transparent Objects) | Manual | Castle Game Engine .

1 Like

Thank you, but I have not yet tried and will not try the changes.

I have published all the code and all the project files, as I promise.

I accomplished my goals, again thank you for your help, considering that I am zero in programming, I will come back sometime later, I hope that this project will at least be a good example to demonstrate the capabilities of the engine

P.S. this does not mean that I am not interested in further development of the project

And yeah, greetings from Russia

Hello,

I’m coming back to finally read and answer some old threads on our forum :slight_smile: I’m not sure if this thread was fully resolved, I see in the last post you ask how to determine what enemy is hit using the ray-cast.

Your code seems OK (except missing FindBehavior, see below) and in general following the “3D FPS Game” template code is the simplest approach. To be clear what happens there:

  • We have a TEnemy class, a descendant of TCastleBehavior ( Behaviors | Manual | Castle Game Engine ) . We add an instance of TEnemy to anything that can be hit by a ray.

  • Each time you press the key / mouse button to shoot, we make a “ray cast” to see whether the ray hits anything that has TEnemy attached.

In the example code, we check MainViewport.TransformUnderMouse. This is actually a simple way of doing a “ray cast” – CGE makes a ray cast to determine the MainViewport.TransformUnderMouse value.

I see in your code you do a “ray cast” using MainViewport.Items.WorldRay(...) which is also perfectly OK, this is a way to check arbitrary ray (from any position, along any direction).

Once you have a RayCollision, you can look at the first object hit. It will be a TCastleTransform instance. To be safe, you can actually chose first TCastleTransform instance on RayCollision that is not csTransient . Though for TCastleScene, choosing just the first TCastleTransform instance on RayCollision by RayCollision[0] will be equally good.

Once you have this, you should check whether the given transform has TEnemy behavior, using ExampleTransform.FindBehavior(TEnemy). This bit seems missing from the screenshot of your code in Doom RPG-like game and questions about it - #48 by sthox .

In total, to account for everything I wrote, I can define a function like this:

{ Extract enemy hit, given RayCollision instance as input.
  RayCollision instance may be obtained by using
  @link(TCastleViewport.MouseRayHit)
  or calling SomeViewport.Items.WorldRay(...).
  Returns nil if we didn't hit any enemy. }
function GetEnemyFromRayCollision(const RayCollision: TRayCollision): TEnemy;
var
  I: Integer;
  T, FirstTransform: TCastleTransform;
begin
  FirstTransform := nil;

  if RayCollision <> nil then
    for I := 0 to MouseRayHit.Count - 1 do
    begin
      T := MouseRayHit[I].Item;
      if not (csTransient in T.ComponentStyle) then
      begin
        FirstTransform := T;
        break;
      end;
    end;

  if FirstTransform <> nil then
    Result := FirstTransform.FindBehavior(TEnemy)
  else
    Result := nil;
end;

And then in your code, you can do

if Event.IsMouseButton(...) then
begin
  RayCollision := MainViewport.Items.WorldRay(...);
  try
    Enemy := GetEnemyFromRayCollision(RayCollision);
    if Enemy <> nil then
    begin
       // act on Enemy being hit
    end;
  finally FreeAndNil(RayCollision) end;
end;

I am thinking how to make writing things like GetEnemyFromRayCollision unnecessary / simpler in future games :slight_smile:

Let me know if this helps, and / or if there are any remaining pending questions here.

Addendum: With latest CGE, the GetEnemyFromRayCollision can be simplified a lot :slight_smile: