从零开始打造
Godot 4计算器应用

使用Godot 4引擎和GDScript脚本语言,构建功能完备的计算器应用。本教程涵盖UI设计、核心计算逻辑、进阶功能实现,以及自定义表达式解析器开发。

GDScript Godot 4 全面指南
2 + 3 × 4 = 14

1. 项目准备与场景设置

1 创建新的Godot项目

启动Godot 4项目管理器,点击"新建项目"按钮,为项目选择合适的路径和名称(如"GodotCalculator")。确保渲染器设置为"Forward+"或"Mobile",这对于2D UI应用已经足够。

专业提示: 对于纯UI应用,可以选择"Mobile"渲染器以获得更好的性能表现。

2 设计计算器UI场景结构

1.2.1 添加主容器节点(Control)

在场景面板中,将默认的根节点类型更改为Control,并重命名为Calculator。这个节点将作为所有UI元素的父容器。

1.2.2 创建显示屏(Label)

添加一个Label节点作为显示屏,命名为Display。设置文本为"0",调整字体大小,并将文本对齐方式设置为右对齐。

# 显示组件配置示例 $Display.theme_override.font_sizes.font_size = 48 $Display.alignment = ALIGNMENT_RIGHT $Display.text = "0"

1.2.3 创建按钮网格(GridContainer)

添加GridContainer节点,命名为ButtonGrid。将列数设置为4,创建标准的计算器按钮布局。

1.2.4 添加数字与运算符按钮

在网格中添加所有需要的按钮,包括数字0-9、小数点、基本运算符(+、-、*、/)、清除(C)、删除(DEL)和等号(=)。

Godot引擎中计算器UI布局

2. 核心逻辑实现:使用Expression类进行计算

2.1 了解Godot的Expression类

Godot的Expression类是一个强大的工具,用于在运行时动态解析和执行字符串形式的表达式 [121]。 它可以处理各种数学运算、布尔逻辑,甚至函数调用。

Expression类的基本用法

工作流程分为两个主要步骤:

  1. 解析(Parsing):使用 parse()方法分析表达式语法
  2. 执行(Execution):使用 execute()方法计算结果

2.2 编写主脚本(Calculator.gd)

创建主脚本作为UI与计算引擎之间的桥梁,处理按钮点击事件、更新显示内容,并协调整个计算流程。

extends Control var current_expression = "" func _ready(): # 连接所有按钮的信号 $ButtonGrid/Button1.pressed.connect(_on_number_button_pressed.bind("1")) $ButtonGrid/ButtonAdd.pressed.connect(_on_operator_button_pressed.bind(" + ")) # 连接其他按钮... func _on_number_button_pressed(number): if current_expression == "0": current_expression = number else: current_expression += number $Display.text = current_expression

2.3 实现计算与结果显示

func _on_equals_button_pressed(): var expression = Expression.new() var error = expression.parse(current_expression) if error == OK: var result = expression.execute() if not expression.has_execute_failed(): $Display.text = str(result) current_expression = str(result) else: $Display.text = "Error" current_expression = "0" else: $Display.text = "Error" current_expression = "0"

2.4 错误处理与边界情况

健壮的计算器应用必须能够妥善处理各种错误情况,包括数学错误(如除以零)和语法错误(如不完整表达式)。

错误情况

  • • 除以零
  • • 不完整表达式(如"1+")
  • • 括号不匹配
  • • 无效运算符

处理方法

  • • 使用 has_execute_failed()
  • • 检查 parse()返回值
  • • 提供友好的错误提示
  • • 自动重置状态

3. 进阶功能实现

3.1 添加清除功能

全部清除(C)按钮

func _on_clear_button_pressed(): current_expression = "" $Display.text = "0"

删除最后一位(DEL)按钮

func _on_delete_button_pressed(): if current_expression.length() > 0: current_expression = current_expression.substr(0, current_expression.length() - 1) $Display.text = current_expression if current_expression else "0"

3.2 扩展运算符支持

添加括号支持

括号是数学表达式中用于明确运算优先级的关键符号。Expression类天然支持括号运算。

func _on_left_parenthesis_pressed(): current_expression += "(" $Display.text = current_expression func _on_right_parenthesis_pressed(): current_expression += ")" $Display.text = current_expression

添加小数点支持

小数点功能需要处理边界情况,防止用户在同一数字中输入多个小数点。

func _on_decimal_button_pressed(): # 检查当前数字部分是否已有小数点 var parts = current_expression.split(" ") var last_part = parts[-1] if "." not in last_part: if current_expression == "": current_expression = "0." else: current_expression += "." $Display.text = current_expression

3.3 自定义表达式预处理

为了让计算器支持更直观的数学符号(如用 ^表示幂运算),可以实现一个表达式预处理层。

