タワーディフェンスタイプのゲームを作ってみようと思う 1日目

このblog、3年以上更新されてなかったんですね。僕は生きてます。

あまりに暇なのでタワーディフェンスタイプのゲームを作ってみようと思う。制作状況はこのblogにアップし、都度ソースコードをgithub(https://github.com/imajuk/tdf)にコミットする。
実行環境はスマートフォン。Adobe AIRでのapp制作を試みる。
完成時にはタワーディフェンスタイプのゲームが作成できるフレームワークが構築されていることを目標とする。
グラフィックやサウンドをどうするかは未定。
モチベーションを保つためのblogでの公開だが完成するかどうかは分からない。

タワーディフェンスのルールの概略

  • リアルタイムで攻めてくる敵軍にたいして自軍ユニットを配置し自軍本拠地にあるタワーを守る
  • タワーが敵軍に占拠されるとゲームオーバー
  • 何らかの方法で資源を獲得する事が出来る。
  • 自軍ユニットの配置には資源を消費する。一般的に高い性能を持つユニットほど多くの資源を消費する。

地形の設計

 
まずは地形の設計から開始する事にする。
地形は柔軟性のある設計にしたい。複雑な地形を従軍してくる敵軍、沼地では速度が遅くなり平野では速度が増す、などなどに対応できるモデルにしたい。
地形のモデルとして有向グラフを採用する事にする。グラフは既存のライブラリは使わずスクラッチする。
以下の図の用に、グラフのエッジにコストを持たせる事で進軍コストを表現する。

地形のモデル

グラフの設計は以下の様にした。最小のプロパティとメソッドから始める。必要に応じて機能を追加してゆく。

グラフのクラス図

以下ソースコード

DirectedGraph.as

package com.imajuk.tdf
{
    /**
     * @author imajuk
     */
    public class DirectedGraph
    {
        private var _begin : GraphNode;
        public function get begin() : GraphNode
        {
            return _begin;
        }
        
        public function DirectedGraph()
        {
            _begin = new GraphNode();
        }        
    }
}

DirectedGraph.as

package com.imajuk.tdf
{
    /**
     * @author imajuk
     */
    public class GraphNode
    {
        private var _cost : Number;
        public function get cost() : Number
        {
            return _cost;
        }
        
        private var _to : Array = [];
        public function get to() : Array
        {
            return _to;
        }
        
        private var _from : Array = [];
        public function get from() : Array
        {
            return _from;
        }
        
        public function GraphNode(cost : Number = 0)
        {
            _cost = cost;
        }
        
        public function toString() : String
        {
            return "GraphNode[" + _id + "]";
        }

        public function add(cost:Number) : GraphNode
        {
            return connect(new GraphNode(cost));
        }

        public function connect(node : GraphNode) : GraphNode
        {
            _to.push(node);
            node._from.push(this);
            return node;
        }
    }
}

Main.as

package com.imajuk.tdf
{
    import flash.display.Sprite;

    public class Main extends Sprite
    {
        private var terrin : DirectedGraph;

        public function Main()
        {
            // テスト用の地形作成
            terrin = new DirectedGraph();
            // 敵軍進軍始点(ノードA)
            var node_A : GraphNode = terrin.begin;
            // ノードAにノードBを追加
            var node_B : GraphNode = node_A.add(3);
            // ノードBにノードCを追加
            var node_C : GraphNode = node_B.add(5);
            // ノードBにノードDを追加
            var node_D : GraphNode = node_B.add(5);
            // ノードCにノードEを追加
            var node_E : GraphNode = node_C.add(5);
            // ノードEに敵軍終点(ノードF)を追加
            var node_F : GraphNode = node_E.add(3);
            // ノードDとノードFを連結
            node_D.connect(node_F);
        }
    }
}

上記のメインクラスの様にコードでグラフを作るのは大変めんどくさいので将来的には地形作成ツールなども必要になるだろう。頭の片隅においておく事にする。

さて、地形のモデルが出来たのでビューを作る。
この時点ではモデルの確認用とし、ゲームとしてのグラフィックは考えない。必要最低限のものにとどめる。
ビューの作成のため、モデルにノードを始点から終点まで一度ずつ巡回するメソッドを追加した。またデバッグのためノードにユニークなIDをもたせた。さらにビュー上でのノードの位置を決定するためモデルにノードの深度とノードの兄弟関係に置けるインデックスを追加した。

グラフのクラス図2

以下ソースコード

DirectedGraph.as

package com.imajuk.tdf
{
    /**
     * @author imajuk
     */
    public class DirectedGraph
    {
        private var _begin : GraphNode;
        public function get begin() : GraphNode
        {
            return _begin;
        }
        
        public function DirectedGraph()
        {
            _begin = new GraphNode();
        }
        
        /**
         * 始点から終点に達するまでノードを巡回する。
         * 各ノードを引数に一度だけ与えられた関数を実行する。
         */
        public function crawl(f:Function):void
        {
            _crowl(_begin, f);
        }
        private function _crowl(node:GraphNode, f:Function) : void
        {
            f(node);
            
            if(node.hasNext)
            {
                var nexts : Array = node.to,
                    i : int = 0, 
                    l : int = nexts.length;
                for (i; i < l; i++)
                {
                    _crowl(nexts[i], f);
                }
            }
        }
        
    }
}

GraphNode.as

package com.imajuk.tdf
{
    /**
     * @author imajuk
     */
    public class GraphNode
    {
        private static var sid : int;
        private var _id : int;
        public function get id() : int
        {
            return _id;
        }
        
        private var _depth : int;
        public function get depth() : int
        {
            return _depth;
        }

        private var _childIndex : uint;
        public function get childIndex() : uint
        {
            return _childIndex;
        }

        private var _cost : Number;
        public function get cost() : Number
        {
            return _cost;
        }
        
        private var _to : Array = [];
        public function get to() : Array
        {
            return _to;
        }
        
        private var _from : Array = [];
        public function get from() : Array
        {
            return _from;
        }
        
        public function GraphNode(cost : Number = 0)
        {
            _cost = cost;
            _id = sid++;
        }
        
        public function toString() : String
        {
            return "GraphNode[" + _id + "] / depth:" + _depth;
        }

        public function add(cost:Number) : GraphNode
        {
            return connect(new GraphNode(cost));
        }

        public function connect(node : GraphNode) : GraphNode
        {
            node._childIndex = _to.length;
            _to.push(node);
            node._from.push(this);
            node._depth = Math.max(node._depth, _depth + 1);
            return node;
        }

        public function get hasNext() : Boolean
        {
            return _to.length > 0;
        }
    }
}

GraphView.as

package com.imajuk.tdf
{
    import flash.display.Graphics;
    import flash.display.Sprite;

    /**
     * @author imajuk
     */
    public class GraphView extends Sprite
    {
        public function GraphView(graph:DirectedGraph)
        {
            graph.crawl(createNodeView);
            graph.crawl(drawEdge);
        }

        private function createNodeView(begin : GraphNode) : void
        {
            var nodeView : NodeView = addChild(NodeView.getView(begin.id)) as NodeView;
            nodeView.x = begin.depth * 30 + 30;
            nodeView.y = begin.childIndex * 30;
        }
        
        public function drawEdge(begin : GraphNode) : void
        {
            var beginNode : NodeView = NodeView.getView(begin.id);
            var g : Graphics = graphics;
            g.lineStyle(0, 0);
            
            begin.to.forEach(function(next:GraphNode, ...rest) : void
            {
                var nextNode : NodeView = NodeView.getView(next.id);
                g.moveTo(beginNode.x, beginNode.y);
                g.lineTo(nextNode.x, nextNode.y);
            });
        }
    }
    
}
import flash.text.TextFieldAutoSize;
import flash.text.TextFormatAlign;
import flash.text.TextFormat;
import flash.display.Sprite;
import flash.text.TextField;
import flash.display.Graphics;

class NodeView extends Sprite
{
    private static var nodes : Array = [];
    private static var g : Graphics;

    public function NodeView(id : int)
    {
        g = graphics;
        g.clear();
        g.beginFill(0);
        g.drawCircle(0, 0, 7);
        g.endFill();
        
        var tf : TextField = addChild(new TextField()) as TextField,
            tfm : TextFormat = new TextFormat();
            
        tfm.size = 8;
        tfm.color = 0xFFFFFF;
        tfm.align = TextFormatAlign.CENTER;
        
        tf.text = id.toString();
        tf.setTextFormat(tfm);
        tf.autoSize = TextFieldAutoSize.CENTER;
        tf.x = -tf.width * .5;
        tf.y = -tf.height * .5;
    }

    public static function getView(id : int) : NodeView
    {
        if (!nodes[id])
            nodes[id] = new NodeView(id);
        return nodes[id];
    }   
}

Main.as

package com.imajuk.tdf
{
    import flash.display.Sprite;

    public class Main extends Sprite
    {
        private var terrin : DirectedGraph;

        public function Main()
        {
            // テスト用の地形作成
            terrin = new DirectedGraph();
            // 敵軍進軍始点(ノードA)
            var node_A : GraphNode = terrin.begin;
            // ノードAにノードBを追加
            var node_B : GraphNode = node_A.add(3);
            // ノードBにノードCを追加
            var node_C : GraphNode = node_B.add(5);
            // ノードBにノードDを追加
            var node_D : GraphNode = node_B.add(5);
            // ノードCにノードEを追加
            var node_E : GraphNode = node_C.add(5);
            // ノードEに敵軍終点(ノードF)を追加
            var node_F : GraphNode = node_E.add(3);
            // ノードDとノードFを連結
            node_D.connect(node_F);
            
            addChild(new GraphView(terrin));
        }
    }
}

実行結果
最初のビュー

今日はここまで。
リビジョンは83c72eb4cc

つづく。。かな?

カテゴリー: ActionScript3, towerDefense パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です