Silverlight MMORPG网页游戏开发课程[一期] 第九课:HUD与背景音乐

引言

征服玩家的不仅仅是创意,无比动人的视觉体验譬如精美的界面UI同样能让人倾慕,辅以优柔的旋律仿若一缕思绪让您身临其境而流连。深刻的第一印象无限大的冲击着玩家那份内敛的狂热,优秀的游戏作品价值将在欢呼声中被最大化激活。

9.1创建自适应布局之HUD (交叉参考:一切起源于这个真实的世界 制作精美的Mini地图① 制作精美的Mini地图② 制作游戏主菜单面板及鼠标左右键快捷技能栏)

Silverlight网页游戏中一切看得见的对象我们都可称之为UI,如精灵、魔法、地图、图标等等。除此之外作为玩家,我们时常需要以“上帝”的名义通过一些按钮去影响游戏中的对象,并获取相应反馈。于是乎UI的另一位伟大成员诞生了,它就是HUD。每款游戏都拥有它独自的一套HUD,本节Demo中我将借用《十年一剑》的相关素材,大致包括主角信息、对象信息、雷达地图、聊天窗口、工具菜单等;它们各自布局于游戏窗口顶层的边缘,并以聊天中提交主角说话内容为例向大家讲解如何实现HUD与游戏中其他对象的交互。

首先我们需要在控件项目中针对HUD的每个部分创建相应的面板类:

它们均继承之Canvas,由于都以UI描述性语句为主,代码大同小异,因此这里我选择以ChatWindow为例向大家进一步作讲解:

    ///   <summary>
    
///  聊天窗口面板
    
///   </summary>
     public   sealed   class  ChatWindow : Canvas {

        
///   <summary>
        
///  获取或设置X、Y坐标
        
///   </summary>
         public  Point Coordinate {
            
get  {  return   new  Point(Canvas.GetLeft( this ), Canvas.GetTop( this )); }
            
set  { Canvas.SetLeft( this , value.X); Canvas.SetTop( this , value.Y); }
        }

        
///   <summary>
        
///  获取或设置Z层次深度
        
///   </summary>
         public   int  Z {
            
get  {  return  Canvas.GetZIndex( this ); }
            
set  { Canvas.SetZIndex( this , value); }
        }

         ///   <summary>
        
///  发送说话内容
        
///   </summary>
         public   event  EventHandler < SendEventArgs >  Send;

        
///   <summary>
        
///  说话参数
        
///   </summary>
         public   sealed   class  SendEventArgs : EventArgs {
            
public   string  Content {  get set ; }
        }

        TextBox textBox  =   new  TextBox() {
            Text  =   " http://Silverfuture.cn 深蓝色右手 " ,
            Width  =   185 ,
            AcceptsReturn  =   false ,
            Foreground  =   new  SolidColorBrush(Colors.White),
            Background  =   new  SolidColorBrush(Colors.Transparent)
        };
        Image image  =   new  Image() { Source  =  Global.GetProjectImage( " HUD/3.png " ) };
        
public  ChatWindow() {
            
this .Width  =   307 ;
            
this .Height  =   243 ;
            
this .Background  =   new  ImageBrush() { ImageSource  =  Global.GetProjectImage( " HUD/4.png " ) };
            
this .Children.Add(textBox); Canvas.SetLeft(textBox,  78 ); Canvas.SetTop(textBox,  212 );
            textBox.KeyDown 
+=  (s, e)  =>  {
                
if  (e.Key  ==  Key.Enter) {
                    
if  (Send  !=   null ) { Send( this new  SendEventArgs() { Content  =  textBox.Text.Trim() }); }
                    textBox.Text 
=   string .Empty;
                    e.Handled 
=   true ;
                }
            };
            
this .Children.Add(image); Canvas.SetLeft(image,  272 ); Canvas.SetTop(image,  208 );
            image.MouseLeftButtonDown 
+=  (s, e)  =>  {
                
if  (Send  !=   null ) { Send( this new  SendEventArgs() { Content  =  textBox.Text.Trim() }); }
                textBox.Text 
=   string .Empty;
                e.Handled 
=   true ;
            };
        }

    }

这是一个仅仅包含背景的聊天窗口,为了实现它与游戏主角之间的交互,我在其中还特意放置了一个文本框及图形按钮,当按钮点击时触发Send事件:

然后在MainPage中为该聊天面板实例注册Send事件:

            chatWindow.Send  +=  (s, e)  =>  {
                hero.Say(e.Content);
            };

    主角将执行说话行为(Say)

         ///   <summary>
        
///  说话
        
