第10回 荷物も滑らかに動かそう
第10回 荷物も滑らかに動かそう
第9回で、コスコマンは200msかけて1マス動くようになりました。
荷物を押したときは、コスコマンだけでなく荷物も同じ方向へ1マス動きます。
今回は、第9回で作った移動アニメーションをそのまま荷物にも使います。
今回の流れ
MoveObject()を実行
↓
荷物を押した移動だった?
↓ YES
荷物はコスコマンの1マス先から動く
↓
第9回のGetMoveDrawPos()で描く
新しい補間計算は作りません。
1.荷物を押したかどうかは移動結果で分かる
第6回で作ったMoveObject()は、移動した結果をMoveResultで返しています。
enum MoveResult
{
MOVE_NONE,
MOVE_WALK,
MOVE_PUSH,
MOVE_PUSH_GOAL
};
このうち、
MOVE_PUSH
MOVE_PUSH_GOAL
なら、コスコマンと一緒に荷物も1マス動いたことが分かります。
荷物の前が床かゴールかを、第10回でもう一度調べる必要はありません。
その判定は、すでにMoveObject()の中で行っています。
2.最後の移動結果を覚えておこう
描画するときにも「今の移動で荷物を押したか」を使いたいので、最後の移動結果をlastMoveResultに覚えておきます。
moveStartTimeの下へ追加してください。
MoveResult lastMoveResult = MOVE_NONE;
第9回で、移動できたときに次の3つを保存しました。
moveStartPos = startPos;
moveDir = dir;
moveStartTime = GetNowCount();
ここへ、移動結果の保存を1行追加します。
if (result != MOVE_NONE)
{
moveStartPos = startPos;
moveDir = dir;
moveStartTime = GetNowCount();
// 今回の移動結果を覚えておく
lastMoveResult = ____________________;
}

ヒント
MoveObject()の結果はresultに入っています。
模範解答を見る
lastMoveResult = result;
ステージを読み直すときは、
lastMoveResult = MOVE_NONE;
へ戻しておきます。
3.荷物はコスコマンの1マス先から動く
荷物を押す前は、
コスコマン → 荷物
と並んでいます。
第9回で保存したmoveStartPosは、コスコマンの移動前の位置です。
移動方向を表すベクトルをmoveVectorとすると、
Point moveVector = DIR_VECTOR[moveDir];
荷物の移動前の位置は、その1マス先です。
この位置をluggageStartPosとすると、
luggageStartPos
= moveStartPos + moveVector
です。
Point luggageStartPos = {
moveStartPos.x + ____________________,
moveStartPos.y + ____________________
};
ヒント
1マス分の移動方向はmoveVector.x、moveVector.yです。
模範解答を見る
Point luggageStartPos = {
moveStartPos.x + moveVector.x,
moveStartPos.y + moveVector.y
};
MoveObject()を呼んだあと、Map上の荷物はすでにもう1マス先へ移動しています。
その位置をluggageEndPosとすると、
Point luggageEndPos = {
luggageStartPos.x + moveVector.x,
luggageStartPos.y + moveVector.y
};
となります。
4.荷物にも同じ描画処理を使おう
コスコマンは第9回で、
GetMoveDrawPos(moveStartPos, moveDir, rate)
を使って描きました。
荷物も開始位置が1マス違うだけです。
GetMoveDrawPos(luggageStartPos, moveDir, rate)
を使えば、同じ方向へ同じ速さで動きます。
Map上では荷物はすでにluggageEndPosへ移動しています。
そこで、荷物を描くところで、
移動アニメーション中
かつ
MOVE_PUSH または MOVE_PUSH_GOAL
かつ
今描こうとしている荷物が luggageEndPos
のときだけ、描画位置を変更します。
else if (obj == LUGG || obj == LUGG_ON_GOAL)
{
// 今回押された荷物だけ、移動中の位置へ描く
if (rate < 1.0f &&
(lastMoveResult == MOVE_PUSH ||
lastMoveResult == MOVE_PUSH_GOAL) &&
x == luggageEndPos.x &&
y == luggageEndPos.y)
{
// 第9回で作った関数をそのまま使う
Point drawPos =
________________________________________________;
drawX = drawPos.x;
drawY = drawPos.y;
}
DrawGraph(drawX, drawY, images->luggage, TRUE);
}

