- 葫芦娃大战妖精
- 1 功能介绍
- 2 代码分析
- 2.1 包com.ttf.gourd.stage
- 2.2 包com.ttf.gourd.server
- 2.3 包com.ttf.gourd.client
- 2.4 包com.ttf.gourd.protocol
- 2.5 包com.ttf.gourd.ai
- 2.6 包 com.ttf.gourd.bullet
- 2.7 包com.ttf.gourd.collision
- 2.8 包com.ttf.gourd.Creature
- 2.9 包com.ttf.gourd.Equipment
- 2.10 包com.ttf.gourd.localplayback
- 2.11 包com.ttf.gourd.constant
- 2.12 包com.ttf.gourd.tool
- 3 游戏测试
- 4 开发过程
- 5 写给玩家
创建服务器:输入Server Port
,再点击“创建服务器”来使用此电脑进行创建服务器,会提示创建成功或者失败。
连接服务器:输入正确的Server IP
和Server Port
,点击“连接服务器“来连接服务器,会提示连接成功或者失败。
点击“准备开始”,当两个客户端都点击之后,进入到排兵布阵界面,玩家有30秒时间,通过移动该阵营的生物到界面上,进行排兵布阵。
30秒倒计时结束之后,玩家双方的界面会同步,然后进行游戏。
具体的游戏逻辑将在“关于游戏”这个功能中介绍。
服务器和两个玩家建立起连接之后,通过传输字符流来保持界面一致,协议便是保证通信的必要和重要手段。
协议分为需要解析协议和不需要解析协议。
不需要解析协议指,用户获得该协议的类型之后,根据类型进行操作,而不需要其他额外的信息。
需要解析协议指,根据用户传过来的消息类型,进行相应的解析,以获取传过来的信息。
在打开游戏的开始界面,有”本地回放“按钮,用户点击”本地回放“按钮之后,会跳转到回放文件目录界面,这个界面首先会加载某个指定文件夹下的文件,如果该文件夹下存在回放文件(这里区分采用文件的后缀.back,来区分该文件是否为本地回放文件),将会将这些文件的相关信息加载到屏幕上,用户可以通过双击,或者选择后,点击右下角的播放进行本地回放。我们本次实验的回放,将会放到"../playbackFiles"
文件夹(即与gourd_vs_monster
文件夹同一个目录下)下。
用户还可以通过导入单个文件和批量导入文件来加载本地回放文件,如果文件名合法并且内容合法,就可以进行回放操作。加载进去之后,需要等待3秒钟,才会开始进行回放。
在回放界面,用户可以通过键盘上的'1', '2', '3', '4', '5', '6', '7', '8', '9', 'q', 'w', 'e', 'r', 't'
按键(分别对应,“大娃”、”二娃“、”三娃“、”四娃“、”五娃“、”六娃“、”七娃“、”爷爷“、”穿山甲“、”蛇精“、”蝎精“、“蜈蚣精”、“蝙蝠精”、“鳄鱼精”)进行对生物的选择,选择之后会在屏幕的左边显示出该生物的图片以及生物的状态信息(比如,生命值,魔法值,攻击力、振奋(技能效果)等)。
回放界面的左边,会有倍速播放回放按钮,用户可以点击来调整回放的播放速度,并且用户可以通过暂停和开始按钮来控制回放的暂停和开始,也可以通过返回按钮,回到选择回放文件界面。
打开本地回放,显示导入回放文件失败的解决方法。
点击添加回放文件或者批量导入(回放文件在playbackFiles
文件夹中,以.back
结尾的文件为回放文件)
此时就可以通过双击或者选择后点击回放进行本地回放。
作者:Xiang-Xiaoyu 联系方式:[email protected]
作者:JansonSong 联系方式:[email protected]
此项目为2020年秋季NJU JAVA大作业,后期可能会进行后续的修改bug以及功能的添加。
游戏中的玩法介绍已经在5 写给玩家处给出。
游戏的运行方式
在pom.xml
文件目录下,打开命令终端,输入mvn package
进行编译,然后输入java -jar target/gourd_vs_monster-1.0.0.jar
运行游戏,或者直接双击gourd_vs_monster-1.0.0.jar
包。
功能:javafx
显示的界面控制。
代码分析:一些界面采用的.fxml
文件进行生成,然后用SceneController.java
对界面进行控制。
App.java
用于加载.fxml
和.css
文件然后进行窗口显示;SceneController.java
用于控制窗口中控件以及绑定事件等。
功能:用于建立服务器,与客户端通信,保证两个阵营的客户端界面同步,以及保存本地回放文件。
代码分析:
GameServer.java
,用于建立服务器,并且等待客户端的连接;SocketController.java
,用于给客户端分配阵营,以及给双方阵营30秒的排兵布阵时间。ServerScene.java
,用于界面的相关信息显示,这里并没有真正显示到屏幕上,而只是将图片以及生物的状态信息进行相应的改变,然后再把这个改变分发给客户端,以达到同步,并且会记录下每次改变到本地文件中,这样可以就行后续的回放。MsgController.java
,用于对通信协议的信息进行解析,以保持服务器和两个客户端的状态信息在一帧之内相同,这个文件与com.ttf.client
包下的MsgController.java
以及com.ttf.localplayback
包下的ContentParse.java
功能都相同,所以后续在介绍相关的两个包的时候,不会对这个文件进行过多解释。
// GameServer.java
// 建立服务器连接后,等待客户端链接,客户端链接上之后,随机分配阵营
while(true) {
try {
if(count == 1) {
campType = randomNum.nextInt(2);
Socket socket = serverSocket.accept();
if(campType == 0)
// 使用SocketServerController.java分配阵营,即告诉该客户端他是什么阵营的,
socketServerController.addGourdPlayer(socket);
else
socketServerController.addMonsterPlayer(socket);
count++;
} else {
Socket socket = serverSocket.accept();
if(campType == 0)
socketServerController.addMonsterPlayer(socket);
else
socketServerController.addGourdPlayer(socket);
break;
}
} catch(Exception e) {
e.printStackTrace();
}
}
// SocketController.java
public void prepareFight() throws IOException {
// 发送开始准备消息给客户端
new NoParseMsg(Msg.PREPARE_GAME_MSG).sendMsg(outGourd);
new NoParseMsg(Msg.PREPARE_GAME_MSG).sendMsg(outMonster);
// ...等待客户端准备就绪的消息,在新的线程中
// ...等待客户端准备就绪的消息,在新的线程中
// 30秒准备倒计时
for(int i = 0; i < 30; i++) {
new CountDownMsg(30 - i).sendMsg(outGourd);
new CountDownMsg(30 - i).sendMsg(outMonster);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
// 30秒倒计时结束,发送开始游戏的消息给两个客户端
new NoParseMsg(Msg.START_GAME_MSG).sendMsg(outGourd);
new NoParseMsg(Msg.START_GAME_MSG).sendMsg(outMonster);
new ServerScene(serverSocket, socketPlayerGourd, socketPlayerMonster, inGourd, outGourd, inMonster, outMonster).startGame();
}
// ServerScene.java 最重要的界面同步
// 初始化资源配置
public void initScene() {
}
// 同步双方摆放阵营的位置信息
public void startGame() throws IOException {
}
// 开始战斗函数
public void startFight() throws IOException {
// 下面在一个新的线程进行监听葫芦娃阵营客户端传来的信息
while (true) {
int gourdMsgType = inGourd.readInt();
if (gourdMsgType == Msg.FINISH_FLAG_MSG) {
gourdFinishFlag = true;
} else if(gourdMsgType == Msg.SOCKET_DISCONNECT_MSG) {
Thread.sleep(3000);
inGourd.close();
outGourd.close();
gourdSocket.close();
if(!serverSocket.isClosed())
serverSocket.close();
break;
}
else {
gourdMsgController.getMsgClass(gourdMsgType, inGourd);
}
}
// 同理,再开一个线程,监听妖精阵营传来的信息,代码同上,省略
// 在这个while(true)中进行服务器与两个客户端的信息同步,并在这里实时发送信息给客户端
while (true) {
// 这里主要是一些信息的设置,比如图片的位置信息设置,生物的状态信息以及子弹和装备的信息设置。
// 举例说明如何同步:葫芦娃阵营的移动是客户端控制的,客户端通过通信,将位置和状态变动信息传给服务器,服务器在上面监听事件中,进行监听,然后在这个线程中进行获取监听获得的信息,并自己设置位置信息以及将葫芦娃的变动信息发给妖精
}
}
//根据阵营以及两个family判断是谁获胜了,-1,0,1,2返回值只可能是这四种状态
private int judgeWin(HashMap<Integer, Creature> myFamily, HashMap<Integer, Creature> enemyFamily) {
}
// MsgController.java
public class MsgController {
// 用于解析接收到的消息,根据消息的类型,用对应的协议进行解析,然后获取解析得到的值,为了保证`ServerScene.java`能够获得完整的解析信息,会定义需要临时列表,供`ServerScene.java`调用,然后进行信息同步
public void getMsgClass(int msgType, ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
switch (msgType) {
case Msg.POSITION_NOTIFY_MSG: {
break;
}
// ......省略了其他消息类型
default: {
break;
}
}
}
}
功能:用于连接服务器,然后进行不同阵营的初始化以及游戏界面的显示和与服务器进行通信。
代码分析:
GameClient.java
用于连接服务器,保持通信Camp.java
用于不同阵营中的相同操作,比如初始化一些都需要初始化的变量,以及摆放生物时的信息同步等GourdCamp.java
和MonsterCamp.java
用于不同阵营的初始化。GameStartFight.java
用于游戏界面的操作和与服务器相关信息同步的操作。MsgController.java
用于通信的信息解析,与com.ttf.gourd.server
包中相似,不在赘述。
// GameStartFight.java
public void start() {
init(campType, myFamily, enemyFamily);
// 下面是在一个新的线程中操作,用于监听服务器端发来的信息
while (true) {
int msgType = in.readInt();
if (msgType == Msg.FINISH_GAME_FLAG_MSG) {
msgController.getMsgClass(msgType, in);
if(campType.equals(msgController.getWinCampType()))
gameOver(Constant.gameOverState.VICTORY);
else
gameOver(Constant.gameOverState.DEFEAT);
gameOverTimeMillis = System.currentTimeMillis();
gameOverFlag = true;
} else if(msgType == Msg.SOCKET_DISCONNECT_MSG) {
break;
}
else
msgController.getMsgClass(msgType, in);
}
// 下面是显示游戏界面和发送信息给服务器的while(true)
while(true) {
}
}
// 下面是初始化一些绑定事件,用于操纵自己放的生物
private void init(String camp, HashMap<Integer, Creature> myFamily, HashMap<Integer, Creature> enemyFamily) {
}
//游戏结束,播放结束的图片
private void gameOver(int gameOverState) {
}
功能:这个包主要用于网络通信以及本地回放功能的依赖功能,协议。
代码分析:首先定义一个接口Msg
,在这个接口中,静态变量定义协议的类型,然后再写多个类用于处理协议内部信息。
// Msg.java
public interface Msg {
// 负责通知位置变动信息
public static final int POSITION_NOTIFY_MSG = 4;
// ...还有很多其他协议类型,详情请参考com.ttf.gourd.protocol下的Msg.java
// 发送信息函数
public void sendMsg(ObjectOutputStream outStream) throws IOException;
// 接受信息函数
public void parseMsg(ObjectInputStream inStream) throws IOException, ClassNotFoundException;
}
// 举例:PositionNotifyMsg.java
// 这样定义,用于序列化
class PositionNotify implements Serializable {
public String campType;
public int creatureId;
public double layoutX;
public double layoutY;
}
// 使用Msg的接口,重写发送和接收函数。
public class PositionNotifyMsg implements Msg {
private static final int msgType = POSITION_NOTIFY_MSG;
PositionNotify positionNotify = new PositionNotify();
// 用于接收解析定义用
public PositionNotifyMsg() {
}
// 用于发送定义用
public PositionNotifyMsg(String campType, int creatureId,
double layoutX, double layoutY) {
positionNotify.campType = campType;
positionNotify.creatureId = creatureId;
positionNotify.layoutX = layoutX;
positionNotify.layoutY = layoutY;
}
@Override // 重写发送函数
public void sendMsg(ObjectOutputStream outStream) throws IOException {
outStream.writeInt(msgType); // 发送协议类型
outStream.writeObject(positionNotify); // 发送协议中需要传递的内容
outStream.flush();
}
@Override
public void parseMsg(ObjectInputStream inStream) throws IOException, ClassNotFoundException {
// 在之前会有一个判断这个协议是什么类型的,然后才会调用这个函数,来获取协议中的信息。
positionNotify = (PositionNotify) inStream.readObject();
}
// 下面还有获取这些解析得到的变量信息,没有展现出来
}
// 如何使用这些协议
// 发送方
ObjectOutputStream out;
new PositionNotifyMsg("GOURD", 2, 300, 300).sendMsg(out);
// 接收方
ObjectInputStream in;
PositionNotifyMsg positionNotifyMsg = new PositionNotifyMsg().parseMsg(in);
String campType = positionNotifyMsg.getCampType();
int creatureId = positionNotifyMsg.getCreatureId();
double layoutX = positionNotifyMsg.getLayoutX();
double layoutY = positionNotifyMsg.getLayoutY();
这个包内主要包括一个接口AiInterface
,和两个实现该接口的类FoolAi
和FirstGenerationAi
,这个接口的设计模式是委托模式,与Creature
类的关系是聚合,Creature
将方向的选择与攻击功能交给AiInterface
来实现
public interface AiInterface {
//观测
public Creature observe(Creature myCreature, HashMap<Integer,Creature> enemies);
//移动方式
public void moveMod(Creature myCreature, HashMap<Integer,Creature> enemies);
//攻击模式
public Bullet aiAttack(Creature myCreature, HashMap<Integer,Creature> enemies);
}
该接口定义了三个功能,分别是观测敌军选取攻击目标,基于观测选择移动方式(设置方向),基于观测攻击目标产生子弹
FoolAi
采用完全随机的选取方向,观测的第一优先级生物是最近的生物,攻击最近的生物。缺点有两点,其一是生物每一帧的方向都可能会发生改变,导致图片频繁抖动。其二是选取最近的生物攻击会导致总体上生物会越来越趋向合在一起,不美观,操作难。
FirstGenerationAi
综合距离,血量,我方攻击力,敌方防御力等多项因素设置第一优先攻击目标,根据攻击距离以及与攻击目标之间的关系设置方向,并在每次设置方向后锁定方向一段时间,解决了图片抖动问题,使得生物在自动攻击与移动时相对分散且较为智能
这个ai
接口的可扩展性好,只要实现三个方法就行,可继承FirstGenerationAi
类实现第二代ai
,也可通过实现接口的方式为任何一个生物设计ai
战斗方式
这个包里面包含了子弹类Bullet
和子弹状态BulletState
子弹分为近战子弹和远程子弹
近战子弹为各种爪痕,远程子弹为各种不同颜色不同半径的圆球
对于远程子弹而言,它是追踪的,当目标生物死亡或者发生碰撞时,它会消失
对于近战子弹而言,它是打出即碰撞的,没有运行轨迹,是打出必定命中的
任何子弹在且仅在命中目标时会给发出子弹的生物回复蓝量
将子弹内部逻辑 移动move()
绘制draw()
检测碰撞封装成一个方法update()
在每一帧内执行这个update
方法,该方法返回一个可能存在的碰撞Collision
子弹包含各种各样的状态,NONE为普通子弹,其他的各种在碰撞时会给目标生物附加各种状态
这个包内仅包含一个类Collision
碰撞类
对于每一个Collision
对象而言,每当其产生时,就会调用碰撞方法执行碰撞,给目标生物扣血并附加状态,回复攻击者的蓝量
这个包内包含三个方面的内容:
1.Creature
类和Creature子类
以及产生它们的对象的工厂类CreatureFactory
2.CreatureState
生物状态枚举和CreatureStateWithClock
生物状态时钟
3.ImagePosition
Creature
是包括葫芦娃阵营的九个生物和妖精阵营的六个生物的父类
CreatureFactory
是生产Creature
子类对象的工厂
15种子类和父类Creature
的构造器都是默认访问权限,包外的生物对象仅能在工厂内创建,体现了面向对象的封装特性
Creature
类的主要功能被封装在update()
和notMyCampUpdate()
两个方法中,分别用来更新本地我的阵营和敌方阵营的生物信息
Creature
内部的主要功能包括
生物图像绘制draw()
生物移动move()
生物攻击ai
对象实现
生物状态信息更新setCreatureState()
生物技能实现QAction(),EAction(),RAction()
生物信息显示showMessage()
......
由于这部分内容过多,这里不详细介绍,具体可见Creature.java
,此类中有详细注释
这里以SnakeMonster
为例,介绍一下Creature子类的设计方法
public class SnakeMonster extends Creature {
//数据域:包括技能标志位,技能使用时间,技能效果时长,技能冷却时间,技能属性改变量等信息
public SnakeMonster(...) {
super(...)
}
@Override
//重写父类update()方法,加入技能的使用和技能状态的更新
public ArrayList<Bullet> update() {
...
}
//更新技能状态,技能持续时间是否结束
private void updateActionState() {
...
}
//重写父类的QER三项技能
@Override
public ArrayList<Bullet> qAction() {
...
}
@Override
public void eAction() {
...
}
@Override
public void rAction() {
...
}
//技能失效时调用相关方法
private void disposeQAction() {
...
}
private void disposeEAction() {
...
}
private void disposeRAction() {
...
}
}
只要子类的行为方式和父类不相同的地方,就可以重写父类方法
但父类方法没有必要定义成abstract
因为有些子类会重写它,而另一些不会(不是所有生物都有三个技能)
CreatureFactory
中有两个对外接口
其一是hasNext()
询问此阵营是否还会有下一个对象
其二是next()
返回下一个Creature
对象
通过这个工厂的设计,我们将生物对象和外部分离开来,使它具有良好的封装性
CreatureState
是个枚举类型,其中包含了多种多样的生物状态,还包括了生物技能状态
CreatureStateWithClock
是一个含有时钟的状态类
它可以通过update()
方法对状态时钟进行实时更新
这个类帮助Creature
实现了各种buff
和debuff
功能,已经这些功能的倒计时,同时也实现了技能的cd
倒计时
这个类表示的是一个位置信息,Creature
和Bullet
以及Equipment
都会用到它
这个包内包含了各种装备的信息以及装备生成工厂
1.Equipment
是一个抽象类,游戏中存在的5种装备都继承自这个类并且实现了这个类中的抽象方法
2.EquipmentFactory
是装备对象的生成工厂
这个类中有四个较为重要的方法
draw()
绘制装备,属于各个装备的共有方法
dispose()
擦除装备,属于各个装备的共有方法
takeEffect()
装备生效,属于具体装备的方法
giveUpTakeEffect()
装备移除生效,属于具体装备的方法
因此后两者被设计成抽象方法,Equipment
被设计成抽象类
与Creature
不同的是,Equipment
的每个装备都需要重写takeEffect()
和giveUpTakeEffect()
方法,所以将其设计为抽象类
Creature
类的子类只有部分会重写update()``QAction()
等方法,所以Creature没有被设计为抽象类
针对每个Equipment
子类,我们重写takeEffect()
和giveUpTakeEffect()
方法
在装备生效和装备失效时调用它们
EquipmentFactory
与CreatureFactory
的设计原则是一样的
基于实现包内的装备与外部完全分离,将装备的产生完全封装在EquipmentFactory
中
同是hasNext()
和next()
方法,其使用方式并无多大区别
内部实现与思想略有区别
EquipmentFactory
的hasNext()
是综合基于对窗口中装备上限显示以及装备显示间隔的考量
CreatureFactory
的hasNext()
是因为阵营的生物个数是恒定的
功能:这个包主要用于本地回放。
代码分析:
PlayBackFile.java
主要用于存储本地回放文件的相关信息;LoadPlayBackFiles.java
主要用于显示本地回放文件,添加本地回放文件到list中,通过选择本地回放文件进行回放;GamePlayBack.java
,关键文件,用于初始化回放中的资源,以及接收本地回放文件中协议的类型,然后调用ContentParse.java
文件进行内容解析,根据解析得到的信息来设置界面上的图片信息以及葫芦娃,子弹和装备的状态信息。
// GamePlayBack.java文件
public class GamePlayBack {
// 首先初始化资源信息
public void initGame() {
}
public void playBackGame() {
initGame();
init();
// 下面是在一个新的线程里进行工作的,每次循环都会sleep一帧的时间,这个时间是可以通过倍速进行修改的。
// ...
// 用于解析回放文件中的信息
while(true) {
int contentType = inputStream.readInt();
// 第一个if,是原游戏的一帧结束,即break,在下面进行设置各项资源,然后继续解析
if(contentType == Msg.FRAME_FINISH_FLAG_MSG) break;
else if(contentType == Msg.FINISH_GAME_FLAG_MSG) {
gameOverFlag = true;
gameOver(Constant.gameOverState.VICTORY);
inputStream.close();
break;
}
else contentParse.parsePlayBackContent(inputStream, contentType);
}
// ...
}
// 这个函数主要就是初始化界面以及绑定一些函数的地方
private void init() {
}
// 这个函数主要是用于检测到游戏结束后,播放结束的一个图片,然后任意点击屏幕即可退出回放界面
private void gameOver(int gameOverState) {
}
}
这个包中包含了游戏过程中各种常量信息,以及很多静态数据
1.各种长度信息,窗口大小信息等
2.各种时间信息,帧时间信息,ai
方向锁定时间信息等
3.各种常量参数,胜败状态,方向状态,爪痕状态等
4.各种生物及装备的ID
5.静态加载的图片信息
功能:主要是作为工具类使用
准备:随机生成一个信息种类,然后根据信息种类随机生成相应的信息,然后保存到本地,与此同时将该信息存放到一个ArrayList
中。
测试:通过读入保存到本地的测试文件,根据协议进行协议,即通过不同的消息种类进行不同的解析,然后与ArrayList
存储的信息进行比较。
结果:完全一致,说明协议相关的类和函数从测试来看是不存很大问题的。
由于葫芦娃阵营的生物较多,人为操控技能释放难度高于妖精阵营,故而预期要达到纯ai
操作胜率六四开,葫芦娃6,妖精4
当前数据下共进行了511局,妖精胜利213局,葫芦娃胜利298局,双方都可以使用技能,都可以拾取装备,完全ai
控制
妖精胜率41.68%,葫芦娃胜率58.32%,几乎符合预期
游戏测试文件在jlog
文件夹下,此数据是在单机情况下测试的
详细测试代码请看github
地址 https://github.com/JansonSong/gourd_vs_monster/tree/dev/gourd_vs_monster/src/main/java/com/sjq/gourd/localtest
耗时约1day
耗时约1day
耗时约12days
详见 https://github.com/JansonSong/gourd_vs_monster/tree/dev
耗时约8days
详见 https://github.com/JansonSong/gourd_vs_monster
耗时约3days
宋鉴清:游戏策划,界面设计,图片的PS,网络通信,远程同步,本地回放,单元测试等功能
项仁浩:游戏单机版的实现,生物类、子弹类、技能装备等设计,单机游戏设计等功能。
功能完成README.md的编写。
游戏分为两个阵营,葫芦娃阵营和妖精阵营,葫芦娃阵营有9个生物,妖精阵营有6个生物
玩家每个时刻最多只能操控唯一的生物,其他生物由ai
控制
当玩家为葫芦娃阵营时,可通过主键盘数字键1~9进行对操控目标的选取
当玩家为妖精阵营时,可通过主键盘数字键1~6进行对操控目标的选取
操控生物选取完毕后,屏幕侧面会显示你当前操控生物的各项信息,你可以通过WSAD
键进行上下左右的移动
你可以通过鼠标左键点击敌方目标进行攻击,一旦选取敌方目标,当前操控生物就会自动对选定的敌方目标进行攻击(无须反复点击,只要在攻击范围内就会自动攻击),并且此时敌方目标的信息会被显示在侧边栏
一旦你通过数字键更换了当前操控生物,此生物就会自动由ai
接管
葫芦娃阵营除了穿山甲之外都有且仅有一个技能Q
妖精阵营蛇精和蝎子精都有QER
三个技能,其他小妖精只有R技能
葫芦娃阵营 大娃,二娃,三娃,四娃,五娃,六娃,七娃,爷爷,穿山甲
妖精阵营 蛇精,蝎子精,蜈蚣精,蝙蝠精,鳄鱼精,蛤蟆精
两个玩家,双方各自控制一方,使用主键盘数字键选择控制生物,对于每个玩家而言,己方阵营的每个生物有着唯一的控制编号
控制编号 | 名称 | 攻击类型 | 攻击效果 | 基础生命 | 基础魔法 | 基础攻击 | 基础防御 | 基础攻速 | 基础移速 | 攻击范围 | 技能及其描述 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 大娃 | 近战 | 爪痕1 | 3500 | 100 | 120 | 40 | 0.5 | 10 | 80 | 技能Q(能屈能伸):消耗所有蓝量,变成原来的一半大小,增加移速5,降低攻击力20,攻击范围增加100,持续五秒,该技能五秒内最多使用一次 |
2 | 二娃 | 远程 | 橙色子弹 | 3000 | 150 | 80 | 35 | 0.5 | 10 | 400 | 技能Q(振奋人心):消耗所有蓝量,给2.0*当前攻击范围的范围内的所有友军增加时长五秒的振奋buff,该技能20秒内最多使用一次 |
3 | 三娃 | 近战 | 爪痕1 | 4500 | 100 | 150 | 60 | 0.4 | 10 | 80 | 技能Q(无敌金身):消耗所有蓝量,大幅度提高攻击力(50)和防御力(100),移速减少3,若移速不足3,减少到0,持续五秒,该技能五秒内最多使用一次 |
4 | 四娃 | 远程 | 红色子弹 | 3000 | 150 | 80 | 40 | 0.5 | 12 | 400 | 技能Q(火焰之神的降临):消耗所有的蓝量,给1.5*当前射程范围内的所有敌军发射一颗带有"火焰之子"特效的子弹,当该子弹命中敌人之后,造成瞬间伤害与四娃普通子弹伤害相同,并给目标附加3秒钟的灼烧效果,该技能没有CD限制,蓝满即可释放 |
5 | 五娃 | 远程 | 蓝色子弹 | 3000 | 150 | 80 | 40 | 0.5 | 12 | 400 | 技能Q(冰霜之神的降临):消耗所有的蓝量,给1.5*当前射程范围内的所有敌军发射一颗带有"冰霜之心"特效的子弹,当该子弹命中敌人之后,造成瞬间伤害与五娃普通子弹伤害相同,并给目标附加2秒钟的冰冻效果,该技能没有CD限制,蓝满即可释放 |
六娃 | 近战 | 爪痕1 | 3500 | 100 | 150 | 20 | 1.0 | 12 | 90.0 | 技能Q(你看不见我):消耗所有蓝量,隐身,移速提升5,攻击力降低20,防御力提升100,该技能持续5秒,5秒内最多使用一次 | |
七娃 | 远程 | 紫色子弹 | 2500 | 100 | 80 | 20 | 0.9 | 15 | 600 | 技能Q(看我法宝):5秒内提升基础攻速的300%,攻击范围提升400,5秒内最多使用一次 | |
8 | 爷爷 | 远程 | 绿色子弹 | 3000 | 200 | 75 | 10 | 0.5 | 12 | 300 | 被动(山神的庇佑):爷爷不会以妖精为攻击目标,只会以葫芦娃为治疗目标,每颗普通子弹都带有“治愈之神”的特效,每颗子弹的治疗值为爷爷的攻击力 技能Q(山神的祝福):爷爷对1.5*当前射程范围内的所有友军发射一颗带有“超大号治愈之神”特效的子弹,命中时对目标治疗1000生命值,并使得目标获得时长5秒的治愈buff |
9 | 穿山甲 | 近战 | 爪痕1 | 3000 | 100 | 75 | 60 | 0.5 | 12 | 100.0 | 没有技能 |
控制编号 | 名称 | 攻击类型 | 攻击效果 | 基础生命 | 基础魔法 | 基础攻击 | 基础防御 | 基础攻速 | 基础移速 | 攻击范围 | 技能及其描述 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 蛇精 | 远程 | 黑色子弹 | 5000 | 200 | 120 | 30 | 0.7 | 15 | 500 | Q技能(剧毒之牙):消耗50%最大蓝量,向2.0*攻击范围内的所有敌方放射一颗带有“剧毒之牙”特效的子弹,命中时对目标造成等同蛇精普通子弹的伤害,并附加“重伤”效果,该技能每五秒只能使用一次 E技能(复活之术):消耗100%最大蓝量,如果己方有妖精死掉了,就复活一只妖精,复活优先级:蝎子精>蜈蚣精>蝙蝠精>鳄鱼精>蛤蟆精。同样死掉的情况下,复活优先级高的,由该技能复活的妖精蓝量为0,蝎子精血量为满血的一半,其他妖精血量为满血,妖精复活位置为其上一次死掉的位置。如果没有妖精死亡,则视为空技能,只扣蓝量,没有效果,该技能每五秒只能使用一次 R技能(金身护罩):消耗50%最大蓝量,瞬间加600血量,在五秒内防御力提升30,该技能每五秒只能使用一次 |
2 | 蝎子精 | 近战 | 爪痕3 | 7500 | 150 | 150 | 55 | 0.5 | 10 | 100 | Q技能(致残之爪):消耗100%最大蓝量,对5.0*当前近战攻击范围内的所有敌方进行致残爪击,并在爪击命中时给目标附加“残废”效果,效果持续5秒,该技能五秒内只能使用一次 E技能(狂暴之心):消耗50%最大蓝量,在五秒内,移速增加10,防御增加20,攻击增加30,攻速增加100%基础攻速,攻击范围增加80,血量增加600,该技能每五秒只能使用一次 R技能(同命):与消耗100%最大蓝量,在效果持续的10秒内,与敌方血量最低的固定(不会实时变动)三个生物绑定,每当蝎子精掉血时,绑定生物掉相同血量,该技能每10秒只能使用一次 |
3 | 蜈蚣精 | 近战 | 爪痕4 | 3500 | 100 | 75 | 30 | 0.5 | 8 | 80 | R技能(复活吧!女王大人):触发条件1.蛇精死掉了 2.四个小怪全部活着 3.四个小怪蓝量都是满的 4.按下R键 效果:四个小怪消耗全部的当前血量和全部的所有蓝量,死亡后复活蛇精,蛇精复活时满血满蓝 |
4 | 蝙蝠精 | 远程 | 褐色子弹 | 3500 | 100 | 70 | 15 | 1.0 | 18 | 600 | 同上 |
5 | 鳄鱼精 | 近战 | 爪痕2 | 3500 | 100 | 100 | 50 | 0.5 | 8 | 80 | 同上 |
6 | 蛤蟆精 | 远程 | 黄色子弹 | 3500 | 100 | 75 | 40 | 0.5 | 10 | 500 | 同上 |
装备名称 | 装备效果 | 装备备注 |
---|---|---|
玉如意 | 所有生物都可以拾取,所有生物都可以使用 对葫芦娃而言是增加8移速,增加20攻击力 对于妖精而言是增加20防御,并瞬间恢复1000生命值 |
|
玉簪 | 所有生物都可以拾取,但只有蛇精才可以使用 蛇精拾取瞬间恢复1500血量,增加5移速,增加200%基础攻速,增加攻击力20,增加防御力20 其他生物可以拾取,但没有作用 |
|
魔镜 | 对于任何远程攻击生物,都可拾取并装备,增加远程攻击射程200 对葫芦娃方生物有额外效果,可在拾取瞬间恢复500生命值 近战生物可拾取但无装备效果 |
|
刚柔阴阳剑 | 任何生物都可以拾取并装备,增加基础攻速100%,增加攻击力20 | |
百宝锦囊 | 任何生物都可拾取,但无法装备,效果是拾取瞬间恢复500生命值 |
这个项目从开始布置,到完成为止,一直在编写,但是依然有很多想实现而没有实现的功能,并且程序的鲁棒性有待提高,主要表现在,对文件路径以及操作等正确性的要求比较高,