///   </summary>
         public   void  Say( string  content) {
            
if  (content.Equals( "" )) {  return ; }
            Dialog dialog 
=   new  Dialog( 5 ) {
                HostWidth 
=  BodyWidth,
                TopOffset 
=   30
            };
            
this .Children.Add(dialog);
            dialog.Completed 
+=   new  EventHandler(dialog_Completed);
            dialog.Show(content);
        }

        
void  dialog_Completed( object  sender, EventArgs e) {
            Dialog dialog 
=  sender  as  Dialog;
            dialog.Completed 
-=  dialog_Completed;
            
this .Children.Remove(dialog);
        }

其中Dialog是本节中我新建的RPG游戏中标准的说话小窗口控件类,通过为其内置一个DispatcherTimer实现该聊天小窗口定时消失:

ExpandedBlockStart.gif 代码
     ///   <summary>
    
///  说话内容框
    
///   </summary>
     public   sealed   class  Dialog : Canvas {

        
#region  属性

        
///   <summary>
        
///  设置属寄主宽
        
///   </summary>
         public   double  HostWidth {
            
set  { Canvas.SetLeft( this , (value  -  width)  /   2 ); }
        }

        
///   <summary>
        
///  获取或设置具体头部偏移量
        
///   </summary>
         public   int  TopOffset {  get set ; }

        
#endregion

        
#region  构造

        
const   int  width  =   140 ;
        Rectangle back 
=   new  Rectangle() {
            Width 
=  width,
            Fill 
=   new  SolidColorBrush(Colors.Black),
            Stroke 
=   new  SolidColorBrush(Colors.Gray),
            StrokeThickness 
=   2 ,
            RadiusX 
=   7 ,
            RadiusY 
=   7 ,
            Opacity 
=   0.4
        };
        TextBlock content 
=   new  TextBlock() {
            Width 
=  width  -   10 ,
            Foreground 
=   new  SolidColorBrush(Colors.White),
            TextWrapping 
=  TextWrapping.Wrap
        };

        DispatcherTimer dispatcherTimer 
=   new  DispatcherTimer();
        
///   <param name="duration"> 持续时间 </param>
         public  Dialog( int  duration) {
            
this .Children.Add(back);
            
this .Children.Add(content);
            Canvas.SetLeft(content, 
5 ); Canvas.SetTop(content,  5 );
            dispatcherTimer.Interval 
=  TimeSpan.FromSeconds(duration); 
            dispatcherTimer.Tick 
+=   new  EventHandler(dispatcherTimer_Tick);
        }

        
void  dispatcherTimer_Tick( object  sender, EventArgs e) {
            Hide();
            
if  (Completed  !=   null ) { Completed( this new  EventArgs()); }
        }

        
#endregion

        
#region  事件

        
///   <summary>
        
///  说话内容显示后
        
///   </summary>
         public   event  EventHandler Completed;

        
#endregion

        
#region  方法

        
///   <summary>
        
///  显示说话框
        
///   </summary>
        
///   <param name="value"> 内容 </param>
         public   void  Show( string  value) {
            content.Text 
=  value;
            back.Height 
=  content.ActualHeight  +   10 ;
            Canvas.SetTop(
this - back.Height  +  TopOffset); // 减去精灵名字高度
            dispatcherTimer.Start();
        }

        
///   <summary>
        
///  隐藏说话框
        
///   </summary>
         public   void  Hide() {
            content.Text 
=   string .Empty;
            dispatcherTimer.Stop();
            dispatcherTimer.Tick 
-=  dispatcherTimer_Tick;
        }

        
#endregion
    }

概括来说,委托和事件是C#中解耦最强有力的工具,通过它我们实现了HUD与其他游戏对象之间的双向交互。

另外,游戏中的HUD各部分布局必须随着Silverlight游戏窗口尺寸的改变而改变,因此我们需要在游戏窗口尺寸改变事件方法中添加对这些部分的自适应布局方案:

         ///   <summary>
        
///  游戏窗口尺寸改变
        
