アマゾンバナーリンク

DXライブラリで作る歌って踊るデスクトップマスコット

こんにちは!ジェイです。先日youtube配信でデスクトップマスコットを作りました!動画でも説明しましたが、今回ソースコードも含めて、改めて説明しようと思います。

DXライブラリの導入

DXライブラリのホームページはこちらです。簡単に説明すると

  1. VisualStdio2019ダウンロード
  2. DXライブラリのダウンロード
  3. マルチバイト文字の設定『構成プロパティ』→『詳細』
  4. インクルードパス『構成プロパティ』→『C/C++』→『全般』→『追加のインクルードディレクトリ』
  5. ライブラリのパス『構成プロパティ』→『リンカー』→『全般』→『追加のライブラリディレクトリ』
  6. デバッグモード→マルチスレッドデバッグ(MTD)『構成プロパティ』→『C/C++』→『コード生成』
  7. リリースモード→マルチスレッドデバッグ(MT)『構成プロパティ』→『C/C++』→『コード生成』
  8. 『リンカー』→『詳細設定』→『安全な例外ハンドラーを含むイメージ』を『いいえ(/SAFESEH:NO)』

以上で準備完了です。できたら以下のテストプログラムを実行してみましょう。真ん中に小さいドットが表示されたら成功です。

#include "DxLib.h"

// プログラムは WinMain から始まります
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
  ChangeWindowMode(TRUE);
	if( DxLib_Init() == -1 )		// DXライブラリ初期化処理
	{
		return -1 ;			// エラーが起きたら直ちに終了
	}

	DrawPixel( 320 , 240 , GetColor( 255,255,255 ) ) ;	// 点を打つ

	WaitKey() ;				// キー入力待ち

	DxLib_End() ;				// DXライブラリ使用の終了処理

	return 0 ;				// ソフトの終了 
}

付属のモデルビューアーで設定する

DXライブラリには付属のモデルビューアーがついてます。読み込めるファイル形式は非圧縮xファイルmqoファイルfbxファイルpmdファイルpmxファイルmv1ファイルの6形式です

それに加えて、アニメーションデータである、vmdを揃えてDXライブラリのオリジナル拡張子であるmv1ファイルを作成します。モデルをロードするときに勝手に連番のファイルを探して一緒にロードしてくれます。

末尾の「L」はループするモーションかどうかを示します。ループしないモーションであればこの「L」はつけないでください。

実際にデスクトップマスコットを動かしてみる

以上の手順でmv1を作成したら、以下の様にソースを記述します。コツはSetUseBackBufferTransColorFlagで背景を透明にすることです!

