1. 程式人生 > >角色和背景遮擋半透明效果的實現

角色和背景遮擋半透明效果的實現

這裡講的是關於2D遊戲的角色和背景以及物體之間的遮擋關係,用半透明角色處理的討論和實現方式。這裡主要是討論關於頁遊《火影忍者》裡對於角色和背景物體之間的遮擋處理方式。同時也實現了和他一樣的效果(可能)。具體是沒分析火影的程式碼,只是猜測了它的原理然後進行實現的。其他的幾種實現方式則簡單的討論,一筆帶過(如果有人有興趣,可以留言,我再單獨寫篇Blog和相關例子來) 
實現語言:ActionScript3.0(傳統的顯示處理,Stage3D可以做到有些不一樣,所以不在討論範圍) 
心急的讀者可以直接從第三點開始看我們今天的主題實現內容。

一、需要實現遮擋關係的原因

個人推測和分析,不妥之處請之處討論。 
1. 因為AS3傳統顯示2D影象的時候,沒有zbuff緩衝這個概念,所以顯示影象的層次關係,是按照addChild的順序來的. 
2. 為了實現最大的渲染效率,一般美術會把遊戲場景渲染成大的一張圖片,然後再切割成小塊進行載入顯示。整個場景的所有元件,包括背景、樹、建築、河流等等,都是渲染出一張圖。 
那麼問題來了,建築和背景樹等物體都渲染地圖上,那人物走過來的時候,這個時候的遮擋關係怎麼處理呢?所以就會衍生出各種處理這種遮擋關係的技術了。

二、角色物體遮擋的幾種做法

首先簡單討論幾種實現方式。 
1. 和角色同層,根據深度重新設定addChild 
這是比較消耗效能的做法,也是早期很多遊戲的做法,表現效果也很好。同時也是一些3D遊戲場景的做法(3D有zbuff緩衝)。出一個地圖編輯器,然後把匯入背景,把各種元件(樹、建築等等)拖到場景去,最後匯出。在遊戲中人物就和元件參與深度排序,處理遮擋關係。 
現在一般是採用後面的兩種做法了,在一些特別需要表現力的時候,才會單獨做成元件,不然都是合在背景裡了。 
這裡寫圖片描述 
2. 地圖編輯器打格子表明走到的角色半透 
這個是頁遊紅極一時的做法,大部分rpg和arpg都是這麼做的。比如《凡人修真2》和雄霸頁遊天下的《傳奇霸業》,下面傳奇霸業的表現效果(剛截的圖)。 
這裡寫圖片描述

 
具體的實現方式大概走格子走到這個建築的時候,會檢測格子的屬性,如果帶有半透效果的引數,則設定角色的alpha值。 
3. 背建築遮擋的部分才半透,效果高大尚。 
讀者:這個是3D做法吧! 
當然不是了,通過2D影象混合一樣可以做到。(原理估計也是3D那一套。就是提供的遮罩圖來擦出下面的的圖片)。這個算是比較新穎的做法,目前我就看騰訊的《火影忍者》,其他的就暫時沒去查看了(我自己做的遊戲是採用這個最新的做法,潮流嘛)。 
開始還擔心效能會比較大損耗,做了簡單測試,發現沒什麼影響。先看火影裡的表現效果吧 
這裡寫圖片描述 
仔細看紅框裡的那個角色,可以發現角色被那個柱子擋住的一半是半透明效果,沒擋住的就是原來的顏色。簡直酷斃了! 
立馬把火影的快取搞下來,發現它是加了背景遮罩圖,然後類似3D混合圖形的做法實現的。後來會詳細地講。(囉嗦了這麼久,終於開始正題了)

三、火影的角色遮擋半透實現分析

仔細分析了火影的新手村場景,從快取了除了提取出背景之外,還有另外一些黑色的古怪圖片。背景還是跟其他遊戲的做法,古怪的是那個黑色怪圖。發現是背景裡的一些建築物的黑色圖。下面是縮圖。 
這裡寫圖片描述 
剛開始還以為是逐畫素匹配,自己手動實現的(可以做到的),但是效率會殘不忍堵。另外想到的就是3D裡 
常用的影象混合處理了。查下AS3的Bitmap的API,還真查詢到了。這裡只列出會用到的API。

