简介
该demo基于OpenHarmony系统使用JS语言进行编写,用户通过不同的触屏方式来实现对战棋游戏的游玩效果。
工程目录
完整的项目结构目录如下
.
├─entry\src\main
│ │ config.json
│ ├─js
│ │ └─MainAbility
│ │ ├─common
│ │ │ ├─ js
│ │ │ │ ├─Attack_Time.js
│ │ │ │ ├─Backstate_Map.js
│ │ │ │ └─Chess.js
│ │ │ └─picture
│ │ ├─i8n
│ │ ├─pages
│ │ │ ├─index
│ │ │ │ ├─index.css
│ │ │ │ ├─index.hml
│ │ │ │ └─index.js
│ │ │ └─second
│ │ │ ├─second.css
│ │ │ ├─second.hml
│ │ │ └─second.js
│ │ └─app.js
│ └─resources
│ ├─base
│ │ ├─element
│ │ ├─media
│ └─rawfile
开发步骤
1.新建OpenHarmony JS项目
在DevEco Studio中点击File -> New Project -> [Standard]Empty Ability->Next,Language 选择JS语言,最后点击Finish即创建成功。
2.编写关卡界面
2.1 背景设置
1)首先首先在second.hml中使用Stack作为容器,达到图片和文字堆叠的效果;
2)接着在其容器里面增加一个由div容器包裹的canvas组件;
<div class="box" id="box">
<canvas class="mapimage" id="draw_Image" ontouchstart="maptouchstart" ontouchmove="maptouchmove" onclick="mapclick" onlongpress="maplongpress">
</canvas>
</div>
2.2 创建显示人物数值的框架
1)使用div容器来编写只显示生命值、攻击力、防御力的简略信息的框架
<div class="my_Attr" id="my_Attr" style="margin-top:{{my_margintop}}; visibility:{{my_show}};">
<text>生命:{{mychessHealth}}</text>
<text>攻击:{{mychessAttack}}</text>
<text style="margin-bottom: 5px;">防御:{{mychessDefence}}</text>
</div>
<div class="enemy_Attr" id="enemy_Attr" style="margin-top:{{enemy_margintop}}; visibility:{{enemy_show}};">
<text>生命:{{enemychessHealth}}</text>
<text>攻击:{{enemychessAttack}}</text>
<text style="margin-bottom: 5px;">防御:{{enemychessDefence}}</text>
</div>
2)使用div容器来编写显示详细信息的框架
//详细信息的框架
<div class="info_container" style="visibility:{{detail_my_show}};">
<div class="info">
<div class="upper">
<div class="upper_top">
<image src="/common/picture/mychess.png"></image>
</div>
<div class="upper_down">
<text class="text" style="font-style: italic;">
{{mychessName}}
</text>
</div>
</div>
<div class="lower">
<div class="lower_container">
<stack class="health_container">
<progress class="progress" type="horizontal" percent= "{{myhealthPercent}}">
</progress>
<text class="text">
{{mychessHealth}} / {{mychessMaxhealth}}
</text>
</stack>
</div>
<div class="lower_container">
<text class="text">
攻击 : {{mychessAttack}}
</text>
</div>
<div class="lower_container">
<text class="text">
防御 : {{mychessDefence}}
</text>
</div>
<div class="lower_container">
<text class="text">
移动 : {{mychessMove}}
</text>
</div>
</div>
</div>
</div>
<div class="info_container" style="left: 520px; visibility:{{detail_enemy_show}};">
<div class="info" style="background-color:red;">
<div class="upper">
<div class="upper_top">
</div>
<div class="upper_down">
<text class="text" style="font-style: italic;">
{{enemychessName}}
</text>
</div>
</div>
<div class="lower">
<div class="lower_container">
<stack class="health_container">
<progress class="progress" type="horizontal" percent= "{{enemyhealthPercent}}">
</progress>
<text class="text">
{{enemychessHealth}} / {{enemychessMaxhealth}}
</text>
</stack>
</div>
<div class="lower_container">
<text class="text">
攻击 : {{enemychessAttack}}
</text>
</div>
<div class="lower_container">
<text class="text">
防御 : {{enemychessDefence}}
</text>
</div>
<div class="lower_container">
<text class="text">
移动 : {{enemychessMove}}
</text>
</div>
</div>
</div>
</div>
3)在second.css里编写相对应的样式
.my_Attr{
flex-direction:column;
justify-content: center;
align-items: center;
background-color: royalblue;
border: 2px solid orange;
width: 110px;
}
.my_Attr text{
color: lightgrey;
font-size: 14px;
margin-top: 5px;
}
.enemy_Attr{
flex-direction:column;
justify-content: center;
align-items: center;
background-color: red;
border: 2px solid orange;
width: 110px;
margin-left: 610px;
}
.enemy_Attr text{
color: lightgray;
font-size: 14px;
margin-top: 5px;
}
.info_container{
flex-direction: row;
justify-content: center;
align-items: center;
}
.info{
flex-direction: column;
justify-content: center;
align-items: center;
width: 200px;
height: 100%;
background-color: royalblue;
border:4px solid darkgoldenrod;
}
.upper{
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 50%;
border-bottom:2px solid darkgoldenrod;
}
.lower{
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 50%;
}
.upper_top{
height: 120px;
width: 120px;
margin-top: 10px;
background-color: burlywood;
border: 4px solid darkgoldenrod;
}
.upper_down{
justify-content: center;
align-items: center;
height: 30px;
min-width: 100px;
max-width: 125px;
margin-top: 7px;
margin-bottom: 7px;
background-color: burlywood;
border: 2px solid darkgoldenrod;
}
.lower_container{
justify-content: center;
align-items: center;
height: 30px;
width: 150px;
margin: 5px;
border: 4px solid darkgoldenrod;
background-color: burlywood;
}
.health_container{
justify-content: center;
align-items: center;
}
.progress {
color: lime;
stroke-width: 20px;
background-color: silver;
}
.text{
text-align: center;
font-size: 17px;
letter-spacing: 5px;
color: saddlebrown;
}
3. 编写地图滑动的逻辑
由于每个关卡的地图或大或小,有的甚至超过整个屏幕的大小。因此我们需要自己编写一套地图滑动的逻辑
代码如下:
maptouchstart:function(msg){
this.local_x = msg.touches[0].localX;
this.local_y = msg.touches[0].localY;
},
maptouchmove:function(msg){
var move_x = msg.changedTouches[0].localX - this.local_x;
var move_y = msg.changedTouches[0].localY - this.local_y;
this.local_x = msg.changedTouches[0].localX;
this.local_y = msg.changedTouches[0].localY;
this.ctx.clearRect(0,0,720,CANVAS_HEIGHT)
if(OFF_SET_X > 0){
move_x = 0;
MAP_DISPLAY_X += move_x;
}
else if(MAP_DISPLAY_X + move_x > 0){
move_x = -(MAP_DISPLAY_X);
MAP_DISPLAY_X = 0;
}
else if(MAP_DISPLAY_X + move_x <= 720 - MAP_WIDTH){
move_x = 720 - MAP_WIDTH - MAP_DISPLAY_X;
MAP_DISPLAY_X += move_x;
}
else{
MAP_DISPLAY_X += move_x;
}
if(OFF_SET_Y > 0){
move_y = 0;
MAP_DISPLAY_Y += move_y;
}
else if(MAP_DISPLAY_Y + move_y > 0){
move_y = -(MAP_DISPLAY_Y);
MAP_DISPLAY_Y = 0;
}
else if(MAP_DISPLAY_Y + move_y <= CANVAS_HEIGHT - MAP_HEIGHT){
move_y = CANVAS_HEIGHT - MAP_HEIGHT - MAP_DISPLAY_Y;
MAP_DISPLAY_Y += move_y;
}
else{
MAP_DISPLAY_Y += move_y;
}
this.ctx.transform(1, 0, 0, 1, move_x, move_y);
this.ctx.transferFromImageBitmap(this.bitmap);
},
4.编写点击事件
在点击的时候,我们要根据不同的情况进行分析。
代码以及分析如下:
mapclick:function(){
if(allow_click && this.local_x >= OFF_SET_X && this.local_x <= 720 - OFF_SET_X && this.local_y >= OFF_SET_Y && this.local_y <= CANVAS_HEIGHT - OFF_SET_Y) {
var click_y;
var click_x;
click_y = parseInt((this.local_y - MAP_DISPLAY_Y - OFF_SET_Y) / GRAPH_WIDTH);
click_x = parseInt((this.local_x - MAP_DISPLAY_X - OFF_SET_X) / GRAPH_WIDTH);
if(myFlag == -1) {
var flag = true;
for (var i = 0; i < myList.length; i++) {
if (myList[i].IsLocation(click_x, click_y)) {
myFlag = i;
if(myList[i].have_moved == false) {
find_Search_Map_1(map, MAP_ROW, MAP_COL, myList[i].x, myList[i].y, myList[i].move);
func_flag = 5;
this.Show_Mychess_Mes(false);
}
else{
this.Show_Mychess_Mes(false);
myFlag = -1;
}
flag = false;
break;
}
}
if(flag){
this.enemy_show = "hidden";
this.detail_enemy_show = "hidden";
for(var i = 0; i < enemyList.length; i++){
if(enemyList[i].IsLocation(click_x,click_y)){
enemyFlag = i;
this.Show_Enemychess_Mes(false);
enemyFlag = -1;
flag = false;
break;
}
}
}
if(flag){
this.my_show = "hidden";
this.detail_my_show = "hidden";
}
}
else{
var search_map = get_Search_Map();
var attack_map = get_Attack_Map();
lastSearchMap = new Array();
lastAttackMap = new Array();
lastSearchMap = get_Search_Map();
lastAttackMap = get_Attack_Map();
if(myList[myFlag].have_moved == false && search_map[click_y][click_x] > 0 && map[click_y][click_x] <= 100){
move_route = get_move_route(click_x, click_y, myList[myFlag].x, myList[myFlag].y);
move_row = myList[myFlag].y;
move_col = myList[myFlag].x;
move_num = map[move_row][move_col];
map[move_row][move_col] = 0;
myList[myFlag].x = -1;
myList[myFlag].y = -1;
move_fps = move_route.length * step;
myList[myFlag].have_moved = true;
allow_click = false;
func_flag = 6;
}
else if(attack_map[click_y][click_x] == 1){
clearMap();
for (var i = 0; i < enemyList.length; i++) {
if (enemyList[i].IsLocation(click_x, click_y)) {
enemyFlag = i;
this.my_show = "hidden";
this.detail_my_show = "hidden";
break;
}
}
attack_fps = 5;
myList[myFlag].have_moved = true;
myList[myFlag].have_attacked = true;
allow_click = false;
func_flag = 7;
}
else if(click_x == myList[myFlag].x && click_y == myList[myFlag].y){
this.Show_Mychess_Mes(false);
}
else{
clearMap();
var flag = true;
for (var i = 0; i < myList.length; i++) {
if (myList[i].IsLocation(click_x, click_y)) {
myFlag = i;
if( myList[i].have_moved == false){
find_Search_Map_1(map, MAP_ROW, MAP_COL, myList[i].x, myList[i].y, myList[i].move);
this.Show_Mychess_Mes(false);
func_flag = 8;
}
else{
this.Show_Mychess_Mes(false);
myFlag = -1;
func_flag = 9;
}
flag = false;
break;
}
}
if(flag == true){
this.my_show = "hidden";
this.detail_my_show = "hidden";
for(var i = 0; i < enemyList.length; i++){
if(enemyList[i].IsLocation(click_x,click_y)){
enemyFlag = i;
this.Show_Enemychess_Mes(false);
enemyFlag = -1;
break;
}
}
myFlag = -1;
func_flag = 9;
}
}
}
console.log("当前位置:" + click_y + "||" + click_x);
}
else{
}
},
5.控制绘制的频率
尽管代码是在OpenHarmony上进行编写的,但因为该应用要设配不同类型的设备,因此我们需要一个类似一个定时器一样的东西来稳定地输出画面。
代码如下:
myLoop:function(){
requestAnimationFrame(this.myLoop);
now = Date.now();
delta = now - then;
if(delta > interval){
then = now - (delta % interval);
this.myDrawing();
}
},
注意:这些变量都要定义为全局变量
6.寻路算法
该代码在Backstate_Map.js中
其代码如下:
function continue_to_find_1(x, y, cost, move_point) {
//如果该点没有走过同时没有超过边界或者走过所花费的点大于现在花费的点数,那表示可以继续走
if((y - 1 >= 0) && (Search_Map[y - 1][x] == 0 || (Search_Map[y - 1][x] > 0 && Search_Map[y - 1][x] > cost))) {
Search_Map[y - 1][x] = cost;
//判断是否还有移动点继续移动,有的话继续递归寻路
if(cost + 1 <= move_point) {
continue_to_find_1(x, y - 1, cost + 1, move_point);
}
}
if((x - 1 >= 0) && (Search_Map[y][x - 1] == 0 || (Search_Map[y][x - 1] > 0 && Search_Map[y][x - 1] > cost))) {
Search_Map[y][x - 1] = cost;
//同上注释
if(cost + 1 <= move_point) {
continue_to_find_1(x - 1, y, cost + 1, move_point);
}
}
if((x + 1 < Search_COL) && (Search_Map[y][x + 1] == 0 || (Search_Map[y][x + 1] > 0 && Search_Map[y][x + 1] > cost))) {
Search_Map[y][x + 1] = cost;
//同上注释
if(cost + 1 <= move_point) {
continue_to_find_1(x + 1, y, cost + 1, move_point);
}
}
if((y + 1 < Search_ROW) && (Search_Map[y + 1][x] == 0 || (Search_Map[y + 1][x] > 0 && Search_Map[y + 1][x] > cost))) {
Search_Map[y + 1][x] = cost;
//同上注释
if(cost + 1 <= move_point) {
continue_to_find_1(x, y + 1, cost + 1, move_point);
}
}
}
7.动画绘制
为了方便管理,我们将它放到一个线程里面运行,同时我们通过一个全局变量func_flag来选择和控制动画。
代码如:
myDrawing:function(){
if(func_flag == -2){
if(Lose_Flag == true){
this.game_over_show = true;
this.game_over_result = "你输了!"
console.log("你输了!!!");
}
else if(Win_Flag == true){
this.game_over_show = true;
this.game_over_result = "你赢了!"
console.log("你赢了!");
}
else{
console.log("未知动向.");
}
func_flag = -1;
}
if(func_flag == 1){
this.draw_Move_Chess(true);
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
}
else if(func_flag == 2){
if(attack_round == 1){
this.draw_Attack_Chess(myList[myFlag], enemyList[enemyFlag]);
}
else if(attack_round == 2){
this.draw_Attack_Chess(enemyList[enemyFlag], myList[myFlag]);
}
else if(attack_round == 3){
attack_round = 0;
}
if(attack_round == 0){
myFlag = -1;
enemyFlag = -1;
if(myList.length == 0){
Lose_Flag = true;
func_flag = -2;
}
else if(enemyList.length == 0){
Win_Flag = true;
func_flag = -2;
}
else{
Player_Round = false;
for (var i = 0; i < myList.length; i++) {
if (myList[i].have_moved == false) {
Player_Round = true;
break;
}
}
if (Player_Round == false) {
func_flag = 10;
}
else {
allow_click = true;
func_flag = -1;
}
}
}
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
}
else if(func_flag == 3){
this.draw_Active_Chess();
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
}
else if(func_flag == 4) {
this.draw_Attack_Map();
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
func_flag = 3;
}
else if(func_flag == 5){
this.draw_Search_Map();
this.draw_Attack_Map();
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
func_flag = 3;
}
else if(func_flag == 6 || func_flag == 7 || func_flag == 8 || func_flag == 9){
this.draw_Low_Refresh_Map();
if(func_flag == 6){
this.my_show = "hidden";
this.detail_my_show = "hidden";
func_flag = 1;
}
else if(func_flag == 7){
change_position(myList[myFlag], enemyList[enemyFlag]);
attack_round = 1;
func_flag = 2;
}
else if(func_flag == 8){
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
func_flag = 5;
}
else if(func_flag == 9){
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
func_flag = -1;
if(Player_Round == true) {
Player_Round = false;
for (var i = 0; i < myList.length; i++) {
if (myList[i].have_moved == false) {
Player_Round = true;
break;
}
}
if(Player_Round == false){
func_flag = 10;
}
}
}
}
else if(func_flag == 10){
func_flag = 23;
for(var i = 0; i < enemyList.length; i++){
enemyList[i].have_moved = false;
enemyList[i].have_attacked = false;
}
allow_click = false;
}
if(func_flag == 21){
this.draw_Move_Chess(false);
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
}
else if(func_flag == 22){
if(attack_round == 1){
this.draw_Attack_Chess(enemyList[enemyFlag], myList[myFlag]);
}
else if(attack_round == 2){
this.draw_Attack_Chess(myList[myFlag], enemyList[enemyFlag]);
}
else if(attack_round == 3){
attack_round = 0;
}
if(attack_round == 0){
myFlag = -1;
enemyFlag = -1;
clearMap();
if(myList.length == 0){
Lose_Flag = true;
func_flag = -2;
}
else if(enemyList.length == 0){
Win_Flag = true;
func_flag = -2;
}
else{
func_flag = 23;
}
}
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
}
else if(func_flag == 23){
for(var i = 0; i < enemyList.length; i++){
if(enemyList[i].have_moved == false){
find_Search_Map_2(map, MAP_ROW, MAP_COL, enemyList[i].x, enemyList[i].y, enemyList[i].move);
enemyFlag = i;
func_flag = 25;
break;
}
if(i == enemyList.length - 1){
func_flag = 30;
break;
}
}
}
else if(func_flag == 24){
this.draw_Attack_Map();
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
this.Select_Attack();
change_position(enemyList[enemyFlag], myList[myFlag]);
attack_round = 1;
func_flag = 22;
}
else if(func_flag == 25){
this.draw_Search_Map();
this.draw_Attack_Map();
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
this.Move_Or_Attack();
}
else if(func_flag == 26 || func_flag == 27 || func_flag == 28){
this.draw_Low_Refresh_Map();
if (func_flag == 26) {
func_flag = 21;
}
else if (func_flag == 27) {
change_position(enemyList[enemyFlag], myList[myFlag]);
attack_round = 1;
func_flag = 22;
}
else if (func_flag == 28) {
func_flag = 24;
}
var bitmap = this.offscreen.transferToImageBitmap();
this.ctx.transferFromImageBitmap(bitmap);
}
else if(func_flag == 30){
func_flag = -1;
for(var i = 0; i < myList.length; i++){
myList[i].have_moved = false;
myList[i].have_attacked = false;
}
if(Round < Max_Round){
Round += 1;
this.show_round = Round;
Player_Round = true;
allow_click = true;
}
else{
Lose_Flag = true;
func_flag = -2;
}
}
},
项目下载和导入
项目仓库地址: https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/FA/WarChess
1)git下载
git clone https://gitee.com/openharmony-sig/knowledge_demo_entainment.git
2)项目导入
打开DevEco Studio,点击File->Open->下载路径/FA/WarChess
约束与限制
1. 设备编译约束
1、标准设备环境准备
2. 应用编译约束
- 参考 应用开发快速入门
- 集成开发环境:DevEco Studio 3.0.0.601版本,下载地址;
- OpenHarmony SDK 3.0.0.0;