2019-09-25

游戏对象与图形基础

本文总阅读量

基本操作演练【建议做】

下载Fantasy Skybox Free,构建自己的游戏场景

打开assert store,download一个Fantasy skybox,然后import就可以在自己项目的assert里找到了
然后选择一个Materials里的天空盒子样式

如果想diy也可以
首先把需要的贴图放到Assert里

然后创建一个Material,并将Shader的值改为Skybox/6 Sided

把天空盒的每个面都贴图上去就好了
下载的资源里还有Terrain地面

已经弄好了地形和颜色等,但是是光秃秃的,不过还可以导入预制里的树和小花小草小蘑菇,自己喜欢什么也可以下载资源

按照说明对地形进行调整

点击edit trees就可以把预制里的树拖进来并选择数量

游戏场景就弄好了

在场景中创建人物并把摄像头对准人物就完成了游戏场景的创建

写一个简单的总结,总结游戏对象的使用

常用的游戏对象:

  • 空对象(Empty):不显示的游戏对象,可以用来挂载脚本
  • 摄像机(Camera):游戏显示画面的来源,创建多个摄像机可以展现多个游戏视角
  • 3D物体(3D Object):游戏中的三维物体
  • 2D物体(2D Object):游戏中的二维物体
  • 光线(Light):营造游戏场景的光线效果
  • 声音(Audio):游戏中的声音素材,背景音乐或者活动音效等
  • 视频(Video):游戏中的视频素材

    游戏对象的创建:

    游戏对象可以在运行前创建也可以在游戏过程中通过脚本动态地创建,也可以使用预设创建对象。使用预设创建可以创建多个类似的重复地对象,避免重复操作

    游戏对象的使用方法

    Unity3d中每个游戏对象都由许多属性构成(包括基本属性transform等,还有第三方可添加的属性),我们可以通过添加脚本来控制每个游戏对象的属性和行为以及游戏对象间的互动和通信,从而构成实现不同的游戏场景

编程实践

牧师与魔鬼 动作分离版

这次与上次的主要区别是实现动作分离,上一次的动作是由专门的类完成的,并且每个对象各自管理自己的移动属性,现在将这些动作管理分离出来,实现动作和物体属性的分离

UML图

实现代码

动作基类

基类创建了动作管理器的基本元素,它继承了Unity的ScriptableObject类,该类是不需要绑定GameObject对象的可编程类。其中的属性包括enable(动作是否进行),destroy(动作是否被销毁)。同时利用接口实现游戏的通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SSAction : ScriptableObject {
public bool enable = true;
public bool destroy = false;

public GameObject GameObject { get; set; }
public Transform Transform { get; set; }
public ISSActionCallback Callback { get; set; }

protected SSAction() { }

// Use this for initialization
public virtual void Start() {
throw new System.NotImplementedException();
}

// Update is called once per frame
public virtual void Update() {
throw new System.NotImplementedException();
}
}
实现直线运动

简单动作类,实现单个动作并继承基类,即物体的移动,其中GetSSAction函数通过获取物体运动的目的地和速度,创建一个动作类,当动作完成时销毁该动作,并告诉管理者动作已经完成。这一功能在Update中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SSMoveToAction : SSAction {
public Vector3 target; // 移动目标
public float speed; // 移动速度

// 创建并返回动作的实例
public static SSMoveToAction GetSSMoveToAction(Vector3 target, float speed) {
SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
// 在 Update 函数中用 Vector3.MoveTowards 实现直线运动
public override void Update() {
this.Transform.position = Vector3.MoveTowards(this.Transform.position, target, speed * Time.deltaTime);
if (this.Transform.position == target) {
this.destroy = true;
// 完成动作后进行动作回掉
this.Callback.SSActionEvent(this);
}
}

// Use this for initialization
public override void Start() {}
}
实现动作序列

这个类同样要先创造一个动作类,但这里要创建的是动作序列。在Update中每一帧完成当前动作,当这一序列的动作完成时,停止并通过回调函数告诉管理者该序列动作已经完成,并删除序列。在执行动作前,需要为每一个动作注入游戏对象,在start中实现

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
public class SSSequenceAction : SSAction, ISSActionCallback {
public List<SSAction> sequence; // 动作队列
public int repeat = -1; // 循环次数,-1表示无限循环
public int start = 0; // 当前执行的动作

// 创建并返回动作序列的实例
public static SSSequenceAction GetSSSequenceAction(int repeat, int start, List<SSAction> sequence) {
SSSequenceAction action = ScriptableObject.CreateInstance<SSSequenceAction>();
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
// 在 Update 中执行当前动作
public override void Update() {
if (sequence.Count == 0) return;
if (start < sequence.Count) {
sequence[start].Update();
}
}
// 更新当前执行的动作
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, object objectParam = null) {
source.destroy = false;
this.start++;
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0) {
this.destroy = true;
this.Callback.SSActionEvent(this);
}
}
}

// Use this for initialization
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) {
DestroyObject(action);
}
}


}
管理动作

动作做完自动回收,在这中有需要等待的动作和已经做完的动作,显然需要等待的动作加入等待列表,需要删除的动作加入删除列表。

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
public class SSActionManager: MonoBehaviour {
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingAdd = new List<SSAction>();
private List<int> waitingDelete = new List<int>();

protected void Update() {
foreach (SSAction action in waitingAdd) {
actions[action.GetInstanceID()] = action;
}
waitingAdd.Clear();

foreach (KeyValuePair<int, SSAction> KeyValue in actions) {
SSAction action = KeyValue.Value;
if (action.destroy) {
// release action
waitingDelete.Add(action.GetInstanceID());
}
else if (action.enable) {
// update action
action.Update();
}
}

foreach (int key in waitingDelete) {
SSAction action = actions[key];
actions.Remove(key);
DestroyObject(action);
}
waitingDelete.Clear();
}

// 执行动作
public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback callback) {
action.GameObject = gameObject;
action.Transform = gameObject.transform;
action.Callback = callback;
waitingAdd.Add(action);
action.Start();
}
}
动作事件接口
1
2
3
4
5
6
7
public enum SSActionEventType : int { Started, Competeted }  

public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}

所有事件管理者必须实现这个接口,来实现事件调度。所以组合事件需要实现它,事件管理器也要实现它

修改游戏事件管理

定义一个动作管理者,继承基础动作管理者,来管理场景的动作

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstActionManager : SSActionManager, ISSActionCallback
{
// 移动船
public void MoveBoat(BoatController boatController)
{
SSMoveToAction action = SSMoveToAction.GetSSMoveToAction(boatController.getDestination(), 20);
RunAction(boatController.boat, action, this);
}
// 移动角色
public void MoveCharacter(MyCharacterController myCharacterController, Vector3 destination)
{
Vector3 current = myCharacterController.character.transform.position;
Vector3 middle = destination; // 利用 middle 实现折线运动
if (destination.y < current.y)
{
middle.y = current.y;
}
else
{
middle.x = current.x;
}
SSAction firstMove = SSMoveToAction.GetSSMoveToAction(middle, 20);
SSAction secondMove = SSMoveToAction.GetSSMoveToAction(destination, 20);
SSAction sequenceAction = SSSequenceAction.GetSSSequenceAction(1, 0, new List<SSAction> { firstMove, secondMove });
RunAction(myCharacterController.character, sequenceAction, this);
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed, int intParam = 0, string strParam = null, object objectParam = null)
{

}
}

然后修改FirstController中的动作内容,利用FirstActionManager来调用动作执行

设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束

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
int check_game_over()
{ // 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 = fromShore.getCharacterNum();
from_priest += fromCount[0];
from_devil += fromCount[1];

int[] toCount = toShore.getCharacterNum();
to_priest += toCount[0];
to_devil += toCount[1];

if (to_priest + to_devil == 6) // win
return 2;

int[] boatCount = boat.getCharacterNum();
if (boat.get_to_or_from() == -1)
{ // boat at toShore
to_priest += boatCount[0];
to_devil += boatCount[1];
}
else
{ // boat at fromShore
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
}

####