C++游戏编程入门(第三版)——Pong 项目(章节 6 - 7)
文章目录
- 第6章概要(如果该书把部分 PONG 内容提前衔接)
- 第7章:面向对象 & 完整 PONG 实现
- Ball.cpp
- Ball.h
- Bat.cpp
- Bat.h
在前面几章里,书(或教程)通常讲完了 C++ 基础、SFML 渲染 / 输入 /音频 /2D 游戏机制(如 Timber 项目)。接下来用 PONG 这个经典 2D 游戏作为练手项目,是为了引入 面向对象编程(OOP)、碰撞检测 / 游戏物理 / 分层结构 等更高阶的概念。这两个章节通常会:
用类(Class)来封装游戏实体(球、挡板、得分器等)
引入对象交互、成员变量 / 方法、封装 / 数据隐藏等特性
实现 Pong 的核心机制:球运动、撞边反弹、挡板推动、得分判定
添加扩展效果如声音、粒子 /特效、可能的难度调整
在 Beginning C++ Game Programming 第 6 章,就有 “Object-Oriented Programming – Starting the Pong Game” 的标题。
第6章概要(如果该书把部分 PONG 内容提前衔接)
如果你的书在第五章就开始引入 PONG 部分,那么它可能是从基础逻辑 /球体运动 / 碰撞起步。大致内容可能包括:
球的运动逻辑
使用速度向量(例如 vx, vy)来控制球在 x / y 方向上的速度
每一帧根据速度与时间增量更新球的位置:x += vx * deltaTime; y += vy * deltaTime;
碰撞边界检测
如果球触顶 /触底,则反转 vy
如果球触左 / 触右墙,则给对方得分 / 重置球位置
挡板(Paddle)移动
用玩家输入控制上下移动挡板
挡板也可能有速度 /边界限制,不能移出游戏区域
球与挡板碰撞
检测球与挡板的交叠区域(通常是矩形碰撞检测)
若碰撞,反转 vx(X 方向速度)并可能略微调整 vy 使球偏斜
重置机制 /得分显示
当一方失球,给对方加分
把球重置到中间并重新发球
显示得分(用 sf::Text / sf::Font)
可选:音效 /视觉反馈
在球碰壁 /碰挡板 /得分时播放音效
添加简单粒子 /闪烁效果等可视反馈
第7章:面向对象 & 完整 PONG 实现
第6章通常是“从理论走向实践”的关键章节,其标题即 “Object-Oriented Programming – Starting the Pong Game”
下面是这章常见要点与架构设计:
- 引入类 / 对象结构
- 设计实体类
Ball 类:保存球的位置、速度、半径、更新方法、绘制方法
Paddle 类:保存挡板的位置、速度、尺寸、更新方法、绘制方法
Score(或 Scoreboard)类 /模块:保存玩家得分、显示文本
可能还有一个 Game 或 PongGame 类作为整体管理类,负责初始化、主循环、对象交互 - 封装 / 接口
每个类提供对外方法(如 update(), draw())
内部成员变量通常设为 private / protected,只通过方法访问
构造函数 /析构函数用于初始化 /清理资源
-
类间通信 / 依赖关系
Game 类或主程序持有 Ball / 两个 Paddle / Score 对象
每帧调用各对象的 update() → 处理输入 /物理 /碰撞
再调用各对象的 draw() → 渲染到窗口
Ball 在其 update() 中检测与挡板 / 边界的碰撞,但可能需要访问挡板的位置 /尺寸
Score 在得分事件发生时更新展示 -
碰撞检测与物理反弹
矩形 / 圆形碰撞检测
通常把球当作圆,挡板当作矩形,检测球与矩形边界是否重叠
若重叠,则根据碰撞面方向反转速度分量
反弹逻辑调整
除了简单反转 vx,有时还按球击中挡板位置调整 vy,制造更丰富球路
有时增加球速度随着时间或得分增加 -
游戏状态管理
游戏有多个状态:Running, Paused, Serve(准备发球状态), GameOver 等
在 “Serve” 状态,球不会自动运动,等待玩家触发发球
在 GameOver 状态停更新,显示“Game Over / Press Enter to Restart”
状态切换逻辑要清晰:切换、重置、输入响应等 -
声音 /特效 /增强体验
使用 SFML 的音效模块:sf::SoundBuffer 和 sf::Sound
在球碰壁 /碰板 /得分时播放不同音效
可以添加粒子效果 /闪烁 /屏幕震动 /背景效果 等增强视觉 -
资源管理与安全
对于纹理 /字体 /音效等资源,可能引入一个资源管理器类(TextureHolder、SoundHolder 等)来统一加载 /缓存资源
通过智能指针 / RAII 管理内存 /资源安全
Ball.cpp
#include "Ball.h"Ball::Ball(float startX, float startY) : m_Position(startX, startY)
{m_Shape.setSize(sf::Vector2f(10, 10));m_Shape.setPosition(m_Position);
}FloatRect Ball::getPosition()
{return m_Shape.getGlobalBounds();
}RectangleShape Ball::getShape()
{return m_Shape;
}float Ball::getXVelocity()
{return m_DirectionX;
}void Ball::reboundSides()
{m_DirectionX = -m_DirectionX;
}void Ball::reboundBatOrTop()
{m_DirectionY = -m_DirectionY;m_DirectionY *= 1.1f;
}void Ball::reboundBottom()
{m_Position.y = 0;m_Position.x = 500;m_DirectionY = -m_DirectionY;
}void Ball::update(Time dt)
{// 更新球的位置m_Position.y += m_DirectionY * m_Speed * dt.asSeconds();m_Position.x += m_DirectionX * m_Speed * dt.asSeconds();// 移动球m_Shape.setPosition(m_Position);
}
Ball.h
#pragma once
#include <SFML/Graphics.hpp>
using namespace sf;class Ball
{
private:Vector2f m_Position;RectangleShape m_Shape;float m_Speed = 300.0f;float m_DirectionX = .2f;float m_DirectionY = .2f;public:Ball(float startX, float startY);FloatRect getPosition();RectangleShape getShape();float getXVelocity();void reboundSides();void reboundBatOrTop();void reboundBottom();void update(Time dt);
};
Bat.cpp
#include "Bat.h"// 这是构造函数,会在创建对象时调用
Bat::Bat(float startX, float startY) : m_Position(startX, startY)
{m_Shape.setSize(sf::Vector2f(50, 5));m_Shape.setPosition(m_Position);
}FloatRect Bat::getPosition()
{return m_Shape.getGlobalBounds();
}RectangleShape Bat::getShape()
{return m_Shape;
}void Bat::moveLeft()
{m_MovingLeft = true;
}void Bat::moveRight()
{m_MovingRight = true;
}void Bat::stopLeft()
{m_MovingLeft = false;
}void Bat::stopRight()
{m_MovingRight = false;
}void Bat::update(Time dt)
{if (m_MovingLeft){m_Position.x -= m_Speed * dt.asSeconds();}if (m_MovingRight){m_Position.x += m_Speed * dt.asSeconds();}m_Shape.setPosition(m_Position);
}
Bat.h
#pragma once
#include <SFML/Graphics.hpp>using namespace sf;class Bat
{
private:Vector2f m_Position;// RectangleShape对象RectangleShape m_Shape;float m_Speed = 1000.0f;bool m_MovingRight = false;bool m_MovingLeft = false;public:Bat(float startX, float startY);FloatRect getPosition();RectangleShape getShape();void moveLeft();void moveRight();void stopLeft();void stopRight();void update(Time dt);
};