ヒント
開始位置だけluggageStartPosへ変えます。
模範解答を見る
Point drawPos =
GetMoveDrawPos(luggageStartPos, moveDir, rate);
これで、
コスコマン
moveStartPosから移動
荷物
その1マス先のluggageStartPosから移動
となり、どちらも同じmoveDirとrateで動きます。
5.DrawStageImageの最初に位置を用意しよう
DrawStageImage()では、最初に第9回のrateを求め、続けて荷物の開始位置と移動後の位置を用意します。
float rate = GetMoveRate();
Point moveVector = DIR_VECTOR[moveDir];
Point luggageStartPos = {
moveStartPos.x + moveVector.x,
moveStartPos.y + moveVector.y
};
Point luggageEndPos = {
luggageStartPos.x + moveVector.x,
luggageStartPos.y + moveVector.y
};
荷物を押していないときは、これらの値は描画には使われません。
MOVE_PUSHまたはMOVE_PUSH_GOALのときだけ、先ほどの荷物描画の条件に入ります。
6.実行して確認しよう
次を確認してください。
- 普通に歩くと、コスコマンだけ滑らかに動く
- 荷物を押すと、コスコマンと荷物が同じ方向へ同時に動く
- 押していない荷物は動かない
- ゴールへ押し込むときも荷物が滑らかに動く
- 壁などで押せなかったときは荷物が動かない
- 第9回で作ったクリア判定のタイミングはそのまま動く
7.第10回の完成コード
第10回の完成コードを見る
#include "DxLib.h"
#define SCREEN_WIDTH 720
#define SCREEN_HEIGHT 600
#define MAX_STAGE_WIDTH 10
#define MAX_STAGE_HEIGHT 10
#define STAGE_COUNT 3
enum OBJNAME
{
FLOOR = 0,
WALL = 1,
LUGG = 2,
GOAL = 3,
HUMAN = 4,
HUMAN_ON_GOAL = 5,
LUGG_ON_GOAL = 6,
OUTSIDE = 7
};
struct Point
{
int x;
int y;
};
struct Map
{
int stage_width;
int stage_height;
int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};
enum Direction
{
DIR_UP,
DIR_LEFT,
DIR_DOWN,
DIR_RIGHT,
DIR_NONE
};
enum MoveResult
{
MOVE_NONE,
MOVE_WALK,
MOVE_PUSH,
MOVE_PUSH_GOAL
};
const Point DIR_VECTOR[] = {
{ 0, -1 },
{-1, 0 },
{ 0, +1 },
{+1, 0 },
{ 0, 0 }
};
#define TILE_SIZE 64
#define STAGE_X 40
#define STAGE_Y 90
struct GameImages
{
int floor;
int wall;
int goal;
int luggage;
int human;
};
#define MOVE_TIME 200
Point moveStartPos = { 0, 0 };
Direction moveDir = DIR_NONE;
int moveStartTime = 0;
MoveResult lastMoveResult = MOVE_NONE;
const Map STAGES[STAGE_COUNT] = {
{
10, 7,
{
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 3, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 2, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 4, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
}
},
{
10, 7,
{
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 1, 0, 3, 0, 0, 1 },
{ 1, 0, 0, 2, 0, 0, 3, 0, 0, 1 },
{ 1, 0, 0, 0, 1, 1, 0, 0, 0, 1 },
{ 1, 0, 4, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
}
},
{
10, 7,
{
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 0, 0, 1, 0, 4, 1 },
{ 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 },
{ 1, 1, 1, 2, 0, 2, 0, 2, 0, 1 },
{ 1, 1, 1, 0, 2, 2, 1, 0, 0, 1 },
{ 1, 3, 3, 3, 3, 3, 0, 0, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
}
}
};
void LoadStage(Map* map, int stageNumber);
Direction GetDirection(void);
Point GetPlayerPos(Map* map);
OBJNAME GetObjectNum(Point pos, Map* map);
void SetObjtoMap(OBJNAME obj, Point pos, Map* map);
int IsKeyPressed(int key);
MoveResult MoveObject(Direction dir, Map* map);
int CheckClear(Map* map);
void LoadGameImages(GameImages* images);
void DeleteGameImages(GameImages* images);
float GetMoveRate(void);
Point GetMoveDrawPos(Point startPos, Direction dir, float rate);
void DrawStageImage(Map* map, GameImages* images);
void UpdateGame(Map* map, int* currentStage, int* clearFlag);
void DrawGame(Map* map, int currentStage, int clearFlag, GameImages* images);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
Map currentMap;
GameImages images;
int currentStage = 0;
int clearFlag = 0;
ChangeWindowMode(TRUE);
SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
SetMainWindowText("Costco Man");
if (DxLib_Init() != 0)
return -1;
SetDrawScreen(DX_SCREEN_BACK);
LoadGameImages(&images);
LoadStage(¤tMap, currentStage);
while (ProcessMessage() == 0)
{
ClearDrawScreen();
UpdateGame(¤tMap, ¤tStage, &clearFlag);
DrawGame(¤tMap, currentStage, clearFlag, &images);
ScreenFlip();
}
DeleteGameImages(&images);
DxLib_End();
return 0;
}
void LoadStage(Map* map, int stageNumber)
{
map->stage_width = STAGES[stageNumber].stage_width;
map->stage_height = STAGES[stageNumber].stage_height;
for (int y = 0; y < map->stage_height; y++)
{
for (int x = 0; x < map->stage_width; x++)
{
map->Dat[y][x] = STAGES[stageNumber].Dat[y][x];
}
}
}
Direction GetDirection(void)
{
// 4方向のキーが押された瞬間を調べる
int up = IsKeyPressed(KEY_INPUT_W);
int left = IsKeyPressed(KEY_INPUT_A);
int down = IsKeyPressed(KEY_INPUT_S);
int right = IsKeyPressed(KEY_INPUT_D);
if (up)
{
return DIR_UP;
}
if (left)
{
return DIR_LEFT;
}
if (down)
{
return DIR_DOWN;
}
if (right)
{
return DIR_RIGHT;
}
return DIR_NONE;
}
Point GetPlayerPos(Map* map)
{
Point result = { -1, -1 };
for (int y = 0; y < map->stage_height; y++)
{
for (int x = 0; x < map->stage_width; x++)
{
int obj = map->Dat[y][x];
if (obj == HUMAN || obj == HUMAN_ON_GOAL)
{
result.x = x;
result.y = y;
return result;
}
}
}
return result;
}
OBJNAME GetObjectNum(Point pos, Map* map)
{
if (pos.x < 0 || pos.x >= map->stage_width || pos.y < 0 || pos.y >= map->stage_height)
return OUTSIDE;
return (OBJNAME)map->Dat[pos.y][pos.x];
}
void SetObjtoMap(OBJNAME obj, Point pos, Map* map)
{
map->Dat[pos.y][pos.x] = obj;
}
int IsKeyPressed(int key)
{
static int previous[256] = { 0 };
int current = CheckHitKey(key);
int pressed = 0;
if (current != 0 && previous[key] == 0)
{
pressed = 1;
}
previous[key] = current;
return pressed;
}
MoveResult MoveObject(Direction dir, Map* map)
{
Point playerPos = GetPlayerPos(map);
Point nextPos = {
playerPos.x + DIR_VECTOR[dir].x,
playerPos.y + DIR_VECTOR[dir].y
};
OBJNAME current = GetObjectNum(playerPos, map);
OBJNAME next = GetObjectNum(nextPos, map);
if (next == FLOOR || next == GOAL)
{
if (current == HUMAN_ON_GOAL)
SetObjtoMap(GOAL, playerPos, map);
else
SetObjtoMap(FLOOR, playerPos, map);
if (next == GOAL)
SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
else
SetObjtoMap(HUMAN, nextPos, map);
return MOVE_WALK;
}
if (next == LUGG || next == LUGG_ON_GOAL)
{
Point nextNextPos = {
nextPos.x + DIR_VECTOR[dir].x,
nextPos.y + DIR_VECTOR[dir].y
};
OBJNAME nextNext = GetObjectNum(nextNextPos, map);
if (nextNext != FLOOR && nextNext != GOAL)
return MOVE_NONE;
if (current == HUMAN_ON_GOAL)
SetObjtoMap(GOAL, playerPos, map);
else
SetObjtoMap(FLOOR, playerPos, map);
if (next == LUGG_ON_GOAL)
SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
else
SetObjtoMap(HUMAN, nextPos, map);
if (nextNext == GOAL)
{
SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
return MOVE_PUSH_GOAL;
}
SetObjtoMap(LUGG, nextNextPos, map);
return MOVE_PUSH;
}
return MOVE_NONE;
}
int CheckClear(Map* map)
{
for (int y = 0; y < map->stage_height; y++)
{
for (int x = 0; x < map->stage_width; x++)
{
if (map->Dat[y][x] == LUGG)
return 0;
}
}
return 1;
}
void LoadGameImages(GameImages* images)
{
images->floor = LoadGraph("images/floor.png");
images->wall = LoadGraph("images/wall.png");
images->goal = LoadGraph("images/goal.png");
images->luggage = LoadGraph("images/luggage.png");
images->human = LoadGraph("images/human.png");
}
void DeleteGameImages(GameImages* images)
{
DeleteGraph(images->floor);
DeleteGraph(images->wall);
DeleteGraph(images->goal);
DeleteGraph(images->luggage);
DeleteGraph(images->human);
}
float GetMoveRate(void)
{
if (moveDir == DIR_NONE)
return 1.0f;
int elapsed = GetNowCount() - moveStartTime;
float rate = (float)elapsed / MOVE_TIME;
if (rate < 0.0f)
rate = 0.0f;
if (rate > 1.0f)
rate = 1.0f;
return rate;
}
Point GetMoveDrawPos(Point startPos, Direction dir, float rate)
{
Point moveVector = DIR_VECTOR[dir];
Point drawPos = {
STAGE_X + startPos.x * TILE_SIZE + (int)(moveVector.x * TILE_SIZE * rate),
STAGE_Y + startPos.y * TILE_SIZE + (int)(moveVector.y * TILE_SIZE * rate)
};
return drawPos;
}
void DrawStageImage(Map* map, GameImages* images)
{
float rate = GetMoveRate();
Point moveVector = DIR_VECTOR[moveDir];
// 荷物を押したとき、荷物はプレイヤーの1マス先から動く
Point luggageStartPos = {
moveStartPos.x + moveVector.x,
moveStartPos.y + moveVector.y
};
// Map上では荷物はすでに1マス移動している
Point luggageEndPos = {
luggageStartPos.x + moveVector.x,
luggageStartPos.y + moveVector.y
};
for (int y = 0; y < map->stage_height; y++)
{
for (int x = 0; x < map->stage_width; x++)
{
int obj = map->Dat[y][x];
int drawX = STAGE_X + x * TILE_SIZE;
int drawY = STAGE_Y + y * TILE_SIZE;
if (obj == WALL)
DrawGraph(drawX, drawY, images->wall, TRUE);
else if (obj == GOAL || obj == HUMAN_ON_GOAL || obj == LUGG_ON_GOAL)
DrawGraph(drawX, drawY, images->goal, TRUE);
else
DrawGraph(drawX, drawY, images->floor, TRUE);
}
}
for (int y = 0; y < map->stage_height; y++)
{
for (int x = 0; x < map->stage_width; x++)
{
int obj = map->Dat[y][x];
int drawX = STAGE_X + x * TILE_SIZE;
int drawY = STAGE_Y + y * TILE_SIZE;
if (obj == HUMAN || obj == HUMAN_ON_GOAL)
{
if (rate < 1.0f)
{
Point drawPos = GetMoveDrawPos(moveStartPos, moveDir, rate);
drawX = drawPos.x;
drawY = drawPos.y;
}
DrawGraph(drawX, drawY, images->human, TRUE);
}
else if (obj == LUGG || obj == LUGG_ON_GOAL)
{
// 荷物を押した移動の途中で、今回動いた荷物だけ描画位置を変える
if (rate < 1.0f &&
(lastMoveResult == MOVE_PUSH ||
lastMoveResult == MOVE_PUSH_GOAL) &&
x == luggageEndPos.x &&
y == luggageEndPos.y)
{
Point drawPos = GetMoveDrawPos(luggageStartPos, moveDir, rate);
drawX = drawPos.x;
drawY = drawPos.y;
}
DrawGraph(drawX, drawY, images->luggage, TRUE);
}
}
}
}
void UpdateGame(Map* map, int* currentStage, int* clearFlag)
{
int enterPressed = IsKeyPressed(KEY_INPUT_RETURN);
int retryPressed = IsKeyPressed(KEY_INPUT_R);
if (GetMoveRate() < 1.0f)
return;
if (*clearFlag)
{
if (enterPressed)
{
(*currentStage)++;
if (*currentStage >= STAGE_COUNT)
*currentStage = 0;
LoadStage(map, *currentStage);
moveDir = DIR_NONE;
lastMoveResult = MOVE_NONE;
*clearFlag = 0;
}
return;
}
if (CheckClear(map))
{
*clearFlag = 1;
return;
}
if (retryPressed)
{
LoadStage(map, *currentStage);
moveDir = DIR_NONE;
lastMoveResult = MOVE_NONE;
return;
}
Direction dir = GetDirection();
if (dir != DIR_NONE)
{
// 移動する前のプレイヤー位置を先に覚えておく
Point startPos = GetPlayerPos(map);
MoveResult result = MoveObject(dir, map);
if (result != MOVE_NONE)
{
moveStartPos = startPos;
moveDir = dir;
moveStartTime = GetNowCount();
lastMoveResult = result;
}
}
}
void DrawGame(Map* map, int currentStage, int clearFlag, GameImages* images)
{
DrawFormatString(24, 20, GetColor(255, 255, 255), "STAGE %d / %d", currentStage + 1, STAGE_COUNT);
DrawString(300, 20, "WASD : MOVE R : RETRY", GetColor(200, 200, 200));
DrawStageImage(map, images);
if (clearFlag)
{
DrawString(250, 540, "CLEAR!", GetColor(255, 220, 80));
DrawString(355, 540, "ENTER : NEXT", GetColor(255, 255, 255));
}
}
No Comments