Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add doors during level generation #39

Closed
wants to merge 1 commit into from
Closed

Conversation

LukaBerkers
Copy link
Contributor

Description

This PR adds automatic placement of doors during level generation. The doors are placed when a room is placed, based on the available directions of that room. Each door gets a unique ID which we will use in the future to match doors with keys.

Testability

There is no level generation yet, but there is a hard-coded level in the TempFillFunction in GridPlacer.cs.

You can play this level in the GridSystem scene. The rooms should have the correct doors placed according to the hard-coded values in TempFillFunction. The doors should open when approached by the player.

Checklist

  • Set the proper pull request name
  • Merged main into your branch
  • Added a scene to the game for your changes

Copy link
Member

@JensSteenmetz JensSteenmetz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codewise, looks great! Only some small nits about style. In the scene, however, the doors in the room closest to the player seem to be incorrectly placed. They are placed on the outside holes instead of the corridor holes.

Comment on lines 12 to 14
/// <summary>
/// The size of the tiles in the x-direction.
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add the s_ prefix to the constants below? ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our wiki actually says it should be PascalCase, but the editorconfig says it should be _camelCase

Copy link
Collaborator

@wingbladedota wingbladedota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very well done overall! I'm happy Jens did most of the nitpicking already :)

public int doorId;
/// <summary>the door object this script is attached to</summary>
[SerializeField] private GameObject parent;
[SerializeField] private int _doorId;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't make this private, because it needs to be accessed to check whether or not the player has the right key

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The door ID could still be private, and a method can be added to generate a key for the given door, and another method to check if the given key is correct. This could all be 'internal' logic, because it is not relevant to the player how this check is performed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that the field shouldn't even be serialized.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why would you do it like that, adding so much complexity to a simple object like a door seems redundant. Just give the door a number than can be checked

Copy link
Member

@JensSteenmetz JensSteenmetz Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I don't feel like it adds complexity. The logic has to be implemented somewhere; doing it here takes away complexity from other classes (otherwise the same logic would have to be implemented in some player or key script).
  2. When you envision a door, you don't care about some ID it has. Doors in real life also don't have IDs.
  3. Deciding whether or not the key is correct for the door is not the responsibility of the player or the key; only the door should be able to decide if it can be opened. The key is just a tool to aid that decision and the player will have to oblige to the door's rules. In real life, you also wouldn't be able to open the door anyways through some hack, but that would be possible if you make the player responsible for deciding whether or not the door is actually locked.
  4. Imagine this scenario; another type of door is added later that adds more flexibility in what keys it allows to be opened with (maybe any key is sufficient for example?). If you implemented this logic in some player class, the player class would have to account for that new door type as well. On the other hand, if it is implemented in a door script, then the player doesn't have to care one bit about this change. The player only cares if the door is open after supplying some key. Or maybe the key must be of some special type and the ID is not relevant at all... then the player can still just supply a key and see if the door is opened after, not worrying about any of the logic.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. that's a fair point, but I think it's equally as logical to implement it somewhere else, the door, the key, and the player turning the key have equal impact when opening a door
  2. The ID is a representation of the notches on the key/inside the lock so in a way it does
  3. This is one solution, but again I think the door is not more responsible for deciding if it opens than the key or player
  4. As long as the door has a function to be removed, I don't think it makes it more difficult at all to open it in other ways. If I were to make an item that opens the next 3 doors, I would create the logic there and just call the destroydoor function where needed

Copy link
Member

