Rewrote entity system

This commit is contained in:
2025-05-09 22:33:51 -05:00
parent a69ec56a2d
commit f465880044
29 changed files with 372 additions and 284 deletions

View File

@@ -1,34 +1 @@
class_name BasicNPCEntity extends "res://scripts/Entity/OverworldEntity.gd"
const Event = preload("res://scripts/Event/Event.gd");
enum BasicNPCMoveType {
STILL,
RANDOM_LOOK
};
@export var interactEvent:Event;
@export var moveType:BasicNPCMoveType = BasicNPCMoveType.STILL;
@export var randomLookMinTime:float = 1.5;
@export var randomLookMaxTime:float = 4.0;
var randomLookTimer:float = 0.0;
func interact(interactor:OverworldEntity) -> void:
if interactEvent == null:
push_error("BasicNPCEntity: interactType EVENT but no event set");
return
interactEvent.onEntityInteract(interactor, self);
func updateMovement(delta:float) -> void:
if moveType == BasicNPCMoveType.STILL:
return
if moveType == BasicNPCMoveType.RANDOM_LOOK:
randomLookTimer -= delta;
if randomLookTimer <= 0:
randomLookTimer = randf_range(randomLookMinTime, randomLookMaxTime);
self.direction = randi_range(0, 3);
return
pass
class_name BasicNPCEntity extends CharacterBody3D

View File

@@ -0,0 +1,78 @@
@tool
class_name EntityDirection extends Node
enum Direction {
SOUTH,
WEST,
NORTH,
EAST,
}
@export var meshInstance:MeshInstance3D = null:
set(newInstance):
meshInstance = newInstance;
_updateMaterial();
@export var direction = Direction.SOUTH:
set(newDirection):
direction = newDirection;
_updateMaterial();
func _ready() -> void:
_updateMaterial();
func _updateMaterial() -> void:
if !meshInstance:
return
for i in range(meshInstance.get_surface_override_material_count()):
var material:ShaderMaterial = meshInstance.get_surface_override_material(i)
if !material:
continue
material.set_shader_parameter("direction", direction)
func getDirectionVector() -> Vector3:
match direction:
Direction.NORTH:
return Vector3(0, 0, -1);
Direction.SOUTH:
return Vector3(0, 0, 1);
Direction.WEST:
return Vector3(-1, 0, 0);
Direction.EAST:
return Vector3(1, 0, 0);
_:
assert(false, "Invalid direction");
return Vector3(0, 0, 0);
func updateDirectionFromMovement(movement:Vector2) -> void:
if movement.x >= abs(movement.y) and(
movement.y == 0 or
(movement.y > 0 and direction != Direction.SOUTH) or
(movement.y < 0 and direction != Direction.NORTH)
):
direction = Direction.EAST;
elif (movement.x <= -abs(movement.y) and (
movement.y == 0 or
(movement.y > 0 and direction != Direction.SOUTH) or
(movement.y < 0 and direction != Direction.NORTH)
)):
direction = Direction.WEST;
elif (movement.y > 0):
direction = Direction.SOUTH;
elif (movement.y < 0):
direction = Direction.NORTH;
# func getDirectionToFace(position:Vector3) -> Direction:
# var diff = position - self.position;
# if abs(diff.x) > abs(diff.z):
# if diff.x > 0:
# return Direction.EAST;
# else:
# return Direction.WEST;
# else:
# if diff.z > 0:
# return Direction.SOUTH;
# else:
# return Direction.NORTH;
# return Direction.SOUTH;

View File

@@ -0,0 +1 @@
uid://cjfcpi7sxentf

View File

