Unityですごろくを作ってみる

ayumegu(プログラマー)
よろしくお願いします。

こんにちは、あゆめぐです。
今回はすごろくの基本的な部分を作成してみました。

作ったもの

サイコロを振る、移動する、分岐にきたら進む方向を選べる。
これだけのシンプルなものです。
あ、一周してスタートに戻ると動けなくなるので注意!w

すごろくのようなもの

使用Asset

  • iTween(キャラを動かすところ)(無料)
  • Dice(さいころ)(無料)
  • 2d Toolkit(UIなど)有料

解説

シーン構成

まず構成から。
カメラを3つほど使用しております。
1カメ:すごろくステージを表示(最背面)
2カメ:さいころ用カメラ
3カメ:UI表示用カメラ(最前面)

さいころ用カメラの映像はこんな感じになってます
床を黒、壁が水色にしてみました。
画面外に出ないようにしています。

壁や床が見えるとかっこわるいのでMesh Rendererのチェックを外してあたり判定を残したまま、表示を消しています

あとはサイコロ用カメラをデフォルト非表示にしておいて、さいころを振るときにSetActive(true)でOK

サイコロを転がす

ここに意外と時間がかかりました。
後の方であ、ってなったのですが、壁に引っかかって斜めになり、値がとれない場合があったので応急処置しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Update () {

  if(gameStatus == 1 && Input.GetMouseButtonDown(0)){
      ChangeGameStatus(2);
      diceStartFlg = true;
      diceObject.rigidbody.AddForce(Force(),ForceMode.Impulse);
      diceObject.rigidbody.AddTorque(new Vector3(-50 * Random.value * diceObject.transform.localScale.magnitude, -50 * Random.value * diceObject.transform.localScale.magnitude, -50 * Random.value * diceObject.transform.localScale.magnitude), ForceMode.Impulse);
              
  }
  
  // Dice転がり終了
  if(diceStartFlg){
      if(diceObject.rigidbody.velocity.magnitude == 0){
          diceStartFlg = false;
          CheckDice();
      }
      
  }
}

function Force(){
  var rollTarget:Vector3 = Vector3.zero + new Vector3(2 + 7 * Random.value, .5F + 4 * Random.value, -2 - 3 * Random.value);
  return Vector3.Lerp(diceObject.transform.position, rollTarget, 1).normalized * (-35 - Random.value * 20);
}

こちら転がすところのソースです。
rigidbodyに衝撃を加えて転がしている部分はDiceのサンプルソースより。
転がり終了判定はrigidbody.velocity.magnitudeが0の時に行っています。

で、壁に引っかかって斜めになり、値がとれない場合。 なのですが、私はとりあえずそこからまた衝撃を加える作戦にしてしまいました。
実際にゲームにするならキャラクターを登場させてサイコロ蹴らせればOKぐらいに考えています。w

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function CheckDice(){
  diceValue = 0;
  while(diceValue == 0){
      diceValue = diceObject.GetComponent(Die).value;
      
      if(diceValue != 0){
          yield WaitForSeconds(1.5);
          diceValue = diceObject.GetComponent(Die).value;
          limitValue = diceValue;
          ChangeGameStatus(4);
          CharaMove();
      }else{
          // 判定できない場合もうちょっと転がす
          diceObject.rigidbody.AddForce(Force(),ForceMode.Impulse);
          yield WaitForSeconds(2.0);
      }
  }
}

こちら出た目の判定場所。
valueが0の場合斜めになっている状態なのでもう一度衝撃を与えています。
だめならもう一度という具合に・・・。
なんだかまだサイコロたまにうまく判定しきれてないときある気がする

全体ソース

ステージデータ

シンプルに配列で。
DataBase.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DataBase{
  // #なし 0道 Sスタート Gゴール
  public static var Stage1:Array = new Array(
      "##0000000G",
      "#00######0",
      "#0###000#0",
      "#00000#0#0",
      "#####0#000",
      "#####0####",
      "000000####",
      "0##0######",
      "0000######",
      "S#########"
  );
}

メインカメラ

MainCameraObject.js
キャラクターが移動したときに追尾するように設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var chara:GameObject;

function Start () {
  chara = GameObject.Find("Player");
}