#include "DxLib.h"

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	int ModelHandle, AttachIndex ;
	float TotalTime, PlayTime ;
	
	SetUseBackBufferTransColorFlag( TRUE );
	SetAlwaysRunFlag( TRUE );
	ChangeWindowMode( TRUE );

	// DXライブラリの初期化
	if( DxLib_Init() < 0 )
	{
		// エラーが発生したら直ちに終了
		return -1 ;
	}
	int dispx = GetSystemMetrics(SM_CXSCREEN);
	int dispy = GetSystemMetrics(SM_CYSCREEN);
	SetGraphMode( dispx , dispy , 32 ) ;
	SetWindowPos(GetMainWindowHandle(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE );

	// 3Dモデルの読み込み
	ModelHandle = MV1LoadModel( "NeGi_Misa_Suama_ver1.02/NeGi_Misa_Suama_ver1.02Z.mv1" ) ;

	// 描画先を裏画面に変更
	SetDrawScreen( DX_SCREEN_BACK ) ;

	// 画面に映る位置に3Dモデルを移動
	MV1SetPosition( ModelHandle, VGet( 0.0f, 0.0f, 0.0f ) ) ;

	// 3Dモデルの0番目のアニメーションをアタッチする
	AttachIndex = MV1AttachAnim( ModelHandle, 0, -1, FALSE ) ;

	// アタッチしたアニメーションの総再生時間を取得する
	TotalTime = MV1GetAttachAnimTotalTime( ModelHandle, AttachIndex ) ;

	// 再生時間の初期化
	PlayTime = 0.0f ;
	SetCameraPositionAndTarget_UpVecY( VGet( -0.000f, 17.175f, -27.740f ), VGet( 0.000f, 9.729f, 0.237f ) );
	SetCameraNearFar( 0.389f, 97.289f );
	SetLightEnable( FALSE );
	SetGlobalAmbientLight( GetColorF( 0.200f, 0.200f, 0.200f, 0.0f ) );

	int Light0Handle = CreateDirLightHandle( VGet( 0.000f, -0.383f, 0.924f ) );
	SetLightDifColorHandle( Light0Handle, GetColorF( 1.000f, 1.000f, 1.000f, 1.000f ) );
	SetLightSpcColorHandle( Light0Handle, GetColorF( 0.500f, 0.500f, 0.500f, 0.000f ) );
	SetLightAmbColorHandle( Light0Handle, GetColorF( 0.000f, 0.000f, 0.000f, 0.000f ) );

	int SoundHandle = LoadSoundMem( "music.wav" );
	PlaySoundMem( SoundHandle, DX_PLAYTYPE_BACK );

	SetGlobalAmbientLight( GetColorF( 1.0f, 1.0f, 1.0f, 1.0f ) );
	const float SOUND_RATE = 1000.0f / 30.0f;
	// 何かキーが押されるかウインドウが閉じられるまでループ
	while( ProcessMessage() == 0 && CheckHitKey( KEY_INPUT_ESCAPE ) == 0 )
	{
		// 画面をクリア
		ClearDrawScreen() ;
		
		// 再生時間を進める
		PlayTime += 1.0f ;

		int sound_time = GetSoundCurrentTime( SoundHandle ) / SOUND_RATE;
		if( PlayTime < TotalTime )
		{
			PlayTime = sound_time ;
		}
		else
		{
			PlayTime = 0.0f;
			SetSoundCurrentTime( 0, SoundHandle );
		}
		// 再生時間をセットする
		MV1SetAttachAnimTime( ModelHandle, AttachIndex, PlayTime ) ;

		// 3Dモデルの描画
		MV1DrawModel( ModelHandle ) ;

		// 裏画面の内容を表画面に反映
		ScreenFlip() ;
	}

	// DXライブラリの後始末
	DxLib_End() ;

	// ソフトの終了
	return 0 ;
}

カメラを動かせるようにしたバージョン

カメラも動かせるようにしたバージョンの歌って踊るデスクトップマスコットのソースです。

#pragma warning(disable:4996)
#include "DxLib.h"
#include <math.h>

const int ALL_KEY_NUM = 256;

int Key[ALL_KEY_NUM];

int GetHitKeyStateAll_2(int* input_key)
{
	char all_key_state[ALL_KEY_NUM];

	if (GetHitKeyStateAll(all_key_state) == -1) return -1;

	for (int i = 0; i < ALL_KEY_NUM; i++)
	{
		if (all_key_state[i] == 1) input_key[i]++;
		else						input_key[i] = 0;
	}
	return 0;
}

int ProcessLoop()
{
	if (ProcessMessage() == -1) return -1;
	if (ClearDrawScreen() == -1) return -1;
	if (GetHitKeyStateAll_2(Key) == -1) return -1;
	if (Key[KEY_INPUT_ESCAPE] > 0) return -1;
	return 0;
}

void Initalize();
void Run();
INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
{
	SetUseBackBufferTransColorFlag(TRUE);
	SetAlwaysRunFlag(TRUE);
	ChangeWindowMode(TRUE);

	if (DxLib_Init() == -1 || SetDrawScreen(DX_SCREEN_BACK) == -1) return -1;

	Initalize();

	while (!ProcessLoop())
	{
		Run();
		ScreenFlip();
	}

	DxLib_End();

	return 0;
}

