项目简介:射击生存类小游戏
项目采用技术:
游戏引擎: Unity
编程语言: Java
图形处理: NVIDIA PhysX (物理引擎), HDRP (High Definition Render Pipeline)
音效与音乐: FMOD, Wwise
版本控制: Git
功能需求分析:
角色控制:玩家能够使用键盘和鼠标控制角色移动、瞄准和射击。
武器系统:提供多种武器供玩家选择,每种武器有不同的伤害、射速和准确度。
敌人AI:敌人能够智能地追踪玩家,并在必要时进行反击。
生存要素:玩家需要收集资源来维持生命,如食物、水和医疗用品。
游戏难度:随着游戏进程,敌人数量增多、攻击力提升,增加游戏挑战性。
成就与奖励:完成特定任务或击杀特定敌人可以获得奖励或成就。
项目亮点:
真实物理效果:使用NVIDIA PhysX物理引擎,实现逼真的武器后坐力、物体碰撞等效果。
高画质渲染:利用HDRP进行高质量渲染,打造逼真的游戏场景。
丰富多样的武器:从手枪到火箭筒,各种武器供玩家选择。
智能敌人AI:敌人具有复杂的AI逻辑,使游戏更具挑战性。
二、功能架构图
三、个人任务简述
负责输入处理模块,以及图片,动画的视觉制作,路径查找功能的实现,角色远程攻击
1. 完成的任务与功能:
简单描述将自己完成的有特色的地方、重难点地方。
|--------|-------------|----------------------|
| 序号 | 完成功能与任务 | 描述 |
| 1 | 路径查找功能 | 使用了路径查找算法,实现了角色的合法移动 |
| 2 | 远程攻击 | 实现了角色的远程攻击方式 |
本人负责功能
* 路径查找功能的实现:
- 核心原理 :路径查找算法的核心在于寻找从起点到终点的最优路径。这通常涉及对空间或网络的建模,以及搜索算法的运用。
- 搜索算法的选择 :包括深度优先搜索(DFS)、广度优先搜索(BFS)、Dijkstra算法、A*算法等。这些算法各有特点,适用于不同的场景和需求。由于时间有限,只选择了一种算法进行学习。
java
package Game;
public class PathNode implements Comparable<PathNode>{
private Grid stateData;
private PathNode parentNode;
private int g, h, f, depth; //g 从起点移动到指定方格的移动代价, h 从指定的方格移动到终点的估算成本
public PathNode getParentNode() {
return parentNode;
}
public void setParentNode(PathNode parentNode) {
this.parentNode = parentNode;
}
public int getG() {
return g;
}
public void setG(int g) {
this.g = g;
}
public int getH() {
return h;
}
public void setH(int h) {
this.h = h;
}
public int getF() {
return f;
}
public void setF(int f) {
this.f = f;
}
public int getDepth() {
return depth;
}
public void setDepth(int depth) {
this.depth = depth;
}
public Grid getStateData() {
return stateData;
}
public void setStateData(Grid stateData) {
this.stateData = stateData;
}
public PathNode(Grid stateData, PathNode parentNode, int g, int h, int depth) {
this.stateData = stateData;
this.parentNode = parentNode;
this.g = g;
this.h = h;
this.f = this.g + this.h;
this.depth = depth;
}
@Override
public int compareTo(PathNode other)
{
int NodeComp = (this.f - other.getF()) * -1;
if (NodeComp == 0)
{
NodeComp = (this.depth - other.getDepth());
}
return NodeComp;
}
}
java
package Game;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Pathfinder{
private List<PathNode> openNodes;
private List<PathNode> closedNodes;
private List<PathNode> nodesToGoal;
private List<Grid> pathToGoal;
private WorldGrids worldGrids;
private int depth;
private GameObject object;
private List<Grid> gridsOfObject;
private int centre;
public Pathfinder(WorldGrids worldGrids, GameObject object) {
this.worldGrids = worldGrids;
this.object = object;
this.pathToGoal = new ArrayList<>();
this.nodesToGoal = new ArrayList<>();
this.gridsOfObject = worldGrids.getGrid(object);
Grid tmp = this.worldGrids.getGrid(object.getX(), object.getY());
for(int i = 0; i < gridsOfObject.size(); i++){
if(gridsOfObject.get(i).equals(tmp)){
centre = i;
break;
}
}
}
public List<PathNode> getOpenNodes() {
return openNodes;
}
public void setOpenNodes(List<PathNode> openNodes) {
this.openNodes = openNodes;
}
public List<PathNode> getClosedNodes() {
return closedNodes;
}
public void setClosedNodes(List<PathNode> closedNodes) {
this.closedNodes = closedNodes;
}
public int getDepth() {
return depth;
}
public void setDepth(int depth) {
this.depth = depth;
}
public List<PathNode> getNodesToGoal() {
return nodesToGoal;
}
public void setNodesToGoal(List<PathNode> nodesToGoal) {
this.nodesToGoal = nodesToGoal;
}
public List<Grid> getPathToGoal() {
return pathToGoal;
}
public void setPathToGoal(List<Grid> pathToGoal) {
this.pathToGoal = pathToGoal;
}
public WorldGrids getWorldGrids() {
return worldGrids;
}
public boolean ownGrid(Grid currentGrid, Grid otherGird){
int deltaX = currentGrid.getGridX() - getCentreGrid().getGridX();
int deltaY = currentGrid.getGridY() - getCentreGrid().getGridY();
for(Grid grid : gridsOfObject){
Grid tmp = worldGrids.get(grid.getGridX() + deltaX, grid.getGridY() + deltaY);
if(tmp.equals(otherGird)){
return true;
}
}
return false;
}
public List<Grid> nextMoves(Grid grid){
List<Grid> moves = new ArrayList<>();
int[] x = {0, 0, -1, 1, -1, -1, 1, 1};
int[] y = {-1, 1, 0, 0, -1, 1, 1, -1};
int deltaX = grid.getGridX() - getCentreGrid().getGridX();
int deltaY = grid.getGridY() - getCentreGrid().getGridY();
for(int i = 0; i < 8; i++){
boolean flag = true;
for(int j = 0; j < gridsOfObject.size(); j++){
int nextX = gridsOfObject.get(j).getGridX() + deltaX + x[i];
int nextY = gridsOfObject.get(j).getGridY() + deltaY + y[i];
Grid next = worldGrids.get(nextX, nextY);
if(!ownGrid(grid, next) && !next.isAccessible()){
flag = false;
break;
}
}
if(flag){
moves.add(worldGrids.get(grid.getGridX() + x[i], grid.getGridY() + y[i]));
}
}
return moves;
}
public int getHeuristic(Grid currentPos, Grid goalPos){
return (Math.abs(goalPos.getGridX() - currentPos.getGridX()) + Math.abs(goalPos.getGridY() - currentPos.getGridY())) * 10;
}
public int getCost(Grid currentPos, Grid goalPos){
if(Math.abs(goalPos.getGridX() - currentPos.getGridX()) != 0 && Math.abs(goalPos.getGridY() - currentPos.getGridY()) != 0){
return 14;
} else {
return 10;
}
}
public int getDistance(int x1, int y1, int x2, int y2){
double deltaX = x1 - x2;
double deltaY = y1 - y2;
return (int)Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
}
public Grid getCentreGrid(){
return this.gridsOfObject.get(centre);
}
public List<Grid> shortestPath(Grid goalPos){
if(((Enemy)object).getTarget() == null) return null;
worldGrids.updateGrids();
Grid startPos = (Grid) getCentreGrid().clone();
openNodes = new ArrayList<>();
closedNodes = new ArrayList<>();
depth = 0;
boolean hasGoal = false;
openNodes.add(new PathNode(startPos, null, 0, getHeuristic(startPos, goalPos), depth));
while(openNodes.size() != 0){
closedNodes.add(openNodes.get(openNodes.size() - 1));
PathNode currentNode = closedNodes.get(closedNodes.size() - 1);
Grid current = currentNode.getStateData();
openNodes.remove(openNodes.size() - 1);
int distance = getDistance(current.getX(), current.getY(), goalPos.getX(), goalPos.getY());
int r = ((Enemy)object).getTarget().getRadius();
if (distance < object.getRadius() + r + 10 || currentNode.getDepth() > 25) {
hasGoal = true;
break;
}
List<Grid> expanded = nextMoves(current);
NodeLoop:
for(int i = 0; (i < openNodes.size() || i < closedNodes.size()); i++){
int s = expanded.size() - 1;
while(s >= 0){
if(i < openNodes.size()){
Grid OpenstateData = openNodes.get(i).getStateData();
if(OpenstateData.equals(expanded.get(s))){
if((currentNode.getG() + getCost(current, OpenstateData)) < openNodes.get(i).getG()){
openNodes.get(i).setG(currentNode.getG() + getCost(current, OpenstateData));
openNodes.get(i).setH(getHeuristic(expanded.get(s), goalPos));
openNodes.get(i).setF(openNodes.get(i).getG() + openNodes.get(i).getH());
openNodes.get(i).setParentNode(currentNode);
}
expanded.remove(s);
if (expanded.isEmpty()) {
break NodeLoop;
}
s--;
continue ;
}
}
if(i < closedNodes.size()){
if (closedNodes.get(i).getStateData().equals(expanded.get(s))){
expanded.remove(s);
if (expanded.isEmpty())
{
break NodeLoop;
}
}
}
s--;
}
}
if (!expanded.isEmpty()) {
for (int i = 0; i < expanded.size(); i++) {
openNodes.add(new PathNode(
expanded.get(i),
currentNode,
currentNode.getG() + getCost(current, expanded.get(i)),
getHeuristic(expanded.get(i), goalPos),
currentNode.getDepth() + 1));
}
}
Collections.sort(openNodes);
}
try {
if (hasGoal) {
int depth = closedNodes.get(closedNodes.size() - 1).getDepth();
PathNode parent = closedNodes.get(closedNodes.size() - 1);
for (int s = 0; s <= depth; s++) {
nodesToGoal.add(parent);
pathToGoal.add(parent.getStateData());
parent = nodesToGoal.get(s).getParentNode();
}
Collections.reverse(pathToGoal);
return pathToGoal;
}
return null;
} catch (NullPointerException e){
if(pathToGoal != null) return pathToGoal;
return null;
}
}
}
监听玩家输入。
java
package Game;
public enum Direction {
LD, RU, LU, RD, L, R, U, D, STOP;
public static double toDegree(Direction dir){
switch (dir){
case R: return 0;
case RU: return 45;
case U: return 90;
case LU: return 135;
case L: return 180;
case LD: return 225;
case D: return 270;
case RD: return 315;
default: return 360;
}
}
public static double getVectorX(Direction dir){
switch (dir){
case R: return 1;
case RU: return Math.cos(Math.toRadians(45));
case U: return 0;
case LU: return -Math.cos(Math.toRadians(45));
case L: return -1;
case LD: return -Math.cos(Math.toRadians(45));
case D: return 0;
case RD: return Math.cos(Math.toRadians(45));
default: return -2;
}
}
public static double getVectorY(Direction dir){
switch (dir){
case R: return 0;
case RU: return Math.cos(Math.toRadians(45));
case U: return 1;
case LU: return Math.cos(Math.toRadians(45));
case L: return 0;
case LD: return -Math.cos(Math.toRadians(45));
case D: return -1;
case RD: return -Math.cos(Math.toRadians(45));
default: return -2;
}
}
}
远程攻击的设定:
java
package Game;
import java.awt.*;
public class Ball extends Weapon implements Cloneable{
private final int attackRange = 40;
private boolean ultimateState;
private int num;
protected int picOffset;
private int[] imgOrder = {4,7,5,6,1,2,3,0};
public Ball(String name, int radius, int speed, int damage, int coldDownTime, Role role, World world){
super(name, radius, speed, damage, coldDownTime, role, 300, 360,true, world);
}
public void initFireball(Direction dir){
if(this.num <= 0) return;
this.setDir(dir);
double cosA = (Math.cos(Math.toRadians(Direction.toDegree(dir))));
double sinA = -(Math.sin(Math.toRadians(Direction.toDegree(dir))));
this.x = (int)(this.host.getX() + this.host.getRadius() * cosA);
this.y = (int)(this.host.getY() + this.host.getRadius() * sinA);
this.xIncrement = (int)(this.speed * cosA);
this.yIncrement = (int)(this.speed * sinA);
world.addObject((Ball) this.clone());
this.num--;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public void Attack(){
}
@Override
public boolean collisionDetection(GameObject object) {
return ((!object.equals(this.host)) && !(object instanceof Weapon) && super.collisionDetection(object));
}
public void draw(Graphics g){
int picY = imgOrder[dir == Direction.STOP ? oldDir.ordinal() : dir.ordinal()];
int picX = maintainState(3);
this.x += xIncrement;
this.y += yIncrement;
drawOneImage(g, this.name, getPicOffset(), this.x, this.y, picX, picY);
world.collisionDetection(this);
}
public void setState(){
initFireball(this.host.getDir() == Direction.STOP ? host.getOldDir() : host.getDir());
super.setState();
}
public void setUltimateState(){
if(getNum() < 8) return;
for(Direction dir : Direction.values()){
if(dir == Direction.STOP) continue;
initFireball(dir);
}
super.setState();
}
public int maintainState(int n){
this.state++;
if(state >= n) {
state = 0;
}
return state;
}
public void collisionResponse(GameObject object){
world.removeObject(this);
resetState();
object.onAttack(this);
}
public int getPicOffset(){
return picOffset;
}
@Override
public String toString(){
String strBuf = name;
strBuf += ":";
strBuf += String.valueOf(getNum());
return strBuf;
}
}