@@ -0,0 +1,77 @@
class_name EntityMovement extends Node
enum MovementType {
PLAYER_INPUT,
STILL,
NPC_RANDOM_LOOK
};
@export var characterBody:CharacterBody3D = null;
@export var entity:Entity = null;
@export var entityDirection:EntityDirection = null;
@export var speed:float = 200;
@export var runSpeed:float = 400;
@export var friction:float = 8.5;
@export var gravity:float = 30;
@export var movementType:MovementType = MovementType.STILL;
@export var randomLookMinTime:float = 1.5;
@export var randomLookMaxTime:float = 4.0;
var randomLookTimer:float = 0.0;
func _physics_process(delta:float) -> void:
if !entity || entity.isPaused() || !characterBody:
return;
# Update movement
_updateVelocity(delta)
# Gravity and friction
if !characterBody.is_on_floor():
characterBody.velocity.y -= gravity * delta;
else:
characterBody.velocity += -(characterBody.velocity * friction * delta);
# if velocity.length() != 0:
# _updateTileData();
# Update character controller.
characterBody.move_and_slide();
func _updateVelocity(delta:float):
match movementType:
MovementType.PLAYER_INPUT:
_updatePlayerInput(delta);
MovementType.STILL:
_updateStill(delta);
MovementType.NPC_RANDOM_LOOK:
_updateNPCRandomLook(delta);
func _updatePlayerInput(delta:float) -> void:
var dir:Vector2 = Input.get_vector("left", "right", "up", "down");
var moveSpeed:float;
if Input.is_action_pressed("run"):
moveSpeed = runSpeed;
else:
moveSpeed = speed;
if dir.x != 0 or dir.y != 0:
characterBody.velocity.x = dir.x * moveSpeed * delta;
characterBody.velocity.z = dir.y * moveSpeed * delta;
if entityDirection:
entityDirection.updateDirectionFromMovement(dir);
func _updateStill(delta:float):
pass
func _updateNPCRandomLook(delta:float):
if !entityDirection:
return
randomLookTimer -= delta;
if randomLookTimer <= 0:
randomLookTimer = randf_range(randomLookMinTime, randomLookMaxTime);
entityDirection.direction = randi_range(0, 3);

View File

@@ -0,0 +1 @@
uid://c5nfs0m6ua4eb

View File

@@ -0,0 +1 @@
uid://dp6itsmbl0ucn

88
scripts/Entity/Entity.gd Normal file
View File

@@ -0,0 +1,88 @@
class_name Entity extends Node
# var meshInstance:MeshInstance3D;
# var underFootTile:int = -1;
# var underFootPosition:Vector3;
# var material:ShaderMaterial;
# func updateMaterial() -> bool:
# if !meshInstance:
# return false
# if !material:
# return false
# return true
# # Virtual Methods
# func updateMovement(delta) -> void:
# pass
# func updateOverworldLogic(delta) -> void:
# pass
func isPaused() -> bool:
var ps = PAUSE.getPauseState();
match ps:
PauseSystem.PauseType.NOT_PAUSED:
return false;
PauseSystem.PauseType.FULLY_PAUSED:
return true;
PauseSystem.PauseType.ENTITY_PAUSED:
if PAUSE.entities.find(self) != -1:
return true;
return false;
PauseSystem.PauseType.CUTSCENE_PAUSED:
if PAUSE.entities.find(self) != -1:
return false;
return true;
_:
assert(false, "Invalid pause state");
return false;
# Private methods
# func _updateTileData() -> void:
# # ray cast down
# var offset = Vector3(0, 0, 0.426);
# var query = PhysicsRayQueryParameters3D.create(
# position + offset,
# position + Vector3(0, -1, 0) + offset
# )
# query.collide_with_areas = true
# query.exclude = [self]
# var result = get_world_3d().direct_space_state.intersect_ray(query)
# if !result or !result.collider:
# return;
# var collider = result.collider;
# var colliderMesh = collider.get_node("../");
# if !colliderMesh or !(colliderMesh is ArrayMesh) or !colliderMesh.mesh or colliderMesh.mesh.get_surface_count() == 0:
# return;
# # Get the face index (triangle)
# var arrays = colliderMesh.mesh.surface_get_arrays(0);
# var indiceIdx = result.face_index * 3;
# # Get each indice of the triangle
# var index0 = arrays[Mesh.ARRAY_INDEX][indiceIdx+0];
# var index1 = arrays[Mesh.ARRAY_INDEX][indiceIdx+1];
# var index2 = arrays[Mesh.ARRAY_INDEX][indiceIdx+2];
# # Get each uv of each indice
# var uv0:Vector2 = arrays[Mesh.ARRAY_TEX_UV][index0];
# var uv1:Vector2 = arrays[Mesh.ARRAY_TEX_UV][index1];
# var uv2:Vector2 = arrays[Mesh.ARRAY_TEX_UV][index2];
# # Determine the lowest texture coordinate
# var min = Vector2(min(uv0.x, uv1.x, uv2.x), min(uv0.y, uv1.y, uv2.y));
# # Convert to column/row
# var w = 256;
# var h = w;
# var tw = 48;
# var th = tw;
# var column = int(roundf(min.x * w)) / tw;
# var row = int(roundf(min.y * h)) / th;
# var columns = 768 / tw;
# underFootPosition = result.position;
# underFootTile = column % columns + row * columns;

