游戏介绍

基本信息

平台:PC(Linux服务器+Windows客户端);

类型:2D联机小游戏;

游戏背景

在一片黑白的世界中,有一位宅家生物研究生魔法师油管主为了博取流量以维持生计,造了一座迷你浮空岛,将饥肠辘辘的克隆小刺猬们和直播摄像头传送到岛屿上,留下了几株最基本的浆果灌木。进入直播间的观众有几率被选为幸运观众,将自己的意识传输到其中的一只刺猬,并加入这场刺猬间的同类残杀……

玩法简介

WASD移动。

游戏目标:把其它刺猬创死。

每个玩家有一定血量,并且攻击力随时间进行变化;

玩家之间可以互相创,创了后攻击力强的一方可以对弱的一方造成伤害,伤害值是两方攻击力的差值;

血量为 0 后就被创死了,肇事者会获得击杀数+1 的奖励;

分工

小组4人:我、指定龙场、ppppp、悠沐潇;

  • 我:服务器程序,部分服务器与客户端接口,与 BUG 斗智斗勇
  • 指定龙场:部分客户端程序及服务器接收消息,部分场景搭建,制作 BUG
  • ppppp:客户端 UI ,程序对接,贴图、序列帧,部分场景搭建;
  • 悠沐潇:策划,测试,宣传视频;

具体策划

联网设计

服务器存玩家昵称和击杀数,玩家通过昵称登录以获取击杀数,不做密码;

击杀后同步到服务器,修改击杀数;

数值

每个玩家登录时随机一个 1-2 的数字,这是自己攻击力的变化速率;

每个玩家初始有 5 滴血;

玩家的攻击力在 0-3 之间浮动;

玩家受到伤害后有 1.5 秒无敌时间 ;

技术路线

数据库相关

数据库中存玩家昵称和击杀数:

数据库

初步操作

让 game 脚本可以给数据库发消息:

1
skynet.register(".mysqldb")

就可以直接转发给数据库了

1
2
-- 转发到 mysqldb.lua
skynet.send(".mysqldb", "lua", fd, 'UpdateWinNum', msgBody)

登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function login(fd, userName)
print("Finding UserName: ".. userName)
-- 根据名称获取杀人数
local winNum = db:query('SELECT winNum FROM player where userName="'..userName..'"')
local winNumStr = cjson.encode(winNum)
-- 如果没找到,就新建一行
if winNumStr == '{}' then
db:query('insert into player (userName, winNum) values ("'..userName..'", 0)')
winNumStr = '[{"winNum":0}]'
else -- 找到信息就好
end
-- 发送 LoginDone 消息给客户端
SendMsg(fd, "LoginDone", winNumStr)
end

更新杀人数

1
2
3
4
5
6
7
function updateWinNum(fd, body)
local userName = body.userName
local winNum = body.winNum
print('玩家'..userName..'的杀人数被更新为'..winNum)
-- 更新数据库
db:query('Update player set winNum='..winNum..'where userName="'..userName..'"')
end

消息设计

发往服务器的:

  • Login
  • Register
  • UserStateChanged
  • BegingHurt
  • Death
  • UpdateWinNum

发往客户端的:

  • Welcome
  • LoginDone
  • RegisterDone
  • OtherUserConnect
  • OtherUserStateChanged
  • OtherUserBeingHurt
  • OtherUserDie
  • OtherUserDisconnected
  • OtherUserWinNumUpdate

玩家生成

在登陆完成后,随机找到一个没有其他玩家的位置,进行 Register。同时随机一个自己的攻击变化速度,也发送到服务器。

随机生成自己的攻击速率:

1
2
3
4
5
6
7
8
9
public float CalculateChanger()
{
float Changer = Random.Range(1.0f, 2.0f);
Changer *= 100;
Changer = Mathf.Floor(Changer);
Changer /= 100;
Debug.Log("Changer = " + Changer);
return Changer;
}

为了防止消息过长导致被裁剪,所以这里只取了 2 位小数。

用于登录的信息:

1
2
3
4
5
6
7
8
class UserInfo
{
public string Id;
public string Name;
public float ATK;
public int Win;
public List<float> Pos;
}

都为了防止消息字符串过长做了简化处理。

角色逻辑

玩家移动

由于是固定视角与纸片角色,不用旋转。

1
2
3
4
5
6
7
8
9
var moved = false;
Vector3 MoveDir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")).normalized;
if (MoveDir.magnitude != 0)
{
moved = true;
rb.MovePosition(rb.position + MoveDir * speed * Time.deltaTime);
var Pic = transform.Find("Pic");
}
if (moved) StartCoroutine(SendMessageNextFrame());

攻击与受伤

我们为角色设置了冷却时间以避免游戏体验的不佳。死亡大致相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (other.ATK > ATK && Cool == false) //被攻击且不在无敌时间
{
BeingHurt(other.ATK - ATK); //自己扣血
if (HP > 0) //害活着,通知别的自己受伤了让它们的自己也扣血
{
var hpChange = new MsgBodyHPChange()
{
UserId = UserId,
HP = HP
};
var msgObj = new NetMessage("BeingHurt", hpChange);
Sockets.Instance.SendMessage(msgObj.ToJson());
Cool = true;
StartCoroutine(ResetCool());
}
}

动画

纸片效果需要的转向,同步信息也只传输 Scale 的 X。

1
2
3
4
if (MoveDir.x > 0 && Pic.localScale.x > 0)
Pic.DOScaleX(-0.15f, 0.5f);
else if (MoveDir.x < 0 && Pic.localScale.x < 0)
Pic.DOScaleX(0.15f, 0.5f);

角色的跑动使用了序列帧。我们使用版本的 Animation 似乎并不能支持序列帧,于是我们结合该脚本的启用与图片的切换实现了一个极其简单的图片切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
switch (Final)
{
case "Normal":
Pic.sprite = Pics[0];
Running.enabled = false;
break;
case "Run":
Running.enabled = true;
break;
case "Death":
Pic.sprite = Pics[1];
Running.enabled = false;
break;
case "Hurt":
Pic.sprite = Pics[2];
Pic.DOColor(Color.red, CoolingTime / 2);
Running.enabled = false;
break;
default:
break;
}

摄像机跟随

1
2
3
4
5
6
public GameObject FollowObj;
Vector3 dir;
void Start()
{ dir = transform.position - FollowObj.transform.position; }
void Update()
{ transform.position = FollowObj.transform.position + dir; }

美术与音乐

一开始,我们根据基础的玩法机制,互相碰撞造成伤害联想到了一群小刺猬主角的形象,进一步构想到作为游戏场景的灌木草丛平台。

为了快速创建出一个画面简约又立体、风格统一又可爱的场景,我们参考了独立游戏《Toem》的风格,制作了简单的模型、绘制 UI 贴图、角色序列帧,最后在引擎中整合——在 3D 的平台上放置一些黑白灰三色的卡通纸片角色、多样的草丛、灌木。

2D动画设计

引擎内场景

工程一开始的小窗口设置意外的令美术同学满意,于是继续搭配了像素风格字体、8-bit 风格的音效与背景音乐。

开始界面

除了参考《Toem》,我们还参考并制作了一些小游戏常有的粒子特效(四散的落叶与跑步的灰尘)使场景更加生动。

烟尘粒子

资源

链接:https://pan.baidu.com/s/1JnjtEzLJ9nNAKdMOk9i-aA

提取码:aad4