成品展示
每两秒会获得一个稳定风速,玩家可以把握住这个瞬间射箭,击中不同环得分不同,不击中不扣分,60s内够100分则获胜
游戏制作
脚本挂载与预制
箭靶子预制
在一个空对象上挂载五个扁平同心圆柱,设置圆柱厚度不同以便箭识别射中第几环,空对象上挂刚体组件,其余五个圆柱挂mesh碰撞器
箭的预制
在一个空对象上,挂上两个柱体分别作箭头和箭身,两个方形作箭尾,空对象设置为刚体,箭头和箭身添加碰撞器,以便和箭靶子碰撞后触发计分和停留。
游戏设计以及代码分析
类的UML图
沿用上一次设计模式,组合动作模式、单实例模式、工厂模式
各类说明
- 导演类:管理游戏全局状态,获取当前游戏的场景,协调各个类之间的通讯,是单实例类,不被Unity内存管理所管理
- 界面:负责与用户交互,检测用户的设计动作
- 场记:负责调动工厂产生和回收箭、用户设计后让动作管理者设计箭的轨迹、让记分员计分、还有管理箭与靶子的碰撞
- 动作管理者:被场记调用,为场景中的对象设计具体动作并执行
- 记分员:记分员按箭射中的环数据计分,拥有计分规则
- 箭工厂:负责生产和回收箭
- 箭的脚本:因为箭是刚体,碰撞后需要触发事件
各类具体代码
因为这次只在飞碟游戏基础上修改了很少,所以这里只给出修改过的部分的代码,其余代码与上一篇飞碟游戏对应的类一样。
- 箭的脚本 ArrowScript
需要OnTriggerEnter方法设置碰撞事件,另外因为在射中箭靶子后需要马上计分,所以在这里需要实例化场记中的记分员计分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowScript : MonoBehaviour
{
public string shootring;
private ScoreRecorder recorder;
void OnTriggerEnter(Collider other)
{
shootring = other.gameObject.tag;
//使得箭停在靶子上
Destroy(GetComponent<Rigidbody>());
//让记分员计分
recorder.Record(shootring);
}
// Use this for initialization
void Awake()
{
recorder = (ScoreRecorder)FindObjectOfType(typeof(ScoreRecorder));
}
}
- 记分员 ScoreRecorder
只是把之前飞碟的计分方法改一下,击中不同的环得分不同
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : MonoBehaviour {
private float score;
public float getScore()
{
return score;
}
public void Record(string target_ring)
{
switch (target_ring)
{
case "Ring10": score += 10; break;
case "Ring8": score += 8; break;
case "Ring6": score += 6; break;
case "Ring4": score += 4; break;
case "Ring2": score += 2; break;
}
}
public void Reset()
{
score = 0;
}
}
- 界面 UserGUI
增加两个Text显示风力和风向,在update方法中显示,其余代码和之前相同
public Text windforce;//风力
public Text winddirection;//风向
void Update()
{
//检测用户射击
action.shoot();
float force = actionmanager.getWindForce();
if (force < 0)
winddirection.text = "风向 : 左";
else if (force > 0)
winddirection.text = "风向 : 右";
else
winddirection.text = "风向 : 没风";
windforce.text = "风力 : " + actionmanager.getWindForce();
}
- 动作管理者
只是在RoundSSActionManager类里新增射箭的函数,其余动作的基本类与之前相同
设置箭的朝向为摄像机与鼠标点击点所成的方向,另外设置箭速和风力大小
public class RoundSSActionManager : SSActionManager, ISSActionCallback
{
public RoundController scene;
public MoveToAction action1, action2;
public SequenceAction saction;
private float speed = 20.0f;//箭速
private float force = 0f; //风力
//增加射箭动作
public void shootArrowAction(GameObject arrow, Vector3 dir)
{
arrow.transform.position = new Vector3(0, 0, -9);
arrow.transform.up = dir;
arrow.GetComponent<Rigidbody>().velocity = dir * speed; //设置箭速
}
public void wind(GameObject arrow, float time)
{
if(time % 2 == 0)//每两秒获取随机的风力
force = UnityEngine.Random.Range(-30, 30);
if(arrow != null && arrow.GetComponent<Rigidbody>() != null)//箭还没射到靶子上,还没解除刚体组件
{
arrow.GetComponent<Rigidbody>().AddForce(new Vector3(force, 0, 0), ForceMode.Force);
}
}
public float getWindForce()
{
return force;
}
protected new void Start()
{
scene = (RoundController)SSDirector.getInstance().currentScenceController;
scene.actionManager = this;
}
protected new void Update()
{
base.Update();
}
public void actionDone(SSAction source)
{
Debug.Log("Done");
}
}
场记 RoundController
这里修改三处地方:- 把之前的射飞碟回收飞碟方法,改为射箭与回收箭的方法
- 修改用户动作接口shoot()方法的具体实现
- 增加FixedUpdate(),在其中添加力作用于箭,因为箭是刚体,所以它的物理运动在每一帧结束后才作用,形成风力效果
完整代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum State { WIN, LOSE, PAUSE, CONTINUE, START };
public interface ISceneController
{
State state { get; set; }
void LoadResources();
void Pause();
void Resume();
void Restart();
}
public class RoundController : MonoBehaviour, IUserAction, ISceneController {
public ArrowFactory arrowFactory;
public RoundSSActionManager actionManager;
public ScoreRecorder scoreRecorder;
public List<GameObject> arrow;
public GameObject new_arrow;
public GameObject target;
//游戏状态
public State state { get; set; }
//计时器, 每关60秒倒计时
public int leaveSeconds;
IEnumerator DoCountDown()
{
while (leaveSeconds >= 0)
{
yield return new WaitForSeconds(1);
leaveSeconds--;
}
}
void Awake()
{
SSDirector director = SSDirector.getInstance();
director.setFPS(60);
director.currentScenceController = this;
SSDirector.getInstance().currentScenceController.state = State.PAUSE;
arrowFactory = Singleton<ArrowFactory>.Instance;
scoreRecorder = Singleton<ScoreRecorder>.Instance;
state = State.PAUSE;
leaveSeconds = 60;
}
// Use this for initialization
void Start () {
actionManager = GetComponent<RoundSSActionManager>() as RoundSSActionManager;
LoadResources();
}
void Update()
{
RecycleArrow();//回收箭
Judge(); //判断输赢
}
private void FixedUpdate()
{
//物理运动在每一帧结束后才作用,添加风力效果
actionManager.wind(new_arrow, leaveSeconds);
}
public void LoadResources()
{
Camera.main.transform.position = new Vector3(0, 0, -10);
target = GameObject.Instantiate(Resources.Load("Prefabs/Target")) as GameObject;
target.transform.position = new Vector3(0, 0, 3);
}
public void shoot()
{
if (Input.GetMouseButtonDown(0) && (state == State.START || state == State.CONTINUE))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.Log("鼠标点击:" + ray.direction);
//从工厂得到箭
new_arrow = arrowFactory.GetArrow();
arrow.Add(new_arrow);
//提供鼠标点击方向,箭由动作管理器射出去
actionManager.shootArrowAction(new_arrow, ray.direction);
}
}
public void RecycleArrow()//回收
{
for(int i = 0; i < arrow.Count; i++)
{
if(arrow[i].transform.position.y < -10 || arrow[i].transform.position.z > 15)
{
arrowFactory.FreeArrow(arrow[i]);//让飞碟工厂回收
arrow.Remove(arrow[i]);
}
}
}
public void Judge()//判断游戏状态,是否射中以及够不够分数进入下一回合
{
if (leaveSeconds == 0 && scoreRecorder.getScore() < 100)//时间到,分数不够,输了
{
StopAllCoroutines();//停止倒数
state = State.LOSE;
}
else if ((leaveSeconds > 0 && scoreRecorder.getScore() >= 100))
{
StopAllCoroutines();//停止倒数
state = State.WIN;
}
}
public void Pause()
{
state = State.PAUSE;
StopAllCoroutines();
for (int i = 0; i < arrow.Count; i++)
{
arrow[i].SetActive(false);//暂停后飞碟不可见
}
}
public void Resume()
{
StartCoroutine(DoCountDown()); //开启协程计时
state = State.CONTINUE;
for (int i = 0; i < arrow.Count; i++)
{
arrow[i].SetActive(true);//恢复后飞碟可见
}
}
public void Restart()
{
scoreRecorder.Reset();
Application.LoadLevel(Application.loadedLevelName);
SSDirector.getInstance().currentScenceController.state = State.START;
}
}
到这里修改就完成了,这次的修改很简单,只是箭多了个刚体的组件处理,因为和上次飞碟游戏类似,所以代码都套用上次的了。