function Update () {
  if(chara != null){
      var movePos:Vector3 = new Vector3(chara.transform.position.x, chara.transform.position.y, transform.position.z);
      transform.position = Vector3.Lerp (transform.position, movePos, Time.deltaTime * 5);
  }else{
      chara = GameObject.Find("Player");
  }
}

プレイヤー

PlayerObject.js
特にアニメーションもステータスもないのでシンプルです
現在位置と向いている方向だけ持っています

1
2
3
4
5
6
7
8
9
10
var dir:String;
var pos:Vector2;

function Start () {

}

function Update () {

}

矢印

ArrowObject.js
分岐にきたときの選択ボタン


4つの矢印ボタンを、移動できる方向のみ表示する形で実装しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var upArrow:GameObject;
var downArrow:GameObject;
var leftArrow:GameObject;
var rightArrow:GameObject;

var manager:SugorokuManager;

function Start () {

}

function Update () {

}

function SetDir(dirList:Array){
  upArrow.SetActive(false);
  downArrow.SetActive(false);
  leftArrow.SetActive(false);
  rightArrow.SetActive(false);
  for(var i:int = 0; i < dirList.length; i++){
      var str = dirList[i];
      if(str == "up"){
          upArrow.SetActive(true);
      }else if(str == "down"){
          downArrow.SetActive(true);
      }else if(str == "left"){
          leftArrow.SetActive(true);
      }else if(str == "right"){
          rightArrow.SetActive(true);
      }
  }
}

function UpClick(){
  manager.UserSelectArrow("up");
}
function DownClick(){
  manager.UserSelectArrow("down");
}
function LeftClick(){
  manager.UserSelectArrow("left");
}
function RightClick(){
  manager.UserSelectArrow("right");
}

本体

なんでもかんでもここに書き過ぎな気がするけど・・・。
SugorokuManager.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
var masuRowSize:int;
var masuColSize:int;
var masuList:Array;         // マスの配列
var masuObjectList:Array;   // マスオブジェクトの配列
var gameStatus:int;         // 0:wait 1:waitDiceClick 2:diceWait 3:playerMenuSelectWait 4:playerMove 5:userWaySelect 6:clear
var mainCamera:GameObject;

var chara:GameObject;
//-------------------- UIまわり ------------------
var UICamera:GameObject;
var menuObject:GameObject;
var limitMoveText:GameObject;
var limitValue:int; // 実際に動ける残りの数
var allows:GameObject;  // →

//-------------------- ステージ -----------------
var startObject:GameObject;
var goalObject:GameObject;
var startPos:Vector2;
var goalPos:Vector2;
//-------------------- さいころ ------------------
var diceCamera:GameObject;
var diceObject:GameObject;  // さいころ
var diceStartFlg:boolean;   // さいころ転がり中フラグ

//-------------------- ターン -----------------
var diceValue:int;      // 出た目

//-------------------- Clear ----------------
var clearText:GameObject;

function Start () {
  
  ChangeGameStatus(0);
  diceStartFlg = false;
  
  StageCreate();
  
  CharaCreate();
  
  ChangeGameStatus(3);
}

function ChangeGameStatus(status:int){
  gameStatus = status;
  if(status == 0){
  }
  
  if(status == 1 || status == 2){
      if(status == 1){
          diceObject.transform.position = Vector3(3, 1, -8);
      }
      diceCamera.SetActive(true);
  }else{
      diceCamera.SetActive(false);
  }
  
  if(status == 3){
      menuObject.SetActive(true);
  }else{
      menuObject.SetActive(false);
  }
  
  if(status == 4 || status == 5){
      limitMoveText.SetActive(true);
      if(status == 4){
          limitMoveText.GetComponent(TextMesh).text = "残り"+ diceValue + "マス";
      }
  }else{
      limitMoveText.SetActive(false);
  }
  
  if(status == 5){
      allows.SetActive(true);
  }else{
      allows.SetActive(false);
  }
  
  if(status == 6){
      var pos:Vector3 = chara.transform.position;
      pos.y += 2;
      clearText.transform.position = pos;
      clearText.SetActive(true);
  }else{
      clearText.SetActive(false);
  }
}

