記事内で紹介する商品を購入することで、当サイトに売り上げの一部が還元されることがあります。
タイピングゲーム制作を通してプログラミングに強くなろう!
ということで、オリジナル 対戦型タイピングゲームの制作過程を紹介しています。
このタイピングゲームは
- HTML
- CSS
- JavaScript
の3種類の言語で制作していて初心者でも取り扱いやすい内容になっていて、
高校の情報1のプログラミング実技授業としてもオススメです!
この記事では制作過程の「Step3装飾して完成させよう」ということで、CSSを中心に更新して、いよいよ完成です!
目次
Step3は以下を実装して完成させましょう。
- ライフバーを作ろう②〜残りライフの表示とプレイヤー&敵の画像表示〜
- バトルスクリーンに画像を表示させよう
- タイピングエリアを装飾しよう
Step3を終えるには目安として2〜3時間必要です。
Step3で入力するコードで正しく機能を実装できれば以下のようなプログラムの状態になります。
最小限ではありますが、必要な機能を備えたタイピングゲームを完成させることができます。
それではStep1をどのように進めていくか順に説明します。
課題①は以下を行いましょう。
- HTMLでdiv要素など、必要な要素をタイピングして記述しましょう。
- CSSファイルでライフバーの色や画像の大きさ、レイアウトなど必要な装飾を加えましょう。
- JavaScriptファイルで攻撃の関数、ライフバー更新の関数を修正しましょう。
課題②は以下を行いましょう。
- HTMLでimg要素を使って、画像を表示させましょう。
- CSSファイルで必要な装飾を加えましょう。
課題③は以下を行いましょう。
- HTMLでタイピングエリアを作成しましょう。
- CSSファイルで装飾を加えましょう。
最後に模範コードと課題用コードの例を紹介します。課題①〜③用となっているので、課題①から順に取り組んでみてください。
HTMLファイルの名前は「index.html」にしましょう。
模範コード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タイピング対戦ゲーム</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="startScreen" class="screen active">
<h1>タイピング対戦ゲーム</h1>
<button id="startButton">ゲーム開始</button>
</div>
<div id="battleScreen" class="screen">
<div id="gameContainer">
<div id="playerStatus" class="status">
<div id="lifeSection" class="character-container">
<div class="playerLifeBar">
<div class="imgBox" id="playerImgBox">
<img src="image/soldier1.png" class="playerImg" alt="Player Image">
</div>
<div id="playerLife" class="lifeBar">
<!-- 残りのライフ(黄色バー) -->
<div class="lifeRemaining" style="width: 100%; background-color: yellow;"></div>
<!-- 減ったライフ(グレーバー) -->
<div class="lifeLost" style="width: 0%; background-color: darkgrey; position: absolute; top: 0; right: 0;"></div>
<span class="lifeText">100</span>
<span style="color: white;">プレイヤー</span>
</div>
</div>
<div class="enemyLifeBar">
<div id="enemyLife" class="lifeBar">
<!-- 残りのライフ(黄色バー) -->
<div class="lifeRemaining" style="width: 100%; background-color: yellow;"></div>
<!-- 減ったライフ(グレーバー) -->
<div class="lifeLost" style="width: 0%; background-color: darkgray; position: absolute; top: 0; right: 0;"></div>
<span class="lifeText">100</span>
<span style="color: white;">敵キャラ</span>
</div>
<div class="imgBox" id="enemyImgBox">
<img src="image/soldier2.png" class="enemyImg" alt="Enemy Image">
</div>
</div>
</div>
</div>
<div id="battleSection">
<div class="character player">
<img src="image/soldier1.png" class="battlePlayerImg" alt="Player Image">
</div>
<div id="result"></div>
<div class="character enemy">
<img src="image/soldier2.png" class="BattleEnemyImg" alt="Enemy Image">
</div>
</div>
<div id="typingSection">
<span id="currentWord"></span>
<input type="text" id="typingInput">
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
課題用コードの例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タイピング対戦ゲーム</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="startScreen" class="screen active">
<h1>タイピング対戦ゲーム</h1>
<button id="startButton">ゲーム開始</button>
</div>
<div id="battleScreen" class="screen">
<div id="gameContainer">
<div id="playerStatus" class="status">
<div id="lifeSection" class="character-container">
<div class="playerLifeBar">
<div class="imgBox" id="playerImgBox">
<img src="image/soldier1.png" class="playerImg" alt="Player Image">
</div>
<div id="playerLife" class="lifeBar">
<!-- 残りのライフ(黄色バー) -->
<div class="lifeRemaining" style="width: 100%; background-color: yellow;"></div>
<!-- 減ったライフ(グレーバー) -->
<div class="lifeLost" style="width: 0%; background-color: darkgrey; position: absolute; top: 0; right: 0;"></div>
<span class="lifeText">100</span>
<span style="color: white;">プレイヤー</span>
</div>
</div>
<div class="enemyLifeBar">
<div id="enemyLife" class="lifeBar">
<!-- 残りのライフ(黄色バー) -->
<div class="lifeRemaining" style="width: 100%; background-color: yellow;"></div>
<!-- 減ったライフ(グレーバー) -->
<div class="lifeLost" style="width: 0%; background-color: darkgray; position: absolute; top: 0; right: 0;"></div>
<span class="lifeText">100</span>
<span style="color: white;">敵キャラ</span>
</div>
<div class="imgBox" id="enemyImgBox">
<img src="image/soldier2.png" class="enemyImg" alt="Enemy Image">
</div>
</div>
</div>
</div>
<div id="battleSection">
<!-- 課題②バトルスクリーンに表示する画像を指定 -->
<div class="character player">
<■■ src="■■" class="battlePlayerImg" alt="Player Image">
</div>
<div id="result"></div>
<div class="character enemy">
<■■ src="■■" class="BattleEnemyImg" alt="Enemy Image">
</div>
</div>
<!-- 課題③タイピングエリアを作成しましょう -->
<div>
■■
■■
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
HTMLファイルで読み込むCSSファイルの名称を指定しているので、「style.css」というファイル名にしておきましょう。
模範コード
/* スタイル.css */
body {
font-family: Arial, sans-serif;
text-align: center;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
body, html {
height: 100%;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.screen {
display: none;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: 100%;
}
.screen.active {
display: flex;
}
#lifeSection, #typingSection {
width: 100%;
box-sizing: border-box; /* パディングとボーダーを幅に含める */
}
.lifeBar {
width: 100%;
height: 30px;
background-color: #ddd;
margin: 5px;
position: relative;
}
.player .lifeBar {
background-color: green;
}
.enemy .lifeBar {
background-color: red;
}
#battleSection {
display: flex;
justify-content: space-around;
width: 100%;/* 対戦画面の幅を設定 */
height: 250px;
background-image: url(image/sougen57.png);
background-repeat: no-repeat;
background-size: cover; /* または contain や特定の長さ */
background-position: center center; /* X軸(水平方向)とY軸(垂直方向)の位置 */
padding: 30px 0;
align-items: center;
}
.character {
transition: transform 1s;
}
.character.player {
transform: translateX(0);
}
/* 攻撃アニメーション */
.character.player.attacking {
transform: translateX(50%);
}
.character.enemy {
transform: translateX(0);
}
/* 攻撃アニメーション */
.character.enemy.attacking {
transform: translateX(-50%);
}
#currentWord {
display: block;
margin: 30px 0;
font-size: 24px;
}
#typingInput {
border: none; /* 枠線を削除 */
outline: none; /* フォーカス時のアウトラインを削除 */
background-color: transparent; /* 背景色を透明に */
font-size: 48px; /* フォントサイズ */
color: #f4f4f4; /* テキストの色 */
text-align: center; /* テキストを中央揃えに */
/* width: 100%; 必要に応じて幅を設定 */
}
#result {
font-size: 5em;
color: red;
}
.character-container {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.character {
text-align: center;
}
.health-bar {
width: 200px;
height: 24px;
background-color: #ddd;
border-radius: 12px;
overflow: hidden;
margin-bottom: 10px;
}
.health {
height: 100%;
background-color: #f00;
border-radius: 12px;
transition: width 0.3s ease-in-out;
}
.character-name {
font-size: 1.2em;
margin-bottom: 10px;
}
#playerStatus{
width: 100%;/* プレイヤーステータスの幅を設定 */
}
#gameContainer {
width: 800px; /* ゲームコンテナの幅を設定 */
height: 600px; /* 画面の高さは適宜設定 */
background-color: #5780e9; /* 背景色 */
}
#typingSection{
height: 200px;
background-color:black;
color: white;
/* display: flex; Flexboxを使って中央揃えに */
justify-content: center; /* 水平方向の中央揃え */
align-items: center; /* 垂直方向の中央揃え */
border: 1px solid #ccc; /* グレー色の枠線を設定 */
}
#lifeSection{
margin: 2rem 0;
}
.playerLifeBar, .enemyLifeBar{
position: relative;
width: 300px; /* ライフバーの幅を固定する */
height: 30px; /* ライフバーの高さを固定する */
/* width: 50%; */
display: flex;
}
.lifeRemaining {
background-color: yellow; /* 残ライフを表す色(黄色) */
height: 100%; /* 高さはライフバーに合わせる */
transition: width 0.5s ease; /* 幅の変更にアニメーションを設定 */
}
.lifeText {
position: relative;
top: -30px;
left: 17%;
text-align: center;
line-height: 30px; /* テキストを垂直中央に配置 */
color: black; /* ライフ数値の色 */
}
.imgBox{
width: 35px;
height: 50px;
}
.playerImg, .enemyImg{
height: 100%;
}
#playerImgBox{
margin-right: 30px;
}
#enemyImgBox{
margin-left: 30px;
}
課題用コードの例
/* スタイル.css */
body {
font-family: Arial, sans-serif;
text-align: center;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
body, html {
height: 100%;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.screen {
display: none;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: 100%;
}
.screen.active {
display: flex;
}
#lifeSection, #typingSection {
width: 100%;
box-sizing: border-box; /* パディングとボーダーを幅に含める */
}
.lifeBar {
width: 100%;
height: 30px;
background-color: #ddd;
margin: 5px;
position: relative;
}
.player .lifeBar {
background-color: green;
}
.enemy .lifeBar {
background-color: red;
}
#battleSection {
display: flex;
justify-content: space-around;
width: 100%;/* 対戦画面の幅を設定 */
height: 250px;
background-image: url(image/sougen57.png);
background-repeat: no-repeat;
background-size: cover; /* または contain や特定の長さ */
background-position: center center; /* X軸(水平方向)とY軸(垂直方向)の位置 */
padding: 30px 0;
align-items: center;
}
.character {
transition: transform 1s;
}
.character.player {
transform: translateX(0);
}
/* 攻撃アニメーション */
.character.player.attacking {
transform: translateX(50%);
}
.character.enemy {
transform: translateX(0);
}
/* 攻撃アニメーション */
.character.enemy.attacking {
transform: translateX(-50%);
}
#currentWord {
display: block;
margin: 30px 0;
font-size: 24px;
}
/* 課題③ タイピングエリアの装飾*/
#typingInput {
}
#result {
font-size: 5em;
color: red;
}
.character-container {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.character {
text-align: center;
}
.health-bar {
width: 200px;
height: 24px;
background-color: #ddd;
border-radius: 12px;
overflow: hidden;
margin-bottom: 10px;
}
.health {
height: 100%;
background-color: #f00;
border-radius: 12px;
transition: width 0.3s ease-in-out;
}
.character-name {
font-size: 1.2em;
margin-bottom: 10px;
}
#playerStatus{
width: 100%;/* プレイヤーステータスの幅を設定 */
}
#gameContainer {
width: 800px; /* ゲームコンテナの幅を設定 */
height: 600px; /* 画面の高さは適宜設定 */
background-color: #5780e9; /* 背景色 */
}
/* 課題③ タイピングエリアの装飾*/
#typingSection{
}
#lifeSection{
margin: 2rem 0;
}
/* 課題① ライフバーの装飾*/
.playerLifeBar, .enemyLifeBar{
}
/* 課題① ライフバーの装飾*/
.lifeRemaining {
}
/* 課題① ライフバーの装飾*/
.lifeText {
}
/* 課題② 画像の装飾*/
.imgBox{
}
/* 課題② 画像の装飾*/
.playerImg, .enemyImg{
}
/* 課題② 画像の装飾*/
#playerImgBox{
}
/* 課題② 画像の装飾*/
#enemyImgBox{
}
JavaScriptのファイル名は「script.js」としておきましょう。
模範コード
// スクリプト.js
// ページのコンテンツが読み込まれた後に実行されるイベントリスナー
document.addEventListener('DOMContentLoaded', () => {
// 各HTML要素を取得
const startButton = document.getElementById('startButton');
const startScreen = document.getElementById('startScreen');
const battleScreen = document.getElementById('battleScreen');
const typingInput = document.getElementById('typingInput');
const currentWordSpan = document.getElementById('currentWord');
const playerLifeBar = document.querySelector('.playerLifeBar .lifeRemaining');
const enemyLifeBar = document.querySelector('.enemyLifeBar .lifeRemaining');
const playerCharacter = document.querySelector('.character.player');
const enemyCharacter = document.querySelector('.character.enemy');
const resultDisplay = document.getElementById('result');
// プレイヤーと敵の初期ライフを設定
let playerLife = 100;
let enemyLife = 100;
// 現在の単語を格納する変数
let currentWord = '';
// タイマーを管理する変数
let timer;
// タイピング用の単語リスト
const words = ["challenge", "opponent", "battle", "victory", "defeat"];
// ゲームを開始する関数
function startGame() {
startScreen.classList.remove('active'); // 開始画面を非表示に
battleScreen.classList.add('active'); // 対戦画面を表示
nextWord(); // 次の単語を表示
}
// 次の単語を表示する関数
function nextWord() {
currentWord = words[Math.floor(Math.random() * words.length)]; // ランダムな単語を選択
currentWordSpan.textContent = currentWord; // 単語を表示
typingInput.value = ''; // 入力フィールドをクリア
typingInput.focus(); // 入力フィールドにフォーカス
startTimer(); // タイマーを開始
}
// 入力をチェックする関数
function checkInput() {
const typedValue = typingInput.value; // 入力された値を取得
if (typedValue === currentWord) { // 入力値が単語と一致した場合
attackEnemy(); // 敵に攻撃
} else { // 一致しない場合
// 入力された部分をグレーで表示
let matchedPart = currentWord.substring(0, typedValue.length);
let remainingPart = currentWord.substring(typedValue.length);
currentWordSpan.innerHTML = `<span style="color: grey;">${matchedPart}</span>${remainingPart}`;
}
}
// プレイヤーが敵に攻撃する関数(修正箇所)
function attackEnemy() {
playerCharacter.classList.add('attacking');
setTimeout(() => {
playerCharacter.classList.remove('attacking');
enemyLife -= 20; // ダメージ量
updateLifeBar(enemyLifeBar, enemyLife); // 敵のライフバーを更新(修正箇所)
checkGameOver();
}, 1000);
}
// 敵がプレイヤーに攻撃する関数(修正箇所)
function enemyAttacks() {
enemyCharacter.classList.add('attacking');
setTimeout(() => {
enemyCharacter.classList.remove('attacking');
playerLife -= 20; // ダメージ量
updateLifeBar(playerLifeBar, playerLife); // プレイヤーのライフバーを更新(修正箇所)
checkGameOver();
}, 1000);
}
// ライフバーを更新する関数(修正箇所)
function updateLifeBar(lifeBarElement, life) {
// ライフバーの幅をライフの割合に基づいて更新(修正箇所)
const percentage = (life / 100) * 100;
// ライフテキストの内容を更新(修正箇所)
lifeBarElement.style.width = percentage + '%'; // ライフの残量に応じて幅を設定
lifeBarElement.parentElement.querySelector('.lifeText').textContent = life; // ライフテキストを更新
lifeBarElement.parentElement.querySelector('.lifeLost').style.width = (100 - percentage) + '%'; // 減ったライフの幅を設定
}
// ゲームオーバーをチェックする関数
function checkGameOver() {
if (playerLife <= 0) {
gameOver("LOSE"); // プレイヤーの負け
} else if (enemyLife <= 0) {
gameOver("WIN"); // プレイヤーの勝ち
}else{
nextWord();
}
}
// ゲームオーバー時の処理を行う関数
function gameOver(result) {
clearTimeout(timer); // タイマーを停止
resultDisplay.textContent = result; // 結果を表示
typingInput.disabled = true; // 入力フォームを無効化
}
// タイマーを開始する関数
function startTimer() {
clearTimeout(timer); // 既存のタイマーをクリア
timer = setTimeout(() => {
enemyAttacks(); // 敵が攻撃
nextWord(); // 次の単語へ
}, 10000); // 10秒後に実行(入力の制限時間)
}
// イベントリスナーの設定
startButton.addEventListener('click', startGame);
typingInput.addEventListener('input', checkInput);
// 入力フォームに常にフォーカスを保つための関数
function keepFocusOnInput() {
typingInput.focus();
}
// ページ全体にイベントリスナーを追加して、どこをクリックしても入力フォームにフォーカスを戻す
document.addEventListener('click', keepFocusOnInput);
});
課題用コードの例
// スクリプト.js
// ページのコンテンツが読み込まれた後に実行されるイベントリスナー
document.addEventListener('DOMContentLoaded', () => {
// 各HTML要素を取得
const startButton = document.getElementById('startButton');
const startScreen = document.getElementById('startScreen');
const battleScreen = document.getElementById('battleScreen');
const typingInput = document.getElementById('typingInput');
const currentWordSpan = document.getElementById('currentWord');
const playerLifeBar = document.querySelector('.■■ .■■');
const enemyLifeBar = document.querySelector('.■■ .■■');
const playerCharacter = document.querySelector('.character.player');
const enemyCharacter = document.querySelector('.character.enemy');
const resultDisplay = document.getElementById('result');
// プレイヤーと敵の初期ライフを設定
let playerLife = 100;
let enemyLife = 100;
// 現在の単語を格納する変数
let currentWord = '';
// タイマーを管理する変数
let timer;
// タイピング用の単語リスト
const words = ["■■", "■■", "■■", "■■", "■■"];//10個ほど作成させる。
// ゲームを開始する関数
function startGame() {
startScreen.classList.remove('active'); // 開始画面を非表示に
battleScreen.classList.add('active'); // 対戦画面を表示
nextWord(); // 次の単語を表示
}
// 次の単語を表示する関数
function nextWord() {
currentWord = words[Math.floor(Math.random() * words.length)]; // ランダムな単語を選択
currentWordSpan.textContent = currentWord; // 単語を表示
typingInput.value = ''; // 入力フィールドをクリア
typingInput.focus(); // 入力フィールドにフォーカス
startTimer(); // タイマーを開始
}
// 入力をチェックする関数
function checkInput() {
const typedValue = typingInput.value; // 入力された値を取得
if (typedValue === currentWord) { // 入力値が単語と一致した場合
let matchedPart = currentWord.substring(0, typedValue.length);
let remainingPart = currentWord.substring(typedValue.length);
currentWordSpan.innerHTML = `<span style="color: grey;">${matchedPart}</span>${remainingPart}`;
attackEnemy(); // 敵に攻撃
} else { // 一致しない場合
// 入力された部分をグレーで表示
let matchedPart = currentWord.substring(0, typedValue.length);
let remainingPart = currentWord.substring(typedValue.length);
currentWordSpan.innerHTML = `<span style="color: grey;">${matchedPart}</span>${remainingPart}`;
}
}
// プレイヤーが敵に攻撃する関数(課題①)
function attackEnemy() {
playerCharacter.classList.add('attacking');
setTimeout(() => {
playerCharacter.classList.remove('attacking');
enemyLife -= 20; // ダメージ量
■■(■■, ■■); // 敵のライフバーを更新
checkGameOver();
}, 1000);
}
// 敵がプレイヤーに攻撃する関数(課題①)
function enemyAttacks() {
enemyCharacter.classList.add('attacking');
setTimeout(() => {
enemyCharacter.classList.remove('attacking');
■■ -= 20; // ダメージ量
■■(■■, ■■); // プレイヤーのライフバーを更新(修正箇所)
■■();
}, 1000);
}
// ライフバーを更新する関数
function updateLifeBar(lifeBarElement, life) {
// ライフバーの幅をライフの割合に基づいて更新(修正箇所)
const percentage = (life / 100) * 100;
// ライフテキストの内容を更新(修正箇所)
lifeBarElement.style.width = percentage + '%'; // ライフの残量に応じて幅を設定
lifeBarElement.parentElement.querySelector('.lifeText').textContent = life; // ライフテキストを更新
lifeBarElement.parentElement.querySelector('.lifeLost').style.width = (100 - percentage) + '%'; // 減ったライフの幅を設定
}
// ゲームオーバーをチェックする関数(課題①)
function checkGameOver() {
if (playerLife <= 0) {
gameOver("LOSE"); // プレイヤーの負け
} else if (enemyLife <= 0) {
gameOver("WIN"); // プレイヤーの勝ち
}else{
■■();
}
}
// ゲームオーバー時の処理を行う関数
function gameOver(result) {
clearTimeout(timer); // タイマーを停止
resultDisplay.textContent = result; // 結果を表示
typingInput.disabled = true; // 入力フォームを無効化
}
// タイマーを開始する関数
function startTimer() {
clearTimeout(timer); // 既存のタイマーをクリア
timer = setTimeout(() => {
enemyAttacks(); // 敵が攻撃
nextWord(); // 次の単語へ
}, 10000); // 10秒後に実行(入力の制限時間)
}
// イベントリスナーの設定
startButton.addEventListener('click', startGame);
typingInput.addEventListener('input', checkInput);
// 入力フォームに常にフォーカスを保つための関数
function keepFocusOnInput() {
typingInput.focus();
}
// ページ全体にイベントリスナーを追加して、どこをクリックしても入力フォームにフォーカスを戻す
document.addEventListener('click', keepFocusOnInput);
});
Step3の詳細と進め方を紹介しました。
これにてタイピングゲームがひとまず完成しました。
次回は時間が余った人に向けたアレンジ案です。お楽しみに!
スポンサーリンク