DisplayObject.blendMode屬性
BlendMode 類中的一個值,用於指定要使用的混合模式。
  • 1
  • 2

用到了BlendMode類的兩個屬性

LAYER : String = "layer"
[靜態] 強制為該顯示物件建立一個透明度組。
  • 1
  • 2
ERASE : String = "erase"
[靜態] 根據顯示物件的 Alpha 值擦除背景。
  • 1
  • 2

其他的API也有趣,有興趣的同學可以試試。

四、角色遮擋半透的實現的例子資源

整理了一下相關使用到的資源。實際上問過美術,美術說出這個遮罩圖非常容易。我們也實際出了相關的資源。 
1. 背景圖 
把火影的背景圖給拼起來,合成一張1274 * 768的大背景圖方便測試 
2. 透明背景遮罩圖 
同樣把透明遮罩剪切出和背景對應部分截取出來,然後對好位置。這個圖的透明度決定了角色的顯示效果 
3. 角色資源圖 
這裡寫圖片描述

五、簡單的程式碼實現原理過程

  1. 背景層獨立出來,不參與影象混合
  2. 角色和透明遮罩單獨一個容器層,但是這兩種都在同一個容器A中
  3. 容器A的blendMode屬性設定為:
//強制為該顯示物件建立一個透明度組
blendMode = BlendMode.LAYER;
  • 1
  • 2
  1. 角色容器不用設定blendMode,因為在容器A中設定就可以了
  2. 透明遮罩這個Bitmap需要設定
blendMode = BlendMode.ERASE;
  • 1

確保透明遮罩Bitmap在角色容器的上層就可以了。下面是具體的程式碼實現過程。

六、詳細的程式碼

看程式碼實現起來非常簡單,程式碼也非常簡潔

package
{
    import flash.display.Bitmap;
    import flash.display.BlendMode;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;

    /**
     * 地圖透明角色測試例子
     * @author sodaChen
     * Date:2017-2-16
     */
    [SWF(width="1274",height="768")]
    public class AlphaMapTest extends Sprite
    {
        /** 背景 **/
        [Embed(source = "res/alpha/bg.jpg")]
        private var bgClass:Class;
        /** 透明遮罩背景 **/
        [Embed(source = "res/alpha/alphaBg.png")]
        private var alphaBgClass:Class;
        /** 角色 **/
        [Embed(source = "res/alpha/role.png")]
        private var roleClass:Class;

        /** 角色容器 ,用來存放角色和透明影象的**/
        private var roleContainer:Sprite;
        /** 角色層,只放角色 **/
        private var roleLayer:Sprite;


        public function AlphaMapTest()
        {
            super();
            addEventListener(Event.ADDED_TO_STAGE,onStage);
        }
        private function onStage(evt:Event):void
        {
            //新增背景
            addChild(new bgClass());

            //新增角色容器
            roleContainer = new Sprite();
            //強制為該顯示物件建立一個透明度組
            roleContainer.blendMode = BlendMode.LAYER;
            addChild(roleContainer);

            //建立角色層,其實角色可以不用單獨容器,但是必須保證alphaBg在所有角色的最上面
            roleLayer = new Sprite();
            roleContainer.addChild(roleLayer);

            //建立角色並新增到角色容器中
            createRole(300,120);
            createRole(230,550);
            //不會被遮擋的角色
            createRole(400,200);

            //根據顯示物件的 Alpha 值擦除背景.這個透明影象必須在最頂層,確保下面的角色會被擦出
            var alphaBg:Bitmap = new alphaBgClass();
            alphaBg.blendMode = BlendMode.ERASE;
            roleContainer.addChild(alphaBg);
        }
        //建立角色
        private function createRole(roleX:int,roleY:int):void
        {
            var role:Sprite = new Sprite();
            var roleBitmap:Bitmap = new roleClass();
            role.x = roleX;
            role.y = roleY;
            role.addChild(roleBitmap);
            roleLayer.addChild(role);
            role.addEventListener(MouseEvent.MOUSE_DOWN,onMouse);
            role.addEventListener(MouseEvent.MOUSE_UP,onMouse);
        }
        private function onMouse(evt:MouseEvent):void
        {
            var role:Sprite = evt.currentTarget as Sprite;
            if(evt.type == MouseEvent.MOUSE_DOWN)
                role.startDrag();
            else
                role.stopDrag();
        }
    }
}
  • 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