// キャラクター移動  
private function CharaMove(){
  yield WaitForSeconds(0.2);
  
  // 現在のマスがゴールだったらクリア
  var charaObject:PlayerObject = chara.GetComponent(PlayerObject);
  var charaDir:String = charaObject.dir;
  var pos:Vector2 = charaObject.pos;
  
  if(GetMasuData(pos.x , pos.y) == "G"){
      Debug.Log("CLEAR");
      ChangeGameStatus(6);
      return;
  }
  
  //while(limitValue > 0){
  if(limitValue <= 0){
      ChangeGameStatus(3);
  }else{
      // 現在位置から自分が来た方向以外の方向のますをチェック
      
      var upFlg:boolean = false;
      var downFlg:boolean = false;
      var leftFlg:boolean = false;
      var rightFlg:boolean = false;
      var count:int = 0;
      var nextPosList:Array = new Array();
      var nextDirList:Array = new Array();

      if(charaDir != "left" && pos.x != masuColSize - 1 && GetMasuData(pos.x + 1, pos.y) != "#"){
          count++;
          upFlg = true;
          nextPosList.push(new Vector2(pos.x + 1, pos.y));
          nextDirList.push("right");
          //Debug.Log("右にいける");
      }
      
      if(charaDir != "right" && pos.x != 0 && GetMasuData(pos.x - 1, pos.y) != "#"){
          count++;
          downFlg = true;
          nextPosList.push(new Vector2(pos.x - 1, pos.y));
          nextDirList.push("left");
          //Debug.Log("左にいける");
      }
      
      if(charaDir != "down" && pos.y != 0 && GetMasuData(pos.x, pos.y - 1) != "#"){
          count++;
          rightFlg = true;
          nextPosList.push(new Vector2(pos.x, pos.y - 1));
          nextDirList.push("up");
          //Debug.Log("上にいける");
      }
      
      if(charaDir != "up" && pos.y != masuRowSize - 1  && GetMasuData(pos.x, pos.y + 1) != "#"){
          count++;
          leftFlg = true;
          nextPosList.push(new Vector2(pos.x, pos.y + 1));
          nextDirList.push("down");
          //Debug.Log("下にいける");
      }

      // 自動で進む
      if(count == 1){
          limitValue--;
          limitMoveText.GetComponent(TextMesh).text = "残り"+ limitValue + "マス";
          var nextPos:Vector2 = nextPosList[0];
          var nextMasuObject:GameObject = GameObject.Find("Masu" + nextPos.x + nextPos.y);
          iTween.MoveTo(chara, iTween.Hash("x", nextMasuObject.transform.position.x, "y", nextMasuObject.transform.position.y,"delay", 0, "time", 0.5, "oncomplete", "CharaMove", "oncompletetarget", this.gameObject));
          
          charaObject.dir = nextDirList[0];
          charaObject.pos = nextPosList[0];
          
      }else{
          // ユーザーに選んでもらう
          allows.transform.position = chara.transform.position;
          allows.GetComponent(ArrowObject).SetDir(nextDirList);
          ChangeGameStatus(5);
      }
  }
}

// キャラクター配置
private function CharaCreate(){
  chara = Instantiate(Resources.Load("Prefabs/Object/Chara"));
  chara.transform.position = startObject.transform.position;
  chara.name = "Player";
  var charaObject:PlayerObject = chara.GetComponent(PlayerObject);
  charaObject.pos = startPos;
  charaObject.dir = "up";
}

private function GetMasuData(x:int, y:int){
  return masuList[x + y * masuColSize];
}

