带宽测试
这个提交包含在:
二进制文件未显示。
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
Checking File Globs
|
Checking File Globs
|
||||||
godot-cpp.vcxproj -> E:\newgame\marry\build\bin\libgodot-cpp.windows.template_debug.x86_64.lib
|
godot-cpp.vcxproj -> E:\newgame\marry\build\bin\libgodot-cpp.windows.template_debug.x86_64.lib
|
||||||
pingpong_ball.cpp
|
register_types.cpp
|
||||||
|
pingpong_game.cpp
|
||||||
正在创建库 E:/newgame/marry/main/bin/Release/pingpang.lib 和对象 E:/newgame/marry/main/bin/Release/pingpang.exp
|
正在创建库 E:/newgame/marry/main/bin/Release/pingpang.lib 和对象 E:/newgame/marry/main/bin/Release/pingpang.exp
|
||||||
pingpang.vcxproj -> E:\newgame\marry\main\bin\Release\pingpang.dll
|
pingpang.vcxproj -> E:\newgame\marry\main\bin\Release\pingpang.dll
|
||||||
复制 pingpang.dll 到 main/bin/ 根目录
|
复制 pingpang.dll 到 main/bin/ 根目录
|
||||||
Building Custom Rule E:/newgame/marry/CMakeLists.txt
|
|
||||||
|
|||||||
+17
-8
@@ -1,15 +1,16 @@
|
|||||||
[gd_scene load_steps=4 format=3]
|
[gd_scene load_steps=4 format=3]
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="circle_ball"]
|
[sub_resource type="CircleShape2D" id="circle_ball"]
|
||||||
radius = 12.0
|
radius = 10.0
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="rect_paddle"]
|
[sub_resource type="RectangleShape2D" id="rect_paddle"]
|
||||||
size = Vector2(20.0, 120.0)
|
size = Vector2(18.0, 100.0)
|
||||||
|
|
||||||
[node name="Game" type="PingPongGame"]
|
[node name="Game" type="PingPongGame"]
|
||||||
|
|
||||||
[node name="Ball" type="PingPongBall" parent="Game"]
|
[node name="Ball" type="PingPongBall" parent="Game"]
|
||||||
position = Vector2(576.0, 324.0)
|
position = Vector2(576.0, 324.0)
|
||||||
|
initial_speed = 350.0
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Game/Ball"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="Game/Ball"]
|
||||||
shape = SubResource("circle_ball")
|
shape = SubResource("circle_ball")
|
||||||
@@ -51,9 +52,17 @@ horizontal_alignment = 1
|
|||||||
theme_override_font_sizes/font_size = 48
|
theme_override_font_sizes/font_size = 48
|
||||||
theme_override_colors/font_color = Color(1.0, 1.0, 1.0, 1.0)
|
theme_override_colors/font_color = Color(1.0, 1.0, 1.0, 1.0)
|
||||||
|
|
||||||
[node name="CenterLine" type="ColorRect" parent="Game/UI"]
|
[node name="Message" type="Label" parent="Game/UI"]
|
||||||
offset_left = 574.0
|
anchor_left = 0.5
|
||||||
offset_top = 0.0
|
anchor_top = 0.5
|
||||||
offset_right = 578.0
|
anchor_right = 0.5
|
||||||
offset_bottom = 648.0
|
anchor_bottom = 0.5
|
||||||
color = Color(1.0, 1.0, 1.0, 0.1)
|
offset_left = -200.0
|
||||||
|
offset_top = -100.0
|
||||||
|
offset_right = 200.0
|
||||||
|
offset_bottom = 100.0
|
||||||
|
text = "按空格键开始"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
theme_override_font_sizes/font_size = 36
|
||||||
|
theme_override_colors/font_color = Color(1.0, 1.0, 1.0, 0.9)
|
||||||
|
|||||||
+79
-23
@@ -11,12 +11,24 @@
|
|||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉颜色常量 — 以后替换素材时删除本段
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
static const Color BALL_COLOR = Color(1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
void PingPongBall::_bind_methods() {
|
void PingPongBall::_bind_methods() {
|
||||||
// 属性
|
// 属性
|
||||||
ClassDB::bind_method(D_METHOD("get_speed"), &PingPongBall::get_speed);
|
ClassDB::bind_method(D_METHOD("get_speed"), &PingPongBall::get_speed);
|
||||||
ClassDB::bind_method(D_METHOD("set_speed", "value"), &PingPongBall::set_speed);
|
ClassDB::bind_method(D_METHOD("set_speed", "value"), &PingPongBall::set_speed);
|
||||||
ClassDB::add_property("PingPongBall", PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
|
ClassDB::add_property("PingPongBall", PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("get_initial_speed"), &PingPongBall::get_initial_speed);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_initial_speed", "value"), &PingPongBall::set_initial_speed);
|
||||||
|
ClassDB::add_property("PingPongBall", PropertyInfo(Variant::FLOAT, "initial_speed"), "set_initial_speed", "get_initial_speed");
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("get_active"), &PingPongBall::get_active);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_active", "value"), &PingPongBall::set_active);
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
ClassDB::bind_method(D_METHOD("reset"), &PingPongBall::reset);
|
ClassDB::bind_method(D_METHOD("reset"), &PingPongBall::reset);
|
||||||
|
|
||||||
@@ -25,7 +37,9 @@ void PingPongBall::_bind_methods() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PingPongBall::PingPongBall() {
|
PingPongBall::PingPongBall() {
|
||||||
speed = 400.0f;
|
speed = 350.0f;
|
||||||
|
initial_speed = 350.0f;
|
||||||
|
is_active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PingPongBall::~PingPongBall() {
|
PingPongBall::~PingPongBall() {
|
||||||
@@ -35,78 +49,98 @@ void PingPongBall::_ready() {
|
|||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉层 — 球体 + 拖尾(几何体占位)
|
||||||
|
// 替换方法:删除本方法 → 添加 Sprite2D 子节点
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
void PingPongBall::_draw() {
|
void PingPongBall::_draw() {
|
||||||
// 画一个白色圆形表示球
|
// 拖尾:从旧到新画渐隐圆
|
||||||
draw_circle(Vector2(0.0f, 0.0f), 12.0f, Color(1.0f, 1.0f, 1.0f));
|
int trail_count = (int)trail_positions.size();
|
||||||
|
for (int i = 0; i < trail_count; i++) {
|
||||||
|
float alpha = (float)(i + 1) / (float)BALL_TRAIL_LENGTH * 0.3f;
|
||||||
|
float radius = BALL_RADIUS * (0.5f + 0.5f * (float)(i + 1) / (float)BALL_TRAIL_LENGTH);
|
||||||
|
draw_circle(trail_positions[i], radius, Color(1.0f, 1.0f, 1.0f, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 球本体
|
||||||
|
draw_circle(Vector2(0.0f, 0.0f), BALL_RADIUS, BALL_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingPongBall::_physics_process(double delta) {
|
void PingPongBall::_physics_process(double delta) {
|
||||||
queue_redraw();
|
queue_redraw();
|
||||||
|
|
||||||
|
if (!is_active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新拖尾
|
||||||
|
trail_positions.push_back(get_position());
|
||||||
|
if ((int)trail_positions.size() > BALL_TRAIL_LENGTH) {
|
||||||
|
trail_positions.erase(trail_positions.begin());
|
||||||
|
}
|
||||||
|
|
||||||
Vector2 velocity = direction * speed * (float)delta;
|
Vector2 velocity = direction * speed * (float)delta;
|
||||||
|
|
||||||
Ref<KinematicCollision2D> collision = move_and_collide(velocity);
|
Ref<KinematicCollision2D> collision = move_and_collide(velocity);
|
||||||
|
|
||||||
if (collision.is_valid()) {
|
if (collision.is_valid()) {
|
||||||
// 计算反弹方向
|
|
||||||
Vector2 normal = collision->get_normal();
|
Vector2 normal = collision->get_normal();
|
||||||
direction = direction.bounce(normal);
|
direction = direction.bounce(normal);
|
||||||
|
|
||||||
// 根据碰撞法线判断碰撞对象类型
|
|
||||||
// 法线主要水平 → 碰到球拍(左右反弹)
|
|
||||||
// 法线主要垂直 → 碰到上下墙(上下反弹)
|
|
||||||
// 如果法线接近水平且球已经接近左右边界 → 得分
|
|
||||||
Object *collider = collision->get_collider();
|
Object *collider = collision->get_collider();
|
||||||
if (collider) {
|
if (collider) {
|
||||||
Node *collider_node = Object::cast_to<Node>(collider);
|
Node *collider_node = Object::cast_to<Node>(collider);
|
||||||
if (collider_node) {
|
if (collider_node) {
|
||||||
StringName node_name = collider_node->get_name();
|
StringName node_name = collider_node->get_name();
|
||||||
// 判断是不是球拍(球拍名字包含 "Paddle")
|
// 碰到球拍
|
||||||
if (node_name.find("Paddle") != -1) {
|
if (node_name.find("Paddle") != -1) {
|
||||||
// 碰到球拍,微调角度(根据击中位置)
|
|
||||||
Node2D *collider_2d = Object::cast_to<Node2D>(collider_node);
|
Node2D *collider_2d = Object::cast_to<Node2D>(collider_node);
|
||||||
if (collider_2d) {
|
if (collider_2d) {
|
||||||
|
// 根据击中位置微调角度
|
||||||
float hit_offset = (get_global_position().y - collider_2d->get_global_position().y) / 60.0f;
|
float hit_offset = (get_global_position().y - collider_2d->get_global_position().y) / 60.0f;
|
||||||
hit_offset = CLAMP(hit_offset, -1.0f, 1.0f);
|
hit_offset = CLAMP(hit_offset, -1.0f, 1.0f);
|
||||||
direction.y += hit_offset * 0.5f;
|
direction.y += hit_offset * 0.5f;
|
||||||
direction = direction.normalized();
|
direction = direction.normalized();
|
||||||
|
|
||||||
|
// 速度递增
|
||||||
|
speed *= BALL_SPEED_INCREMENT;
|
||||||
|
if (speed > BALL_MAX_SPEED) {
|
||||||
|
speed = BALL_MAX_SPEED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保方向始终有效
|
|
||||||
direction = direction.normalized();
|
direction = direction.normalized();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测是否出界(得分判定)
|
// 得分判定
|
||||||
Viewport *viewport = get_viewport();
|
Viewport *viewport = get_viewport();
|
||||||
if (viewport) {
|
if (viewport) {
|
||||||
Vector2 screen_size = viewport->get_visible_rect().size;
|
Vector2 screen_size = viewport->get_visible_rect().size;
|
||||||
Vector2 pos = get_global_position();
|
Vector2 pos = get_global_position();
|
||||||
|
|
||||||
if (pos.x < -50.0f) {
|
if (pos.x < -50.0f) {
|
||||||
// 球从左边出界,右边得分
|
|
||||||
emit_signal("scored", 1);
|
emit_signal("scored", 1);
|
||||||
reset();
|
reset();
|
||||||
} else if (pos.x > screen_size.x + 50.0f) {
|
} else if (pos.x > screen_size.x + 50.0f) {
|
||||||
// 球从右边出界,左边得分
|
|
||||||
emit_signal("scored", 0);
|
emit_signal("scored", 0);
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y 方向边界限制(防止飞出屏幕)
|
// Y 边界限制
|
||||||
if (pos.y < 0.0f) {
|
if (pos.y < BALL_RADIUS) {
|
||||||
set_global_position(Vector2(pos.x, 0.0f));
|
set_global_position(Vector2(pos.x, BALL_RADIUS));
|
||||||
direction.y = std::abs(direction.y);
|
direction.y = std::abs(direction.y);
|
||||||
} else if (pos.y > screen_size.y) {
|
} else if (pos.y > screen_size.y - BALL_RADIUS) {
|
||||||
set_global_position(Vector2(pos.x, screen_size.y));
|
set_global_position(Vector2(pos.x, screen_size.y - BALL_RADIUS));
|
||||||
direction.y = -std::abs(direction.y);
|
direction.y = -std::abs(direction.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingPongBall::reset() {
|
void PingPongBall::reset() {
|
||||||
// 回到屏幕中央
|
|
||||||
Viewport *viewport = get_viewport();
|
Viewport *viewport = get_viewport();
|
||||||
if (viewport) {
|
if (viewport) {
|
||||||
Vector2 screen_size = viewport->get_visible_rect().size;
|
Vector2 screen_size = viewport->get_visible_rect().size;
|
||||||
@@ -115,18 +149,32 @@ void PingPongBall::reset() {
|
|||||||
set_global_position(Vector2(640.0f, 360.0f));
|
set_global_position(Vector2(640.0f, 360.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 随机方向(-45° 到 45° 之间,向左或向右随机)
|
// 重置速度
|
||||||
float angle = (UtilityFunctions::randf() - 0.5f) * Math_PI / 2.0f; // -45° ~ 45°
|
speed = initial_speed;
|
||||||
|
|
||||||
|
// 清空拖尾
|
||||||
|
trail_positions.clear();
|
||||||
|
|
||||||
|
// 随机方向
|
||||||
|
float angle = (UtilityFunctions::randf() - 0.5f) * Math_PI / 2.0f;
|
||||||
int side = (UtilityFunctions::randf() > 0.5f) ? 1 : -1;
|
int side = (UtilityFunctions::randf() > 0.5f) ? 1 : -1;
|
||||||
direction = Vector2((float)side * std::cos(angle), std::sin(angle)).normalized();
|
direction = Vector2((float)side * std::cos(angle), std::sin(angle)).normalized();
|
||||||
|
|
||||||
// 确保水平分量足够(避免球过于垂直)
|
// 确保水平分量足够
|
||||||
if (std::abs(direction.x) < 0.3f) {
|
if (std::abs(direction.x) < 0.3f) {
|
||||||
direction.x = (direction.x > 0 ? 1.0f : -1.0f) * 0.3f;
|
direction.x = (direction.x > 0 ? 1.0f : -1.0f) * 0.3f;
|
||||||
direction = direction.normalized();
|
direction = direction.normalized();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PingPongBall::set_active(bool p_active) {
|
||||||
|
is_active = p_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PingPongBall::get_active() const {
|
||||||
|
return is_active;
|
||||||
|
}
|
||||||
|
|
||||||
void PingPongBall::set_speed(float p_speed) {
|
void PingPongBall::set_speed(float p_speed) {
|
||||||
speed = p_speed;
|
speed = p_speed;
|
||||||
}
|
}
|
||||||
@@ -134,3 +182,11 @@ void PingPongBall::set_speed(float p_speed) {
|
|||||||
float PingPongBall::get_speed() const {
|
float PingPongBall::get_speed() const {
|
||||||
return speed;
|
return speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PingPongBall::set_initial_speed(float p_speed) {
|
||||||
|
initial_speed = p_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
float PingPongBall::get_initial_speed() const {
|
||||||
|
return initial_speed;
|
||||||
|
}
|
||||||
|
|||||||
+23
-1
@@ -2,15 +2,30 @@
|
|||||||
#define PINGPONG_BALL_H
|
#define PINGPONG_BALL_H
|
||||||
|
|
||||||
#include <godot_cpp/classes/character_body2d.hpp>
|
#include <godot_cpp/classes/character_body2d.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉常量 — 以后替换为 Sprite2D 时删除本段
|
||||||
|
// (颜色值定义在 pingpong_ball.cpp 中)
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
static constexpr int BALL_TRAIL_LENGTH = 8;
|
||||||
|
static constexpr float BALL_RADIUS = 10.0f;
|
||||||
|
static constexpr float BALL_SPEED_INCREMENT = 1.03f;
|
||||||
|
static constexpr float BALL_MAX_SPEED = 800.0f;
|
||||||
|
|
||||||
class PingPongBall : public CharacterBody2D {
|
class PingPongBall : public CharacterBody2D {
|
||||||
GDCLASS(PingPongBall, CharacterBody2D)
|
GDCLASS(PingPongBall, CharacterBody2D)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float speed = 300.0f;
|
float speed = 350.0f;
|
||||||
|
float initial_speed = 350.0f;
|
||||||
Vector2 direction = Vector2(1.0f, 0.0f);
|
Vector2 direction = Vector2(1.0f, 0.0f);
|
||||||
|
bool is_active = false;
|
||||||
|
|
||||||
|
// 拖尾位置记录(视觉层)
|
||||||
|
std::vector<Vector2> trail_positions;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
@@ -26,10 +41,17 @@ public:
|
|||||||
// 重置球到屏幕中央,以随机角度发射
|
// 重置球到屏幕中央,以随机角度发射
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
// 活跃状态控制
|
||||||
|
void set_active(bool p_active);
|
||||||
|
bool get_active() const;
|
||||||
|
|
||||||
// 属性 getter/setter
|
// 属性 getter/setter
|
||||||
void set_speed(float p_speed);
|
void set_speed(float p_speed);
|
||||||
float get_speed() const;
|
float get_speed() const;
|
||||||
|
|
||||||
|
void set_initial_speed(float p_speed);
|
||||||
|
float get_initial_speed() const;
|
||||||
|
|
||||||
// 信号: scored(int which) — 0=左边得分, 1=右边得分
|
// 信号: scored(int which) — 0=左边得分, 1=右边得分
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+165
-5
@@ -4,10 +4,21 @@
|
|||||||
#include <godot_cpp/core/class_db.hpp>
|
#include <godot_cpp/core/class_db.hpp>
|
||||||
#include <godot_cpp/variant/utility_functions.hpp>
|
#include <godot_cpp/variant/utility_functions.hpp>
|
||||||
#include <godot_cpp/classes/engine.hpp>
|
#include <godot_cpp/classes/engine.hpp>
|
||||||
|
#include <godot_cpp/classes/input_event_key.hpp>
|
||||||
|
#include <godot_cpp/classes/input.hpp>
|
||||||
#include <godot_cpp/classes/scene_tree.hpp>
|
#include <godot_cpp/classes/scene_tree.hpp>
|
||||||
|
#include <godot_cpp/classes/viewport.hpp>
|
||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉颜色常量 — 以后替换素材时删除本段
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
static const Color FIELD_BG_COLOR = Color(0.06f, 0.10f, 0.18f);
|
||||||
|
static const Color FIELD_BORDER_COLOR = Color(1.0f, 1.0f, 1.0f, 0.25f);
|
||||||
|
static const Color FIELD_CENTER_LINE_COLOR = Color(1.0f, 1.0f, 1.0f, 0.12f);
|
||||||
|
static const Color FIELD_CIRCLE_COLOR = Color(1.0f, 1.0f, 1.0f, 0.06f);
|
||||||
|
|
||||||
void PingPongGame::_bind_methods() {
|
void PingPongGame::_bind_methods() {
|
||||||
// 属性
|
// 属性
|
||||||
ClassDB::bind_method(D_METHOD("get_left_score"), &PingPongGame::get_left_score);
|
ClassDB::bind_method(D_METHOD("get_left_score"), &PingPongGame::get_left_score);
|
||||||
@@ -24,6 +35,7 @@ void PingPongGame::_bind_methods() {
|
|||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
ClassDB::bind_method(D_METHOD("reset_game"), &PingPongGame::reset_game);
|
ClassDB::bind_method(D_METHOD("reset_game"), &PingPongGame::reset_game);
|
||||||
|
ClassDB::bind_method(D_METHOD("start_countdown"), &PingPongGame::start_countdown);
|
||||||
ClassDB::bind_method(D_METHOD("_on_ball_scored", "which"), &PingPongGame::_on_ball_scored);
|
ClassDB::bind_method(D_METHOD("_on_ball_scored", "which"), &PingPongGame::_on_ball_scored);
|
||||||
|
|
||||||
// 信号
|
// 信号
|
||||||
@@ -34,41 +46,162 @@ PingPongGame::PingPongGame() {
|
|||||||
left_score = 0;
|
left_score = 0;
|
||||||
right_score = 0;
|
right_score = 0;
|
||||||
max_score = 5;
|
max_score = 5;
|
||||||
|
state = WAITING;
|
||||||
|
state_timer = 0.0f;
|
||||||
|
countdown_number = COUNTDOWN_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
PingPongGame::~PingPongGame() {
|
PingPongGame::~PingPongGame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingPongGame::_ready() {
|
void PingPongGame::_ready() {
|
||||||
// 查找比分 Label
|
// 查找子节点
|
||||||
left_score_label = Object::cast_to<Label>(get_node_or_null("UI/LeftScore"));
|
left_score_label = Object::cast_to<Label>(get_node_or_null("UI/LeftScore"));
|
||||||
right_score_label = Object::cast_to<Label>(get_node_or_null("UI/RightScore"));
|
right_score_label = Object::cast_to<Label>(get_node_or_null("UI/RightScore"));
|
||||||
|
message_label = Object::cast_to<Label>(get_node_or_null("UI/Message"));
|
||||||
|
ball = Object::cast_to<PingPongBall>(get_node_or_null("Ball"));
|
||||||
|
|
||||||
// 连接球的得分信号
|
// 连接球的得分信号
|
||||||
PingPongBall *ball = Object::cast_to<PingPongBall>(get_node_or_null("Ball"));
|
|
||||||
if (ball) {
|
if (ball) {
|
||||||
ball->connect("scored", Callable(this, "_on_ball_scored"));
|
ball->connect("scored", Callable(this, "_on_ball_scored"));
|
||||||
}
|
}
|
||||||
|
|
||||||
update_score_display();
|
update_score_display();
|
||||||
|
show_message("按空格键开始");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PingPongGame::_process(double delta) {
|
||||||
|
queue_redraw();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case WAITING:
|
||||||
|
// 等待用户按空格(在 _input 中处理)
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COUNTDOWN: {
|
||||||
|
state_timer -= (float)delta;
|
||||||
|
if (state_timer <= 0.0f) {
|
||||||
|
countdown_number--;
|
||||||
|
if (countdown_number > 0) {
|
||||||
|
show_message(String::num_int64(countdown_number));
|
||||||
|
state_timer = COUNTDOWN_INTERVAL;
|
||||||
|
} else if (countdown_number == 0) {
|
||||||
|
show_message("GO!");
|
||||||
|
state_timer = COUNTDOWN_INTERVAL * 0.5f;
|
||||||
|
} else {
|
||||||
|
// 倒计时结束,开始游戏
|
||||||
|
show_message("");
|
||||||
|
set_state(PLAYING);
|
||||||
|
if (ball) {
|
||||||
|
ball->set_active(true);
|
||||||
|
ball->reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PLAYING:
|
||||||
|
// 正常游戏进行中,球和球拍自行运作
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCORED: {
|
||||||
|
state_timer -= (float)delta;
|
||||||
|
if (state_timer <= 0.0f) {
|
||||||
|
show_message("");
|
||||||
|
set_state(PLAYING);
|
||||||
|
if (ball) {
|
||||||
|
ball->set_active(true);
|
||||||
|
ball->reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GAME_OVER:
|
||||||
|
// 等待用户按空格重置
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PingPongGame::_input(const Ref<InputEvent> &event) {
|
||||||
|
// 只有空格键按下事件
|
||||||
|
const InputEventKey *key_event = Object::cast_to<InputEventKey>(event.ptr());
|
||||||
|
if (!key_event || !key_event->is_pressed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key_event->get_keycode() != KEY_SPACE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == WAITING) {
|
||||||
|
start_countdown();
|
||||||
|
} else if (state == GAME_OVER) {
|
||||||
|
reset_game();
|
||||||
|
start_countdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉层 — 场地绘制(几何体占位)
|
||||||
|
// 替换方法:删除本方法 → 用 TileMap 或 Sprite2D 铺背景
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
void PingPongGame::_draw() {
|
||||||
|
Viewport *viewport = get_viewport();
|
||||||
|
if (!viewport) return;
|
||||||
|
|
||||||
|
Vector2 size = viewport->get_visible_rect().size;
|
||||||
|
float w = size.x;
|
||||||
|
float h = size.y;
|
||||||
|
float cx = w / 2.0f;
|
||||||
|
float cy = h / 2.0f;
|
||||||
|
|
||||||
|
// ── 场地背景 ──
|
||||||
|
draw_rect(Rect2(Vector2(0, 0), size), FIELD_BG_COLOR);
|
||||||
|
|
||||||
|
// ── 场地边界线 ──
|
||||||
|
float l = FIELD_INSET;
|
||||||
|
float r = w - FIELD_INSET;
|
||||||
|
float t = FIELD_INSET;
|
||||||
|
float b = h - FIELD_INSET;
|
||||||
|
draw_line(Vector2(l, t), Vector2(r, t), FIELD_BORDER_COLOR, 2.0f);
|
||||||
|
draw_line(Vector2(l, b), Vector2(r, b), FIELD_BORDER_COLOR, 2.0f);
|
||||||
|
|
||||||
|
// ── 虚线中线 ──
|
||||||
|
float dash_total = CENTER_LINE_DASH + CENTER_LINE_GAP;
|
||||||
|
for (float y = t; y < b; y += dash_total) {
|
||||||
|
float dash_end = MIN(y + CENTER_LINE_DASH, b);
|
||||||
|
draw_line(Vector2(cx, y), Vector2(cx, dash_end), FIELD_CENTER_LINE_COLOR, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 中央发球圈 ──
|
||||||
|
draw_arc(Vector2(cx, cy), 80.0f, 0.0f, Math_PI * 2.0f, 32, FIELD_CIRCLE_COLOR, 1.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingPongGame::_on_ball_scored(int which) {
|
void PingPongGame::_on_ball_scored(int which) {
|
||||||
if (which == 0) {
|
if (which == 0) {
|
||||||
// 左边得分
|
|
||||||
right_score++;
|
right_score++;
|
||||||
} else {
|
} else {
|
||||||
// 右边得分
|
|
||||||
left_score++;
|
left_score++;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_score_display();
|
update_score_display();
|
||||||
|
|
||||||
|
if (ball) {
|
||||||
|
ball->set_active(false);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否有人获胜
|
// 检查是否有人获胜
|
||||||
if (left_score >= max_score) {
|
if (left_score >= max_score) {
|
||||||
|
show_message("玩家获胜!\n按空格键重新开始");
|
||||||
|
set_state(GAME_OVER);
|
||||||
emit_signal("game_over", 0);
|
emit_signal("game_over", 0);
|
||||||
} else if (right_score >= max_score) {
|
} else if (right_score >= max_score) {
|
||||||
|
show_message("AI 获胜!\n按空格键重新开始");
|
||||||
|
set_state(GAME_OVER);
|
||||||
emit_signal("game_over", 1);
|
emit_signal("game_over", 1);
|
||||||
|
} else {
|
||||||
|
show_message("得分!");
|
||||||
|
set_state(SCORED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +209,33 @@ void PingPongGame::reset_game() {
|
|||||||
left_score = 0;
|
left_score = 0;
|
||||||
right_score = 0;
|
right_score = 0;
|
||||||
update_score_display();
|
update_score_display();
|
||||||
|
if (ball) {
|
||||||
|
ball->set_active(false);
|
||||||
|
ball->reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PingPongGame::start_countdown() {
|
||||||
|
countdown_number = COUNTDOWN_START;
|
||||||
|
state_timer = COUNTDOWN_INTERVAL;
|
||||||
|
set_state(COUNTDOWN);
|
||||||
|
show_message(String::num_int64(countdown_number));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PingPongGame::set_state(GameState p_state) {
|
||||||
|
state = p_state;
|
||||||
|
state_timer = 0.0f;
|
||||||
|
|
||||||
|
if (p_state == SCORED) {
|
||||||
|
state_timer = SCORED_PAUSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PingPongGame::show_message(const String &p_text) {
|
||||||
|
if (message_label) {
|
||||||
|
message_label->set_text(p_text);
|
||||||
|
message_label->set_visible(!p_text.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingPongGame::update_score_display() {
|
void PingPongGame::update_score_display() {
|
||||||
|
|||||||
@@ -3,9 +3,34 @@
|
|||||||
|
|
||||||
#include <godot_cpp/classes/node2d.hpp>
|
#include <godot_cpp/classes/node2d.hpp>
|
||||||
#include <godot_cpp/classes/label.hpp>
|
#include <godot_cpp/classes/label.hpp>
|
||||||
|
#include <godot_cpp/classes/input_event.hpp>
|
||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
|
|
||||||
|
class PingPongBall;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉常量 — 以后替换素材时删除本段
|
||||||
|
// (颜色值定义在 pingpong_game.cpp 中)
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
static constexpr float FIELD_INSET = 15.0f;
|
||||||
|
static constexpr float CENTER_LINE_DASH = 20.0f;
|
||||||
|
static constexpr float CENTER_LINE_GAP = 12.0f;
|
||||||
|
|
||||||
|
// 游戏状态
|
||||||
|
enum GameState {
|
||||||
|
WAITING,
|
||||||
|
COUNTDOWN,
|
||||||
|
PLAYING,
|
||||||
|
SCORED,
|
||||||
|
GAME_OVER
|
||||||
|
};
|
||||||
|
|
||||||
|
// 倒计时参数
|
||||||
|
static constexpr float COUNTDOWN_INTERVAL = 0.75f;
|
||||||
|
static constexpr int COUNTDOWN_START = 3;
|
||||||
|
static constexpr float SCORED_PAUSE = 1.2f;
|
||||||
|
|
||||||
class PingPongGame : public Node2D {
|
class PingPongGame : public Node2D {
|
||||||
GDCLASS(PingPongGame, Node2D)
|
GDCLASS(PingPongGame, Node2D)
|
||||||
|
|
||||||
@@ -14,10 +39,20 @@ private:
|
|||||||
int right_score = 0;
|
int right_score = 0;
|
||||||
int max_score = 5;
|
int max_score = 5;
|
||||||
|
|
||||||
|
// 状态机
|
||||||
|
GameState state = WAITING;
|
||||||
|
float state_timer = 0.0f;
|
||||||
|
int countdown_number = COUNTDOWN_START;
|
||||||
|
|
||||||
|
// 子节点引用
|
||||||
Label *left_score_label = nullptr;
|
Label *left_score_label = nullptr;
|
||||||
Label *right_score_label = nullptr;
|
Label *right_score_label = nullptr;
|
||||||
|
Label *message_label = nullptr;
|
||||||
|
PingPongBall *ball = nullptr;
|
||||||
|
|
||||||
void update_score_display();
|
void update_score_display();
|
||||||
|
void set_state(GameState p_state);
|
||||||
|
void show_message(const String &p_text);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
@@ -27,12 +62,16 @@ public:
|
|||||||
~PingPongGame();
|
~PingPongGame();
|
||||||
|
|
||||||
void _ready() override;
|
void _ready() override;
|
||||||
|
void _process(double delta) override;
|
||||||
|
void _draw() override;
|
||||||
|
void _input(const Ref<InputEvent> &event) override;
|
||||||
|
|
||||||
// 得分处理
|
// 得分处理
|
||||||
void _on_ball_scored(int which);
|
void _on_ball_scored(int which);
|
||||||
|
|
||||||
// 重置整局游戏
|
// 重置整局游戏
|
||||||
void reset_game();
|
void reset_game();
|
||||||
|
void start_countdown();
|
||||||
|
|
||||||
// 属性 getter/setter
|
// 属性 getter/setter
|
||||||
void set_left_score(int p_score);
|
void set_left_score(int p_score);
|
||||||
|
|||||||
+48
-10
@@ -1,6 +1,7 @@
|
|||||||
#include "pingpong_paddle.h"
|
#include "pingpong_paddle.h"
|
||||||
|
|
||||||
#include <godot_cpp/core/class_db.hpp>
|
#include <godot_cpp/core/class_db.hpp>
|
||||||
|
#include <godot_cpp/variant/utility_functions.hpp>
|
||||||
#include <godot_cpp/classes/input.hpp>
|
#include <godot_cpp/classes/input.hpp>
|
||||||
#include <godot_cpp/classes/engine.hpp>
|
#include <godot_cpp/classes/engine.hpp>
|
||||||
#include <godot_cpp/classes/node.hpp>
|
#include <godot_cpp/classes/node.hpp>
|
||||||
@@ -9,8 +10,13 @@
|
|||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉颜色常量 — 以后替换素材时删除本段
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
static const Color PADDLE_PLAYER_COLOR = Color(0.2f, 0.7f, 1.0f);
|
||||||
|
static const Color PADDLE_AI_COLOR = Color(1.0f, 0.35f, 0.25f);
|
||||||
|
|
||||||
void PingPongPaddle::_bind_methods() {
|
void PingPongPaddle::_bind_methods() {
|
||||||
// 属性
|
|
||||||
ClassDB::bind_method(D_METHOD("get_speed"), &PingPongPaddle::get_speed);
|
ClassDB::bind_method(D_METHOD("get_speed"), &PingPongPaddle::get_speed);
|
||||||
ClassDB::bind_method(D_METHOD("set_speed", "value"), &PingPongPaddle::set_speed);
|
ClassDB::bind_method(D_METHOD("set_speed", "value"), &PingPongPaddle::set_speed);
|
||||||
ClassDB::add_property("PingPongPaddle", PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
|
ClassDB::add_property("PingPongPaddle", PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
|
||||||
@@ -27,14 +33,33 @@ void PingPongPaddle::_bind_methods() {
|
|||||||
PingPongPaddle::PingPongPaddle() {
|
PingPongPaddle::PingPongPaddle() {
|
||||||
speed = 350.0f;
|
speed = 350.0f;
|
||||||
is_player = true;
|
is_player = true;
|
||||||
|
ai_reaction_counter = 0.0f;
|
||||||
|
ai_target_y = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
PingPongPaddle::~PingPongPaddle() {
|
PingPongPaddle::~PingPongPaddle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉层 — 圆角球拍(几何体占位)
|
||||||
|
// 替换方法:删除本方法 → 添加 Sprite2D 子节点
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
void PingPongPaddle::_draw() {
|
void PingPongPaddle::_draw() {
|
||||||
// 画一个白色矩形表示球拍
|
Color color = is_player ? PADDLE_PLAYER_COLOR : PADDLE_AI_COLOR;
|
||||||
draw_rect(Rect2(Vector2(-10.0f, -60.0f), Vector2(20.0f, 120.0f)), Color(1.0f, 1.0f, 1.0f));
|
float hw = PADDLE_WIDTH / 2.0f;
|
||||||
|
float hh = PADDLE_HEIGHT / 2.0f;
|
||||||
|
float cr = PADDLE_CORNER_RADIUS;
|
||||||
|
|
||||||
|
// 主体矩形
|
||||||
|
draw_rect(Rect2(Vector2(-hw, -hh + cr), Vector2(PADDLE_WIDTH, PADDLE_HEIGHT - cr * 2.0f)), color);
|
||||||
|
// 上下两端矩形
|
||||||
|
draw_rect(Rect2(Vector2(-hw + cr, -hh), Vector2(PADDLE_WIDTH - cr * 2.0f, cr)), color);
|
||||||
|
draw_rect(Rect2(Vector2(-hw + cr, hh - cr), Vector2(PADDLE_WIDTH - cr * 2.0f, cr)), color);
|
||||||
|
// 四角圆形
|
||||||
|
draw_circle(Vector2(-hw + cr, -hh + cr), cr, color);
|
||||||
|
draw_circle(Vector2(hw - cr, -hh + cr), cr, color);
|
||||||
|
draw_circle(Vector2(-hw + cr, hh - cr), cr, color);
|
||||||
|
draw_circle(Vector2(hw - cr, hh - cr), cr, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingPongPaddle::_physics_process(double delta) {
|
void PingPongPaddle::_physics_process(double delta) {
|
||||||
@@ -42,7 +67,7 @@ void PingPongPaddle::_physics_process(double delta) {
|
|||||||
float move_amount = 0.0f;
|
float move_amount = 0.0f;
|
||||||
|
|
||||||
if (is_player) {
|
if (is_player) {
|
||||||
// 玩家控制:读取上下输入
|
// ── 玩家控制 ──
|
||||||
Input *input = Input::get_singleton();
|
Input *input = Input::get_singleton();
|
||||||
if (input->is_action_pressed("ui_up")) {
|
if (input->is_action_pressed("ui_up")) {
|
||||||
move_amount = -1.0f;
|
move_amount = -1.0f;
|
||||||
@@ -50,17 +75,29 @@ void PingPongPaddle::_physics_process(double delta) {
|
|||||||
move_amount = 1.0f;
|
move_amount = 1.0f;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// AI 控制:追踪球的位置
|
// ── AI 控制(有延迟和不精确性)──
|
||||||
if (!ball_path.is_empty()) {
|
if (!ball_path.is_empty()) {
|
||||||
Node *node = get_node_or_null(ball_path);
|
Node *node = get_node_or_null(ball_path);
|
||||||
if (node) {
|
if (node) {
|
||||||
Node2D *ball = Object::cast_to<Node2D>(node);
|
Node2D *ball = Object::cast_to<Node2D>(node);
|
||||||
if (ball) {
|
if (ball) {
|
||||||
float ball_y = ball->get_global_position().y;
|
float ball_y = ball->get_global_position().y;
|
||||||
|
|
||||||
|
// 反应延迟计数器
|
||||||
|
ai_reaction_counter -= (float)delta;
|
||||||
|
if (ai_reaction_counter <= 0.0f) {
|
||||||
|
// 更新目标位置(加入随机偏移)
|
||||||
|
float noise = (UtilityFunctions::randf() - 0.5f) * 2.0f * AI_TRACKING_NOISE;
|
||||||
|
ai_target_y = ball_y + noise;
|
||||||
|
// 随机重置延迟
|
||||||
|
ai_reaction_counter = AI_REACTION_DELAY_MIN +
|
||||||
|
UtilityFunctions::randf() * (AI_REACTION_DELAY_MAX - AI_REACTION_DELAY_MIN);
|
||||||
|
}
|
||||||
|
|
||||||
float paddle_y = get_global_position().y;
|
float paddle_y = get_global_position().y;
|
||||||
if (ball_y < paddle_y - 20.0f) {
|
if (ai_target_y < paddle_y - 15.0f) {
|
||||||
move_amount = -1.0f;
|
move_amount = -1.0f;
|
||||||
} else if (ball_y > paddle_y + 20.0f) {
|
} else if (ai_target_y > paddle_y + 15.0f) {
|
||||||
move_amount = 1.0f;
|
move_amount = 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,11 +105,12 @@ void PingPongPaddle::_physics_process(double delta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动
|
// 移动(AI 速度略慢)
|
||||||
Vector2 velocity = Vector2(0.0f, move_amount * speed * (float)delta);
|
float effective_speed = is_player ? speed : speed * AI_SPEED_FACTOR;
|
||||||
|
Vector2 velocity = Vector2(0.0f, move_amount * effective_speed * (float)delta);
|
||||||
move_and_collide(velocity);
|
move_and_collide(velocity);
|
||||||
|
|
||||||
// 限制移动范围(不超出屏幕)
|
// 限制移动范围
|
||||||
Viewport *viewport = get_viewport();
|
Viewport *viewport = get_viewport();
|
||||||
if (viewport) {
|
if (viewport) {
|
||||||
Vector2 screen_size = viewport->get_visible_rect().size;
|
Vector2 screen_size = viewport->get_visible_rect().size;
|
||||||
|
|||||||
+19
-1
@@ -5,6 +5,20 @@
|
|||||||
|
|
||||||
namespace godot {
|
namespace godot {
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// 视觉常量 — 以后替换为 Sprite2D 时删除本段
|
||||||
|
// (颜色值定义在 pingpong_paddle.cpp 中)
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
static constexpr float PADDLE_WIDTH = 18.0f;
|
||||||
|
static constexpr float PADDLE_HEIGHT = 100.0f;
|
||||||
|
static constexpr float PADDLE_CORNER_RADIUS = 6.0f;
|
||||||
|
|
||||||
|
// AI 参数
|
||||||
|
static constexpr float AI_SPEED_FACTOR = 0.85f;
|
||||||
|
static constexpr float AI_REACTION_DELAY_MIN = 3.0f;
|
||||||
|
static constexpr float AI_REACTION_DELAY_MAX = 10.0f;
|
||||||
|
static constexpr float AI_TRACKING_NOISE = 30.0f;
|
||||||
|
|
||||||
class PingPongPaddle : public CharacterBody2D {
|
class PingPongPaddle : public CharacterBody2D {
|
||||||
GDCLASS(PingPongPaddle, CharacterBody2D)
|
GDCLASS(PingPongPaddle, CharacterBody2D)
|
||||||
|
|
||||||
@@ -12,7 +26,11 @@ private:
|
|||||||
float speed = 350.0f;
|
float speed = 350.0f;
|
||||||
bool is_player = true;
|
bool is_player = true;
|
||||||
NodePath ball_path;
|
NodePath ball_path;
|
||||||
float half_height = 60.0f; // 球拍半高,用于限制移动范围
|
float half_height = PADDLE_HEIGHT / 2.0f + 10.0f;
|
||||||
|
|
||||||
|
// AI 内部状态
|
||||||
|
float ai_reaction_counter = 0.0f;
|
||||||
|
float ai_target_y = 0.0f;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户