const float PI = 3.14f;
const float RotRate = PI * 2.0f / 60.0f;
const float MoveRate = 5.0f;
typedef struct
{
	VECTOR Position;
	VECTOR Target;
	VECTOR Head;
}TagCamera;

int ModelHandle;
TagCamera Camera;
MATRIX Matrix;
void InitCamera(TagCamera* camera)
{
	camera->Position = VGet(0.000f, 17.175f, -27.740f);
	camera->Target = VGet(0.000f, 9.729f, 0.237f);
	camera->Head = VGet(0.0f, 1.0f, 0.0f);
}

int AttachIndex, SoundHandle;
float TotalTime = 0.0f, PlayTime = 0.0f;
void Initalize()
{
	SetLightEnable(FALSE);
	SetGlobalAmbientLight(GetColorF(0.200f, 0.200f, 0.200f, 0.0f));

	int Light0Handle = CreateDirLightHandle(VGet(0.000f, -0.383f, 0.924f));
	SetLightDifColorHandle(Light0Handle, GetColorF(1.000f, 1.000f, 1.000f, 1.000f));
	SetLightSpcColorHandle(Light0Handle, GetColorF(0.500f, 0.500f, 0.500f, 0.000f));
	SetLightAmbColorHandle(Light0Handle, GetColorF(0.000f, 0.000f, 0.000f, 0.000f));
	int dispx = GetSystemMetrics(SM_CXSCREEN);
	int dispy = GetSystemMetrics(SM_CYSCREEN);
	SetGraphMode(dispx, dispy, 32);
	SetWindowPos(GetMainWindowHandle(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
	ModelHandle = MV1LoadModel("NeGi_Misa_Suama_ver1.02/NeGi_Misa_Suama_ver1.02Z.mv1");
	InitCamera(&Camera);
	// 画面に映る位置に3Dモデルを移動
	MV1SetPosition(ModelHandle, VGet(0.0f, 0.0f, 0.0f));

	SetCameraNearFar(1.0f, 1000.0f);
	// 3Dモデルの0番目のアニメーションをアタッチする
	AttachIndex = MV1AttachAnim(ModelHandle, 0, -1, FALSE);

	// アタッチしたアニメーションの総再生時間を取得する
	TotalTime = MV1GetAttachAnimTotalTime(ModelHandle, AttachIndex);
	SoundHandle = LoadSoundMem("music.wav");
	PlaySoundMem(SoundHandle, DX_PLAYTYPE_BACK);
}

const float SOUND_RATE = 1000.0f / 30.0f;

void Run()
{
	// 再生時間を進める
	PlayTime += 1.0f;

	int sound_time = GetSoundCurrentTime(SoundHandle) / SOUND_RATE;
	if (PlayTime < TotalTime)
	{
		PlayTime = sound_time;
	}
	else
	{
		PlayTime = 0.0f;
		SetSoundCurrentTime(0, SoundHandle);
	}
	// 再生時間をセットする
	MV1SetAttachAnimTime(ModelHandle, AttachIndex, PlayTime);
	if (Key[KEY_INPUT_SPACE] > 0)
	{
		InitCamera(&Camera);
	}
	else if (Key[KEY_INPUT_LSHIFT] > 0)
	{
		if (Key[KEY_INPUT_UP] > 0)
		{
			Camera.Target = VAdd(Camera.Target, VScale(VNorm(Camera.Head), MoveRate));
		}
		if (Key[KEY_INPUT_DOWN] > 0)
		{
			Camera.Target = VAdd(Camera.Target, VScale(VNorm(Camera.Head), -MoveRate));
		}
		if (Key[KEY_INPUT_LEFT] > 0)
		{
			Camera.Target = VAdd(Camera.Target, VScale(VNorm(VCross(Camera.Position, Camera.Head)), -MoveRate));
		}
		if (Key[KEY_INPUT_RIGHT] > 0)
		{
			Camera.Target = VAdd(Camera.Target, VScale(VNorm(VCross(Camera.Position, Camera.Head)), MoveRate));
		}
		if (Key[KEY_INPUT_Z] > 0)
		{
			Camera.Target = VAdd(Camera.Target, VScale(VNorm(Camera.Position), MoveRate));
		}
		if (Key[KEY_INPUT_X] > 0)
		{
			Camera.Target = VAdd(Camera.Target, VScale(VNorm(Camera.Position), -MoveRate));
		}
	}
	else if (Key[KEY_INPUT_RSHIFT])
	{
		if (Key[KEY_INPUT_UP] > 0)
		{
			if (VSize(Camera.Position) > 300.0f)
			{
				Camera.Position = VAdd(Camera.Position, VScale(VNorm(Camera.Position), -MoveRate));
			}
		}
		if (Key[KEY_INPUT_DOWN])
		{
			if (VSize(Camera.Position) < 2000.0f)
			{
				Camera.Position = VAdd(Camera.Position, VScale(VNorm(Camera.Position), MoveRate));
			}
		}
	}
	else
	{
		if (Key[KEY_INPUT_UP] > 0)
		{
			Matrix = MGetRotAxis(VCross(Camera.Position, Camera.Head), RotRate);
			Camera.Position = VTransform(Camera.Position, Matrix);
			Camera.Head = VTransform(Camera.Head, Matrix);
		}
		if (Key[KEY_INPUT_DOWN] > 0)
		{
			Matrix = MGetRotAxis(VCross(Camera.Position, Camera.Head), -RotRate);
			Camera.Position = VTransform(Camera.Position, Matrix);
			Camera.Head = VTransform(Camera.Head, Matrix);
		}
		if (Key[KEY_INPUT_LEFT] > 0)
		{
			Matrix = MGetRotAxis(Camera.Head, RotRate);
			Camera.Position = VTransform(Camera.Position, Matrix);
			Camera.Head = VTransform(Camera.Head, Matrix); // いらない?
		}
		if (Key[KEY_INPUT_RIGHT] > 0)
		{
			Matrix = MGetRotAxis(Camera.Head, -RotRate);
			Camera.Position = VTransform(Camera.Position, Matrix);
			Camera.Head = VTransform(Camera.Head, Matrix); // いらない?
		}
		if (Key[KEY_INPUT_Z] > 0)
		{
			Matrix = MGetRotAxis(Camera.Position, -RotRate);
			Camera.Position = VTransform(Camera.Position, Matrix); // いらない?
			Camera.Head = VTransform(Camera.Head, Matrix);
		}
		if (Key[KEY_INPUT_X] > 0)
		{
			Matrix = MGetRotAxis(Camera.Position, RotRate);
			Camera.Position = VTransform(Camera.Position, Matrix); // いらない?
			Camera.Head = VTransform(Camera.Head, Matrix);
		}
	}

	int white = GetColor(255, 255, 255);
	SetCameraPositionAndTargetAndUpVec(VAdd(Camera.Target, Camera.Position), Camera.Target, Camera.Head);

	DrawPixel3D(Camera.Target, white);
	DrawLine3D(VGet(0.0f, 0.0f, 0.0f), VGet(1000.0f, 0.0f, 0.0f), GetColor(255, 0, 0));
	DrawLine3D(VGet(0.0f, 0.0f, 0.0f), VGet(0.0f, 1000.0f, 0.0f), GetColor(0, 255, 0));
	DrawLine3D(VGet(0.0f, 0.0f, 0.0f), VGet(0.0f, 0.0f, 1000.0f), GetColor(0, 0, 255));

	MV1DrawModel(ModelHandle);


	DrawFormatString(0, 0, white, "position x=%f y=%f z=%f size=%f", Camera.Position.x, Camera.Position.y, Camera.Position.z, VSize(Camera.Position));
	DrawFormatString(0, 20, white, "target x=%f y=%f z=%f size=%f", Camera.Target.x, Camera.Target.y, Camera.Target.z, VSize(Camera.Target));
	DrawFormatString(0, 40, white, "head x=%f y=%f z=%f size=%f", Camera.Head.x, Camera.Head.y, Camera.Head.z, VSize(Camera.Head));
}

実行結果