// ステージの配置
private function StageCreate(){
  
  masuObjectList = new Array();
  masuList = new Array();
  var stageStrList:Array = DataBase.Stage1;
  masuRowSize = stageStrList.length;
  for(var y:int = 0; y < stageStrList.length; y++){
      var str:String = stageStrList[y];
      masuColSize = str.Length;
      for(var x:int = 0; x < str.Length; x++){
          var masuStr:String = str.Substring(x, 1);
          var masuObject:GameObject = null;
          masuList[x + y * str.Length] = masuStr;
          if(masuStr != "#"){
              masuObject = Instantiate(Resources.Load("Prefabs/Object/Masu"), new Vector3(1.0* x, -1.0 * y, 0)  ,Quaternion.identity);
              masuObject.name = "Masu"+ x + y;
          }
          if(masuStr == "#"){
          }else if(masuStr == "0"){
          }else if(masuStr == "S"){
              var tex:tk2dSprite = masuObject.GetComponent(tk2dSprite);
              tex.color = Color.blue;
              mainCamera.transform.position.x = masuObject.transform.position.x;
              mainCamera.transform.position.y = masuObject.transform.position.y;
              startObject = masuObject;
              startPos = new Vector2(x,y);
          }else if(masuStr == "G"){
              tex = masuObject.GetComponent(tk2dSprite);
              tex.color = Color.red;
              goalObject = masuObject;
              goalPos = new Vector2(x,y);
          }
          masuObjectList.push(masuObject);
      }
  }
}
function Force(){
  var rollTarget:Vector3 = Vector3.zero + new Vector3(2 + 7 * Random.value, .5F + 4 * Random.value, -2 - 3 * Random.value);
  return Vector3.Lerp(diceObject.transform.position, rollTarget, 1).normalized * (-35 - Random.value * 20);
}

function Update () {

  if(gameStatus == 1 && Input.GetMouseButtonDown(0)){
      ChangeGameStatus(2);
      diceStartFlg = true;
      //diceObject.rigidbody.AddExplosionForce(00, diceObject.transform.position - Vector3.left * 3.0, 10, 3.0);
      diceObject.rigidbody.AddForce(Force(),ForceMode.Impulse);
      diceObject.rigidbody.AddTorque(new Vector3(-50 * Random.value * diceObject.transform.localScale.magnitude, -50 * Random.value * diceObject.transform.localScale.magnitude, -50 * Random.value * diceObject.transform.localScale.magnitude), ForceMode.Impulse);
              
  }
  
  // Dice転がり終了
  if(diceStartFlg){
      if(diceObject.rigidbody.velocity.magnitude == 0){
          diceStartFlg = false;
          CheckDice();
      }
      
  }
}

// ユーザーの方向選択完了イベント
function UserSelectArrow(dir:String){
  ChangeGameStatus(4);
  
  // 移動
  limitValue--;
  limitMoveText.GetComponent(TextMesh).text = "残り"+ limitValue + "マス";
  var charaObject:PlayerObject = chara.GetComponent(PlayerObject);
  var pos:Vector2 = charaObject.pos;
  var nextPos:Vector2;
  if(dir == "left"){
      nextPos = new Vector2(pos.x - 1, pos.y);
  }else if(dir == "right"){
      nextPos = new Vector2(pos.x + 1, pos.y);
  }else if(dir == "up"){
      nextPos = new Vector2(pos.x, pos.y -1);
  }else if(dir == "down"){
      nextPos = new Vector2(pos.x, pos.y + 1);
  }
      
  var nextMasuObject:GameObject = GameObject.Find("Masu" + nextPos.x + nextPos.y);
  iTween.MoveTo(chara, iTween.Hash("x", nextMasuObject.transform.position.x, "y", nextMasuObject.transform.position.y,"delay", 0, "time", 0.7, "oncomplete", "CharaMove", "oncompletetarget", this.gameObject));
  
  charaObject.dir = dir;
  charaObject.pos = nextPos;
}

function CheckDice(){
  diceValue = 0;
  while(diceValue == 0){
      diceValue = diceObject.GetComponent(Die).value;
      
      if(diceValue != 0){
          yield WaitForSeconds(1.5);
          diceValue = diceObject.GetComponent(Die).value;
          limitValue = diceValue;
          ChangeGameStatus(4);
          CharaMove();
      }else{
          // 判定できない場合もうちょっと転がす
          diceObject.rigidbody.AddForce(Force(),ForceMode.Impulse);
          yield WaitForSeconds(2.0);
      }
  }
}


function ClickDiceBtn(){
  ChangeGameStatus(1);
}

今回の感想

次はここに経路探索を入れてマップを広げて桃太郎電○のようにするか〜
ちょっとタワーディフェンス系も作りたいのですよね〜。
まぁ気分次第ですが。
はい、今回はここまで