View File

@@ -0,0 +1 @@
uid://dtjjqp2atjyhr

View File

@@ -0,0 +1 @@
uid://bcei5b2x3gu8y

View File

@@ -1,160 +0,0 @@
class_name OverworldEntity extends CharacterBody3D
enum Direction {
SOUTH,
WEST,
NORTH,
EAST,
}
var speed:float = 200;
var runSpeed:float = 400;
var friction:float = 8.5;
var gravity:float = 30;
@export var direction = Direction.SOUTH:
set(newDirection):
direction = newDirection;
_updateMaterial();
var meshInstance:MeshInstance3D;
var underFootTile:int = -1;
var underFootPosition:Vector3;
func _updateMaterial():
if !meshInstance:
return
var material:ShaderMaterial = meshInstance.get_surface_override_material(0)
if !material:
return
material.set_shader_parameter("direction", direction)
func getDirectionVector() -> Vector3:
match direction:
Direction.NORTH:
return Vector3(0, 0, -1);
Direction.SOUTH:
return Vector3(0, 0, 1);
Direction.WEST:
return Vector3(-1, 0, 0);
Direction.EAST:
return Vector3(1, 0, 0);
return Vector3(0, 0, 0);
func getDirectionToFace(position:Vector3) -> Direction:
var diff = position - self.position;
if abs(diff.x) > abs(diff.z):
if diff.x > 0:
return Direction.EAST;
else:
return Direction.WEST;
else:
if diff.z > 0:
return Direction.SOUTH;
else:
return Direction.NORTH;
return Direction.SOUTH;
# Virtual Methods
func updateMovement(delta) -> void:
pass
func updateOverworldLogic(delta) -> void:
pass
func isPaused() -> bool:
var ps = PAUSE.getPauseState();
if ps == PauseSystem.PauseType.NOT_PAUSED:
return false;
elif ps == PauseSystem.PauseType.FULLY_PAUSED:
return true;
elif ps == PauseSystem.PauseType.ENTITY_PAUSED:
if PAUSE.entities.find(self) != -1:
return true;
return false
elif ps == PauseSystem.PauseType.CUTSCENE_PAUSED:
if PAUSE.entities.find(self) != -1:
return false;
return true;
return false;
# Private methods
func _updateTileData() -> void:
# ray cast down
var offset = Vector3(0, 0, 0.426);
var query = PhysicsRayQueryParameters3D.create(
position + offset,
position + Vector3(0, -1, 0) + offset
)
query.collide_with_areas = true
query.exclude = [self]
var result = get_world_3d().direct_space_state.intersect_ray(query)
if !result or !result.collider:
return;
var collider = result.collider;
var colliderMesh = collider.get_node("../");
if !colliderMesh or !(colliderMesh is ArrayMesh) or !colliderMesh.mesh or colliderMesh.mesh.get_surface_count() == 0:
return;
# Get the face index (triangle)
var arrays = colliderMesh.mesh.surface_get_arrays(0);
var indiceIdx = result.face_index * 3;
# Get each indice of the triangle
var index0 = arrays[Mesh.ARRAY_INDEX][indiceIdx+0];
var index1 = arrays[Mesh.ARRAY_INDEX][indiceIdx+1];
var index2 = arrays[Mesh.ARRAY_INDEX][indiceIdx+2];
# Get each uv of each indice
var uv0:Vector2 = arrays[Mesh.ARRAY_TEX_UV][index0];
var uv1:Vector2 = arrays[Mesh.ARRAY_TEX_UV][index1];
var uv2:Vector2 = arrays[Mesh.ARRAY_TEX_UV][index2];
# Determine the lowest texture coordinate
var min = Vector2(min(uv0.x, uv1.x, uv2.x), min(uv0.y, uv1.y, uv2.y));
# Convert to column/row
var w = 256;
var h = w;
var tw = 48;
var th = tw;
var column = int(roundf(min.x * w)) / tw;
var row = int(roundf(min.y * h)) / th;
var columns = 768 / tw;
underFootPosition = result.position;
underFootTile = column % columns + row * columns;
# Events
func _ready() -> void:
meshInstance = get_node("MeshInstance3D")
_updateTileData();
_updateMaterial();
pass
func _process(delta:float) -> void:
if isPaused():
return;
# Update logic
updateOverworldLogic(delta)
func _physics_process(delta: float) -> void:
if isPaused():
return;
# Update movement
updateMovement(delta);
# Gravity and friction
if !is_on_floor():
velocity.y -= gravity * delta;
else:
velocity += -(velocity * friction * delta);
if velocity.length() != 0:
_updateTileData();
# Update character controller.
move_and_slide();

