版權聲明:
- 本文原創發布於博客園"優夢創客"的博客空間(網址:
http://www.cnblogs.com/raymondking123/
)以及微信公眾號“優夢創客”(微信號:unitymaker) - 您可以自由轉載,但必須加入完整的版權聲明!
松鼠大作戰游戲制作
-
游戲介紹 1990年,經迪士尼授權,由日本卡普空(Capcom)電視游戲公司制作的基於任天堂FC主機的電視游戲《松鼠大作戰》出版發行。游戲延用迪士尼動畫片《松鼠大作戰》里的兩只可愛花栗鼠Chip and Dale(奇奇和蒂蒂),從尋找小貓咪的委托任務開始,到擺脫肥貓陷阱與其一決高下為故事內容。可單人玩又可雙人對戰,可互助又可互攻,流暢度,創造力與可玩性均為同類游戲中的領軍者。
1993年,卡普空制作發行了《松鼠大作戰2》再一次將經典搬到屏幕。除了制作更加精良外,故事也更加驚險刺激。
-
主角Chip登場 將主角圖片拖入層級面板上,命名為“player”,添加剛體2D組件和合適的碰撞器組件。為player設置子節點hand,添加碰撞器組件設置為觸發,用於觸碰箱子的判斷;設置子節點foot,調整恰當的位置,用於跳躍,以及主角跳躍動畫的條件判定;再設置兩個節點用於主角投擲物品發射的位置。
-
主角Chip動畫制作 打開動畫控制器,創建主角的Idel動畫,在合適的時間軸拖上相應的主角圖片,重復操作將主角的動畫設置好。
-
主角Chip基本動作實現 為player添加腳本組件,命名為PlayerController,主角的移動需要通過輸入設備為其提供指令,並進行相應的操作,Unity中Input可以獲得這些操作。horizontal與vertical均是浮點數范圍為-1至1,方向與卡迪爾坐標相同,isPressjump與isPressFire均為Bool類型。通過射線的方式我們可以判斷處人物是否在跳躍狀態下。Chip的移動與跳躍我們通過物理運動進行操作,在FixedUpdate()下進行代碼操作;
isJump = !(Physics2D.Linecast(transform.position, foot.position, 1 << LayerMask.NameToLayer("Map")) || Physics2D.Linecast(transform.position, foot.position, 1 << LayerMask.NameToLayer("Box")));
horizontal = Input.GetAxis("Horizontal");//上下
vertical = Input.GetAxis("Vertical");//左右
isPressjump = Input.GetButtonDown("Jump");//跳躍
isPressFire = Input.GetButtonDown("Fire1");//攻擊
void FixedUpdate()
{
if (isControl)
{
if (!isDown && isPressjump && !isJump)
{
if (rig.velocity.y < 1F)
{
rig.AddForce(Vector3.up * force);
}
isJump = true;
}
if (isDown)
{
rig.velocity = new Vector3(0, rig.velocity.y);
}
else
{
rig.velocity = new Vector3(speed * horizontal, rig.velocity.y);
}
}
}
- 主角Chip攻擊實現 與射擊游戲的區別,在本作中,CHip是通過搬物品再投擲出去進行攻擊的。這里的設想是將場景的箱子與投擲的箱子區別開來,方便判斷。player下的節點hand添加腳本,在OnTriggerStay2D下調用MoveBox方法,isHandBox判斷手中是否有箱子,isThrow則判斷有沒有進行投擲操作。這里貼出主要代碼:
public void MoveBox(Collider2D collision)
{
//搬箱子
if (!isDown && collision.tag.StartsWith("Box") && isPressFire && !isHandBox && !isThrow)
{
Destroy(collision.gameObject);//場景中的箱子被銷毀了,游戲中松鼠頭頂上的箱子僅僅是外觀的區別
isHandBox = true;
animator.SetTrigger("moveBox");
Instantiate(movebox);
}
}
private void ThrowBox()
{
if (isHandBox && isPressFire)
{
isHandBox = false;
isThrow = true;
GameObject o = Instantiate(throwingbox);//投擲箱子時實例化一個投擲的箱子(與場景的箱子功能不同)
if (!isDown)
{
o.transform.position = transform.Find("fireposition").position;
}
else
{
o.transform.position = transform.Find("downfireposition").position;
}
o.GetComponent<ThrowingBox>().Throw(transform.localScale.x, vertical);
StartCoroutine(ResetIsThrow());
animator.SetTrigger("throwbox");
Instantiate(throwbox);
}
}
-
場景箱子的制作 將箱子圖片拖進層級面板中,為它設置合適的碰撞器,做成預制體備用。
-
投擲箱子的制作 與場景箱子類似,設置觸發器,另外需要增加剛體2D組件與腳本,腳本主要用於處理與怪物接觸的交互,同樣也制成預制體。
-
其他道具的制作 其他道具有花(吃了一定的數量可以獎勵生命);蜂蜜(蜜蜂怪物攻擊的道具,玩家觸碰會造成傷害);堅果(若主角受傷,吃到該道具會增加1點Hp值);叉子(投擲小鼠投擲的道具,玩家觸碰會造成1點Hp傷害)。
-
一些其他的制定 在本場景的末尾添加添加合適的觸發器,綁上腳本可以通過下一個場景;在懸崖處也添加一個長條的觸發器,用於觸發主角死亡判定並重新加載本場景。
-
蜜蜂怪物的制作 在層級面板中,拖進一張蜜蜂怪物的圖片,添加剛體組件,和合適的碰撞器,設置為觸發。為這個對象設置兩個動畫,一個為停止動畫,一個為飛行動畫。攝像機還沒有看到蜜蜂時,蜜蜂處於靜止狀態,當蜜蜂被攝像機照到時,蜜蜂處於飛行狀態。蜜蜂飛到玩家的X位置時,投擲蜂蜜道具。附上蜜蜂的物理運動時腳本代碼:
public void FixedUpdate()
{
if (isHit)
{
rig.velocity = new Vector2(masterFlyX, masterFlyY);
}
else if (isRest)
{
rig.velocity = Vector2.zero;
}
else if (!isStop && !isHit)
{
rig.velocity = new Vector2(flyspeedx, flyspeedy);
if (this.transform.position.x < PlayerController.instance.transform.position.x && honeyNum > 0)
{
Attack();
honeyNum--;
isRest = true;
StartCoroutine(ResetIsRest());
}
}
}
- 投叉小鼠的制作 與蜜蜂怪物類似,添加剛體組件和合適的碰撞器。為這個對象設置四個動畫,停止動畫,巡邏動畫,攻擊動畫,跳躍動畫。給這個對象加一個點進行射線是不是發現玩家,發現時,執行攻擊邏輯播放攻擊動畫。這里附上部分代碼:
public void Update()
{
if (state == State.Stop && this.transform.position.x < cam.position.x + xoffset)
{
//怪物行為被激活
state = State.Walk;
animator.SetTrigger("Walk");
}
else if (this.transform.position.x < cam.position.x - xoffset || this.transform.position.y > cam.position.y + yoffset)
{
Destroy(this.gameObject);
}
Walk();
Throw();
Escape();
}
-
Boss制作 在層級面包板中拖入Boss的圖片,組件與合適的觸發器,Boss主要兩種狀態,添加剛體,走動狀態與停下攻擊狀態。這里附上Boss的代碼:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Boss : MonoBehaviour { public float range; public float speed; public float stopTime; public float walkTime; private float dir = 1;//方向正1表示坐標向右 private float leftrange; private float rightrange; private Rigidbody2D rig; private Animator ani; private SpriteRenderer spriterender; private State state = State.Stop; private float currentTime; public GameObject puke; public float pukeSpeed; public int hp; public int totalHp; private bool isFlash = false; public float flashTime; public Color flashColor; public Color normalColor; public Color dangerColor; public float destoryTime; public GameObject bosspuke; public GameObject kill; public enum State { Stop, Walk, Attack, Die } // Use this for initialization void Start () { rig = this.GetComponent<Rigidbody2D>(); ani = this.GetComponent<Animator>(); spriterender = this.GetComponent<SpriteRenderer>(); leftrange = this.transform.position.x - range; rightrange = this.transform.position.x + range; } void Update () { ChangeColor(); ani.SetInteger("state", (int)state); if (state == State.Stop) { StartCoroutine(CancelStop()); } else if (state == State.Walk) { currentTime += Time.deltaTime; if (currentTime > walkTime) { currentTime = 0f; state = State.Attack; } else { if (this.transform.position.x > rightrange) { transform.localScale = new Vector3(-1F, 1F, 1F); dir = -1F; } if (this.transform.position.x < leftrange) { transform.localScale = new Vector3(1F, 1F, 1F); dir = 1F; } } } else if (state == State.Die) { ani.enabled = false; this.GetComponent<Collider2D>().enabled = false; Destroy(this.gameObject, destoryTime); } } public void ChangeColor() { if (isFlash) spriterender.color = flashColor; else spriterender.color = normalColor; if (hp <= 1) { spriterender.color = dangerColor; if (hp <= 0) { state = State.Die; } } } public void FixedUpdate() { if (state == State.Stop || state == State.Attack) { rig.velocity = Vector2.zero; } else if (state == State.Walk) { rig.velocity = new Vector2(dir * speed, 0); } else if (state == State.Die) { rig.velocity = new Vector2(-3F,-1F); } } IEnumerator CancelStop() { yield return new WaitForSeconds(stopTime); state = State.Walk; } public void OnTriggerEnter2D(Collider2D collision) { if (collision.gameObject.tag == "Player" && !PlayerController.instance.isFlash) { PlayerController.instance.Hurt(); PlayerInfo.instance.SubHp(); } } public void ChangeWalkState() { state = State.Walk; } public void Attack() { GameObject o = Instantiate(puke); Vector2 start = this.transform.Find("firePos").position; Vector2 end = GameObject.Find("player").transform.position; o.transform.position = start; Vector2 dir = end - start; dir = dir.normalized; o.GetComponent<Rigidbody2D>().velocity = dir*pukeSpeed; Instantiate(bosspuke); } public void Hit() { hp--; isFlash = true; StartCoroutine(ResetFlash()); if (hp<=0) { Instantiate(kill); } } IEnumerator ResetFlash() { yield return new WaitForSeconds(flashTime); isFlash = false; } }