///   </summary>
         void  Content_Resized( object  sender, EventArgs e) {
            hero_CoordinateChanged(hero, 
new  DependencyPropertyChangedEventArgs());
            
if  (transition.Visibility  ==  Visibility.Visible) { transition.AdaptToWindowSize(); }
            
// HUD各部件自适应窗体尺寸
            Canvas.SetLeft(targetInfo, Application.Current.Host.Content.ActualWidth  /   2   -  targetInfo.ActualWidth  /   2 ); Canvas.SetTop(targetInfo,  0 );
            Canvas.SetLeft(radarMap, Application.Current.Host.Content.ActualWidth 
-  radarMap.ActualWidth); Canvas.SetTop(radarMap,  0 );
            Canvas.SetLeft(menuBar, Application.Current.Host.Content.ActualWidth 
-  menuBar.ActualWidth); Canvas.SetTop(menuBar, Application.Current.Host.Content.ActualHeight  -  menuBar.ActualHeight);
            Canvas.SetTop(chatWindow, Application.Current.Host.Content.ActualHeight 
-  chatWindow.ActualHeight);
        }

    HUD各部分自己的宽、高以及游戏窗体的宽、高为依据进行边缘布局;如此,我们不论是拉伸窗体、最大化窗体、全屏还是OOB模式时,HUD将永远能保持在相对正确的位置上。

除此之外,如果游戏窗口尺寸小到一定程度后HUD中各面板会出现重叠,如果游戏中不允许出现这类现象解决方案大致有两种:

1)将HUD中的所有面板均设定成可拖动窗体;

2)在游戏窗口尺寸改变事件中检测新尺寸是否为最低限制,如果超出的取消新尺寸改变。

最后,我为游戏额外添加一个按钮用作“全屏/窗口”模式的切换以进一步测试HUD的自适应性:

    

ExpandedBlockStart.gif 代码
            button0.Click  +=  (s, e)  =>  {
                
if  (Application.Current.Host.Content.IsFullScreen) {
                    Application.Current.Host.Content.IsFullScreen 
=   false ;
                } 
else  {
                    Application.Current.Host.Content.IsFullScreen 
=   true ;
                }
                Content_Resized(
null null );
            };

9.2场景之背景音乐

游戏的趣味性将伴随着恢弘磅礴的背景音乐无限延伸,Silverlight中自带的MediaElement控件已无法满足我们对游戏音乐的多方面操控,因而需要对其重新进行了封装,取名为MusicPlayer

     ///   <summary>
    
///  媒体播放器控件
    
///   </summary>
     public   sealed   class  MediaPlayer : Canvas {

        
#region  构造

        MediaElement media 
=   new  MediaElement() {
            IsHitTestVisible 
=   false ,
            Visibility 
=  Visibility.Collapsed,
            AutoPlay 
=   true ,
        };

        
public  MediaPlayer() {
            
this .Children.Add(media);
        }

        
#endregion

        
#region  属性

        
///   <summary>
        
///  获取或设置音量
        
///   </summary>
         public   double  Volume {
            
get  {  return  media.Volume; }
            
set  { media.Volume  =  value; }
        }

        
#endregion

        
#region  方法

        
///   <summary>
        
///  播放媒体
        
///   </summary>
        
///   <param name="uri"> 路径 </param>
        
///   <param name="loop"> 是否循环 </param>
         public   void  Play( string  uri,  bool  loop) {
            media.Source 
=   new  Uri(Global.WebPath( string .Format( " Media/{0}.mp3 " , uri)), UriKind.Relative);
            media.Position 
=  TimeSpan.Zero;
            Start(loop);
        }

        
///   <summary>
        
///  媒体开始
        
///   </summary>
         public   void  Start( bool  loop) {
            media.MediaEnded 
-=  media_MediaEnded;
            
if  (loop) { media.MediaEnded  +=  media_MediaEnded; }
            media.Play();
        }

        
///   <summary>
        
///  媒体停止
        
///   </summary>
         public   void  Stop() {
            media.MediaEnded 
-=  media_MediaEnded;
            media.Stop();
        }

        
void  media_MediaEnded( object  sender, RoutedEventArgs e) {
            MediaElement media 
=  sender  as  MediaElement;
            media.Position 
=  TimeSpan.Zero;
            media.Play();
        }

        
#endregion

    }

该播放器是一个简单实现,可以实现循环,同时使用起来也很简单:

music.Play(Media,  true );

以场景为单位,通过在它们的配置文件中添加参数完全可以实现独立于场景的不同背景音乐,肆意张扬个性的同时大可放心,这些媒体文件都是以数据流的形式逐步获取,无须任何等待及预加载。强大的Silverlight框架为我们铺垫了一切。

本课小结:游戏界面与游戏音乐相辅相成,能否运筹帷幄关系到一款游戏产品的成与败毫不言过。十数年的经验告诉我们倾注设计者灵魂的游戏产品都将成就伟大史诗,选择进步亦或倒退??期待让世界热血沸腾那一刻的降临!

本课源码点击进入目录下载

梦想起航:届Silverlight游戏开发者论坛

参考资料:中游在线[WOWO世界] Silverlight C# 游戏开发:游戏开发技术

教程Demo在线演示地址http://cangod.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值