2D遊戲平滑的迷霧戰爭效果

最近剛好有做2D遊戲的點光源效果,然後就擴展一下,研究了一下戰爭迷霧的效果。主要是想實現類似魔獸爭霸那種人物走動,然後黑色的戰爭迷霧隨着人物的移動漸漸打開的效果。使用具有漸變透明圖片作爲光源來使得戰爭迷霧呈現出平滑的效果。本文後面介紹了兩個簡單的實現方法,效果有細微的差別,有興趣的同學可以分別研究。最後也有完整展示代碼和提供例子下載。

一、常見的戰爭迷霧效果

早期的紅警的戰爭迷霧大家應該也比較熟悉,不過看起來沒那麼平滑,應該是採用圖塊拼出來。可以明顯看得出一些方方塊塊。
這裏寫圖片描述
可見早期魔獸爭霸2也是沒那麼平滑的。
後面出的一些有些的戰爭迷霧就會有比較平滑的過渡效果了。比如英雄聯盟,魔獸3(這兩個是3D的)。但是也有2D也做得比較平滑(看圖的最後一個截圖)
這裏寫圖片描述

二、實現的簡單原理講解

由於是做2D遊戲,所以思路基本上是2D,但是同時也有3D思想,例如圖層混合方式處理等。主要原理就是根據圖片的alpha值來進行反向擦除(alpha爲1時完全擦除,alpha越小則越不透明,實現漸變過程效果)。最後用了兩種比較簡單的方法來實現了這種效果,兩者有微弱的差別。這裏採用的是AS3實現的,源碼也提供了幾種漸變圖片,可以作爲點光源來擦亮迷霧,可以替換上去看各種效果的。
這裏寫圖片描述

  1. 遮罩擦除做法
    最先想到的原理,是基於之前實現類似點光源效果的做法。通過一層帶有alpha值遮罩圖來擦掉對應的戰爭迷霧,就是移動版增大的點光源效果。首先人物背景,然後一層戰爭迷霧在最頂層,人物帶了個點光源,然後人物移動的時候,不會把那個點光源層進行繪製,那麼光源層就會原來越大。那個迷霧自然就會越來越大了。
    下圖:剛開始是一個圓圈,然後隨着人物移動,圓圈會擴大。
    這裏寫圖片描述
    得到這個圖之後,就是對黑色戰爭迷霧層根據alpha進行擦了。
//一個專門做點光源的頂層容器
var topContainer:Sprite = new Sprite();
//強制爲該顯示對象創建一個透明度組
topContainer.blendMode = BlendMode.LAYER;
//根據顯示對象的 Alpha 值擦除迷霧
openFogBitmap.blendMode = BlendMode.ERASE;

合成遮罩圖來去除迷霧的代碼是,最後一個參數true是合併alpha:

bitmapData.copyPixels(bitmapData,pointRect,new Point(role.x,role.y),null,null,true);

這樣跟隨人物移動不斷地把遮罩擴大。除了最開始的合成遮罩圖,後面的處理跟之前講的新手引導遮罩和點光源實現機制一樣,後面會給出相關的代碼。不過這種實現是有點不好的是合併alpha,這樣會導致範圍突然變亮(因爲alpha相加大於1,就全部擦了,大部分亮了,也就是會有個逐漸變亮的效果,使得戰爭迷霧開啓效果沒那麼真實)。最終表現效果如下圖:
這裏寫圖片描述
2. 直接擦出戰爭迷霧方法(橡皮擦功能)
實際測試了下,對遮罩擦除做法這個效果不太滿意。於是再研究了一下,想到了橡皮效果,直接用點光源圖片把戰爭迷霧一點點擦掉又如何呢?趕緊仔細看了相關api,還真有類似的實現效果。主要還是bitmapData的draw方法,重點是這個方法的第四個參數,

source:IBitmapDrawable — 要繪製到 BitmapData 對象的顯示對象或 BitmapData 對象 
matrix:Matrix (default = null) — 一個 Matrix 對象,用於縮放、旋轉位圖或轉換位圖的座標。。 
colorTransform:flash.geom:ColorTransform (default = null) — 一個 ColorTransform 對象
blendMode:String (default = null) — 指定要應用於所生成位圖的混合模式。 

所以我們每次在戰爭迷霧這個層次這裏每次根據玩家移動,調用draw方法把角色帶的點光源圖片給draw進入戰爭迷霧的BitmapData中,然後設置爲根據alpha的參數來擦出,露出最終的背景就行了。

BlendMode.ERASE //提供混合模式可視效果的常量值的類。
//設置需要draw的座標位置
var matrix:Matrix = new Matrix(1,0,0,1,role.x,role.y);
fogBitmapData.draw(pointBitmap,matrix,null,BlendMode.ERASE);

最終效果圖:
這裏寫圖片描述

代碼實現

代碼已經有比較詳細的註釋了,這裏不做解釋,具體自己看代碼了。可以運行兩個例子來比較。
代碼例子源碼下載:2D遊戲戰爭迷霧的實現例子(AS3版本)
1. 遮罩擦除做法代碼,FogLightTest.as

