Mostly finished VN textbox
This commit is contained in:
@@ -31,7 +31,7 @@ class_name NPC extends CharacterBody3D
|
|||||||
return 64.0
|
return 64.0
|
||||||
|
|
||||||
func onInteract(player:Player) -> void:
|
func onInteract(player:Player) -> void:
|
||||||
print("Interacted with NPC")
|
UI.TEXTBOX.setText("Hello, I'm an NPC!\nThis is the second line here, I am purposefully adding a tonne of words so that it is forced to go across multiple lines and you can see how the word wrapping works, not only using Godot's built in word wrapping but with my advanced visibile characters smart wrapping. Now I am doing a multiline thing\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10");
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
|
@@ -1,12 +1 @@
|
|||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
func _ready():
|
|
||||||
UI.MADTALK.start_dialog("bare_minimum")
|
|
||||||
|
|
||||||
func _input(event):
|
|
||||||
if (
|
|
||||||
(event is InputEventKey) and (event.pressed) and (not event.echo) and (event.keycode in [KEY_SPACE, KEY_ENTER, KEY_KP_ENTER])
|
|
||||||
) or (
|
|
||||||
(event is InputEventMouseButton) and (event.pressed) and (event.button_index == MOUSE_BUTTON_LEFT)
|
|
||||||
):
|
|
||||||
UI.MADTALK.dialog_acknowledge()
|
|
||||||
|
@@ -11,7 +11,7 @@ script = ExtResource("1_6ms5s")
|
|||||||
[node name="TestMapBase" parent="." instance=ExtResource("1_ox0si")]
|
[node name="TestMapBase" parent="." instance=ExtResource("1_ox0si")]
|
||||||
|
|
||||||
[node name="Player" parent="." instance=ExtResource("2_0d2qr")]
|
[node name="Player" parent="." instance=ExtResource("2_0d2qr")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 27.142, 1.94879, -59.112)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.04397, 1.9488, -16.5251)
|
||||||
facingDirection = 1
|
facingDirection = 1
|
||||||
|
|
||||||
[node name="NPC" parent="." instance=ExtResource("3_0vfw4")]
|
[node name="NPC" parent="." instance=ExtResource("3_0vfw4")]
|
||||||
|
@@ -1,51 +1,49 @@
|
|||||||
class_name VNTextbox extends PanelContainer
|
class_name VNTextbox extends PanelContainer
|
||||||
|
|
||||||
const VN_REVEAL_TIME = 0.01
|
const VN_REVEAL_TIME = 0.01
|
||||||
const VISIBLE_LINES:int = 4
|
|
||||||
|
|
||||||
var label:RichTextLabel;
|
var label:AdvancedRichText;
|
||||||
var text:String = ""
|
|
||||||
var parsedOutText = ""
|
var parsedOutText = ""
|
||||||
var visibleCharacters:int = 0;
|
|
||||||
var revealTimer:float = 0;
|
var revealTimer:float = 0;
|
||||||
|
|
||||||
var lineStarts:Array[int] = [];
|
var lineStarts:Array[int] = [];
|
||||||
var newlineIndexes:Array[int] = [];
|
var newlineIndexes:Array[int] = [];
|
||||||
var wrappedText:String = "";
|
|
||||||
var currentLine = 0;
|
|
||||||
var currentViewScrolled = true;
|
var currentViewScrolled = true;
|
||||||
var isSpeedupDown = false;
|
var isSpeedupDown = false;
|
||||||
var isMoreViews = false;
|
|
||||||
var isClosed = true;
|
var isClosed:bool = false:
|
||||||
|
get():
|
||||||
|
return !self.visible;
|
||||||
|
set(value):
|
||||||
|
self.visible = !value;
|
||||||
|
|
||||||
signal textboxClosing
|
signal textboxClosing
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
label = $MarginContainer/Label
|
label = $MarginContainer/Label
|
||||||
self.visible = false;
|
isClosed = true
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if text == "":
|
if label.getFinalText() == "":
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if currentViewScrolled:
|
if label.visible_characters >= label.getCharactersDisplayedCount():
|
||||||
if Input.is_action_just_pressed("interact"):
|
if (label.maxLines + label.startLine) < label.getTotalLineCount():
|
||||||
if isMoreViews:
|
# Not on last page.
|
||||||
visibleCharacters = 0;
|
if Input.is_action_just_pressed("interact"):
|
||||||
currentLine += VISIBLE_LINES;
|
label.startLine += label.maxLines;
|
||||||
|
label.visible_characters = 0;
|
||||||
currentViewScrolled = false;
|
currentViewScrolled = false;
|
||||||
recalculateWrapping();
|
|
||||||
return
|
return
|
||||||
setText("");
|
|
||||||
textboxClosing.emit();
|
|
||||||
return;
|
|
||||||
|
|
||||||
if visibleCharacters >= getCountOfCharactersToScrollInView():
|
currentViewScrolled = true;
|
||||||
currentViewScrolled = true;
|
else:
|
||||||
#print("Scrolled view");
|
# On last page
|
||||||
#if isMoreViews:
|
if Input.is_action_just_released("interact"):
|
||||||
#print("More views");
|
textboxClosing.emit();
|
||||||
|
isClosed = true;
|
||||||
|
currentViewScrolled = true
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if Input.is_action_just_pressed("interact"):
|
if Input.is_action_just_pressed("interact"):
|
||||||
@@ -61,63 +59,15 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
if revealTimer > VN_REVEAL_TIME:
|
if revealTimer > VN_REVEAL_TIME:
|
||||||
revealTimer = 0;
|
revealTimer = 0;
|
||||||
visibleCharacters += 1;
|
label.visible_characters += 1;
|
||||||
label.visible_characters = visibleCharacters;
|
|
||||||
|
|
||||||
func getCountOfCharactersToScrollInView() -> int:
|
|
||||||
if lineStarts.size() <= VISIBLE_LINES:
|
|
||||||
return wrappedText.length();
|
|
||||||
if currentLine + VISIBLE_LINES >= lineStarts.size():
|
|
||||||
return wrappedText.length() - lineStarts[currentLine];
|
|
||||||
return lineStarts[min(lineStarts.size(), currentLine + VISIBLE_LINES)] - lineStarts[currentLine];
|
|
||||||
|
|
||||||
func recalculateWrapping():
|
|
||||||
# Reset label to default.
|
|
||||||
label.advancedText = text;
|
|
||||||
label.visible_characters = -1;
|
|
||||||
label.fit_content = true;
|
|
||||||
isMoreViews = false;
|
|
||||||
|
|
||||||
# Determine where the wrapped newlines are
|
|
||||||
lineStarts = [ 0 ];
|
|
||||||
var line = 0;
|
|
||||||
var wasNewLine = false;
|
|
||||||
for i in range(0, text.length()):
|
|
||||||
var tLine = label.get_character_line(i);
|
|
||||||
if tLine == line:
|
|
||||||
wasNewLine = false
|
|
||||||
if text[i] == "\n":
|
|
||||||
wasNewLine = true
|
|
||||||
continue;
|
|
||||||
if !wasNewLine:
|
|
||||||
newlineIndexes.append(i);
|
|
||||||
lineStarts.append(i);
|
|
||||||
line = tLine;
|
|
||||||
|
|
||||||
# Create fake pre-wrapped text.
|
|
||||||
wrappedText = "";
|
|
||||||
for i in range(lineStarts[currentLine], text.length()):
|
|
||||||
if newlineIndexes.find(i) != -1 and i != lineStarts[currentLine]:
|
|
||||||
wrappedText += "\n";
|
|
||||||
wrappedText += text[i];
|
|
||||||
|
|
||||||
label.advancedText = wrappedText;
|
|
||||||
label.fit_content = false;
|
|
||||||
label.visible_characters = 0;
|
|
||||||
|
|
||||||
if lineStarts.size() > currentLine + VISIBLE_LINES:
|
|
||||||
isMoreViews = true;
|
|
||||||
|
|
||||||
func setText(text:String) -> void:
|
func setText(text:String) -> void:
|
||||||
self.text = text;
|
|
||||||
if text == "":
|
|
||||||
self.visible = false;
|
|
||||||
return;
|
|
||||||
|
|
||||||
isClosed = false;
|
isClosed = false;
|
||||||
revealTimer = 0;
|
revealTimer = 0;
|
||||||
visibleCharacters = 0;
|
|
||||||
currentLine = 0;
|
|
||||||
currentViewScrolled = false;
|
currentViewScrolled = false;
|
||||||
recalculateWrapping();
|
label.visible_characters = 0;
|
||||||
self.visible = true;
|
label.startLine = 0;
|
||||||
|
label.text = ""
|
||||||
|
await get_tree().process_frame# Absolutely needed to make the text wrap
|
||||||
|
label.text = text;
|
||||||
|
label.visible_characters = 0;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://bkx3l0kckf4a8"]
|
[gd_scene load_steps=4 format=3 uid="uid://bkx3l0kckf4a8"]
|
||||||
|
|
||||||
[ext_resource type="Theme" uid="uid://dm7ee4aqjr2dl" path="res://ui/UI Theme.tres" id="1_wx4lp"]
|
[ext_resource type="Theme" uid="uid://dm7ee4aqjr2dl" path="res://ui/UI Theme.tres" id="1_wx4lp"]
|
||||||
[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://c6av3xe4m2ujl" path="res://addons/madtalk/runtime/madtalk_runtime.gd" id="3_2x6s1"]
|
|
||||||
[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="VNTextbox" type="PanelContainer"]
|
||||||
|
clip_contents = true
|
||||||
anchors_preset = 12
|
anchors_preset = 12
|
||||||
anchor_top = 1.0
|
anchor_top = 1.0
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -16,13 +16,8 @@ grow_vertical = 0
|
|||||||
theme = ExtResource("1_wx4lp")
|
theme = ExtResource("1_wx4lp")
|
||||||
script = ExtResource("2_uo1gm")
|
script = ExtResource("2_uo1gm")
|
||||||
|
|
||||||
[node name="MadTalk" type="Node" parent="."]
|
|
||||||
script = ExtResource("3_2x6s1")
|
|
||||||
DialogMainControl = NodePath("..")
|
|
||||||
DialogMessageBox = NodePath("../MarginContainer")
|
|
||||||
DialogMessageLabel = NodePath("../MarginContainer/Label")
|
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
clip_contents = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme = ExtResource("1_wx4lp")
|
theme = ExtResource("1_wx4lp")
|
||||||
theme_override_constants/margin_left = 4
|
theme_override_constants/margin_left = 4
|
||||||
@@ -34,8 +29,43 @@ theme_override_constants/margin_bottom = 4
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme = ExtResource("1_wx4lp")
|
theme = ExtResource("1_wx4lp")
|
||||||
bbcode_enabled = true
|
bbcode_enabled = true
|
||||||
text = "To get through tight spaces, press[input action=down][/input]"
|
text = "Hello, I'm an NPC!
|
||||||
fit_content = true
|
This is the second line here, I am purposefully adding a tonne of words so that it is forced to go across multiple lines and you can see how the word wrapping works, not only using Godot's built in word wrapping but with my advanced visibile characters smart wrapping. Now I am doing a multiline thing
|
||||||
|
Line 1
|
||||||
|
Line 2
|
||||||
|
Line 3
|
||||||
|
Line 4
|
||||||
|
Line 5
|
||||||
|
Line 6
|
||||||
|
Line 7
|
||||||
|
Line 8
|
||||||
|
Line 9
|
||||||
|
Line 10"
|
||||||
script = ExtResource("3_m60k3")
|
script = ExtResource("3_m60k3")
|
||||||
userText = "To get through tight spaces, press[input action=down][/input]"
|
userText = "Hello, I'm an NPC!
|
||||||
finalText = "To get through tight spaces, press[img height=12 valign=center,center]res://ui/input/down.tres[/img]"
|
This is the second line here, I am purposefully adding a tonne of words so that it is forced to go across multiple lines and you can see how the word wrapping works, not only using Godot's built in word wrapping but with my advanced visibile characters smart wrapping. Now I am doing a multiline thing
|
||||||
|
Line 1
|
||||||
|
Line 2
|
||||||
|
Line 3
|
||||||
|
Line 4
|
||||||
|
Line 5
|
||||||
|
Line 6
|
||||||
|
Line 7
|
||||||
|
Line 8
|
||||||
|
Line 9
|
||||||
|
Line 10"
|
||||||
|
_finalText = "Hello, I'm an NPC!
|
||||||
|
This is the second line here, I am purposefully adding a tonne of words so that it is forced to go across multiple lines and you can see how the word wrapping works, not only using Godot's built in word wrapping but with my advanced visibile characters smart wrapping. Now I am doing a multiline thing
|
||||||
|
Line 1
|
||||||
|
Line 2
|
||||||
|
Line 3
|
||||||
|
Line 4
|
||||||
|
Line 5
|
||||||
|
Line 6
|
||||||
|
Line 7
|
||||||
|
Line 8
|
||||||
|
Line 9
|
||||||
|
Line 10"
|
||||||
|
_newLineIndexes = Array[int]([0])
|
||||||
|
_lines = PackedStringArray("Hello, I\'m an NPC!", "This is the second line here, I am purposefully adding a tonne of words so that it is forced to go across multiple lines and you can see how the word wrapping works, not only using Godot\'s built in word wrapping but with my advanced visibile characters smart wrapping. Now I am doing a multiline thing", "Line 1", "Line 2", "Line 3", "Line 4", "Line 5", "Line 6", "Line 7", "Line 8", "Line 9", "Line 10")
|
||||||
|
maxLines = 4
|
||||||
|
@@ -2,7 +2,10 @@
|
|||||||
class_name AdvancedRichText extends RichTextLabel
|
class_name AdvancedRichText extends RichTextLabel
|
||||||
|
|
||||||
@export_multiline var userText:String = "" # The text the user is asking for
|
@export_multiline var userText:String = "" # The text the user is asking for
|
||||||
@export_multiline var finalText:String = "" # The final text after processing (translation, wrapping, etc.)
|
@export_multiline var _finalText:String = "" # The final text after processing (translation, wrapping, etc.)
|
||||||
|
|
||||||
|
@export var _newLineIndexes:Array[int] = [] # The indexes of where each line starts in finalText
|
||||||
|
@export var _lines:PackedStringArray = [];
|
||||||
|
|
||||||
# Hides the original RichTextLabel text property
|
# Hides the original RichTextLabel text property
|
||||||
func _set(property: StringName, value) -> bool:
|
func _set(property: StringName, value) -> bool:
|
||||||
@@ -36,17 +39,43 @@ func _get(property: StringName):
|
|||||||
get():
|
get():
|
||||||
return smartWrap
|
return smartWrap
|
||||||
|
|
||||||
func _init() -> void:
|
@export var maxLines:int = -1:
|
||||||
_recalcText()
|
set(value):
|
||||||
|
maxLines = value
|
||||||
|
_recalcText()
|
||||||
|
get():
|
||||||
|
return maxLines
|
||||||
|
|
||||||
|
@export var startLine:int = 0:
|
||||||
|
set(value):
|
||||||
|
startLine = value
|
||||||
|
_recalcText()
|
||||||
|
get():
|
||||||
|
return startLine
|
||||||
|
|
||||||
|
# Returns count of characters that can be displayed, assuming visible_chars = -1
|
||||||
|
func getCharactersDisplayedCount() -> int:
|
||||||
|
# Count characters
|
||||||
|
var count = 0
|
||||||
|
var lineCount = min(startLine + maxLines, _lines.size()) - startLine
|
||||||
|
for i in range(startLine, startLine + lineCount):
|
||||||
|
count += _lines[i].length()
|
||||||
|
if lineCount > 1:
|
||||||
|
count += lineCount - 1 # Add newlines
|
||||||
|
return count
|
||||||
|
|
||||||
|
func getFinalText() -> String:
|
||||||
|
return _finalText
|
||||||
|
|
||||||
|
func getTotalLineCount() -> int:
|
||||||
|
return _lines.size()
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
_recalcText()
|
self.threaded = false;
|
||||||
self.resized.connect(_recalcText)
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
self.resized.disconnect(_recalcText)
|
|
||||||
|
|
||||||
func _recalcText() -> void:
|
func _recalcText() -> void:
|
||||||
|
_lines.clear()
|
||||||
|
|
||||||
if userText.is_empty():
|
if userText.is_empty():
|
||||||
self.richtextlabel_text = ""
|
self.richtextlabel_text = ""
|
||||||
return
|
return
|
||||||
@@ -63,8 +92,6 @@ func _recalcText() -> void:
|
|||||||
for match in regex.search_all(textTranslated):
|
for match in regex.search_all(textTranslated):
|
||||||
var action = match.get_string(1).to_lower()
|
var action = match.get_string(1).to_lower()
|
||||||
var height:int = get_theme_font_size("normal_font_size")
|
var height:int = get_theme_font_size("normal_font_size")
|
||||||
# var device = get_current_device_type()
|
|
||||||
# var icon_path = get_icon_for_action(action, device)
|
|
||||||
var img_tag = "[img height=%d valign=center,center]res://ui/input/%s.tres[/img]" % [ height, action ]
|
var img_tag = "[img height=%d valign=center,center]res://ui/input/%s.tres[/img]" % [ height, action ]
|
||||||
inputIconText = inputIconText.replace(match.get_string(0), img_tag)
|
inputIconText = inputIconText.replace(match.get_string(0), img_tag)
|
||||||
|
|
||||||
@@ -76,29 +103,39 @@ func _recalcText() -> void:
|
|||||||
self.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART;
|
self.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART;
|
||||||
self.richtextlabel_text = unwrappedText
|
self.richtextlabel_text = unwrappedText
|
||||||
self.visible_characters = -1;
|
self.visible_characters = -1;
|
||||||
self.fit_content = true;
|
self.fit_content = false;
|
||||||
var newlineIndexes = [];
|
_newLineIndexes = [];
|
||||||
|
|
||||||
# Determine where the wrapped newlines are
|
# Determine where the wrapped newlines are
|
||||||
var line = 0;
|
var line = 0;
|
||||||
var wasNewLine = false;
|
var wasNewLine = false;
|
||||||
for i in range(0, unwrappedText.length()):
|
for i in range(0, self.richtextlabel_text.length()):
|
||||||
var tLine = self.get_character_line(i);
|
var tLine = self.get_character_line(i);
|
||||||
if tLine == line || tLine == -1:
|
if tLine == line:
|
||||||
wasNewLine = false
|
wasNewLine = false
|
||||||
if unwrappedText[i] == "\n":
|
if self.richtextlabel_text[i] == "\n":
|
||||||
wasNewLine = true
|
wasNewLine = true
|
||||||
continue;
|
continue;
|
||||||
if !wasNewLine:
|
if !wasNewLine:
|
||||||
newlineIndexes.append(i);
|
_newLineIndexes.append(i);
|
||||||
line = tLine;
|
line = tLine;
|
||||||
|
|
||||||
# Create fake pre-wrapped text.
|
# Create fake pre-wrapped text.
|
||||||
wrappedText = "";
|
wrappedText = "";
|
||||||
for i in range(0, unwrappedText.length()):
|
for i in range(0, self.richtextlabel_text.length()):
|
||||||
if newlineIndexes.find(i) != -1 and i != 0:
|
if _newLineIndexes.find(i) != -1 and i != 0:
|
||||||
wrappedText += "\n";
|
wrappedText += "\n";
|
||||||
wrappedText += unwrappedText[i];
|
wrappedText += self.richtextlabel_text[i];
|
||||||
|
|
||||||
finalText = wrappedText
|
# Handle max and start line(s)
|
||||||
self.richtextlabel_text = wrappedText
|
var maxText = wrappedText
|
||||||
|
if maxLines > 0:
|
||||||
|
_lines = maxText.split("\n", true);
|
||||||
|
var selectedLines = [];
|
||||||
|
for i in range(startLine, min(startLine + maxLines, _lines.size())):
|
||||||
|
selectedLines.append(_lines[i]);
|
||||||
|
maxText = "\n".join(selectedLines);
|
||||||
|
|
||||||
|
_finalText = maxText
|
||||||
|
self.richtextlabel_text = maxText
|
||||||
|
print("Updated text")
|
||||||
|
Reference in New Issue
Block a user