MySuperMario----2D复刻之旅,缅怀童年时光
超级玛丽/超级马里奥
你好!欢迎浏览我的博客!超级玛丽是一款非常经典的2D游戏,今天我将代领大家学习如何从小白开始,用unity一步一步复刻这一经典。
一、开发工具准备
-
unity2017以上版本 ,我制作的时候用的unity2018.1.0版本;
下载链接: unity发行版本.
- 任何支持c#编程语言的集成开发环境 ,我使用的是visual studio2015;
-
搭建游戏的素材 ,这里我直接将做好的素材分享给大家,其中包括 游戏背景地图,相关人物设置,背景音乐 等。不过有兴趣的同学可以去爱给网上下载自己喜欢的素材,然后搭建自己喜欢的游戏素材;
素材链接:https://pan.baidu.com/s/16Kj96XLQ77gEtH1-r8KsHw 提取码: 2333
二、项目进展
1、场景布置:
- 游戏界面地图初始化
- 第一关卡最终场景布置
- 游戏音效
- [x]素材图片
- 动态效果
超级玛丽
2、基本角色及相关功能介绍:
马里奥:
*能够左右行走、跑步、跳跃;
*能够通过吃金币得分;
*马里奥吃到蘑菇会变大,拥有二次生命;
*顶碎砖块开辟道路;
*与敌人正面碰撞或者掉入深渊会死亡;
敌人:
*能够左右行走
*能够和马里奥大叔交互
*死亡消失
3、登录注册界面
4、开始结束界面
5、场景之间的跳转
可以把现阶段所以的场景合起来,实现每个不同场景之间的切换。
6、隐形关卡(增加难度)
马里奥从第一关卡某个管道进入这个挑战关卡,关卡隐形。
7、第二关卡基本地图设计完成
8、第三阶段基本地图设计完成
三、项目基本功能架构和功能流程图示
1、基本功能架构
2、相关类介绍及关联
designed by Modelio Open Source 4.0
下一步计划
- 这一周把第所有关卡设计好
- 第十五周综合测试所有关卡、维护项目
- 第十五完善所有的设计文档,并细化制作细节
- 第十六周报告展示
点击下方链接,访问我们的github
链接: https://github.com/CAdom/MySuperMario.
目前项目提交情况:
##关键代码展示
音频控制器
.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
GameObject obj;
AudioSource bgmPlayer;
AudioSource sePlayer;
//单例模式
public static AudioManager Instance;//声明当前类静态实例
private void Awake()
{
Instance = this;//this就代表当前这个类
}
void Start ()
{
obj = GameObject.Find("AudioPlayer");
bgmPlayer = obj.GetComponent<AudioSource>();
sePlayer = obj.GetComponent<AudioSource>();
}
public void PlayMusic(string name)
{
AudioClip clip = Resources.Load<AudioClip>("Audios/" + name);//加载音乐片段
bgmPlayer.clip = clip;//切换音乐
bgmPlayer.Play();//播放新的音乐
}
public void StopMusic()
{
bgmPlayer.Stop();
}
public void PlaySound(string name)
{
AudioClip clip = Resources.Load<AudioClip>("Audios/" + name);//加载特效片段
sePlayer.PlayOneShot(clip);//播放加载的音频
}
}
相机控制器
.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraControl : MonoBehaviour
{
float minX;
float maxX;
public static Transform mario;
void Start()
{
minX = -16;
maxX = 16;
mario = GameObject.Find(PlayerControl.wanjia).transform;
}
void Update()
{
//mario = GameObject.Find(PlayerControl.wanjia).transform;
Vector3 pos = transform.position;//获取相机当前的位置
pos.x = mario.position.x;//更改位置的X轴的位置
if (pos.x > maxX)
{
pos.x = maxX;
}
if (pos.x < minX)
{
pos.x = minX;
}
transform.position = pos;//将更改后的值重新附给相机,实现相机位置更新
}
}
马里奥大叔控制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerControl : MonoBehaviour
{
//public static int Hp.PlayerHp;//血量
float f;//移动方向
Animator ani;//玩家动画
SpriteRenderer spr;//玩家精灵
Rigidbody2D rig;//刚体
bool isGround;//玩家是否在地面上
RaycastHit2D dhit;//玩家发射的向下的射线所碰到的物体信息
RaycastHit2D hit;//玩家向左或者右侧发射射线所碰到的物体信息
public float aaa;
public static string wanjia; //玩家状态
public GameObject PlayWj; //小马里奥
public GameObject HUQ; //加载火球
public static GameObject HUQ2; //火球
float tf;
void Start ()
{
//Hp.PlayerHp = 1;//血量初始值为1
wanjia = "Mario";
ani = GetComponent<Animator>();//获取动画控制器组件
spr = GetComponent<SpriteRenderer>();//获取精灵组件
rig = GetComponent<Rigidbody2D>();//获取刚体组件
isGround = true;
PlayWj = (GameObject)Resources.Load("Mario");
HUQ = (GameObject)Resources.Load("HuoQ");
tf = 1;
}
void Update ()
{
Debug.Log(Hp.PlayerHp);
f = Input.GetAxis("H");//按右为正,按左为负,不按为零
if (Hp.PlayerHp > 0 && f != 0)
{
if (f < 0)//左走
{
spr.flipX = true;//反转
hit = Physics2D.Raycast(transform.position,Vector2.left,0.1f,1 << 8);
}
else//右走
{
spr.flipX = false;//不反转
hit = Physics2D.Raycast(transform.position, Vector2.right, 0.1f, 1 << 8);
}
if (!hit.collider)//左右没有墙
{
transform.Translate(transform.right * 1.5f * Time.deltaTime * f);
}
ani.SetBool("IsRun",true);
}
if(f == 0)
{
ani.SetBool("IsRun", false);
}
//发射射线并获取射线碰撞到的物体信息:发射起点为玩家自身坐标原点,方向为向下,射线长度为0.1f,只检测第8层
dhit = Physics2D.Raycast(transform.position, Vector2.down, aaa, 1 << 8);
if (dhit.collider)//射线碰到物体的碰撞器
{
isGround = true;
ani.SetBool("IsJump", false);
}
else
{
isGround = false;
ani.SetBool("IsJump", true);
}
//跳
if (Input.GetKeyDown(KeyCode.Space) && isGround)//按下空格
{
rig.AddForce(Vector2.up * 200);
AudioManager.Instance.PlaySound("跳");
}
if(Input.GetKeyDown(KeyCode.J))
{
HUQ2 = Instantiate(HUQ, transform.position, Quaternion.identity);
HuoQ.FS2();
}
if (Input.GetKeyDown(KeyCode.K))
{
HUQ2 = Instantiate(HUQ, transform.position, Quaternion.identity);
HuoQ.FS1();
}
if (gameObject.tag =="WuDi")
{
tf -= Time.deltaTime;
if (tf <= 0)
{
gameObject.tag = "Player";
tf = 1;
}
}
}
public void playdeth() //人物死亡
{
Hp.PlayerHp--;
if(Hp.PlayerHp <= 0)
{
ani.SetTrigger("IsDead");
AudioManager.Instance.StopMusic();
AudioManager.Instance.PlaySound("死亡1");
Invoke("Deth2",0.8f);
SceneManager.LoadScene(2);
Destroy(GetComponent<BoxCollider2D>()); //销毁组件
rig.velocity = Vector2.zero; //速度归零 保护措施
rig.AddForce(Vector2.up * 150); //死亡向上跳一下
//Destroy(gameObject, 3);
SceneManager.LoadScene(2);
}
if(Hp.PlayerHp == 1 && wanjia == "Mario")
{
GameObject da = GameObject.Find("Mario2");
da.SetActive(false);
GameObject obj = Instantiate(PlayWj, transform.position, Quaternion.identity);
obj.tag = "WuDi";
obj.name = "Mario";
PlayerControl.wanjia = obj.name;
CameraControl.mario = obj.transform;
Destroy(da);
AudioManager.Instance.PlaySound("吃到蘑菇或花");
}
}
void Deth2() //死亡2音效
{
AudioManager.Instance.PlaySound("死亡2");
}
}
敌人(蘑菇)的控制器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class EnemyControl : MonoBehaviour
{
int Hp;//敌人血量
int dir;//敌人移动的方向
RaycastHit2D hit;
Animator ani;
void Start ()
{
Hp = 1;
dir = -1;
ani = gameObject.GetComponent<Animator>();
}
void Update ()
{ if(Hp > 0)
{
transform.Translate(transform.right * 0.2f * Time.deltaTime * dir);
}
if (dir == 1)
{
hit = Physics2D.Raycast(transform.position +Vector3.up*0.1f, Vector2.right, 0.1f, ~(1 << 9));
}
else if (dir == -1)
{
hit = Physics2D.Raycast(transform.position+ Vector3.up * 0.1f, Vector2.left, 0.1f, ~(1 << 9));
}
if (hit.collider)
{
dir = -dir;
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
dir = -dir;
if(collision.collider.tag == "Player")
{
if(collision.contacts[0].normal == Vector2.down) //contacts数组代表接触点 第0个点。 normal:接触点垂直的法线。
{
collision.gameObject.GetComponent<Rigidbody2D>().AddForce(Vector2.up * 100);
Emdeth();
}
else
{
collision.gameObject.GetComponent<PlayerControl>().playdeth(); //拿到碰撞人物 获取人物绑定的脚本,在调用方法。
}
}
}
private void OnTriggerEnter2D(Collider2D collision) //火球攻击
{
if (collision.tag == "TouSW")
{
Emdeth();
}
}
void Emdeth() //敌人死亡
{
Hp--;
if (Hp <= 0)
{
Destroy(gameObject,0.6f); //在0.6秒以后执行销毁。Destroy并不是立即销毁(在本帧结束之前),第二帧开始之前才会销毁。
ani.SetTrigger("emenydeth");
AudioManager.Instance.PlaySound("踩敌人");
GetComponent<Collider2D>().enabled = false; //关闭组件(未激活)
Destroy(GetComponent<Rigidbody2D>()); //销毁组件
}
}
}
登录设计
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.UI;
public class Script_Login : MonoBehaviour {
string txt_ID;
string txt_Psw;
Text txt_info;
string filePath;
// Use this for initialization
void Start () {
txt_info = GameObject.Find("Txt_Info").GetComponent<Text>();
filePath = Application.dataPath + "/" + "users.txt";
}
// Update is called once per frame
void Update () {
}
//********************************************
public void Login()
{
txt_ID = GameObject.Find("AccountInput/Text").GetComponent<Text>().text;
txt_Psw = GameObject.Find("PasswordInput/Text").GetComponent<Text>().text;
if (txt_ID == "")
{
txt_info.text = "请输入账号";
return;
}
if (txt_Psw == "")
{
txt_info.text = "请输入密码";
return;
}
if (Check_Login(txt_ID, txt_Psw))
{
txt_info.text = "登录成功";
}
else
{
txt_info.text = "账号或密码错误";
}
}
bool Check_Login(string id, string psw)
{
string[] Users = File.ReadAllLines(filePath);
for (int i = 0; i < Users.Length; i++)
{
string user_id = Users[i].Split(' ')[0];
string user_psw = Users[i].Split(' ')[1];
if (id == user_id && psw == user_psw)
{
return true;
}
}
return false;
}
}
注册设计
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class Script_Regist : MonoBehaviour {
string txt_ID;
string txt_Psw;
string txt_Psw_2;
bool b_IfLoginSuccess;
Text txt_info;
string filePath;
StreamWriter sw;
// Use this for initialization
void Start () {
txt_info = GameObject.Find("Txt_Info").GetComponent<Text>();
filePath = Application.dataPath + "/" + "users.txt";
}
// Update is called once per frame
void Update () {
}
public void Regist()
{
txt_ID = GameObject.Find("InputF_ID/Text").GetComponent<Text>().text;
txt_Psw = GameObject.Find("InputF_Psw/Text").GetComponent<Text>().text;
txt_Psw_2 = GameObject.Find("InputF_Psw_2/Text").GetComponent<Text>().text;
if (txt_ID == "")
{
txt_info.text = "请输入账号";
return;
}
if (txt_Psw == "")
{
txt_info.text = "请输入密码";
return;
}
if (txt_Psw_2 == "")
{
txt_info.text = "请输入密码";
return;
}
if (txt_Psw != txt_Psw_2)
{
txt_info.text = "两次输入的密码不一致";
}
else
{
if (CheckID(txt_ID) == false)
{
txt_info.text = "已存在相同账号";
}
else
{
txt_info.text = "注册成功";
//
WriteUserInfo(txt_ID, txt_Psw);
}
}
}
void WriteUserInfo(string id, string psw)
{
if (!File.Exists(filePath))
{
sw = File.CreateText(filePath);
}
sw = File.AppendText(filePath);
sw.WriteLine(id + " " + psw);
sw.Close();
}
bool CheckID(string id)
{
string[] Users = File.ReadAllLines(filePath);
for (int i = 0; i < Users.Length; i++)
{
string user_id = Users[i].Split(' ')[0];
//string user_psw = Users[i - 1].Split( )[1];
if (id == user_id)
{
return false;
}
}
return true;
}
}
团队成员任务分配
-
团队成员
队长:锁亚
队员:庄子凡、张锦鸿、陈建枭、王大营
-
任务分配
锁亚:马里奥动画制作及第一关卡场景设置、登录注册界面、推送博客及展示
庄子凡:第二关卡场景设置和角色控制
张锦鸿:开始界面和结束界面,及隐形关卡场景设置、关卡之间的转换
陈建枭:第一阶段场景布置及敌人相关角色控制
王大营:第三阶段场景制作和角色控制