只有你足够强,别人才会和你讲道理。
文章: 280
浏览: 2,949,605
  • 博主:笨木头
  • 关注:Unity、Net Core、Blazor
  • 邮箱:musicvs@163.com
笨木头  2021-12-28 09:07         阅读(2427)     评论(0)
转载请注明,原文地址: http://www.benmutou.com/archives/2930
文章来源:笨木头与游戏开发

其实最关键的就是游戏的逻辑变化怎么样反映到组件上,毕竟我们现在要靠组件来渲染游戏对象。

比如渲染一个精灵,那实际上就是用一个img元素来实现,然后通过变换坐标来移动。

我们要解决的就是,怎么样可以让组件只负责渲染,而坐标之类的数据独立运作。

 

1.游戏对象

所有与Blazor组件无关的代码,我都放在AntDesignGameFramework项目里,其中,游戏对象是参考了Unity3D,大致的结构如下:

Object

-> GameObject

-> Component

-> Transform

-> 各种游戏对象的组件

 

结构很简单。

Object:是基础类,里面有一个UID,用来做唯一标记。

GameObject:就是我们的游戏对象,游戏对象包含各种组件(Component

Component:组件,实现游戏对象的各种属性或逻辑,像血量条这些UI也通过组件附加到游戏对象上。

Transform:记录游戏对象的坐标、大小等属性,同时还记录游戏对象的所有子对象,是最重要的一个组件。

 

当然,这些不是重点,这部分的封装还比较初级,仅供演示。

 

重点是,这些GameObject怎么渲染出来?

 

2.游戏对象的渲染

我们有一个GameWorld.razor组件,这个组件只做一件事情,渲染世界里的所有GameObject

foreach(var gameObject in GameContext.GameObjects)
{
	<RenderGameWordObject @key=@gameObject.Uid @ref="_renderGameWorldObject" GameObject="@gameObject" />
}

 

GameContext可以理解为是一个游戏世界,里面就是存放了所有的GameObject对象。

RenderGameWordObject也是一个Blazor组件,它是真正负责渲染GameObject的组件,它会渲染GameObject本身以及下级GameObject

<DynamicComponent 
			Type="@GameObject.WebComponentType" 
			Parameters=@(GameObject.WebParameters)
/>
		
@for(int i = 0; i< GameObject.Transform.GetChildCount(); i++)
{
	var childGameObject = GameObject.Transform.GetChild(i);
	
	if(childGameObject.Transform.GetChildCount() > 0)
	{
		<RenderGameWordObject GameObject="@childGameObject" />
	}
	else
	{
		<DynamicComponent 
			Type="@childGameObject.WebComponentType" 
			Parameters=@(childGameObject.WebParameters)
		/>
	}
}

@code
{
	[Parameter]
	public GameObject GameObject { get; set; }
}

 

GameObject的上下级关系是通过Transform组件来实现的,即,Transform记录了GameObject的所有子对象(熟悉Unity3D的应该就很清楚了)。

因此,在渲染GameObject的同时,还要递归渲染它的子对象。

 

这些逻辑,有游戏开发经验的各位,肯定都清楚。这里主要解释一下DynamicComponent

DynamicComponent是Blazor的一个很强大的功能——动态渲染组件。

通过传递Type属性,它就能把Type对应类型的组件给渲染出来。

 

比如,我们有个Sprite.razor组件,那么,通过下面的代码就能把它渲染出来:

<DynamicComponent Type="typeof(Sprite)" />

 

因为它支持动态设置Type,所以,我们就可以随时切换要渲染的组件类型,达到动态渲染游戏对象的目的。

 

3.传递参数

渲染是没问题了,那组件的参数怎么传递?

DynamicComponent还支持Parameters属性,通过传递一个Dictionary对象来给组件传递各种参数。

于是,最终,我们的GameObject对象是通过类似下面的逻辑来渲染的:

 

游戏对象,SpriteGameObject.cs

public class SpriteGameObject : GameObject
{
    public string AssetName { get; set; }
}

 

游戏对象渲染组件,Sprite.razor:

<img 
	src="@GameObject.AssetName" 
	width="@GameObject.Transform.Size.Width" 
	height="@GameObject.Transform.Size.Height" 
/>

@code{
    [Parameter]
    public GameObject GameObject { get; set; }
}

 

世界对象渲染组件,GameWorld.razor:

<DynamicComponent Type="typeof(Sprite)" Parameters="new Dictionary<string, object>() {  { "GameObject", new SpriteGameObject()} }"/>

 

也就是说,我们每一个类型的游戏对象(如SpriteGameObject.cs),都需要对应一个渲染组件(如Sprite.razor),游戏对象负责处理游戏逻辑,渲染组件负责渲染对象。

GameWorld组件负责渲染所有的游戏对象。

 

4.GameWorld组件放哪?

比如我这个Demo有一个页面,用来展示简单的战斗逻辑,这个页面的组件是LetFight.razor

那么,GameWorld组件就作为LetFight的核心组件,然后在游戏循环的每一帧里调用GameWorld里的所有GameObjectUpdate函数,以此来更新游戏逻辑。

具体看一下源码就清楚了,以下是简化后的代码。

 

LetFight.razor:

<GameWorld @ref="@_gameWorld" />

 

LetFight.razor.cs:

public partial class LetFight : ComponentBase
{
    private GameLoop _gameLoop;
    private GameWorld _gameWorld;


    protected override void OnAfterRender(bool firstRender)
    {
        if (_gameLoop != null)
        {
            return;
        }

        var gameContext = new DemoGameContext();

        gameContext.Display.Size = new Size(1200, 600);

        // 创建英雄
        ActorGameObject heroGameObject = new ActorGameObject(typeof(Actor));
        gameContext.AddGameObject(heroGameObject);

        _gameWorld.SetGameContext(gameContext);
        _gameWorld.Refresh();

        _gameLoop = new();
        _gameLoop.Logic += Logic;
        _gameLoop.Start();
    }

    private async Task Logic(object sender, GameLoopLogicEventArgs e)
    {
       // 游戏每一帧调用GameContext的Step函数,在Step函数里调用每一个GameObject的Update函数,更新游戏对象逻辑
        await _gameWorld.GameContext.Step(e.ElapsedTime);

        _gameWorld.Refresh();
    }
}

 

5.总结

可以说,这是我这个Demo最重要的一个部分,我要解决的问题其实也就是这些,其它的就是更加细节的实现了。

通过把Blazor组件和游戏对象的剥离,我们就可以尽情地写游戏逻辑,不需要管渲染、不需要考虑组件的问题。

甚至,在一定程度上,我们可以复用Unity3D里的一些逻辑代码。

0 条评论
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发