# GirlsWar詳細
## 概要
<!--こっちにもスクショ-->
2年次の10月から1月までの期間で制作したゲームです。
このゲームは2Dタワーディフェンスで、スライムの進行を受けている、とある世界で女の子に協力してもらいスライムを退治してもらうゲームです。
基本的な操作はゲームプレイ中に女の子が写っているボタンを押すことで女の子を出撃させ、戦うことができます。負けないように女の子に戦ってもらいましょう。
スマートフォンのゲームを作ることや、既存のゲームを模倣してゲームを制作することが目標のチームです。チームメンバー2人ともモバイル系のゲーム企業に就職を希望しています。
<!--公開用のGitHubリポジトリをつくる-->
GitHubのリンク
[https://github.com/inoharayuuya/Public_GirlsWar](https://github.com/inoharayuuya/Public_GirlsWar)
---
::: spoiler {state="open"} <font size="4">**作品のスクリーンショット**</font>
**タイトル**

**ステージセレクト**


**戦闘**


:::
## アピールポイント
|番号|項目|
|-:|:-|
|1|[画面の中心に一番近いオブジェクトを検索](#1.画面の中心に一番近いオブジェクトを検索)|
|2|[オブジェクトを画面の中心に移動](#2.オブジェクトを画面の中心に移動)|
|3|[ステージ間の点線の自動生成](#3.ステージ間の点線の自動生成)|
|4|[スマートフォン特有のカメラ操作](#4.スマートフォン特有のカメラ操作)|
|5|[ステージ情報をJsonデータで管理](#5.ステージ情報をJsonデータで管理)|
<!--
アピールポイントと同じ事を書く
ソースの前に工夫した点などを書く
アピールポイントは画像または図で説明する
-->
### 1.画面の中心に一番近いオブジェクトを検索
画面の中心に一番近いオブジェクトを検索し、オブジェクトの透明度とサイズを初期化して、選択されているオブジェクトの強調表現をしています。
画像または図での説明を入れる
```c#=688
/// <summary>
/// 毎フレーム画面中心に一番近いステージの中心座標を計算する
/// </summary>
/// <returns>中心に一番近かった中心座標のインデックスを返す</returns>
int CheckCenter()
{
float sum = Common.SS_INIT_SUM;
int index = 0;
for (int i = 0; i < stageNamePos.Length; i++)
{
// ステージ名オブジェクトのα値を初期化する
stageNameObject[i].GetComponent<Image>().color = halfAlpha;
stageNameObject[i].GetComponentInChildren<Text>().color = new Color(1f, 0.5f, 0, 0.5f);
// ステージ名オブジェクトのサイズを初期化
var size = 1f;
stageNameObject[stageSpaceIndex].GetComponent<RectTransform>().localScale = new Vector3(size, size, size);
// ステージの座標と各ステージの中心点の座標の差を計算
var stageDirection = Mathf.Abs(Mathf.Abs(stage.GetComponent<RectTransform>().localPosition.x) - stageNamePos[i]);
// 各ステージ名オブジェクトの距離を判定、sumより小さければsumを書き換える
if (Mathf.Abs(sum) > stageDirection)
{
sum = stageDirection;
index = i;
}
}
return index;
}
```
---
### 2.オブジェクトを画面の中心に移動
操作がない間オブジェクトが自動で画面の中心に移動します。
```c#=423
// 画面が押されていない間は減速し続ける
if (!isTap)
{
speed *= Common.SS_SLOW_DOWN_SPEED;
if (speed < 0.05f || Mathf.Abs(direction) < 50)
{
// 通常の移動を消したいのでfalse
isMove = false;
// 追尾する中心点を確定させる
stageIndex = CheckCenter();
// ステージ名の中心に寄らせる
if (stage.GetComponent<RectTransform>().localPosition.x < -(stageNamePos[stageIndex] + 20))
{
stage.GetComponent<RectTransform>().localPosition += new Vector3(Common.SS_DIRECTION_SPEED, 0, 0) * Time.deltaTime;
}
else if (stage.GetComponent<RectTransform>().localPosition.x > -(stageNamePos[stageIndex] - 20))
{
stage.GetComponent<RectTransform>().localPosition += new Vector3(-Common.SS_DIRECTION_SPEED, 0, 0) * Time.deltaTime;
}
else
{
if (speed < Common.SS_MIN_SPEED)
{
// 中心の座標を代入
stage.GetComponent<RectTransform>().localPosition = new Vector3(-stageNamePos[stageIndex],
stage.GetComponent<RectTransform>().localPosition.y,
stage.GetComponent<RectTransform>().localPosition.z);
}
// 止める
speed = 0;
}
}
}
```
---
### 3.ステージ間の点線の自動生成
ステージの座標を参照してステージ間の点線を自動生成しています。設定した間隔で点を置いています。ステージ間の距離が、設定した距離以内であれば正常に処理ができます。また、色々な方向においても対応できるように設計しました。
```c#=288
/// <summary>
/// circleを生成
/// </summary>
void CreateCircle()
{
// ステージオブジェクトの間に間隔を持ってcircleを生成
for (int i = 0; i < clearCnt; i++)
{
// ステージの距離を計算
var pos = stageObject[i].transform.position;
var nextPos = stageObject[i + 1].transform.position;
var distance = pos - nextPos;
// x座標とy座標それぞれ距離と点線の間隔に基づき何個置けるかを計算する
var spaceX = Mathf.Abs(distance.x / Common.SS_CIRCLE_SPACE);
var spaceY = Mathf.Abs(distance.y / Common.SS_CIRCLE_SPACE);
// x座標とy座標を比較してどちらを基準にするかを決定する
if (spaceX > spaceY)
{
// x座標の方を基準にするのでy座標の方は計算しなおす
spaceY = Mathf.Abs(distance.y / spaceX);
spaceX = Common.SS_CIRCLE_SPACE;
// y座標がずれていない場合はx座標と同じ間隔で点を置く
if (distance.y == 0)
{
spaceY = Common.SS_CIRCLE_SPACE;
}
}
else if (spaceX < spaceY)
{
// y座標の方を基準にするのでx座標の方は計算しなおす
spaceX = Mathf.Abs(distance.x / spaceY);
spaceY = Common.SS_CIRCLE_SPACE;
// x座標がずれていない場合はy座標と同じ間隔で点を置く
if (distance.x == 0)
{
spaceX = Common.SS_CIRCLE_SPACE;
}
}
else
{
// どちらも同じ距離の場合は同じ間隔で点を置く
spaceX = Common.SS_CIRCLE_SPACE;
spaceY = Common.SS_CIRCLE_SPACE;
}
int cnt = 0;
var minDistance = 0.5f;
// x座標とy座標のどちらも次の座標と指定した距離以内になった場合ループから抜ける
while (Mathf.Abs(distance.x) > minDistance || Mathf.Abs(distance.y) > minDistance)
{
// 無限ループしないために最大ループ数を決めている(基本最大ループする距離まで離れた位置にステージを置かない)
if (cnt > 100)
{
break;
}
// 座標がプラス方向かマイナス方向かで足す値を変えている
if (distance.x > 0)
{
pos.x += -spaceX;
distance.x += -spaceX;
}
else if (distance.x < 0)
{
pos.x += spaceX;
distance.x += spaceX;
}
if (distance.y > 0)
{
pos.y += -spaceY;
distance.y += -spaceY;
}
else if (distance.y < 0)
{
pos.y += spaceY;
distance.y += spaceY;
}
// 決定した座標に点を生成する
Instantiate(circle, pos, Quaternion.identity);
cnt++;
}
}
}
```
---
### 4.スマートフォン特有のカメラ操作
初期化時に、実行しているデバイスのスクリーンサイズを取得してアスペクト比を計算し、カメラの最大サイズを動的に変更しています。また、画面を2本の指でタッチしている場合に2本の指の距離に応じてカメラのサイズを変化させています。設定したカメラの最大サイズを超えないようにしています。
```c#=56
private void Start()
{
// フレームレート最大値を60に設定
Application.targetFrameRate = 60;
// 現在のカメラのサイズを取得
v = cameraMain.orthographicSize;
speed = 0;
isTap = false;
startPosTime = Common.CC_START_POS_TIME;
// カメラの映している範囲を取得
GetCameraViewport();
// カメラサイズが1の時のカメラの映している範囲を計算
var width = (Mathf.Abs(cameraPosLeftBottom.x - cameraPosRightTop.x)) / cameraMain.orthographicSize;
// カメラの移動限界を設定
cameraLeftLimit = gameObjects[0].transform.position.x - Common.CASTLE_WIDTH * 2; // 左側の限界値
cameraRightLimit = gameObjects[1].transform.position.x + Common.CASTLE_WIDTH * 2; // 右側の限界値
// カメラの横のサイズを計算
cameraWidth = Mathf.Abs(cameraRightLimit - cameraLeftLimit);
// 実行しているデバイスのスクリーンサイズを取得してアスペクト比を計算
m = ((float)Screen.currentResolution.height / (float)Screen.currentResolution.width) * cameraWidth;
// 確認用
print("cameraSize:" + width);
print("cameraWidth:" + cameraWidth);
print("アスペクト比:" + m);
// カメラの最大サイズを設定
maxViewSize = m / 2;
}
```
```c#=130
// マルチタッチかどうか確認
if (Input.touchCount >= 2)
{
isTap = true;
// タッチしている2点を取得
Touch t1 = Input.GetTouch(0);
Touch t2 = Input.GetTouch(1);
//2点タッチ開始時の距離を記憶
if (t2.phase == TouchPhase.Began)
{
backDist = Vector2.Distance(t1.position, t2.position);
}
else if (t1.phase == TouchPhase.Moved || t2.phase == TouchPhase.Moved)
{
// 更新時間がくるまでdeltaTimeで計算
startPosTime -= Time.deltaTime;
// タッチ位置の移動後、長さを再測し、前回の距離からの相対値を取る。
float newDist = Vector2.Distance(t1.position, t2.position);
// 更新時間が来たら初期化する
if (startPosTime < 0)
{
startPosTime = Common.CC_START_POS_TIME;
backDist = newDist;
}
// カメラサイズの限界値をオーバーした際の処理
if (v > maxViewSize)
{
cameraMain.orthographicSize = maxViewSize;
v = maxViewSize;
}
else if (v < vMin)
{
cameraMain.orthographicSize = vMin;
v = vMin;
}
else
{
v += (backDist - newDist) / Common.CC_SPEED;
}
}
}
```
### 5.ステージ情報をJsonデータで管理
記入した情報をJsonデータに変換し保存しています。この部分のステージ情報を書き足すことでステージを増やすことができます。
```c#=108
/// <summary>
/// ステージ情報の保存
/// </summary>
void SaveStage()
{
if (PlayerPrefs.HasKey(Common.KEY_STAGE_DATA))
{
loadData = JsonUtility.FromJson<Data>(PlayerPrefs.GetString(Common.KEY_STAGE_DATA));
print("データがあります。");
// テストでJsonデータを表示
print("Json型:" + PlayerPrefs.GetString(Common.KEY_STAGE_DATA));
}
else
{
// 実態を生成
loadData = new Data();
loadData.stages = new Stage[4];
print("データがありませんでした。新規作成します。");
// 各ステージの情報をセットする
EnemyInfo[] tmpEnemyInfos;
// チュートリアル
tmpEnemyInfos = new EnemyInfo[]
{
new EnemyInfo(EnemyID.NORMAL, 10, 20, int.MaxValue) // ノーマルスライム
};
// ステージ情報をセット
SetStage(STAGE_ID.TUTORIAL, 10, true, tmpEnemyInfos);
// ステージ1
tmpEnemyInfos = new EnemyInfo[]
{
new EnemyInfo(EnemyID.NORMAL, 0, 15, int.MaxValue), // ノーマルスライム
new EnemyInfo(EnemyID.LIGHTNING, 60, 20, 1) // ライトニングスライム
};
// ステージ情報をセット
SetStage(STAGE_ID.STAGE1, 100, true, tmpEnemyInfos);
// ステージ2
tmpEnemyInfos = new EnemyInfo[]
{
new EnemyInfo(EnemyID.NORMAL, 0, 10, int.MaxValue), // ノーマルスライム
new EnemyInfo(EnemyID.LIGHTNING, 15, 20, 10) // ライトニングスライム
};
// ステージ情報をセット
SetStage(STAGE_ID.STAGE2, 200, true, tmpEnemyInfos);
// ステージ3
tmpEnemyInfos = new EnemyInfo[]
{
new EnemyInfo(EnemyID.NORMAL, 0, 10, int.MaxValue), // ノーマルスライム
new EnemyInfo(EnemyID.LIGHTNING, 60, 20, 3), // ライトニングスライム
new EnemyInfo(EnemyID.FIRE, 100, 50, 3), // ファイアースライム
};
// ステージ情報をセット
SetStage(STAGE_ID.STAGE3, 300, true, tmpEnemyInfos);
// Jsonデータに変換
string tmp = JsonUtility.ToJson(loadData);
// 指定したキーにJsonデータを保存
PlayerPrefs.SetString(Common.KEY_STAGE_DATA, tmp);
PlayerPrefs.Save();
// データ表示
print("Json型" + PlayerPrefs.GetString(Common.KEY_STAGE_DATA));
}
}
```