文章目录
游戏对象与图形基础——牧师与魔鬼(动作分离版)
一、基本操作演练
1.1 下载 Fantasy Skybox FREE, 构建自己的游戏场景
首先在Asset Store中,下载Fantasy Skybox FREE素材包,之后再unity中导入。
之后,我们可以看到素材包中包含了天空盒、terrain等材料的模型,我们这里直接使用模板的素材来构建游戏场景首先为camera添加天空盒:
之后将素材包中的SimpleTerrain地形加入场景,我们再用工具对地形进行一定的修改(挖点洞,更改一下地皮,设置地形平缓,种点花花草草),这样我们就完成了游戏场景的设置:‘
离近点可以看到我们种得草:
1.2 总结:
- 首先我们最常见的游戏对象就是角色、地形等的实例游戏对象,就像我们牧师与魔鬼中的船、牧师、魔鬼等实例游戏对象;对这类游戏对象,我们的常见操作有对象的移动、实例化预设加载游戏对象、挂载脚本、设置对象形状属性、添加删改组件等,这是我们游戏过程中最直接操作和接触的游戏对象;
- 游戏场景中的光照和摄像头是一种辅助对象,用于玩家在实际游戏过程中,对游戏场景进行观察。
- 空GameObject常常用于挂载脚本,进行资源加载等操作;
- Terrain等地图编辑对象,可用于游戏地形等的设定。
二、牧师与魔鬼(动作分离版)
2.1 UML图
程序设计的流程图根据老师课件中给出的UML图架构:
2.2 程序实现:
我们此次实在上一次作业的基础上,实现动作与游戏场景的分离,所以我们要在源代码的基础上,分离一个动作管理类(SSAction)出来,在动作管理类中,管理所有动作。动作管理类的实现,均按照课间代码方式,所以下面主要po出来具体实现代码和与上一次不同代码;
- 这里给出上一次作业的博客和gitee仓库地址:空间与运动——模拟太阳系、牧师与魔鬼游戏实现详解、仓库地址
FirstSceneActionManagerSS
FirstSceneActionManagerSS是动作控制器,它继承了动作管理基类和SSActionCallback接口,实现人物和船的移动的调用
public class FirstSceneActionManager : SSActionManager, SSActionCallback
{
public SSActionEventType Complete = SSActionEventType.Completed;
public void BoatMove(BoatController Boat)
{
Complete = SSActionEventType.Started;
CCMoveToAction action = CCMoveToAction.getAction(Boat.GetDestination(), Boat.GetMoveSpeed());
addAction(Boat.GetGameObject(), action, this);
Boat.ChangeState();
}
public void CharacterMove(Character GameObject, Vector3 Destination)
{
Complete = SSActionEventType.Started;
Vector3 CurrentPos = GameObject.GetPosition();
Vector3 MiddlePos = CurrentPos;
if (Destination.y > CurrentPos.y)
{
MiddlePos.y = Destination.y;
}
else
{
MiddlePos.x = Destination.x;
}
SSAction action1 = CCMoveToAction.getAction(MiddlePos, GameObject.GetMoveSpeed());
SSAction action2 = CCMoveToAction.getAction(Destination, GameObject.GetMoveSpeed());
SSAction seqAction = CCSequenceAction.getAction(1, 0, new List<SSAction> { action1, action2 });
this.addAction(GameObject.GetGameobject(), seqAction, this);
}
public void SSActionCallback(SSAction source)
{
Complete = SSActionEventType.Completed;
}
}
新增动作事件接口
public enum SSActionEventType : int { Started, Completed }
public interface SSActionCallback
{
void SSActionCallback(SSAction source);
}
SSACTION动作管理类
包含了动作管理基类 – SSActionManager、 顺序动作组合类实现CCSequenceAction、 简单动作实现CCMoveToAction、动作基类(SSAction),代码均使用课件上所给代码
- 动作管理基类 SSActionManager:使用AddAction将游戏对象和动作绑定,并实现动作的自动回收
public class SSActionManager : MonoBehaviour
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingToAdd = new List<SSAction>();
private List<int> watingToDelete = new List<int>();
protected void Update()
{
foreach (SSAction ac in waitingToAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingToAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
watingToDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}
foreach (int key in watingToDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
Object.Destroy(ac);
}
watingToDelete.Clear();
}
public void addAction(GameObject gameObject, SSAction action, SSActionCallback ICallBack)
{
action.gameObject = gameObject;
action.transform = gameObject.transform;
action.CallBack = ICallBack;
waitingToAdd.Add(action);
action.Start();
}
}
- 动作基类(SSAction):创建了动作的基本元素,继承ScriptableObject类,不需要绑定具体的游戏对象的可编程类;
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameObject;
public Transform transform;
public SSActionCallback CallBack;
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
- 简单动作实现CCMoveToAction:实现物体的运动,就像非动作分离版本中的move类功能相似
public class CCMoveToAction : SSAction
{
public Vector3 target;
public float speed;
private CCMoveToAction() { }
public static CCMoveToAction getAction(Vector3 target, float speed)
{
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
public override void Update()
{
this.transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
if (transform.position == target)
{
destroy = true;
CallBack.SSActionCallback(this);
}
}
public override void Start()
{
}
}
- 顺序动作组合类实现CCSequenceAction:实现动作的顺序实现,也是原本版本中的Move类中的函数的分离实现
public class CCSequenceAction : SSAction, SSActionCallback
{
public List<SSAction> sequence;
public int repeat = 1; // 1->only do it for once, -1->repeat forever
public int currentActionIndex = 0;
public static CCSequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence)
{
CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
action.sequence = sequence;
action.repeat = repeat;
action.currentActionIndex = currentActionIndex;
return action;
}
public override void Update()
{
if (sequence.Count == 0) return;
if (currentActionIndex < sequence.Count)
{
sequence[currentActionIndex].Update();
}
}
public void SSActionCallback(SSAction source)
{
source.destroy = false;
this.currentActionIndex++;
if (this.currentActionIndex >= sequence.Count)
{
this.currentActionIndex = 0;
if (repeat > 0) repeat--;
if (repeat == 0)
{
this.destroy = true;
this.CallBack.SSActionCallback(this);
}
}
}
public override void Start()
{
foreach (SSAction action in sequence)
{
action.gameObject = this.gameObject;
action.transform = this.transform;
action.CallBack = this;
action.Start();
}
}
void OnDestroy()
{
foreach (SSAction action in sequence)
{
Object.Destroy(action);
}
}
}
Judge类
Judge类其实就是上个版本作业中的checkGameover函数的分离出来,单独作为一个脚本
public class Judge : MonoBehaviour
{
CoastController fromCoast;
CoastController toCoast;
public BoatController boat;
public Judge(CoastController c1,CoastController c2, BoatController b)
{
fromCoast = c1;
toCoast = c2;
boat = b;
}
public int Check()
{ // 0->not finish, 1->lose, 2->win
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;
int[] fromCount = fromCoast.GetobjectsNumber();
from_priest += fromCount[0];
from_devil += fromCount[1];
int[] toCount = toCoast.GetobjectsNumber();
to_priest += toCount[0];
to_devil += toCount[1];
if (to_priest + to_devil == 6) // win
return 2;
int[] boatCount = boat.GetobjectsNumber();
if (boat.get_State() == -1)
{ // boat at toCoast
to_priest += boatCount[0];
to_devil += boatCount[1];
}
else
{ // boat at fromCoast
from_priest += boatCount[0];
from_devil += boatCount[1];
}
if (from_priest < from_devil && from_priest > 0)
{ // lose
return 1;
}
if (to_priest < to_devil && to_priest > 0)
{
return 1;
}
return 0; // not finish
}
}
控制器
将原本在Director类中的CharacterController控制器,BoatController控制器和LandController控制器拆分成单独的文件,并且删除Move控制器,因为被分离后的动作管理基类替代。
GUI
与原先版本基本相同,还是实现Label显示和Button的Restart功能和计时器功能
2.3 效果展示
- 添加了天空盒
2.4 仓库地址
三、材料与渲染联系
3.1 Standard Shader 自然场景渲染器相关效果的实现
- 首先我们进行金属关泽属性的渲染工作,我们首先新建5个material,均使用RGB(200,80,80)创建,之后使用不同的程度的渲染,查看它们之间的区别
未进行渲染图:
首先尝试使用不同的金属光泽来更改着色器,通过Shader中的Metallic来实现:
从左到右金属光泽程度分别为:0、0.25、0.5、0.75、1.0
我们可以看到,随着金属光泽程度的加深,物体的反光性更高,并且颜色加深,类似于在原材质上方涂了一层包浆。
尝试使用不同的平滑程度来渲染小球:
从左到右平滑程度为:0、0.25、0.5、0.75、1.0
我们可以看到,随着平滑度的增加,材质从类似于磨砂的不反光材质,变成了全反光的类似与玻璃球的材质
最后尝试一下镜面模式,首先要更换材质的类型为镜面模式,而非金属模式
我们选择几个不同的颜色镜面,最右侧两个选择相同颜色,光滑度不同的镜面
我们可以看到,镜面类似于在物体上添加了一个对应颜色的反光镜,最后的呈现颜色是材质颜色和镜面颜色的混合。
3.2 用博客给出游戏中利用 Reverb Zones 呈现车辆穿过隧道的声效的案例
我们首先加入Audio Reverb Zone和Audio Source。
我们设定相机位置(0,0,0),所以设置音源位置为(-5,0,-12),这样我们的距离音源为13,我们的ReverbZone的最小距离为10,最大距离15,所以13位于梯度混响区;
我们这里将Reverb Preset设置为Cave,并为Audio Source加入一段音源(Asset Store中下载),之后这只音源为Loop,即可开始播放(这里没有找到开车音源,用了一段警笛音源代替)