Windowsにファミコン版ロックマンを移植して、更にハックロム的作成をしていきましょうというプロジェクトです。
今回も前回に引き続き、ロックマンの基礎挙動のコードをガリガリ書いていきます。
はしごの処理
はしごを昇り降りするためには、まずロックマン(以下プレイヤー)の(中央)座標がはしごと重なった状態の時に、十字キーの上または下を押す必要があります。
これはオリジナル版の挙動です。
はしごに掴まるトリガーは上記が基本的であり、それはどんな状況からでも呼び出されます。
これはプロジェクトのうち、メインプログラムルーチン視点のクラス図になります。
よく分からないと思うので詳細は何も言いません😓
PlayerBehavior(右下の部分)と書かれている部分がいわゆるプレイヤーの挙動を司っている部分で、この下にあるBasicBehavior、StandingBehaviorなどが具象クラスとなっています。
今現在のプレイヤーの挙動はこのクラスに全て実装されていきます。
例えばBasicBehaviorにはStanding(地上動作)、Hovering(空中動作)などに対して共通する処理が含まれます。
class BasicBehavior : public IPlayerBehavior {
public:
explicit BasicBehavior() {}
~BasicBehavior() {}
protected:
const TextureNumber ORIGINAL_TILESIZE = 100;
// NOTE : Refer to the graphic tileset to find the location of the left and right sprites.
const TextureNumber DR = 0;
const TextureNumber DL = ORIGINAL_TILESIZE;
const double GRAVITY = 0x00.40P0; // Falling Newton velocity.
const double MAX_IMPULSE = -0x04.DFP0; // Jump power.
const double TERMINAL_VELOCITY = 0x13.00P0; // Maximum falling velocity.
void falling(bool isJump); // 落下処理
void jumping(); // ジャンプする時の処理
};
たとえば「落下」に関しては、プレイヤーが崖から飛び出した時やジャンプから落下に転じた時に発動する必要があります。
これは地上にいる場合から発生する場合もあるし、空中にいる場合は重力運動によって発生するためメソッドとしては共通的なアクションに位置付けられます。
はしごの処理も同様に地上にいる時、空中にいる時からどちらでもはしごに掴まったりするため、共通クラスにはしごに掴まる処理を実装していきます。
class BasicBehavior : public IPlayerBehavior {
public:
explicit BasicBehavior(SpectacleScalar& scalar)
: _spectacleScalar(scalar) {}
~BasicBehavior() {}
protected:
SpectacleScalar& _spectacleScalar;
const TextureNumber ORIGINAL_TILESIZE = 100;
// NOTE : Refer to the graphic tileset to find the location of the left and right sprites.
const TextureNumber DR = 0;
const TextureNumber DL = ORIGINAL_TILESIZE;
const double GRAVITY = 0x00.40P0; // Falling Newton velocity.
const double MAX_IMPULSE = -0x04.DFP0; // Jump power.
const double TERMINAL_VELOCITY = 0x13.00P0; // Maximum falling velocity.
void falling(bool isJump);
void jumping();
eStatus grabLadder(IChangeAttribute& player, int64_t upKey, int64_t downKey, int32_t x, const std::vector<uint_fast64_t> ladderNo);
};
eStatus BasicBehavior::grabLadder(IChangeAttribute& player, int64_t upKey, int64_t downKey, int32_t x, const std::vector<uint_fast64_t> ladderNo) {
constexpr int TILE_SIZE = 16;
auto xAdjustments = [=]() {
auto tempX = ((x + TILE_SIZE) / TILE_SIZE) * TILE_SIZE;
_spectacleScalar.speedX = (tempX + (TILE_SIZE / 2)) - (x + TILE_SIZE);
};
if (checkBehindAttributesType(player, _spectacleScalar.behindBG[2], eTiles::SPACE_TILE) && checkBehindAttributesType(player, _spectacleScalar.bottomBG[1], eTiles::T_LADDER) && downKey) {
_spectacleScalar.status = eStatus::LADDERING;
_spectacleScalar.speedY = 0x0F.00P0;
xAdjustments();
}
else if (!checkBehindAttributesType(player, _spectacleScalar.bottomBG[1], eTiles::T_LADDER) && downKey) {
return _spectacleScalar.status;
}
else if (checkBehindAttributesType(player, _spectacleScalar.behindBG[1], eTiles::SPACE_TILE) && checkBehindAttributesType(player, _spectacleScalar.behindBG[2], eTiles::T_LADDER) && upKey) {
return _spectacleScalar.status;
}
else {
for (size_t i = 0; i < _spectacleScalar.behindBG.size(); ++i) {
for (auto& v : ladderNo) {
if (v == _spectacleScalar.behindBG[i] && (upKey || downKey)) {
_spectacleScalar.status = eStatus::LADDERING;
xAdjustments();
break;
}
}
}
}
return _spectacleScalar.status;
}
struct SpectacleScalar {
std::wstring name;
eStatus status = eStatus::DISABLED;
structure::TextureNumber textureNumber;
double speedX;
double speedY;
int8_t direction;
std::array<uint8_t, 3> behindBG;
std::array<uint8_t, 3> bottomBG;
FrameCounter generalCounter;
};
上記のgrabLadderメソッドでプレイヤーがはしごに掴まるかどうかを判断します。
具体的にはそれが
「if (checkBehindAttributesType(player, _spectacleScalar.behindBG[2], eTiles::SPACE_TILE) && checkBehindAttributesType(player, _spectacleScalar.bottomBG[1], eTiles::T_LADDER) && downKey)」
「else if (!checkBehindAttributesType(player, _spectacleScalar.bottomBG[1], eTiles::T_LADDER) && downKey)」
などに該当します。
例えば、_spectacleScalar.behindBG[2]は何を意味しているかですが、
プレイヤーキャラクターのX軸中央に3つの属性判定点を設けており、このポイントの背景タイル番号が「_spectacleScalar.behindBG」です。
「_spectacleScalar.behindBG[2]」は上から0、1、2の順番で割り当てられているので、一番下のマーカー点が該当します。
checkBehindAttributesTypeメソッドは指定のタイル番号が引数で指定するタイルタイプに該当しているかをチェックするメソッドです。
bool BasicBehavior::checkBehindAttributesType(IChangeAttribute& player, uint8_t expression, eTiles type) const {
if (expression < 0x20 && type == eTiles::SPACE_TILE) return true;
else if (expression < 0x60 && type == eTiles::BLOCK_TILE) return true;
else if (expression == 0x60 && type == eTiles::T_LADDER) return true;
else if (expression < 0x70 && type == eTiles::SPACE_EVENT) return true;
else if (expression < 0x80 && type == eTiles::BLOCK_EVENT) return true;
else return false;
}
上記 expression がタイル番号で、それと定数を比較しています。
これはタイルの種類で、それぞれ0x00~0x1Fが空間タイル(eTiles::SPACE_TILE)、0x20~0x5Fがブロックタイル(eTiles::BLOCK_TILE)、0x60がはしご(eTiles::T_LADDER)という風な恰好でタイルの種類を判定します。
96 (16進数で0x60) のタイル番号だった場合は、プレイヤーの背景がはしごである、という判断をします。それが「checkBehindAttributesType(player, _spectacleScalar.behindBG[2], eTiles::SPACE_TILE)」などのメソッドです。
それを行った上でステータスをはしごに変えて、X軸の速度、Y軸の速度、プレイヤーをはしごと同じ位置に重ねたりします。
説明が長くなってしまうので、はしごの処理の説明は省きます😂🙏
class LadderBehavior : public BasicBehavior {
public:
explicit LadderBehavior(SpectacleScalar& scalar)
: BasicBehavior(scalar) {}
~LadderBehavior() {}
bool execute(IChangeAttribute& player, FrameworkConnector& parameter) override;
private:
void processLadderUpAndDown(IChangeAttribute& player, FrameworkConnector& parameter, int64_t upKey, int64_t downKey, double y);
void processFalldown(IChangeAttribute& player, FrameworkConnector& parameter, TextureNumber LRView);
void appendingInCoordinateDifference(IChangeAttribute& player, FrameworkConnector& parameter) const;
bool checkUp(IChangeAttribute& player, FrameworkConnector& parameter, int64_t upKey, double y);
bool checkDown(IChangeAttribute& player, FrameworkConnector& parameter, int64_t downKey);
bool noControl(int64_t upKey, int64_t downKey);
};
bool LadderBehavior::execute(IChangeAttribute& player, FrameworkConnector& parameter) {
return exceptions::ErrorHandler::tryCatchWithLogging([&]() {
const auto upKey = parameter.getPressKey(JPBTN::UP);
const auto downKey = parameter.getPressKey(JPBTN::DOWN);
auto fallDown = [&]() {
player.reverseDirection();
const auto LRView = player.getDirection() == DirectArrows::DIR_R ? DR : DL;
processFalldown(player, parameter, LRView);
};
_spectacleScalar.speedX = 0x00.00P0;
if (!upKey && !downKey && parameter.getPressKey(JPBTN::A)) {
fallDown();
}
else if (checkBehindAttributesType(player, _spectacleScalar.behindBG[0], eTiles::SPACE_TILE) &&
checkBehindAttributesType(player, _spectacleScalar.behindBG[1], eTiles::SPACE_TILE) &&
checkBehindAttributesType(player, _spectacleScalar.behindBG[2], eTiles::SPACE_TILE)) {
fallDown();
}
else {
processLadderUpAndDown(player, parameter, upKey, downKey, player.getCollision().getCoordinate().Y - parameter.getBasePoint().Y);
}
appendingInCoordinateDifference(player, parameter);
}
);
}
void LadderBehavior::processLadderUpAndDown(IChangeAttribute& player, FrameworkConnector& parameter, int64_t upKey, int64_t downKey, double y) {
auto ladderNo = parameter.getMapAttributeIndexer()->findValuesByKeyPattern(L"LADDER");
const auto yMoveSign = 0 < upKey ? -1 : 0 < downKey ? 1 : 0;
if (noControl(upKey, downKey)) {
if (checkUp(player, parameter, upKey, y + 9) || checkDown(player, parameter, downKey)) {
_spectacleScalar.textureNumber = laddering(player, yMoveSign);
}
}
}
void LadderBehavior::processFalldown(IChangeAttribute& player, FrameworkConnector& parameter, TextureNumber LRView) {
falling(parameter.getPressKey(JPBTN::A));
_spectacleScalar.textureNumber = static_cast<TextureNumber>(eHeroTiles::JUMP) + LRView;
}
void LadderBehavior::appendingInCoordinateDifference(IChangeAttribute& player, FrameworkConnector& parameter) const {
parameter.setDifference(_spectacleScalar.name, DiffEval::X, _spectacleScalar.speedX);
parameter.setDifference(_spectacleScalar.name, DiffEval::Y, _spectacleScalar.speedY);
player.appendingInCoordinateDifference(_spectacleScalar.name, parameter);
}
bool LadderBehavior::checkUp(IChangeAttribute& player, FrameworkConnector& parameter, int64_t upKey, double y) {
const auto TILE_SIZE = 16;
const auto range = -0x01P0;
const auto eval = DirectionEval::CEIL;
auto rising = [&]() {
auto moveY = std::floor((y + 9) / TILE_SIZE + 1) * TILE_SIZE - 8;
_spectacleScalar.speedY = moveY - (y + 24.0);
_spectacleScalar.status = eStatus::STANDING;
_spectacleScalar.generalCounter.fill(0);
player.reverseDirection();
const auto LRView = player.getDirection() == DirectArrows::DIR_R ? DR : DL;
_spectacleScalar.textureNumber = static_cast<TextureNumber>(eHeroTiles::WAIT) + LRView;
};
if (!upKey) return false;
if (checkBehindAttributesType(player, _spectacleScalar.behindBG[1], eTiles::SPACE_TILE) && checkBehindAttributesType(player, _spectacleScalar.behindBG[2], eTiles::T_LADDER)) {
rising();
return false;
}
player.registerWidthLineCollisionPoint(eval, DiffPointF(0.0, range, 0.0), -1i8);
auto progress = player.getCollision().getMinimumMoveDiffPoints(parameter.getPageNo(), parameter.getBasePoint(), eval, range);
if (!progress) {
_spectacleScalar.speedY = 0x00P0;
_spectacleScalar.generalCounter.fill(0);
return false;
}
return true;
}
bool LadderBehavior::checkDown(IChangeAttribute& player, FrameworkConnector& parameter, int64_t downKey) {
auto landing = [&]() {
_spectacleScalar.status = eStatus::STANDING;
_spectacleScalar.generalCounter.fill(0);
player.reverseDirection();
const auto LRView = player.getDirection() == DirectArrows::DIR_R ? DR : DL;
_spectacleScalar.textureNumber = static_cast<TextureNumber>(eHeroTiles::WAIT) + LRView;
};
const auto range = 0x01P0;
const auto eval = DirectionEval::BOTTOM;
if (!downKey) return false;
player.registerWidthLineCollisionPoint(eval, DiffPointF(0.0, range, 0.0), 1i8);
auto progress = player.getCollision().getMinimumMoveDiffPoints(parameter.getPageNo(), parameter.getBasePoint(), eval, range);
if (!progress) {
landing();
return false;
}
return true;
}
bool LadderBehavior::noControl(int64_t upKey, int64_t downKey) {
if (!upKey && !downKey) {
_spectacleScalar.speedY = 0x00P0;
return false;
}
return true;
}
コードだけ乗っけられても・・・って感じでしょうが、また機会があれば説明します。
簡単に言うと、はしごに掴まった後の処理です。上を押したとき、背景がはしごではなくなったときなどの処理が含まれます。
動作検証
簡単な動作検証動画です。
コメント