func preprocess_expression(expr: String) -> String: # 替换用户友好的符号 expr = expr.replace("^", "pow") expr = expr.replace("√", "sqrt") # 添加更多替换规则... return expr # 在计算前调用预处理函数 var processed_expr = preprocess_expression(current_expression) var error = expression.parse(processed_expr)

4. 替代方案:自定义表达式解析器

4.1 理解调度场算法

调度场算法(Shunting-Yard Algorithm)是一种经典算法,用于将中缀表达式转换为后缀表达式(Reverse Polish Notation),从而简化计算过程。

算法核心思想

使用两个数据结构:输出队列和运算符栈。算法根据运算符优先级和结合性规则,将中缀表达式转换为无需括号的后缀形式。

4.2 在GDScript中实现调度场算法

定义运算符优先级

const OPERATORS = { "+": {"precedence": 2, "associativity": "left"}, "-": {"precedence": 2, "associativity": "left"}, "*": {"precedence": 3, "associativity": "left"}, "/": {"precedence": 3, "associativity": "left"}, "^": {"precedence": 4, "associativity": "right"} }

实现中缀到后缀转换

func infix_to_postfix(expression: String) -> String: var output_queue = [] var operator_stack = [] var tokens = tokenize(expression) for token in tokens: if token.is_valid_float(): output_queue.append(token) elif token in OPERATORS: while (operator_stack.size() > 0 and operator_stack[-1] in OPERATORS and OPERATORS[operator_stack[-1]]["precedence"] >= OPERATORS[token]["precedence"]): output_queue.append(operator_stack.pop_back()) operator_stack.append(token) elif token == "(": operator_stack.append(token) elif token == ")": while operator_stack.size() > 0 and operator_stack[-1] != "(": output_queue.append(operator_stack.pop_back()) if operator_stack.size() > 0 and operator_stack[-1] == "(": operator_stack.pop_back() while operator_stack.size() > 0: output_queue.append(operator_stack.pop_back()) return " ".join(output_queue)

5. 完整代码示例与总结

5.1 主脚本(Calculator.gd)完整代码

extends Control var current_expression = "0" func _ready(): # 连接所有按钮的信号 $ButtonGrid/Button0.pressed.connect(_on_button_0_pressed) $ButtonGrid/Button1.pressed.connect(_on_button_1_pressed) # 连接其他数字按钮... $ButtonGrid/ButtonAdd.pressed.connect(_on_button_add_pressed) $ButtonGrid/ButtonSubtract.pressed.connect(_on_button_subtract_pressed) # 连接其他运算符按钮... $ButtonGrid/ButtonEquals.pressed.connect(_on_button_equals_pressed) $ButtonGrid/ButtonC.pressed.connect(_on_button_c_pressed) $ButtonGrid/ButtonDel.pressed.connect(_on_button_del_pressed) # 数字按钮处理函数 func _on_button_0_pressed(): _append_to_expression("0") func _on_button_1_pressed(): _append_to_expression("1") # 运算符按钮处理函数 func _on_button_add_pressed(): _append_to_expression(" + ") func _on_button_subtract_pressed(): _append_to_expression(" - ") # 等号按钮处理函数 func _on_button_equals_pressed(): var expr = Expression.new() var error = expr.parse(current_expression) if error == OK: var result = expr.execute() if not expr.has_execute_failed(): $Display.text = str(result) current_expression = str(result) else: $Display.text = "Error" current_expression = "0" else: $Display.text = "Error" current_expression = "0" # 清除按钮 func _on_button_c_pressed(): current_expression = "0" $Display.text = current_expression # 删除按钮 func _on_button_del_pressed(): if current_expression.length() > 1: current_expression = current_expression.left(current_expression.length() - 1) else: current_expression = "0" $Display.text = current_expression # 辅助函数 func _append_to_expression(value: String): if current_expression == "0" and value != ".": current_expression = value else: current_expression += value $Display.text = current_expression

5.2 项目总结与扩展思路

核心知识点回顾

  • UI设计:使用Control、Label、Button和GridContainer节点构建用户界面
  • 信号与槽:连接按钮的pressed信号到脚本函数
  • GDScript编程:编写脚本处理逻辑、更新UI并执行计算
  • Expression类:使用内置类解析和计算数学表达式
  • 错误处理:处理除以零、无效表达式等错误情况

扩展功能建议

用户体验增强
  • • 历史记录功能
  • • 主题切换
  • • 键盘输入支持
  • • 动画效果
功能扩展
  • • 科学计算模式
  • • 记忆功能(M+、M-、MR)
  • • 百分比计算
  • • 单位转换

开始你的Godot开发之旅

通过这个计算器项目,你已经掌握了Godot UI系统和GDScript的核心技能。 继续探索更多可能性,构建更复杂的应用吧!

深入学习GDScript
探索游戏开发
构建移动应用