in-world ui textboxes
This commit is contained in:
@@ -11,6 +11,7 @@ static func dialogueCallable(params:Dictionary) -> int:
|
|||||||
var resource:DialogueResource = params['resource']
|
var resource:DialogueResource = params['resource']
|
||||||
var title:String = params.get('title', 'start')
|
var title:String = params.get('title', 'start')
|
||||||
var extraStates:Array = params.get('extraStates', [])
|
var extraStates:Array = params.get('extraStates', [])
|
||||||
|
var characterTargets:Dictionary = params.get('characterTargets', {})
|
||||||
|
|
||||||
UI.dialogueActive = true
|
UI.dialogueActive = true
|
||||||
var line:DialogueLine = await DialogueManager.get_next_dialogue_line(resource, title, extraStates)
|
var line:DialogueLine = await DialogueManager.get_next_dialogue_line(resource, title, extraStates)
|
||||||
@@ -19,10 +20,12 @@ static func dialogueCallable(params:Dictionary) -> int:
|
|||||||
if line.character:
|
if line.character:
|
||||||
text = line.character + ": " + text
|
text = line.character + ": " + text
|
||||||
|
|
||||||
|
var target:Node3D = _getLineTarget(line.character, characterTargets)
|
||||||
|
|
||||||
if line.responses.size() > 0:
|
if line.responses.size() > 0:
|
||||||
# Show text then auto-pick the first allowed response.
|
# Show text then auto-pick the first allowed response.
|
||||||
# Replace this block with a real response UI when branching dialogue is needed.
|
# Replace this block with a real response UI when branching dialogue is needed.
|
||||||
await UI.TEXTBOX.setTextAndWait(text)
|
await UI.MAIN_CHATBOX.setTextAndWait(text, target)
|
||||||
var nextId:String = ""
|
var nextId:String = ""
|
||||||
for response:DialogueResponse in line.responses:
|
for response:DialogueResponse in line.responses:
|
||||||
if response.is_allowed:
|
if response.is_allowed:
|
||||||
@@ -30,16 +33,26 @@ static func dialogueCallable(params:Dictionary) -> int:
|
|||||||
break
|
break
|
||||||
line = await DialogueManager.get_next_dialogue_line(resource, nextId, extraStates)
|
line = await DialogueManager.get_next_dialogue_line(resource, nextId, extraStates)
|
||||||
else:
|
else:
|
||||||
await UI.TEXTBOX.setTextAndWait(text)
|
await UI.MAIN_CHATBOX.setTextAndWait(text, target)
|
||||||
line = await DialogueManager.get_next_dialogue_line(resource, line.next_id, extraStates)
|
line = await DialogueManager.get_next_dialogue_line(resource, line.next_id, extraStates)
|
||||||
|
|
||||||
UI.dialogueActive = false
|
UI.dialogueActive = false
|
||||||
return Cutscene.CUTSCENE_CONTINUE
|
return Cutscene.CUTSCENE_CONTINUE
|
||||||
|
|
||||||
static func getDialogueCallable(resource:DialogueResource, title:String = 'start', extraStates:Array = []) -> Dictionary:
|
# "player" (case-insensitive) maps to the player entity; any other named
|
||||||
|
# character maps to the npc entity; empty character (narrator) uses no target.
|
||||||
|
static func _getLineTarget(character:String, characterTargets:Dictionary) -> Node3D:
|
||||||
|
if characterTargets.is_empty() or character == "":
|
||||||
|
return null
|
||||||
|
if character.to_lower() == "player":
|
||||||
|
return characterTargets.get("player", null)
|
||||||
|
return characterTargets.get("npc", null)
|
||||||
|
|
||||||
|
static func getDialogueCallable(resource:DialogueResource, title:String = 'start', extraStates:Array = [], characterTargets:Dictionary = {}) -> Dictionary:
|
||||||
return {
|
return {
|
||||||
'function': dialogueCallable,
|
'function': dialogueCallable,
|
||||||
'resource': resource,
|
'resource': resource,
|
||||||
'title': title,
|
'title': title,
|
||||||
'extraStates': extraStates,
|
'extraStates': extraStates,
|
||||||
|
'characterTargets': characterTargets,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ enum InteractType {
|
|||||||
ONE_TIME_ITEM,
|
ONE_TIME_ITEM,
|
||||||
CUTSCENE,
|
CUTSCENE,
|
||||||
BATTLE_TEST,
|
BATTLE_TEST,
|
||||||
|
CHATBOX,
|
||||||
|
PROXIMITY_CHATBOX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@export_category("Identification")
|
@export_category("Identification")
|
||||||
@@ -33,6 +35,8 @@ var button := func():
|
|||||||
@export var dialogueTitle:String = "start"
|
@export var dialogueTitle:String = "start"
|
||||||
@export var oneTimeItem:ItemResource = null
|
@export var oneTimeItem:ItemResource = null
|
||||||
@export var cutscene:CutsceneResource = null
|
@export var cutscene:CutsceneResource = null
|
||||||
|
@export var chatboxMessage:String = ""
|
||||||
|
@export var chatboxDuration:float = 3.0
|
||||||
|
|
||||||
# TEST BATTLE
|
# TEST BATTLE
|
||||||
@export_category("Test Battle")
|
@export_category("Test Battle")
|
||||||
|
|||||||
@@ -33,15 +33,19 @@ func isInteractable() -> bool:
|
|||||||
if entity.interactType == Entity.InteractType.BATTLE_TEST:
|
if entity.interactType == Entity.InteractType.BATTLE_TEST:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
if entity.interactType == Entity.InteractType.CHATBOX:
|
||||||
|
return entity.chatboxMessage != ""
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
func _onConversationInteract(_other:Entity) -> void:
|
func _onConversationInteract(other:Entity) -> void:
|
||||||
assert(entity.dialogueResource != null)
|
assert(entity.dialogueResource != null)
|
||||||
var cutscene:Cutscene = Cutscene.new()
|
var cutscene:Cutscene = Cutscene.new()
|
||||||
cutscene.addCallable(DialogueAction.getDialogueCallable(
|
cutscene.addCallable(DialogueAction.getDialogueCallable(
|
||||||
entity.dialogueResource,
|
entity.dialogueResource,
|
||||||
entity.dialogueTitle,
|
entity.dialogueTitle,
|
||||||
[entity]
|
[entity],
|
||||||
|
{"npc": entity, "player": other}
|
||||||
))
|
))
|
||||||
cutscene.start()
|
cutscene.start()
|
||||||
|
|
||||||
@@ -83,5 +87,11 @@ func onInteract(other:Entity) -> void:
|
|||||||
cutscene.start()
|
cutscene.start()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
Entity.InteractType.CHATBOX:
|
||||||
|
assert(entity.chatboxMessage != "")
|
||||||
|
var chatbox:WorldChatBox = UI.spawnWorldChatBox(entity)
|
||||||
|
chatbox.showAdvanceable(entity.chatboxMessage)
|
||||||
|
return
|
||||||
|
|
||||||
_:
|
_:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ func _applyGravity() -> void:
|
|||||||
func _applyPlayerMovement(delta:float):
|
func _applyPlayerMovement(delta:float):
|
||||||
# Interactions, may move
|
# Interactions, may move
|
||||||
if Input.is_action_just_pressed("interact") && interactingArea && interactingArea.hasInteraction():
|
if Input.is_action_just_pressed("interact") && interactingArea && interactingArea.hasInteraction():
|
||||||
|
if !UI.hasAdvanceableChatBox():
|
||||||
interactingArea.interact()
|
interactingArea.interact()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ func _applyFriction(delta:float) -> void:
|
|||||||
func _canMove() -> bool:
|
func _canMove() -> bool:
|
||||||
if UI.dialogueActive:
|
if UI.dialogueActive:
|
||||||
return false
|
return false
|
||||||
if !UI.TEXTBOX.isClosed:
|
if !UI.MAIN_CHATBOX.isClosed:
|
||||||
return false
|
return false
|
||||||
if UI.GAME_MENU && UI.GAME_MENU.isOpen():
|
if UI.GAME_MENU && UI.GAME_MENU.isOpen():
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class_name EntityProximityArea extends Area3D
|
||||||
|
|
||||||
|
@export var entity:Entity
|
||||||
|
|
||||||
|
var _triggered:bool = false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
body_entered.connect(_onBodyEntered)
|
||||||
|
|
||||||
|
func _onBodyEntered(body:Node3D) -> void:
|
||||||
|
if _triggered:
|
||||||
|
return
|
||||||
|
if !(body is Entity):
|
||||||
|
return
|
||||||
|
if (body as Entity).entityId != "player":
|
||||||
|
return
|
||||||
|
_triggered = true
|
||||||
|
assert(entity != null && entity.chatboxMessage != "")
|
||||||
|
var chatbox:WorldChatBox = UI.spawnWorldChatBox(entity)
|
||||||
|
chatbox.showTimed(entity.chatboxMessage, entity.chatboxDuration)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bq2lsd8uyrtcf
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=9 format=3 uid="uid://d0ywgijpuqy0r"]
|
[gd_scene load_steps=11 format=3 uid="uid://d0ywgijpuqy0r"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://xe6pcuq741xi" path="res://overworld/map/TestMap.gd" id="1_6ms5s"]
|
[ext_resource type="Script" uid="uid://xe6pcuq741xi" path="res://overworld/map/TestMap.gd" id="1_6ms5s"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cluuhtfjeodwb" path="res://overworld/map/TestMapBase.tscn" id="1_ox0si"]
|
[ext_resource type="PackedScene" uid="uid://cluuhtfjeodwb" path="res://overworld/map/TestMapBase.tscn" id="1_ox0si"]
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://38ya6vphm5bu" path="res://item/ItemResource.gd" id="4_xf0pb"]
|
[ext_resource type="Script" uid="uid://38ya6vphm5bu" path="res://item/ItemResource.gd" id="4_xf0pb"]
|
||||||
[ext_resource type="Script" uid="uid://b5c8g5frishjs" path="res://cutscene/cutscene/TestCutscene.gd" id="5_125nt"]
|
[ext_resource type="Script" uid="uid://b5c8g5frishjs" path="res://cutscene/cutscene/TestCutscene.gd" id="5_125nt"]
|
||||||
[ext_resource type="Script" uid="uid://8tsov4ihmnxl" path="res://overworld/camera/OverworldCamera.gd" id="7_tr4a0"]
|
[ext_resource type="Script" uid="uid://8tsov4ihmnxl" path="res://overworld/camera/OverworldCamera.gd" id="7_tr4a0"]
|
||||||
|
[ext_resource type="Script" uid="uid://bq2lsd8uyrtcf" path="res://overworld/entity/EntityProximityArea.gd" id="8_prox"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_125nt"]
|
[sub_resource type="Resource" id="Resource_125nt"]
|
||||||
script = ExtResource("4_xf0pb")
|
script = ExtResource("4_xf0pb")
|
||||||
@@ -17,6 +18,9 @@ metadata/_custom_type_script = "uid://38ya6vphm5bu"
|
|||||||
script = ExtResource("5_125nt")
|
script = ExtResource("5_125nt")
|
||||||
metadata/_custom_type_script = "uid://b5c8g5frishjs"
|
metadata/_custom_type_script = "uid://b5c8g5frishjs"
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_prox"]
|
||||||
|
radius = 3.0
|
||||||
|
|
||||||
[node name="TestMap" type="Node3D"]
|
[node name="TestMap" type="Node3D"]
|
||||||
script = ExtResource("1_6ms5s")
|
script = ExtResource("1_6ms5s")
|
||||||
|
|
||||||
@@ -42,6 +46,27 @@ entityId = "ad5a1504-7fbf-45d6-b1bf-6e7af6314066"
|
|||||||
interactType = 3
|
interactType = 3
|
||||||
cutscene = SubResource("Resource_tr4a0")
|
cutscene = SubResource("Resource_tr4a0")
|
||||||
|
|
||||||
|
[node name="ChatboxNPC" parent="." instance=ExtResource("2_jmygs")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 1.11219, -3)
|
||||||
|
entityId = "c1a2b3c4-d5e6-7890-abcd-ef1234567891"
|
||||||
|
interactType = 5
|
||||||
|
chatboxMessage = "Hey! Press interact again to close this box."
|
||||||
|
|
||||||
|
[node name="ProximityNPC" parent="." instance=ExtResource("2_jmygs")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 1.11219, -4)
|
||||||
|
entityId = "c1a2b3c4-d5e6-7890-abcd-ef1234567892"
|
||||||
|
interactType = 6
|
||||||
|
chatboxMessage = "Hey, over here!"
|
||||||
|
|
||||||
|
[node name="EntityProximityArea" type="Area3D" parent="ProximityNPC" node_paths=PackedStringArray("entity")]
|
||||||
|
collision_layer = 0
|
||||||
|
collision_mask = 2
|
||||||
|
script = ExtResource("8_prox")
|
||||||
|
entity = NodePath("..")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="ProximityNPC/EntityProximityArea"]
|
||||||
|
shape = SubResource("SphereShape3D_prox")
|
||||||
|
|
||||||
[node name="TestMapBase" parent="." instance=ExtResource("1_ox0si")]
|
[node name="TestMapBase" parent="." instance=ExtResource("1_ox0si")]
|
||||||
|
|
||||||
[node name="Player" parent="." instance=ExtResource("2_jmygs")]
|
[node name="Player" parent="." instance=ExtResource("2_jmygs")]
|
||||||
|
|||||||
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
class_name RootUI extends Control
|
class_name RootUI extends Control
|
||||||
|
|
||||||
@export var debugMenu:DebugMenu
|
@export var debugMenu:DebugMenu
|
||||||
@export var textBox:VNTextbox
|
@export var mainChatBox:ChatBox
|
||||||
@export var gameMenu:GameMenu
|
@export var gameMenu:GameMenu
|
||||||
@export var pauseMenu:PauseMenu
|
@export var pauseMenu:PauseMenu
|
||||||
|
@export var chatBoxContainer:Control
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
UI.rootUi = self
|
UI.rootUi = self
|
||||||
|
|||||||
+13
-3
@@ -6,7 +6,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://b38dr0wkix76t" path="res://ui/debugmenu/DebugMenu.tscn" id="4_u132g"]
|
[ext_resource type="PackedScene" uid="uid://b38dr0wkix76t" path="res://ui/debugmenu/DebugMenu.tscn" id="4_u132g"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bv5r2x9m4k7n1" path="res://ui/gamemenu/GameMenu.tscn" id="5_gmenu"]
|
[ext_resource type="PackedScene" uid="uid://bv5r2x9m4k7n1" path="res://ui/gamemenu/GameMenu.tscn" id="5_gmenu"]
|
||||||
|
|
||||||
[node name="RootUI" type="Control" node_paths=PackedStringArray("debugMenu", "textBox", "gameMenu", "pauseMenu")]
|
[node name="RootUI" type="Control" node_paths=PackedStringArray("debugMenu", "mainChatBox", "gameMenu", "pauseMenu", "chatBoxContainer")]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -16,9 +16,10 @@ grow_vertical = 2
|
|||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
script = ExtResource("1_son71")
|
script = ExtResource("1_son71")
|
||||||
debugMenu = NodePath("DebugMenu")
|
debugMenu = NodePath("DebugMenu")
|
||||||
textBox = NodePath("VNTextbox")
|
mainChatBox = NodePath("ChatBox")
|
||||||
gameMenu = NodePath("GameMenu")
|
gameMenu = NodePath("GameMenu")
|
||||||
pauseMenu = NodePath("PauseMenu")
|
pauseMenu = NodePath("PauseMenu")
|
||||||
|
chatBoxContainer = NodePath("WorldChatBoxContainer")
|
||||||
metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
metadata/_custom_type_script = "uid://dq3qyyayugt5l"
|
||||||
|
|
||||||
[node name="DebugMenu" parent="." instance=ExtResource("4_u132g")]
|
[node name="DebugMenu" parent="." instance=ExtResource("4_u132g")]
|
||||||
@@ -34,6 +35,15 @@ process_mode = 3
|
|||||||
visible = false
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
|
||||||
[node name="VNTextbox" parent="." instance=ExtResource("1_1mtk3")]
|
[node name="WorldChatBoxContainer" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ChatBox" parent="." instance=ExtResource("1_1mtk3")]
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
|||||||
+24
-3
@@ -6,16 +6,18 @@ var rootUi:RootUI = null
|
|||||||
# between lines where the textbox is momentarily closed.
|
# between lines where the textbox is momentarily closed.
|
||||||
var dialogueActive:bool = false
|
var dialogueActive:bool = false
|
||||||
|
|
||||||
|
var _chatBoxes:Array = []
|
||||||
|
|
||||||
var DEBUG_MENU:
|
var DEBUG_MENU:
|
||||||
get():
|
get():
|
||||||
if rootUi && rootUi.debugMenu:
|
if rootUi && rootUi.debugMenu:
|
||||||
return rootUi.debugMenu
|
return rootUi.debugMenu
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var TEXTBOX:
|
var MAIN_CHATBOX:
|
||||||
get():
|
get():
|
||||||
if rootUi && rootUi.textBox:
|
if rootUi && rootUi.mainChatBox:
|
||||||
return rootUi.textBox
|
return rootUi.mainChatBox
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var GAME_MENU:
|
var GAME_MENU:
|
||||||
@@ -29,3 +31,22 @@ var PAUSE_MENU:
|
|||||||
if rootUi && rootUi.pauseMenu:
|
if rootUi && rootUi.pauseMenu:
|
||||||
return rootUi.pauseMenu
|
return rootUi.pauseMenu
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
func addChatBox(box:WorldChatBox) -> void:
|
||||||
|
_chatBoxes.append(box)
|
||||||
|
|
||||||
|
func removeChatBox(box:WorldChatBox) -> void:
|
||||||
|
_chatBoxes.erase(box)
|
||||||
|
|
||||||
|
func hasAdvanceableChatBox() -> bool:
|
||||||
|
for box in _chatBoxes:
|
||||||
|
if box.mode == WorldChatBox.Mode.ADVANCEABLE:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
func spawnWorldChatBox(target:Node3D = null) -> WorldChatBox:
|
||||||
|
assert(rootUi != null)
|
||||||
|
var chatbox:WorldChatBox = WorldChatBox.SCENE.instantiate()
|
||||||
|
rootUi.chatBoxContainer.add_child(chatbox)
|
||||||
|
chatbox.worldTarget = target
|
||||||
|
return chatbox
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
class_name VNTextbox extends PanelContainer
|
class_name ChatBox extends PanelContainer
|
||||||
|
|
||||||
const VN_REVEAL_TIME = 0.01
|
const VN_REVEAL_TIME = 0.01
|
||||||
|
const _DEFAULT_ANCHOR_TOP = 1.0
|
||||||
|
const _DEFAULT_ANCHOR_RIGHT = 1.0
|
||||||
|
const _DEFAULT_ANCHOR_BOTTOM = 1.0
|
||||||
|
const _DEFAULT_OFFSET_TOP = -58.0
|
||||||
|
|
||||||
var label:AdvancedRichText;
|
var label:AdvancedRichText;
|
||||||
var parsedOutText = ""
|
var parsedOutText = ""
|
||||||
@@ -12,6 +16,7 @@ var currentViewScrolled = true;
|
|||||||
var isSpeedupDown = false;
|
var isSpeedupDown = false;
|
||||||
|
|
||||||
var hasLetGoOfInteract:bool = true;
|
var hasLetGoOfInteract:bool = true;
|
||||||
|
var worldTarget:Node3D = null
|
||||||
|
|
||||||
var isClosed:bool = false:
|
var isClosed:bool = false:
|
||||||
get():
|
get():
|
||||||
@@ -19,16 +24,59 @@ var isClosed:bool = false:
|
|||||||
set(value):
|
set(value):
|
||||||
self.visible = !value;
|
self.visible = !value;
|
||||||
|
|
||||||
signal textboxClosing
|
signal chatboxClosing
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
label = $MarginContainer/Label
|
label = $MarginContainer/Label
|
||||||
isClosed = true
|
isClosed = true
|
||||||
|
|
||||||
|
func _setWorldLayout(hasTarget:bool) -> void:
|
||||||
|
if hasTarget:
|
||||||
|
anchor_left = 0.0
|
||||||
|
anchor_top = 0.0
|
||||||
|
anchor_right = 0.0
|
||||||
|
anchor_bottom = 0.0
|
||||||
|
offset_left = 0.0
|
||||||
|
offset_top = 0.0
|
||||||
|
offset_right = 0.0
|
||||||
|
offset_bottom = 0.0
|
||||||
|
custom_minimum_size = Vector2(90, 0)
|
||||||
|
else:
|
||||||
|
if label != null:
|
||||||
|
label.fit_content = false
|
||||||
|
anchor_left = 0.0
|
||||||
|
anchor_top = _DEFAULT_ANCHOR_TOP
|
||||||
|
anchor_right = _DEFAULT_ANCHOR_RIGHT
|
||||||
|
anchor_bottom = _DEFAULT_ANCHOR_BOTTOM
|
||||||
|
offset_left = 0.0
|
||||||
|
offset_top = _DEFAULT_OFFSET_TOP
|
||||||
|
offset_right = 0.0
|
||||||
|
offset_bottom = 0.0
|
||||||
|
custom_minimum_size = Vector2.ZERO
|
||||||
|
|
||||||
|
func _updateWorldPosition() -> void:
|
||||||
|
if worldTarget == null:
|
||||||
|
return
|
||||||
|
var camera:Camera3D = get_viewport().get_camera_3d()
|
||||||
|
if camera == null:
|
||||||
|
return
|
||||||
|
var screenPos:Vector2 = camera.unproject_position(worldTarget.global_position + Vector3(0, 2.5, 0))
|
||||||
|
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
|
||||||
|
position = screenPos - size * 0.5
|
||||||
|
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
|
||||||
|
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if isClosed:
|
if isClosed:
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_updateWorldPosition()
|
||||||
|
|
||||||
|
# _recalcText always resets fit_content to false; re-apply so the
|
||||||
|
# PanelContainer can grow to its content height in world-space mode.
|
||||||
|
if worldTarget != null and !label.fit_content:
|
||||||
|
label.fit_content = true
|
||||||
|
|
||||||
if label.getFinalText() == "":
|
if label.getFinalText() == "":
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
return;
|
return;
|
||||||
@@ -51,7 +99,7 @@ func _process(delta: float) -> void:
|
|||||||
else:
|
else:
|
||||||
# On last page
|
# On last page
|
||||||
if Input.is_action_just_released("interact") && hasLetGoOfInteract:
|
if Input.is_action_just_released("interact") && hasLetGoOfInteract:
|
||||||
textboxClosing.emit();
|
chatboxClosing.emit();
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
currentViewScrolled = true
|
currentViewScrolled = true
|
||||||
return;
|
return;
|
||||||
@@ -73,15 +121,15 @@ func _process(delta: float) -> void:
|
|||||||
revealTimer = 0;
|
revealTimer = 0;
|
||||||
label.visible_characters += 1;
|
label.visible_characters += 1;
|
||||||
|
|
||||||
func setText(text:String) -> void:
|
func setText(text:String, target:Node3D = null) -> void:
|
||||||
# Prepare textbox for scrolling
|
worldTarget = target
|
||||||
|
_setWorldLayout(worldTarget != null)
|
||||||
|
|
||||||
# Resets scroll
|
# Resets scroll
|
||||||
revealTimer = 0;
|
revealTimer = 0;
|
||||||
currentViewScrolled = false;
|
currentViewScrolled = false;
|
||||||
label.startLine = 0;
|
label.startLine = 0;
|
||||||
|
|
||||||
# I had a frame wait here before.
|
|
||||||
label.text = text;
|
label.text = text;
|
||||||
label.visible_characters = 0;
|
label.visible_characters = 0;
|
||||||
|
|
||||||
@@ -90,7 +138,7 @@ func setText(text:String) -> void:
|
|||||||
isSpeedupDown = false
|
isSpeedupDown = false
|
||||||
isClosed = false;
|
isClosed = false;
|
||||||
|
|
||||||
func setTextAndWait(text:String) -> void:
|
func setTextAndWait(text:String, target:Node3D = null) -> void:
|
||||||
self.setText(text);
|
self.setText(text, target);
|
||||||
await self.textboxClosing
|
await self.chatboxClosing
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://h8lw23ypcfty" path="res://ui/component/VNTextbox.gd" id="2_uo1gm"]
|
[ext_resource type="Script" uid="uid://h8lw23ypcfty" path="res://ui/component/VNTextbox.gd" id="2_uo1gm"]
|
||||||
[ext_resource type="Script" uid="uid://bjj6upgk1uvxd" path="res://ui/component/advancedrichtext/AdvancedRichText.gd" id="3_m60k3"]
|
[ext_resource type="Script" uid="uid://bjj6upgk1uvxd" path="res://ui/component/advancedrichtext/AdvancedRichText.gd" id="3_m60k3"]
|
||||||
|
|
||||||
[node name="VNTextbox" type="PanelContainer"]
|
[node name="ChatBox" type="PanelContainer"]
|
||||||
clip_contents = true
|
clip_contents = true
|
||||||
anchors_preset = 12
|
anchors_preset = 12
|
||||||
anchor_top = 1.0
|
anchor_top = 1.0
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
class_name WorldChatBox extends PanelContainer
|
||||||
|
|
||||||
|
enum Mode { ADVANCEABLE, TIMED }
|
||||||
|
|
||||||
|
const SCENE = preload("res://ui/component/WorldChatBox.tscn")
|
||||||
|
|
||||||
|
signal chatboxClosed
|
||||||
|
|
||||||
|
var mode:Mode = Mode.TIMED
|
||||||
|
var worldTarget:Node3D = null
|
||||||
|
var _timer:float = 0.0
|
||||||
|
var _label:Label
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_label = $MarginContainer/Label
|
||||||
|
UI.addChatBox(self)
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func showTimed(text:String, duration:float) -> void:
|
||||||
|
mode = Mode.TIMED
|
||||||
|
_timer = duration
|
||||||
|
_label.text = text
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
func showAdvanceable(text:String) -> void:
|
||||||
|
mode = Mode.ADVANCEABLE
|
||||||
|
_label.text = text
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
func showAndWait(text:String) -> void:
|
||||||
|
showAdvanceable(text)
|
||||||
|
await chatboxClosed
|
||||||
|
|
||||||
|
func close() -> void:
|
||||||
|
UI.removeChatBox(self)
|
||||||
|
chatboxClosed.emit()
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
func _updateWorldPosition() -> void:
|
||||||
|
if worldTarget == null:
|
||||||
|
return
|
||||||
|
var camera:Camera3D = get_viewport().get_camera_3d()
|
||||||
|
if camera == null:
|
||||||
|
return
|
||||||
|
var screenPos:Vector2 = camera.unproject_position(worldTarget.global_position + Vector3(0, 2.5, 0))
|
||||||
|
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
|
||||||
|
position = screenPos - size * 0.5
|
||||||
|
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
|
||||||
|
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
|
||||||
|
|
||||||
|
func _process(delta:float) -> void:
|
||||||
|
if !visible:
|
||||||
|
return
|
||||||
|
_updateWorldPosition()
|
||||||
|
match mode:
|
||||||
|
Mode.TIMED:
|
||||||
|
_timer -= delta
|
||||||
|
if _timer <= 0.0:
|
||||||
|
close()
|
||||||
|
Mode.ADVANCEABLE:
|
||||||
|
if Input.is_action_just_pressed("interact"):
|
||||||
|
close()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://m1keb2pw8bqq
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[gd_scene load_steps=3 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Theme" uid="uid://dm7ee4aqjr2dl" path="res://ui/UI Theme.tres" id="1_wx4lp"]
|
||||||
|
[ext_resource type="Script" path="res://ui/component/WorldChatBox.gd" id="2_wcb"]
|
||||||
|
|
||||||
|
[node name="WorldChatBox" type="PanelContainer"]
|
||||||
|
custom_minimum_size = Vector2(90, 0)
|
||||||
|
mouse_filter = 2
|
||||||
|
theme = ExtResource("1_wx4lp")
|
||||||
|
script = ExtResource("2_wcb")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 5
|
||||||
|
theme_override_constants/margin_top = 3
|
||||||
|
theme_override_constants/margin_right = 5
|
||||||
|
theme_override_constants/margin_bottom = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
autowrap_mode = 3
|
||||||
@@ -41,7 +41,7 @@ func _unhandled_input(event:InputEvent) -> void:
|
|||||||
if event.is_action_pressed("menu"):
|
if event.is_action_pressed("menu"):
|
||||||
if visible:
|
if visible:
|
||||||
close()
|
close()
|
||||||
elif !UI.dialogueActive && UI.TEXTBOX.isClosed:
|
elif !UI.dialogueActive && UI.MAIN_CHATBOX.isClosed:
|
||||||
open()
|
open()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user