@JensSteenmetz JensSteenmetz Apr 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. They might have equal impact in the sense that they all play a role in opening a door. However, they don't have equal responsibilities.
  2. Correct; "inside the lock". A real-life door uses an 'ID' in the same way a virtual door should: for the inner logic. As a frequent door opener myself, I can say I have never seen the insides of a lock. I'm a simple man: I see a keyhole, I put my key in it, and the door opens. So private seems the desirable accessibility for the ID.
  3. Of course the exact responsibility is debatable, but consider real life again. You cannot move through a door if the lock is broken. The key, or me as a door opener, cannot decide to 'open the door anyway', despite the broken lock. A broken key, however, is no problem in most cases. Just grab another key. Or we do not have another key; pity, but then we just have to accept that we cannot open the door. We have no power to open the door at that point. But the door is still working as it should, waiting for the right key.
  4. It doesn't necessarily make it more difficult to open the door in other ways. In fact, I think it makes it easier to open the door in other ways if you give the door a public function to be removed. However, I'm convinced this will cause tightly coupled code, illogical code architecture, etc., giving rise to bugs, increasing the difficulty to implement new features, and overall just making the code harder to read and understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @JensSteenmetz on this. I will remove the SerializeField attribute.

Comment on lines +132 to +141
Vector3 roomPosition = new(x * _tileSizeX, 0, y * _tileSizeY);
Quaternion roomRotation = Quaternion.Euler(0, room.Rotation * _tileRotation, 0);
(Vector3 relativeDoorPosition, Quaternion relativeDoorRotation) = direction switch
{
0 => (new Vector3(-_tileSizeX / 2f, 0, 0), Quaternion.Euler(0, -_tileRotation, 0)),
1 => (new Vector3(0, 0, _tileSizeY / 2f), Quaternion.identity),
2 => (new Vector3(_tileSizeX / 2f, 0, 0), Quaternion.Euler(0, _tileRotation, 0)),
3 => (new Vector3(0, 0, -_tileSizeY / 2f), Quaternion.Euler(0, 2f * _tileRotation, 0)),
_ => throw new UnityException("Invalid direction when placing door")
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this a little bit hard to read (maybe it's just me, but I wouldn't mind some extra comments here)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction value is about to change to an Enum with extension methods, see #34 . This would make this code highly better readable. Please await #34.

Comment on lines +132 to +141
Vector3 roomPosition = new(x * _tileSizeX, 0, y * _tileSizeY);
Quaternion roomRotation = Quaternion.Euler(0, room.Rotation * _tileRotation, 0);
(Vector3 relativeDoorPosition, Quaternion relativeDoorRotation) = direction switch
{
0 => (new Vector3(-_tileSizeX / 2f, 0, 0), Quaternion.Euler(0, -_tileRotation, 0)),
1 => (new Vector3(0, 0, _tileSizeY / 2f), Quaternion.identity),
2 => (new Vector3(_tileSizeX / 2f, 0, 0), Quaternion.Euler(0, _tileRotation, 0)),
3 => (new Vector3(0, 0, -_tileSizeY / 2f), Quaternion.Euler(0, 2f * _tileRotation, 0)),
_ => throw new UnityException("Invalid direction when placing door")
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction value is about to change to an Enum with extension methods, see #34 . This would make this code highly better readable. Please await #34.

Comment on lines +98 to 106
Corner _ => _roomObjects.Corner,
Crossing _ => _roomObjects.Crossing,
DeadEnd _ => _roomObjects.DeadEnd,
Empty _ => _roomObjects.Empty,
Room _ => _roomObjects.Room,
Straight _ => _roomObjects.Straight,
TSection _ => _roomObjects.TSection,
_ => throw new UnityException("Unknown tile type when placing tile")
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue the _'s can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@SilasPeters
Copy link
Contributor

SilasPeters commented Apr 18, 2024

Btw, this branch is named incorrectly. It should start with feat/, and the 68 should be 0068

@LukaBerkers
Copy link
Contributor Author

Btw, this branch is named incorrectly. It should start with feat/, and the 68 should be 0068

That last part is not true, see the wiki and other branches.

@LukaBerkers LukaBerkers deleted the 68-spawn-doors branch April 20, 2024 13:03
@LukaBerkers
Copy link
Contributor Author

This PR is closed now because I changed the branch name. If you have a local copy of the branch please run:

git branch -m 68-spawn-doors feat/68-spawn-doors
git fetch origin
git branch -u origin/feat/68-spawn-doors feat/68-spawn-doors
git remote set-head origin -a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants