一、游戏说明
飞碟游戏
游戏内容:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩,大小;发射位置,速度,角 度,受该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
游戏涉及的知识点:
- 世界与屏幕坐标、射线与碰撞、动态修改shader;
- 支持mono的单实例、游戏对象工厂、扩展游戏对象属 性与行为的方法。
二、相关知识
1.为什么需要工厂对象
- 游戏对象的创建与销毁高成本,必须减少销毁次数。如游戏中子弹
- 屏蔽创建与销毁的业务逻辑,使程序易于扩展
2.diskFactory的设计
- 前面游戏,由导演、场记、运动管理师、演员构成。
- 新游戏中,场记请了记分员、飞碟管理员
- 其中记分员按飞碟的数据计分,记分员拥有计分规则
- 场记只需要管理出飞碟规则与管理碰撞就可以了
3.带缓存工厂模式的实现
- getDisk(ruler) 伪代码
IF (free list has disk) THEN
a_disk = remove one from list ELSE
a_disk = clone from Prefabs
ENDIF
Set DiskData of a_disk with the ruler Add a_disk to used list
Return a_disk - FreeDisk(disk)
Find disk in used list
IF (not found) THEN THROW exception Move disk from used to free list
三、关键实现分析
1.首先是飞碟信息的存储类
挂在飞碟上制作为预制
//射击当前飞碟得分
public int score = 1;
//飞碟颜色设置
public Color color = Color.white;
//飞碟初始的位置
public Vector3 direction;
//飞碟大小
public Vector3 scale = new Vector3( 1 ,0.5f, 1);
2.飞碟工厂类
主要有的功能为:飞碟预制的生产、飞碟的选择、未使用飞碟的回收
生产:
//在空闲的飞碟列表中尝试找到符合tag的飞碟
for(int i=0;i<free.Count;i++)
{
if(free[i].tag == tag)
{
disk_prefab = free[i].gameObject;
free.Remove(free[i]);
break;
}
}
//如果空闲列表中没有所需求的预制飞碟,则需要生产预制飞碟
if(disk_prefab == null)
{
if (tag == "disk1")
{
disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
}
else if (tag == "disk2")
{
disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
}
else
{
disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
}
//设定颜色、位置等属性
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk_prefab.GetComponent<Renderer>().material.color = disk_prefab.GetComponent<DiskData>().color;
disk_prefab.GetComponent<DiskData>().direction = new Vector3(ran_x, start_y, 0);
disk_prefab.transform.localScale = disk_prefab.GetComponent<DiskData>().scale;
}
//添加到使用的列表中
used.Add(disk_prefab.GetComponent<DiskData>());
飞碟的选择:
不同的回合对应不同的飞碟
if (round == 1)
{
choice = Random.Range(0, scope1);
}
else if(round == 2)
{
choice = Random.Range(0, scope2);
}
else
{
choice = Random.Range(0, scope3);
}
飞碟回收:
未被击中的飞碟,将其回收至空闲列表中,并将其移出正在使用的飞碟列表
for(int i = 0;i < used.Count; i++)
{
if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID())
{
used[i].gameObject.SetActive(false);
free.Add(used[i]);
used.Remove(used[i]);
break;
}
}
3.场景控制FirstSceneControl类
负责游戏场景中的各种事件的处理,以及游戏规则如游戏开始结束等的执行。
发送飞碟的处理:
void Update ()
{
if(game_start)
{
//游戏结束时取消发送飞碟
if (game_over)
{
CancelInvoke("LoadResources");
}
//游戏开始的判定
if (!playing_game)
{
InvokeRepeating("LoadResources", 1f, speed);
playing_game = true;
}
SendDisk();
//难度加大的配置
if (score_recorder.score >= score_round2 && round == 1)
{
round = 2;
speed = speed - 0.6f;
CancelInvoke("LoadResources");
playing_game = false;
}
else if (score_recorder.score >= score_round3 && round == 2)
{
round = 3;
speed = speed - 0.5f;
CancelInvoke("LoadResources");
playing_game = false;
}
}
}
击中飞碟的相关判断:
public void Hit(Vector3 pos)
{
Ray ray = Camera.main.ScreenPointToRay(pos);
RaycastHit[] hits;
hits = Physics.RaycastAll(ray);
bool not_hit = false;
for (int i = 0; i < hits.Length; i++)
{
RaycastHit hit = hits[i];
if (hit.collider.gameObject.GetComponent<DiskData>() != null)
{
for (int j = 0; j < disk_notshot.Count; j++)
{
if (hit.collider.gameObject.GetInstanceID() == disk_notshot[j].gameObject.GetInstanceID())
{
not_hit = true;
}
}
if(!not_hit)
{
return;
}
disk_notshot.Remove(hit.collider.gameObject);
score_recorder.Record(hit.collider.gameObject);
Transform explode = hit.collider.gameObject.transform.GetChild(0);
explode.GetComponent<ParticleSystem>().Play();
StartCoroutine(WaitingParticle(0.08f, hit, disk_factory, hit.collider.gameObject));
break;
}
}
}
回收飞碟
IEnumerator WaitingParticle(float wait_time, RaycastHit hit, DiskFactory disk_factory, GameObject obj)
{
yield return new WaitForSeconds(wait_time);
hit.collider.gameObject.transform.position = new Vector3(0, -9, 0);
disk_factory.FreeDisk(obj);
}
4.飞碟飞行动作
public static diskFlyAction GetSSAction(Vector3 direction, float angle, float power)
{
diskFlyAction action = CreateInstance<diskFlyAction>();
if (direction.x == -1)
{
action.start_vector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
}
else
{
action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
}
return action;
}
5.GUI界面渲染
void OnGUI ()
{
bold_style.normal.textColor = new Color(1, 0, 0);
bold_style.fontSize = 16;
text_style.normal.textColor = new Color(0,0,0, 1);
text_style.fontSize = 16;
score_style.normal.textColor = new Color(1,0,1,1);
score_style.fontSize = 16;
over_style.normal.textColor = new Color(1, 0, 0);
over_style.fontSize = 25;
if (game_start)
{
if (Input.GetButtonDown("Fire1"))
{
Vector3 pos = Input.mousePosition;
action.Hit(pos);
}
GUI.Label(new Rect(10, 5, 200, 50), "分数:", text_style);
GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), score_style);
GUI.Label(new Rect(Screen.width - 120, 5, 50, 50), "血量:", text_style);
//显示血量
for (int i = 0; i < life; i++)
{
GUI.Label(new Rect(Screen.width - 75 + 10 * i, 5, 50, 50), "+", bold_style);
}
if (life == 0)
{
high_score = high_score > action.GetScore() ? high_score : action.GetScore();
GUI.Label(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 250, 100, 100), "游戏结束", over_style);
GUI.Label(new Rect(Screen.width / 2 - 10, Screen.width / 2 - 200, 50, 50), "你获得的分数:", text_style);
GUI.Label(new Rect(Screen.width / 2 + 50, Screen.width / 2 - 200, 50, 50), high_score.ToString(), text_style);
if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 150, 100, 50), "重新开始"))
{
life = 6;
action.ReStart();
return;
}
action.GameOver();
}
}
else
{
if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2-290, 100, 50), "开始游戏"))
{
game_start = true;
action.BeginGame();
}
}
}
四、成品
视频
最后感谢师兄的博客给了很大的帮助!不然真的很难实现。