27
scripts/Entity/Rosa.gd Normal file
View File

@@ -0,0 +1,27 @@
class_name Rosa extends CharacterBody3D
# var interactRange = 0.7;
# func updateOverworldLogic(delta) -> void:
# # Check if interact button is pressed
# if(Input.is_action_just_pressed("interact")):
# var rayDirection = getDirectionVector();
# # cast ray
# var query = PhysicsRayQueryParameters3D.create(
# position,
# position + (rayDirection * interactRange)
# )
# query.collide_with_areas = true
# query.exclude = [self]
# var result = get_world_3d().direct_space_state.intersect_ray(query)
# if result and result.collider:
# var collider = result.collider
# if(collider.has_method("interact")):
# collider.interact(self)
# if Input.is_action_just_pressed("pause"):
# PAUSE.playerPauseToggle();

View File

@@ -1,7 +1,7 @@
extends Camera3D
@export var pixelScale:float = 1.0;
@export var follow:Node;
@export var follow:Node3D;
const WORLD_UNITS:float = 32.0;

View File

@@ -1,57 +0,0 @@
class_name RosaController extends "res://scripts/Entity/OverworldEntity.gd"
var interactRange = 0.7;
func updateOverworldLogic(delta) -> void:
# Check if interact button is pressed
if(Input.is_action_just_pressed("interact")):
var rayDirection = getDirectionVector();
# cast ray
var query = PhysicsRayQueryParameters3D.create(
position,
position + (rayDirection * interactRange)
)
query.collide_with_areas = true
query.exclude = [self]
var result = get_world_3d().direct_space_state.intersect_ray(query)
if result and result.collider:
var collider = result.collider
if(collider.has_method("interact")):
collider.interact(self)
if Input.is_action_just_pressed("pause"):
PAUSE.playerPauseToggle();
func updateMovement(delta) -> void:
# User movement
var dir:Vector2 = Input.get_vector("left", "right", "up", "down");
var moveSpeed:float;
if Input.is_action_pressed("run"):
moveSpeed = runSpeed;
else:
moveSpeed = speed;
if(dir.x != 0 or dir.y != 0):
velocity.x = dir.x * moveSpeed * delta;
velocity.z = dir.y * moveSpeed * delta;
# Update direction
if(dir.x >= abs(dir.y) and(
dir.y == 0 or
(dir.y > 0 and direction != Direction.SOUTH) or
(dir.y < 0 and direction != Direction.NORTH)
)):
direction = Direction.EAST;
elif (dir.x <= -abs(dir.y) and (
dir.y == 0 or
(dir.y > 0 and direction != Direction.SOUTH) or
(dir.y < 0 and direction != Direction.NORTH)
)):
direction = Direction.WEST;
elif (dir.y > 0):
direction = Direction.SOUTH;
elif (dir.y < 0):
direction = Direction.NORTH;

