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

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

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

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

すごろくのようなもの
##使用Asset

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

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

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

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

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

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

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

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

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
キャラクターが移動したときに追尾するように設定

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
特にアニメーションもステータスもないのでシンプルです
現在位置と向いている方向だけ持っています

var dir:String;
var pos:Vector2;
function Start () {
}
function Update () {
}

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


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

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

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);
}

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