/**
     * 戰爭迷霧遮罩燈效果測試例子
     * @author sodaChen
     * Date:2017-2-16
     */
    [SWF(width="1274",height="768")]
    public class FogLightTest extends Sprite
    {
        /** 背景 **/
        [Embed(source = "res/alpha/bg.jpg")]
        private var bgClass:Class;
        //點光源圖片
        [Embed(source = "res/alpha/light4.png")]
        private var shadowClass:Class;
        /** 打開的迷霧圖像源,用來擦出迷霧 **/
        private var openFogBitmap:Bitmap;
        /** 原始點光源圖片,用來確定一個角色視野範圍的 **/
        private var pointBitmap:Bitmap;
        /** 存放已經開啓的迷霧圖片 **/
        private var pointBitmapDatas:Vector.<uint>;
        /** 點光源的大小範圍 **/
        private var pointRect:Rectangle;
        private var role:Sprite;
        /** 光源的移動 **/
        private var speed:int = 10;


        public function FogLightTest()
        {
            addEventListener(Event.ADDED_TO_STAGE,onStage);
        }
        private function onStage(evt:Event):void
        {
            //添加背景
            addChild(new bgClass());
            /////////////////////////////文本的正式測試代碼啦/////////////////////////////
            //新建一個專門做點光源的頂層容器
            var topContainer:Sprite = new Sprite();
            topContainer.mouseEnabled = false;
            //強制爲該顯示對象創建一個透明度組
            topContainer.blendMode = BlendMode.LAYER;
            addChild(topContainer);
            //創建黑色的迷霧
            var mask:Shape = new Shape();
            mask.graphics.beginFill(0x000000);
            mask.graphics.drawRect(0,0,1274,768);
            mask.graphics.endFill();
            topContainer.addChild(mask);

            //製作點光源,用來擦亮迷霧,具體的擦出在下面的鼠標事件那裏
            pointBitmap = new shadowClass();
            //創建擦亮迷霧後的視野圖片
            pointRect = new Rectangle(0,0,pointBitmap.bitmapData.width,pointBitmap.bitmapData.height);
            openFogBitmap = new Bitmap(new BitmapData(1274,768,true,0));
            //複製最開始的位置
            openFogBitmap.bitmapData.copyPixels(pointBitmap.bitmapData,pointRect,new Point(450,300));
            //根據顯示對象的 Alpha 值擦除迷霧
            openFogBitmap.blendMode = BlendMode.ERASE;
            topContainer.addChild(openFogBitmap);

            /** 移動中的主角 **/
            role = new Sprite();
            role.x = 450; role.y = 300;
            topContainer.addChild(role);
            stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
        }

        private function onKeyDown(evt:KeyboardEvent):void
        {
            //4個方向鍵的控制
            if(evt.keyCode == Keyboard.DOWN) role.y += speed;
            else if(evt.keyCode == Keyboard.UP) role.y -= speed;
            else if(evt.keyCode == Keyboard.LEFT) role.x -= speed;
            else if(evt.keyCode == Keyboard.RIGHT) role.x += speed;
            //暫時不考慮性能,不停地寫新的圖像數據
    openFogBitmap.bitmapData.copyPixels(pointBitmap.bitmapData,pointRect,new Point(role.x,role.y),null,null,true);
        }
  1. 直接擦出戰爭迷霧方法(橡皮擦功能)代碼,EraserFogTest.as
   /**
     * 迷霧戰爭擦除效果
     * @author sodaChen
     * Date:2017-2-16
     */
    [SWF(width="1274",height="768")]
    public class EraserFogTest extends Sprite
    {
        /** 背景 **/
        [Embed(source = "res/alpha/bg.jpg")]
        private var bgClass:Class;
        //點光源圖片
        [Embed(source = "res/alpha/light4.png")]
        private var shadowClass:Class;
        /** 打開的迷霧圖像源,用來擦出迷霧 **/
        private var openFogBitmap:Bitmap;
        /** 光源,擦亮迷霧的範圍 **/
        private var pointBitmap:Bitmap;
        private var role:Sprite;
        /** 光源的移動 **/
        private var speed:int = 10;
        /** 迷霧的圖像數據源 **/
        private var fogBitmapData:BitmapData;

        public function EraserFogTest()
        {
            addEventListener(Event.ADDED_TO_STAGE,onStage);
        }
        private function onStage(evt:Event):void
        {
            //添加背景
            addChild(new bgClass());

            /////////////////////////////文本的正式測試代碼啦/////////////////////////////
            //新建一個專門做點光源的頂層容器
            var topContainer:Sprite = new Sprite();
            topContainer.mouseEnabled = false;
            //強制爲該顯示對象創建一個透明度組
            topContainer.blendMode = BlendMode.LAYER;
            addChild(topContainer);
            //創建黑色的迷霧
            var mask:Shape = new Shape();
            //顏色可以選自己喜歡的
            mask.graphics.beginFill(0x000000);
            mask.graphics.drawRect(0,0,1274,768);
            mask.graphics.endFill();
            fogBitmapData = new BitmapData(1274,768);
            fogBitmapData.draw(mask);
            topContainer.addChild(new Bitmap(fogBitmapData));

            //製作點光源,用來擦亮迷霧,具體的擦出在下面的鼠標事件那裏
            pointBitmap = new shadowClass();

            /** 移動中的主角 **/
            role = new Sprite();
            role.x = 450; role.y = 300;
            topContainer.addChild(role);
            //默認位置
            openFog();
            stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
        }

        private function onKeyDown(evt:KeyboardEvent):void
        {
            //4個方向鍵的控制
            if(evt.keyCode == Keyboard.DOWN) role.y += speed;
            else if(evt.keyCode == Keyboard.UP) role.y -= speed;
            else if(evt.keyCode == Keyboard.LEFT) role.x -= speed;
            else if(evt.keyCode == Keyboard.RIGHT) role.x += speed;
            openFog();
        }
        private function openFog():void
        {
            //設置需要draw的座標位置
            var matrix:Matrix = new Matrix(1,0,0,1,role.x,role.y);
            //正常的daw方法,主要參數是後面的BlendMode.ERASE。根據pointBitmap的透明度來擦除fogBitmapData
            fogBitmapData.draw(pointBitmap,matrix,null,BlendMode.ERASE);
        }
發佈了91 篇原創文章 · 獲贊 72 · 訪問量 78萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章