View File

@@ -1,9 +1,9 @@
class_name EventEntityTurn extends "res://scripts/Event/Event.gd"
@export var entity:OverworldEntity = null
@export var direction:OverworldEntity.Direction = OverworldEntity.Direction.SOUTH
@export var entity:Entity = null
@export var direction:EntityDirection.Direction = EntityDirection.Direction.SOUTH
func start():
if entity == null:
return
entity.direction = direction
entity.direction = direction

View File

@@ -1,12 +1,12 @@
class_name Event extends Node
const OverworldEntity = preload("res://scripts/Entity/OverworldEntity.gd");
const Entity = preload("res://scripts/Entity/Entity.gd");
var started:bool = false;
var ended:bool = false;
var interactor:OverworldEntity = null
var interactee:OverworldEntity = null
var interactor:Entity = null
var interactee:Entity = null
# Godot Methods
func _init() -> void:
@@ -43,8 +43,8 @@ func reset() -> void:
interactee = null
func onEntityInteract(
interactor:OverworldEntity,
interactee:OverworldEntity
interactor:Entity,
interactee:Entity
) -> void:
self.reset()
self.interactor = interactor

View File

@@ -3,7 +3,7 @@ class_name EventConversation extends "res://scripts/Event/Flow/EventGroup.gd"
@export var startPauseType:PauseSystem.PauseType = PauseSystem.PauseType.ENTITY_PAUSED
@export var endPauseType:PauseSystem.PauseType = PauseSystem.PauseType.NOT_PAUSED
@export var entities:Array[OverworldEntity] = []
@export var entities:Array[Entity] = []
@export var pauseInteractee:bool = true
@export var pauseInteractor:bool = true

View File

@@ -3,12 +3,12 @@ class_name EventPause extends "res://scripts/Event/Event.gd"
const PauseSystem = preload("res://scripts/Singleton/Pause.gd")
@export var pauseType:PauseSystem.PauseType = PauseSystem.PauseType.ENTITY_PAUSED
@export var entities:Array[OverworldEntity] = []
@export var entities:Array[Entity] = []
@export var includeInteractee:bool = true
@export var includeInteractor:bool = true
func start() -> void:
var ents:Array[OverworldEntity] = entities
var ents:Array[Entity] = entities
if interactor != null and includeInteractor:
ents.append(interactor)
if interactee != null and includeInteractee:

View File

@@ -13,12 +13,13 @@ func _ready() -> void:
for child in get_children():
if child is QuestObjective:
objectives.append(child)
child.onQuestReady(self)
func start() -> void:
questStarted = true
questComplete = false
QUEST.questStarted.emit(questKey)
QUEST.questUpdated.emit(questKey)
QUEST.questStarted.emit(self)
QUEST.questUpdated.emit(self)
func isCompleted() -> bool:
return questComplete

View File

@@ -1,7 +1,13 @@
class_name QuestObjective extends Node
# enum Type {
# }
enum Type {
Item,
}
@export var objectiveName:String = "Some objective"
# @export var objectiveType:Type = Type.Item
@export var objectiveType:Type = Type.Item
var quest:Quest
func onQuestReady(quest:Quest) -> void:
self.quest = quest

1
scripts/Sign.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cctqgee7dumwg

View File

@@ -6,9 +6,10 @@ enum QuestKey {
var quests:Dictionary[int, Quest]
signal questStarted(questKey:QuestKey)
signal questUpdated(questKey:QuestKey)
signal questCompleted(questKey:QuestKey)
signal questStarted(quest:Quest)
signal questUpdated(quest:Quest)
signal questCompleted(quest:Quest)
# signal questObjectiveCompleted(quest:Quest, objective:QuestObjective)
func _ready() -> void:
_updateQuests()

View File

@@ -100,7 +100,7 @@ func _onQuestSelected(index:int) -> void:
func _onQuestObjectiveSelected(index:int) -> void:
setQuestObjective(index)
func _onQuestUpdated(questKey:QuestSystem.QuestKey) -> void:
func _onQuestUpdated(_q:Quest) -> void:
_updateQuestList()
func _onCloseClicked() -> void: