目录
宠物栏
1. 宠物的显示
2. 宠物的使用
3. 宠物的饱食度
4. 宠物栏的解锁[默认每个用户只有一个宠物栏, 需要2个的话,需要激活新的宠物栏]
服务端提供显示宠物的api接口
1. 宠物信息,在保存的时候,因为不同的用户,拥有的宠物数量不一样,所以数据结构不稳定的,而且要记录宠物总数的同时,还要记录用户的宠物详细信息,需要保存的数据结果是多层,这种情况下我们可以考虑数据保存在mongoDB。
数据格式
user_pet: {
"user_id": 1, # 当前用户ID
"pet_position": 1, # 当前宠物栏位数量
"pet_1":{ # 1号栏位的宠物信息
"start_time": "2021-03-31 14:00:00",
"pet_image": "pet1.png",
"pet_id": 1,
"life_time": -1, # 生命周期,
"hunger_num": 20, # 饱食度: 饥饿状态
'setiety_num': 80 # 饱食度: 饱食状态
"hit_rare": 10, # 保护作物的成功率
},
"pet_2":{}, # 2号栏位的宠物信息, {} 表示没有宠物
}
2. 基于上面的模拟数据格式在终端下创建到mongoDB中,直接编写api接口
pet = {
"user_id" : 1,
"pet_position" : 2,
"pet_1": {
"start_time" : "2021-03-31 14:00:00",
"pet_image" : "pet2.png",
"pet_id" : 1,
"life_time" : -1,
"hunger_num" : 20,
"satiety_num" : 80,
"hit_rate" : 10
},
"pet_2": {
"start_time" : "2021-07-02 14:00:30",
"pet_image" : "pet2.png",
"pet_id" : 3,
"life_time" : -1,
"hunger_num" : 20,
"satiety_num" : 80,
"hit_rate" : 10
}
}
3. 把计算宠物实际饱食度的数据保存到redis中
user:2:pet:1 用户ID=2,宠物1号宠物,通过ttl计算获取宠物存活
user:2:pet:2 用户ID=2,宠物2号宠物,通过ttl计算获取宠物存活
- 创建用户宠物文档模型,
orchard.documents
,代码:
# 用户宠物信息文档模型
class UserPetDocument(mgdb.Document):
'''用户宠物信息文档模型'''
meta = {
'collection': 'user_pet', # 集合名称
'ordering': ['_id'], # 排序字段
'strict': False, # 是否严格语法
}
# 字段声明
user_id = mgdb.IntField(required=True, unique=True, verbose_name='用户ID')
pet_position = mgdb.IntField(required=True, verbose_name='用户宠物栏容量')
pet_1 = mgdb.DictField(null=True, verbose_name='用户1号宠物栏的宠物信息')
pet_2 = mgdb.DictField(null=True, verbose_name='用户2号宠物栏的宠物信息')
- 终端命令生成manage测试数据,
application.utils.commands
,代码:
# 批量生成测试数据
class FakerCommand(Command):
"""批量生成测试数据"""
name = 'faker' # 生成命令名称
# 传递的参数
option_list = [
Option('--type', '-t', dest='type', default='user'), # 指定生成数据的类型(用户数据,其他数据)
Option('--num', '-n', dest='num', default=1), # 生成数据的数量
]
# 以后想要生成数据类型列表
type_list = ['user','seed','pet','pet_food','plant','user_pet']
def __call__(self, app, type, num):
# 判断想要生成数据的类型是否在类型列表中
if type not in self.type_list:
print("数据类型不正确
当前Faker生成数据类型仅支持: %s" % self.type_list)
return None
num = int(num)
if num < 1:
print("生成数量不正确
当前Faker生成数据至少1个以上")
return None
if type == 'user':
# 生成用户测试数据
self.create_user(app, num)
elif type == 'seed':
# 生成种子道具测试数据
self.create_seed(app, num)
elif type == 'pet':
# 生成宠物道具测试数据
self.create_pet(app, num)
elif type == 'pet_food':
# 生成宠物食物测试数据
self.create_pet_food(app, num)
elif type == 'plant':
# 生成植物加速成长道具测试数据
self.create_plant(app, num)
elif type == 'user_pet':
# 生成用户宠物测试数据(mongodb)
self.create_user_pet(app, num)
# 1. 生成指定数量的测试用户信息
def create_user(self,app,num):
"""生成指定数量的测试用户信息"""
from application.apps.users.models import User,UserProfile
faker = app.faker
faker.add_provider(internet)
user_list = [] # 用户模型对象列表
# 从配置文件中读取默认测试用户的登录密码
password = app.config.get("DEFAULT_TEST_USER_PASSWORD", "12345678")
for i in range(num):
sex = bool(random.randint(0,2))
nickname = faker.name()
# 登录账号[随机字母,6-16位]
name = faker.pystr(min_chars=6, max_chars=16)
age = random.randint(13, 50)
birthday = faker.date_time_between(start_date="-%sy" % age, end_date="-12y", tzinfo=None)
hometown_province = faker.province()
hometown_city = faker.city()
hometown_area = faker.district()
living_province = faker.province()
living_city = faker.city()
living_area = faker.district()
user = User(
nickname=nickname,
sex=sex,
name=name,
age=age,
password=password,
pay_password=password,
money=random.randint(100, 99999),
credit=random.randint(100, 99999),
ip_address=faker.ipv4_public(),
email=faker.ascii_free_email(),
mobile=faker.phone_number(),
unique_id=faker.uuid4(),
province=faker.province(),
city=faker.city(),
area=faker.district(),
info=UserProfile(
birthday=birthday,
hometown_province=hometown_province,
hometown_city=hometown_city,
hometown_area=hometown_area,
hometown_address=hometown_province + hometown_city + hometown_area + faker.street_address(),
living_province=living_province,
living_city=living_city,
living_area=living_area,
living_address=living_province + living_city + living_area + faker.street_address()
)
)
user_list.append(user)
# 存储数据
with app.app_context():
User.add_all(user_list)
print("生成%s个用户信息完成..." % num)
# 2. 生成指定数量的种子道具测试数据
def create_seed(self, app, num):
"""生成指定数量的种子道具测试数据"""
from application.apps.orchard.models import SeedItem
seed_list = [] # 模型对象列表
for i in range(num):
seed = SeedItem(
name = f'苹果{i}号',
price = random.randint(1, 20),
credit = random.randint(100, 1000),
image = 'fruit_tree.png',
remark = '果实饱满香甜',
)
seed_list.append(seed)
# 存储数据
with app.app_context():
SeedItem.add_all(seed_list)
print("生成%s个种子道具信息完成..." % num)
# 3. 生成指定数量的宠物道具测试数据
def create_pet(self, app, num):
"""生成指定数量的宠物道具测试数据"""
from application.apps.orchard.models import PetItem
pet_list = [] # 模型对象列表
pet_name_list = ["中华田园犬", "贵宾犬", "哮天犬"]
pet_img_list = ["pet1.png", "pet2.png", "pet3.png"]
for i in range(num):
name = random.choice(pet_name_list)
if name == '中华田园犬':
image = pet_img_list[0]
elif name == '贵宾犬':
image = pet_img_list[1]
elif name == '哮天犬':
image = pet_img_list[2]
pet = PetItem(
name = name,
price = random.randint(1, 20),
credit = random.randint(100, 1000),
remark = '看家护院好帮手',
image = image,
)
pet_list.append(pet)
# 存储数据
with app.app_context():
PetItem.add_all(pet_list)
print("生成%s个宠物道具信息完成..." % num)
# 4. 生成指定数量的宠物食物道具测试数据
def create_pet_food(self, app, num):
"""生成指定数量的宠物食物道具测试数据"""
from application.apps.orchard.models import PetFoodItem
pet_food_list = [] # 模型对象列表
for i in range(num):
pet_food = PetFoodItem(
name = f'牛奶{i}号',
price = random.randint(1, 20),
credit = random.randint(100, 1000),
remark = '补充体力',
image = 'prop4.png',
)
pet_food_list.append(pet_food)
# 存储数据
with app.app_context():
PetFoodItem.add_all(pet_food_list)
print("生成%s个宠物食物道具信息完成..." % num)
# 5. 生成指定数量的植物成长加速道具测试数据
def create_plant(self, app, num):
"""生成指定数量的植物成长加速道具测试数据"""
from application.apps.orchard.models import PlantItem
plant_list = [] # 模型对象列表
for i in range(num):
plant = PlantItem(
name = f'化肥{i}号',
price = random.randint(1, 20),
credit = random.randint(100, 1000),
remark = '补充体力',
image = 'prop1.png',
)
plant_list.append(plant)
# 存储数据
with app.app_context():
PlantItem.add_all(plant_list)
print("生成%s个植物加速成长道具信息完成..." % num)
# 6.生成用户宠物测试数据(mongodb)
def create_user_pet(self,app, num):
# 引入用户宠物文档模型
from application.apps.orchard.documents import UserPetDocument
# 用户宠物信息
pet = {
"user_id": 1,
"pet_position": 2,
"pet_1": {
"start_time": "2021-03-31 14:00:00",
"pet_image": "pet1.png",
"pet_id": 1,
"life_time": -1,
"hunger_num": 20,
"satiety_num": 80,
"hit_rate": 10
},
"pet_2": {
"start_time": "2021-07-02 14:00:30",
"pet_image": "pet2.png",
"pet_id": 3,
"life_time": -1,
"hunger_num": 20,
"satiety_num": 80,
"hit_rate": 10
}
}
# 添加数据
UserPetDocument.objects.create(**pet)
print("生成%s个用户宠物信息完成..." % num)
终端命令生成用户宠物数据,
python manage.py faker -tuser_pet
Redis中记录用户宠物存活时间的数据。
redis-cli
# 选择种植园存储库
select 3
# 用户宠物1存活时间
setex user:1:pet:1 1800 _
# 用户宠物2存活时间
setex user:1:pet:2 1800 _
- 服务端响应请求用户宠物信息数据
orchard.socket
,代码:
# 获取用户宠物信息数据
def on_user_pet_info(self):
'''获取用户宠物信息数据'''
# 1.根据回话sid获取用户模型对象(检查回话状态)
user = user_services.get_user_by_sid(request.sid)
# 判断用户是否存在
if user is None:
# 响应数据
emit('user_pet_info_response',{
'errno':code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 获取用户宠物信息
user_pet_info = services.get_user_pet_info(user.id)
emit('user_pet_info_response',{
'errno': code.CODE_OK,
'errmsg': message.ok,
'user_pet_info': user_pet_info
})
- 用户宠物模型构造器
orchard/marshmallow.py
# 用户宠物信息构造器
class UserPetSchema(Schema):
'''用户宠物信息构造器'''
user_id = fields.Integer()
pet_position = fields.Integer()
pet_1 = fields.Dict()
pet_2 = fields.Dict()
- 数据服务层,
orchard/services.py
,代码:
# 初始化用户宠物信息
def init_user_pet_info(user_id):
user_pet_info = UserPetDocument.objects.create(
user_id = user_id,
pet_position = 1,
pet_1 = {},
pet_2 = {}
)
return user_pet_info
# 获取用户宠物信息
def get_user_pet_info(user_id):
'''
获取用户宠物信息
:param user_id: 用户ID
:return:
'''
# 1.先从mongoDB中提取用户宠物信息
user_pet_info = UserPetDocument.objects.filter(UserPetDocument.user_id == user_id).first()
# 判断
if user_pet_info is None:
# 用户宠物信息不存在,初始化用户宠物信息
user_pet_info = init_user_pet_info(user_id)
# 2.使用构造器进行数据序列化
from .marshmallow import UserPetSchema
ups = UserPetSchema()
pet_data = ups.dump(user_pet_info)
# 3.从redis中提取用户宠物1的剩余生命值[剩余时间]
# 剩余时间没用了,宠物挂了,返回值就是-2
pet_data['pet_1']['has_time'] = redis_orchard.ttl(f'user:{user_id}:pet:1')
# 宠物的最大存活时间
pet_data['pet_1']['max_time'] = config.PET_MAX_TIME
if pet_data['pet_position'] == 2:
# 用户激活了2号宠物位置,则有可能有2号宠物
pet_data['pet_2']['has_time'] = redis_orchard.ttl(f'user:{user_id}:pet:2')
# 宠物的最大存活时间
pet_data['pet_2']['max_time'] = config.PET_MAX_TIME
# 4.返回用户宠物信息
return pet_data
- 用户宠物存活时间配置信息
applicaiton.settings.plant
,代码:
# 宠物不喂养的情况下最大存活时间
PET_MAX_TIME = 1 * 24 * 3600
客户端展示宠物栏和宠物信息
- 种植园页面结束获取用户宠物信息通知, 获取成功后并发布广播, 使其他页面接收,
html/orchard.html
, 代码:
<!DOCTYPE html>
<html>
<head>
<title>种植园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info">
<div class="avatar" @click='to_user'>
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">{{user_data.nickname}}</p>
</div>
<div class="wallet" @click='user_recharge'>
<div class="balance">
<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
<p class="num">{{game.number_format(user_data.money)}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">{{game.number_format(user_data.credit)}}</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
签到有礼
</div>
<div class="menu">
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
邮件中心
</div>
</div>
</div>
<div class="footer" >
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='to_package'>背包</li>
<li class="menu-center" @click="to_shop">商店</li>
<li class="menu">消息</li>
<li class="menu">好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_data:{}, // 当前用户信息
music_play:true,
namespace: '/orchard', // websocket命名空间
socket: null, // websocket连接对象
recharge_list: [], // 允许充值的金额列表
package_init_setting: {}, // 背包初始配置信息
user_info: {}, // 用户登陆初始化化信息
user_package_info: {}, // 用户背包信息
user_pet_info: {}, // 用户宠物信息
}
},
created(){
// socket建立连接
this.socket_connect();
// 自动加载我的果园页面
this.to_my_orchard();
// 获取用户数据
this.get_user_data()
// 监听事件变化
this.listen()
// 获取充值金额列表
this.get_recharge_list()
},
methods:{
back(){
this.game.openWin("root");
},
// 监听事件
listen(){
// 1.监听token更新的通知
this.listen_update_token();
// 2.监听是否购买道具成功的通知
this.listen_buy_prop_success();
// 3.监听是否使用道具成功的通知
// this.listen_use_prop_success();
// 4.监听解锁背包容量的通知
this.listen_unlock_package();
// 5.监听是否用其他页面需要获取用户宠物信息
this.listen_get_user_pet_info();
},
// 1.监听token更新的通知
listen_update_token(){
api.addEventListener({
name: 'update_token'
}, (ret, err) => {
// 更新用户数据
this.get_user_data()
});
},
// 2.监听是否购买道具成功的通知
listen_buy_prop_success(){
api.addEventListener({
name: 'buy_prop_success'
}, (ret, err)=>{
// 发送请求,更新用户背包数据
this.socket.emit('user_package');
});
},
// 3.监听解锁背包容量的通知
listen_unlock_package(){
api.addEventListener({
name: 'unlock_package'
}, (ret, err)=>{
// 发送解锁背包的请求
this.socket.emit('unlock_package');
// 获取解锁背包响应数据
this.socket.on('unlock_package_response',(response)=>{
if(response.errno === 1000){
// 更新用户背包数据
this.socket.emit('user_package')
// 用户积分改变,重新刷新token值
this.refresh_user_token();
}
})
});
},
// 5.监听是否用其他页面需要获取用户宠物信息
listen_get_user_pet_info(){
api.addEventListener({
name: 'get_user_pet_info'
}, (ret, err)=>{
// 获取用户宠物信息
this.get_user_pet_info();
});
},
// 获取用户宠物信息
get_user_pet_info(){
// 发起请求用户宠物信息
this.socket.emit('user_pet_info');
// 获取响应
this.socket.on('user_pet_info_response', (response)=>{
if(response.errno === 1000){
// 响应成功,获取宠物信息
this.user_pet_info = response.user_pet_info
// 通知其他页面,已经获取用户宠物信息
this.game.sendEvent('get_user_pet_info_success',{
user_pet_info: this.user_pet_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 用户积分改变,重新刷新token值
refresh_user_token(){
let self = this
self.game.check_user_login(self, ()=>{
let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
self.game.post(self,{
'method': 'Users.refresh',
'params':{},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 刷新token值
let token = self.game.getfs("refresh_token");
if(token){
// 记住密码的情况
self.game.deldata(["access_token","refresh_token"]);
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.delfs(["access_token","refresh_token"]);
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 发布全局广播,token更新
self.game.sendEvent('update_token')
}
}
})
})
},
// 通过token值获取用户数据
get_user_data(){
let self = this;
// 检查token是否过期,过期从新刷新token
self.game.check_user_login(self, ()=>{
// 获取token
let token = this.game.getfs('access_token') || this.game.getdata('access_token')
// 根据token获取用户数据
this.user_data = this.game.get_user_by_token(token)
})
},
// websocket连接处理
socket_connect(){
this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("开始连接服务端");
// 获取背包初始配置信息
this.get_package_setting()
// websocket登陆处理
this.user_websocket_login()
});
},
// 获取背包初始配置信息
get_package_setting(){
this.socket.on('init_config_response', (response)=>{
// this.game.print(response,1)
this.package_init_setting = response
})
},
// websocket登陆处理
user_websocket_login(){
// 客户端发送用户登陆请求
this.socket.emit('login',{'uid': this.user_data.unique_id});
// 接收登陆响应
this.login_response();
// 接收用户背包响应
this.user_package_response();
},
// 接收登陆初始化信息
login_response(){
this.socket.on('login_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_info = response.user_info
}
})
},
// 接收用户背包信息
user_package_response(){
this.socket.on('user_package_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_package_info = response.package_info;
// 全局广播用户背包更新
this.game.sendEvent('package_update',{
user_package_info: this.user_package_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 跳转我的果园页面
to_my_orchard(){
this.game.check_user_login(this, ()=>{
this.game.openFrame("my_orchard","my_orchard.html","from_right",{
marginTop: 174, //相对父页面上外边距的距离,数字类型
marginLeft: 0, //相对父页面左外边距的距离,数字类型
marginBottom: 54, //相对父页面下外边距的距离,数字类型
marginRight: 0 //相对父页面右外边距的距离,数字类型
});
})
},
// 点击商店打开道具商城页面
to_shop(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('shop', 'shop.html', 'from_top');
})
},
// 点击头像,跳转用户中心页面
to_user(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('user', 'user.html', 'from_right');
})
},
// 获取充值金额列表
get_recharge_list(){
let self = this;
self.game.check_user_login(self,()=>{
self.game.post(self,{
'method': 'Users.recharge_list',
'params': {},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.recharge_list = data.result.recharge_list
}
}
})
})
},
// 点击钱包进行用户充值,设置充值金额
user_recharge(){
api.actionSheet({
title: '余额充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret.buttonIndex <= this.recharge_list.length ){
// 充值金额
let money = this.recharge_list[ret.buttonIndex-1];
// this.game.print(money,1);
// 发送支付宝充值请求
this.recharge_app_pay(money)
}
});
},
// 发送支付宝充值请求
recharge_app_pay(money){
// 获取支付宝支付对象
let aliPayPlus = api.require("aliPayPlus");
let self = this;
// 向服务端发送请求获取终止订单信息
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.recharge',
'params':{
'money': money,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 本次充值的订单参数
let order_string = data.result.order_string;
// 支付完成以后,支付APP返回的响应状态码
let resultCode = {
"9000": "支付成功!",
"8000": "支付正在处理中,请稍候!",
"4000": "支付失败!请联系我们的工作人员~",
"5000": "支付失败,重复的支付操作",
"6002": "网络连接出错",
"6004": "支付正在处理中,请稍后",
}
// 唤醒支付宝APP,发起支付
aliPayPlus.payOrder({
orderInfo: order_string,
sandbox: data.result.sandbox, // 将来APP上线需要修改成false
},(ret, err)=>{
if(resultCode[ret.code]){
// 提示支付结果
if(ret.code != 9000){
self.game.tips(resultCode[ret.code]);
}else {
// 支付成功,向服务端请求验证支付结果 - 参数订单号
self.check_recharge_result(data.result.order_number);
}
}
})
}
}
})
})
},
// 向服务端发送请求,校验充值是否成功
check_recharge_result(order_number){
let self = this;
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.check_recharge_result',
'params':{
'order_number':order_number,
},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 充值成功
self.game.tips('充值成功!')
// 用户数据更改过,重新刷新token值
let token = self.game.getfs("access_token");
// 删除token值
self.game.deldata(["access_token","refresh_token"]);
self.game.delfs(["access_token","refresh_token"]);
if(token){
// 记住密码的情况
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 全局广播充值成功
self.game.sendEvent('recharge_success')
// 全局广播刷新token值
self.game.sendEvent('update_token')
}
}
})
})
},
// 点击背包,跳转到背包页面,并传递背包初始配置信息
to_package(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('package', 'package.html', 'from_top',null, {
'package_init_setting': this.package_init_setting,
'user_package_info': this.user_package_info,
'user_info': this.user_info
})
})
},
}
});
}
</script>
</body>
</html>
- 我的种植园页面, 发起获取宠物信息广播, 监听宠物信息获取成功的通知,
html/my_orchard.html
, 代码:
<!DOCTYPE html>
<html>
<head>
<title>我的果园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard orchard-frame" id="app">
<div class="background">
<img class="grassland2" src="../static/images/grassland2.png" alt="">
<img class="mushroom1" src="../static/images/mushroom1.png" alt="">
<img class="stake1" src="../static/images/stake1.png" alt="">
<img class="stake2" src="../static/images/stake2.png" alt="">
</div>
<div class="pet-box">
<div class="pet">
<img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
</div>
<div class="pet turned_off" v-if='user_pet_info.pet_position < 2'>
<img class="turned_image" src="../static/images/turned_off.png" alt="">
<p>请购买宠物</p>
</div>
<div class="pet" v-else>
<img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
</div>
</div>
<div class="tree-list">
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree1.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
</div>
</div>
<div class="prop-list">
<div class="prop">
<img src="../static/images/prop1.png" alt="">
<span>1</span>
<p>化肥</p>
</div>
<div class="prop">
<img src="../static/images/prop2.png" alt="">
<span>0</span>
<p>修剪</p>
</div>
<div class="prop">
<img src="../static/images/prop3.png" alt="">
<span>1</span>
<p>浇水</p>
</div>
<div class="prop">
<img src="../static/images/prop4.png" alt="">
<span>1</span>
<p>宠物粮</p>
</div>
</div>
<div class="pet-hp-list">
<div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
<p>宠物1 饱食度</p>
<div class="hp">
<div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
</div>
</div>
<div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
<p>宠物2 饱食度</p>
<div class="hp">
<div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_pet_info:{ // 用户宠物信息
pet_1:{}, // 防止调用时报错
pet_2:{},
}
}
},
// 计算数据
computed:{
// 计算用户宠物血量百分比
pet_1_hp(){
persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
return persent
},
pet_2_hp(){
persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
return persent
},
},
created(){
// 监听事件
this.listen();
},
methods:{
// 监听事件
listen(){
// 监听获取用户宠物信息成功的通知
this.listen_get_user_pet_info_success();
},
// 监听获取用户宠物信息成功的通知
listen_get_user_pet_info_success(){
let self = this
// 发起获取用户宠物信息的全局通知
self.game.sendEvent('get_user_pet_info');
// 监听获取用户宠物信息成功的通知
api.addEventListener({
name: 'get_user_pet_info_success'
}, (ret, err)=>{
// 获取用户宠物信息
self.user_pet_info = ret.value.user_pet_info;
self.game.print(self.user_pet_info)
// 设置定时器,宠物存活时间需要递减
setInterval(()=>{
self.user_pet_info.pet_1.has_time--;
self.user_pet_info.pet_2.has_time--;
},1000);
});
},
}
});
}
</script>
</body>
</html>
socket会话状态装饰器
把对socket状态的判断代码进行封装成类方法的装饰器。
- 装饰器函数封装
utils.decorator
,代码:
from flask_jwt_extended import get_jwt_identity
from functools import wraps
from flask import request
from application.apps.users import services
from application.utils import code, message
# 根据token获取用户模型对象,装饰器
def get_user_object(func):
"""获取用户模型对象"""
def inner(*args, **kwargs):
user_data = get_jwt_identity()
user = services.get_user_by_id(id=user_data['id'])
# 判断用户是否存在
if user is None:
return {
'errno': code.CODE_AUTOORIZATION_ERROR,
'errmsg': message.authorization_is_invalid
}
return func(user, *args, **kwargs)
return inner
# 根据websocket回话sid获取用户模型对象,装饰器
# 要使用装饰器对类方法进行装饰,必须要考虑self参数的传递
# functools模块中提供了一个wraps装饰器,这个装饰器的作用可以帮我们把类方法转换成偏函数
def get_user_object_by_socket_sid(func):
@wraps(func)
def inner(self, *args, **kwargs):
# 获取用户模型对象,检查回话id
user = services.get_user_by_sid(request.sid)
# 检查回话状态,在装饰的函数上判断
return func(self, user, *args, **kwargs)
return inner
- websocket会话视图方法加装装饰器
orchard.socket
,代码:
from flask_socketio import emit, Namespace, join_room
from flask import request
from application import code, message
from application.apps.users import services as user_services
from application.utils.decorator import get_user_object_by_socket_sid
from . import services
"""基于类视图接口"""
# 种植园模块命名空间
class OrchardNamespace(Namespace):
'''种植园模块命名空间'''
# socket链接
def on_connect(self):
print("用户[%s]进入了种植园!" % request.sid)
# 返回初始化信息[不涉及当前的用户,因为我们现在不知道当前用户是谁]
self.init_config()
def init_config(self, ):
"""系统基本信息初始化"""
# 返回背包初始配置信息
package_settings = services.get_package_settings()
emit("init_config_response", package_settings)
# socket断开连接
def on_disconnect(self):
print("用户[%s]退出了种植园!" % request.sid)
# 接收登录信息
def on_login(self, data):
# 加入房间
self.on_join_room(data['uid']) # 接受客户端发送过来的用户 unique id
# 1.用户websocket登录处理,获取用户初始信息(uid,sid,背包初始容量)
# request.sid websocket链接客户端的会话ID
user_info = user_services.user_websocket_login(data['uid'],request.sid)
# todo 返回种植园的相关配置参数[种植果树的数量上限]
msg = {
'errno': code.CODE_OK,
'errmsg': message.ok,
'user_info': user_info
}
# 响应登录信息
emit("login_response", msg)
# 2.用户登录获取用户背包信息
self.on_user_package()
# 房间分发
def on_join_room(self, room):
'''加入房间'''
# 默认用户自己一个人一个房间,也就是一个单独的频道
join_room(room)
# 获取用户背包信息
@get_user_object_by_socket_sid
def on_user_package(self, user):
'''获取用户背包信息'''
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('user_package_response',{
'errno':code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 2.根据用户id获取背包信息
package_info = user_services.get_package_info_by_id(user.id)
# print("package_info =", package_info)
# 响应数据
emit('user_package_response',{
'errno': code.CODE_OK,
'errmsg': message.ok,
'package_info': package_info
})
# 解锁背包格子
@get_user_object_by_socket_sid
def on_unlock_package(self, user):
'''解锁背包格子'''
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('unlock_package_response', {
'errno': code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 根据回话id获取websocket用户信息
user_info = user_services.get_user_info_by_sid(request.sid)
# 2.根据用户id获取背包信息
package_info = user_services.get_package_info_by_id(user.id)
# 3.获取用户背包初始配置信息
package_settings = services.get_package_settings()
# 4.判断用户是否满足激活背包格子的条件
# 4.1 判断用户背包格子使用情况
if package_info['capacity'] >= package_settings['max_package_capacity']:
# 背包空间不足
emit('unlock_package_response',{
'errno': code.CODE_PACKAGE_SPACE_NOT_ENOUGH,
'errmsg': message.package_space_not_enough
})
return
# 4.2 # 是否有足够的积分可以激活[获取剩余可激活空间,查看本次第几次激活,剩余激活的次数]
# 4.2.1 获取已经激活的次数?(现有格子 - 注册时初始数量)/ 每次激活数量 = 已激活次数
# print('现有格子:', package_info['capacity'])
# print('注册时初始数量:', user_info.package_number)
# print('每次激活数量:', package_info['capacity'])
this_time = (package_info['capacity'] - user_info.package_number) // package_settings['unlock_package_item']
# 4.2.2 获取本次激活所需要的积分数量
this_time_credit = package_settings['unlock_package'][this_time]
# 4.2.3 判断用户积分是否足够
if user.credit < this_time_credit:
# 积分不足
emit('unlock_package_response', {
'errno': code.CODE_NOT_ENOUGH_CREDIT,
'errmsg': message.not_enough_credit
})
return
# 解锁背包格子 - 参数(用户对象, 解锁格子数量,需要的积分)
user_services.unlock_package_num(user, package_settings["unlock_package_item"], this_time_credit)
emit('unlock_package_response', {
'errno': code.CODE_OK,
'errmsg': message.ok
})
# 获取用户宠物信息数据
@get_user_object_by_socket_sid
def on_user_pet_info(self, user):
'''获取用户宠物信息数据'''
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('user_pet_info_response',{
'errno':code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 获取用户宠物信息
user_pet_info = services.get_user_pet_info(user.id)
emit('user_pet_info_response',{
'errno': code.CODE_OK,
'errmsg': message.ok,
'user_pet_info': user_pet_info
})
宠物死亡处理
服务端
- 宠物死亡, 更新用户宠物信息为空
orchard.socket
,代码:
# 用户宠物死亡处理
@get_user_object_by_socket_sid
def on_user_pet_die(self, user, data):
"""
宠物死亡处理
:param user: 根据回话ID获取的用户模型对象
:param data: 客户端发送过来的宠物相关信息
:return:
"""
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('user_pet_die_response', {
'errno': code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 2.获取死亡宠物所在的宠物栏位置
pet_position = data.get('pet_position')
# 3.到redis提取当前对应的宠物剩余时间
pet_ttl = services.get_pet_ttl(user.id, pet_position)
# 判断宠物是否已经死了
if pet_ttl <= 1:
# 宠物已死,进行死亡处理,更改用户宠物信息
services.user_pet_die(user.id, pet_position)
emit('user_pet_die_response', {
'errno': code.CODE_OK,
'errmsg': message.ok,
})
# 停止程序继续运行
return
emit('user_pet_die_response', {
'errno': code.CODE_PET_NOT_DIE,
'errmsg': message.pet_not_die,
})
- 数据服务层,
orchard.services
,代码:
# 获取宠物的剩余时间
def get_pet_ttl(user_id, pet_position):
'''
获取宠物的剩余时间
:param user_id: 用户ID
:param pet_position: 宠物对应的宠物栏位置
:return:
'''
pet_ttl = redis_orchard.ttl(f'user:{user_id}:pet:{pet_position}')
return pet_ttl
# 用户宠物死亡处理
def user_pet_die(user_id, pet_position):
'''
用户宠物死亡处理
:param user_id: 用户ID
:param pet_position: 宠物对应的宠物栏位置
:return:
'''
# 获取用户宠物信息对象
document = UserPetDocument.objects.get(user_id = user_id)
# 更改死亡宠物信息为空
updater = {}
updater[f'pet_{pet_position}'] = {}
document.update(**updater)
- 状态提示码与提示信息
提示码 application/utils/code.py
CODE_PET_NOT_DIE = 1106 # 宠物没有死亡
提示信息 application/utils/message.py
pet_not_die = '宠物没有死亡'
客户端
- 我的种植园页面,发送宠物死亡的通知
html/my_orchard.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>我的果园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard orchard-frame" id="app">
<div class="background">
<img class="grassland2" src="../static/images/grassland2.png" alt="">
<img class="mushroom1" src="../static/images/mushroom1.png" alt="">
<img class="stake1" src="../static/images/stake1.png" alt="">
<img class="stake2" src="../static/images/stake2.png" alt="">
</div>
<div class="pet-box">
<div class="pet">
<img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
</div>
<div class="pet turned_off" v-if='user_pet_info.pet_position < 2'>
<img class="turned_image" src="../static/images/turned_off.png" alt="">
<p>请购买宠物</p>
</div>
<div class="pet" v-else>
<img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
</div>
</div>
<div class="tree-list">
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree1.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
</div>
</div>
<div class="prop-list">
<div class="prop">
<img src="../static/images/prop1.png" alt="">
<span>1</span>
<p>化肥</p>
</div>
<div class="prop">
<img src="../static/images/prop2.png" alt="">
<span>0</span>
<p>修剪</p>
</div>
<div class="prop">
<img src="../static/images/prop3.png" alt="">
<span>1</span>
<p>浇水</p>
</div>
<div class="prop">
<img src="../static/images/prop4.png" alt="">
<span>1</span>
<p>宠物粮</p>
</div>
</div>
<div class="pet-hp-list">
<div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
<p>宠物1 饱食度</p>
<div class="hp">
<div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
</div>
</div>
<div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
<p>宠物2 饱食度</p>
<div class="hp">
<div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_pet_info:{ // 用户宠物信息
pet_1:{}, // 防止调用时报错
pet_2:{},
}
}
},
// 计算数据
computed:{
// 计算用户宠物血量百分比
pet_1_hp(){
persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
return persent
},
pet_2_hp(){
persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
return persent
},
},
// 监听用户信息
watch:{
// 监听用户宠物剩余时间,如果死亡发送通知
'user_pet_info.pet_1.has_time':function(){
// 提示用户宠物还剩1分钟死亡,尽快喂食
if(this.user_pet_info.pet_1.has_time == 300){
this.game.tips('您的宠物1还剩5分钟死亡,请尽快投喂!')
}
if(this.user_pet_info.pet_1.has_time < 1){
this.user_pet_die(1)
}
},
'user_pet_info.pet_2.has_time':function(){
if(this.user_pet_info.pet_2.has_time == 300){
this.game.tips('您的宠物2还剩5分钟死亡,请尽快投喂!')
}
if(this.user_pet_info.pet_2.has_time < 1){
this.user_pet_die(2)
}
},
},
created(){
// 监听事件
this.listen();
},
methods:{
// 监听事件
listen(){
// 监听获取用户宠物信息成功的通知
this.listen_get_user_pet_info_success();
},
// 监听获取用户宠物信息成功的通知
listen_get_user_pet_info_success(){
let self = this
// 发起获取用户宠物信息的全局通知
self.game.sendEvent('get_user_pet_info');
// 监听获取用户宠物信息成功的通知
api.addEventListener({
name: 'get_user_pet_info_success'
}, (ret, err)=>{
// 获取用户宠物信息
self.user_pet_info = ret.value.user_pet_info;
self.game.print(self.user_pet_info)
// 设置定时器,宠物存活时间需要递减
setInterval(()=>{
// 宠物剩余时间小于0,就不必再减了
if(self.user_pet_info.pet_1.has_time > 0){
self.user_pet_info.pet_1.has_time--;
}
if(self.user_pet_info.pet_2.has_time > 0){
self.user_pet_info.pet_2.has_time--;
}
},1000);
});
},
// 用户宠物死亡,发送通知
user_pet_die(pet_position){
this.game.sendEvent('user_pet_die',{
pet_position:pet_position
})
},
}
});
}
</script>
</body>
</html>
- 种植园页面, 结束用户宠物死亡的通知, 发送请求,更改数据
html/orchard.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>种植园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info">
<div class="avatar" @click='to_user'>
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">{{user_data.nickname}}</p>
</div>
<div class="wallet" @click='user_recharge'>
<div class="balance">
<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
<p class="num">{{game.number_format(user_data.money)}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">{{game.number_format(user_data.credit)}}</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
签到有礼
</div>
<div class="menu">
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
邮件中心
</div>
</div>
</div>
<div class="footer" >
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='to_package'>背包</li>
<li class="menu-center" @click="to_shop">商店</li>
<li class="menu">消息</li>
<li class="menu">好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_data:{}, // 当前用户信息
music_play:true,
namespace: '/orchard', // websocket命名空间
socket: null, // websocket连接对象
recharge_list: [], // 允许充值的金额列表
package_init_setting: {}, // 背包初始配置信息
user_info: {}, // 用户登陆初始化化信息
user_package_info: {}, // 用户背包信息
user_pet_info: {}, // 用户宠物信息
pet_position:null, // 宠物栏位置
}
},
created(){
// socket建立连接
this.socket_connect();
// 自动加载我的果园页面
this.to_my_orchard();
// 获取用户数据
this.get_user_data()
// 监听事件变化
this.listen()
// 获取充值金额列表
this.get_recharge_list()
},
methods:{
back(){
this.game.openWin("root");
},
// 监听事件
listen(){
// 1.监听token更新的通知
this.listen_update_token();
// 2.监听是否购买道具成功的通知
this.listen_buy_prop_success();
// 3.监听是否使用道具成功的通知
// this.listen_use_prop_success();
// 4.监听解锁背包容量的通知
this.listen_unlock_package();
// 5.监听是否用其他页面需要获取用户宠物信息
this.listen_get_user_pet_info();
// 6.监听用户宠物是否死亡的通知
this.listen_user_pet_die();
},
// 1.监听token更新的通知
listen_update_token(){
api.addEventListener({
name: 'update_token'
}, (ret, err) => {
// 更新用户数据
this.get_user_data()
});
},
// 2.监听是否购买道具成功的通知
listen_buy_prop_success(){
api.addEventListener({
name: 'buy_prop_success'
}, (ret, err)=>{
// 发送请求,更新用户背包数据
this.socket.emit('user_package');
});
},
// 3.监听解锁背包容量的通知
listen_unlock_package(){
api.addEventListener({
name: 'unlock_package'
}, (ret, err)=>{
// 发送解锁背包的请求
this.socket.emit('unlock_package');
// 获取解锁背包响应数据
this.socket.on('unlock_package_response',(response)=>{
if(response.errno === 1000){
// 更新用户背包数据
this.socket.emit('user_package')
// 用户积分改变,重新刷新token值
this.refresh_user_token();
}
})
});
},
// 5.监听是否用其他页面需要获取用户宠物信息
listen_get_user_pet_info(){
api.addEventListener({
name: 'get_user_pet_info'
}, (ret, err)=>{
// 获取用户宠物信息
this.get_user_pet_info();
});
},
// 6.监听用户宠物是否死亡的通知
listen_user_pet_die(){
api.addEventListener({
name: 'user_pet_die'
}, (ret, err)=>{
// 结束宠物死亡位置栏参数
this.pet_position = ret.value.pet_position
// 用户宠物死亡,发送请求
this.socket.emit('user_pet_die',{
pet_position:this.pet_position
})
// 响应用户宠物死亡
this.socket.on('user_pet_die_response',(response)=>{
if(response.errno === 1000){
this.game.tips("宠物"+this.pet_position+"已经死亡了")
}else {
this.game.tips(response.errmsg)
}
})
});
},
// 获取用户宠物信息
get_user_pet_info(){
// 发起请求用户宠物信息
this.socket.emit('user_pet_info');
// 获取响应
this.socket.on('user_pet_info_response', (response)=>{
if(response.errno === 1000){
// 响应成功,获取宠物信息
this.user_pet_info = response.user_pet_info
// 通知其他页面,已经获取用户宠物信息
this.game.sendEvent('get_user_pet_info_success',{
user_pet_info: this.user_pet_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 用户积分改变,重新刷新token值
refresh_user_token(){
let self = this
self.game.check_user_login(self, ()=>{
let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
self.game.post(self,{
'method': 'Users.refresh',
'params':{},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 刷新token值
let token = self.game.getfs("refresh_token");
if(token){
// 记住密码的情况
self.game.deldata(["access_token","refresh_token"]);
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.delfs(["access_token","refresh_token"]);
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 发布全局广播,token更新
self.game.sendEvent('update_token')
}
}
})
})
},
// 通过token值获取用户数据
get_user_data(){
let self = this;
// 检查token是否过期,过期从新刷新token
self.game.check_user_login(self, ()=>{
// 获取token
let token = this.game.getfs('access_token') || this.game.getdata('access_token')
// 根据token获取用户数据
this.user_data = this.game.get_user_by_token(token)
})
},
// websocket连接处理
socket_connect(){
this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("开始连接服务端");
// 获取背包初始配置信息
this.get_package_setting()
// websocket登陆处理
this.user_websocket_login()
});
},
// 获取背包初始配置信息
get_package_setting(){
this.socket.on('init_config_response', (response)=>{
// this.game.print(response,1)
this.package_init_setting = response
})
},
// websocket登陆处理
user_websocket_login(){
// 客户端发送用户登陆请求
this.socket.emit('login',{'uid': this.user_data.unique_id});
// 接收登陆响应
this.login_response();
// 接收用户背包响应
this.user_package_response();
},
// 接收登陆初始化信息
login_response(){
this.socket.on('login_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_info = response.user_info
}
})
},
// 接收用户背包信息
user_package_response(){
this.socket.on('user_package_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_package_info = response.package_info;
// 全局广播用户背包更新
this.game.sendEvent('package_update',{
user_package_info: this.user_package_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 跳转我的果园页面
to_my_orchard(){
this.game.check_user_login(this, ()=>{
this.game.openFrame("my_orchard","my_orchard.html","from_right",{
marginTop: 174, //相对父页面上外边距的距离,数字类型
marginLeft: 0, //相对父页面左外边距的距离,数字类型
marginBottom: 54, //相对父页面下外边距的距离,数字类型
marginRight: 0 //相对父页面右外边距的距离,数字类型
});
})
},
// 点击商店打开道具商城页面
to_shop(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('shop', 'shop.html', 'from_top');
})
},
// 点击头像,跳转用户中心页面
to_user(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('user', 'user.html', 'from_right');
})
},
// 获取充值金额列表
get_recharge_list(){
let self = this;
self.game.check_user_login(self,()=>{
self.game.post(self,{
'method': 'Users.recharge_list',
'params': {},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.recharge_list = data.result.recharge_list
}
}
})
})
},
// 点击钱包进行用户充值,设置充值金额
user_recharge(){
api.actionSheet({
title: '余额充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret.buttonIndex <= this.recharge_list.length ){
// 充值金额
let money = this.recharge_list[ret.buttonIndex-1];
// this.game.print(money,1);
// 发送支付宝充值请求
this.recharge_app_pay(money)
}
});
},
// 发送支付宝充值请求
recharge_app_pay(money){
// 获取支付宝支付对象
let aliPayPlus = api.require("aliPayPlus");
let self = this;
// 向服务端发送请求获取终止订单信息
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.recharge',
'params':{
'money': money,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 本次充值的订单参数
let order_string = data.result.order_string;
// 支付完成以后,支付APP返回的响应状态码
let resultCode = {
"9000": "支付成功!",
"8000": "支付正在处理中,请稍候!",
"4000": "支付失败!请联系我们的工作人员~",
"5000": "支付失败,重复的支付操作",
"6002": "网络连接出错",
"6004": "支付正在处理中,请稍后",
}
// 唤醒支付宝APP,发起支付
aliPayPlus.payOrder({
orderInfo: order_string,
sandbox: data.result.sandbox, // 将来APP上线需要修改成false
},(ret, err)=>{
if(resultCode[ret.code]){
// 提示支付结果
if(ret.code != 9000){
self.game.tips(resultCode[ret.code]);
}else {
// 支付成功,向服务端请求验证支付结果 - 参数订单号
self.check_recharge_result(data.result.order_number);
}
}
})
}
}
})
})
},
// 向服务端发送请求,校验充值是否成功
check_recharge_result(order_number){
let self = this;
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.check_recharge_result',
'params':{
'order_number':order_number,
},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 充值成功
self.game.tips('充值成功!')
// 用户数据更改过,重新刷新token值
let token = self.game.getfs("access_token");
// 删除token值
self.game.deldata(["access_token","refresh_token"]);
self.game.delfs(["access_token","refresh_token"]);
if(token){
// 记住密码的情况
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 全局广播充值成功
self.game.sendEvent('recharge_success')
// 全局广播刷新token值
self.game.sendEvent('update_token')
}
}
})
})
},
// 点击背包,跳转到背包页面,并传递背包初始配置信息
to_package(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('package', 'package.html', 'from_top',null, {
'package_init_setting': this.package_init_setting,
'user_package_info': this.user_package_info,
'user_info': this.user_info
})
})
},
}
});
}
</script>
</body>
</html>
宠物道具使用
宠物保存在背包,所以用户如果要使用宠物,则只需要在2个宠物栏的情况下,让用户选择宠物放置位置。
服务端提供宠物道具使用api接口
- 用户使用宠物道具, 更改用户宠物信息, 更改用户背包宠物道具信息 ,
orchard.socket
,新增代码:
# 使用宠物道具
@get_user_object_by_socket_sid
def on_use_pet_prop(self, user, data):
'''
使用宠物道具
:param user: 根据回话ID获取的用户模型对象
:param data: 客户端传来的宠物道具相关信息
:return:
'''
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('use_pet_prop_response', {
'errno': code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
prop_id = data.get('prop_id') # 获取宠物道具id
pet_position = data.get('pet_position') # 获取宠物使用位置信息
# 2. 获取宠物道具信息
pet_prop = services.get_pet_item(prop_id)
# 判断宠物道具是否存在
if len(pet_prop) == 0:
emit('use_pet_prop_response', {
'errno': code.CODE_NO_SUCH_PROP,
'errmsg': message.no_such_prop,
})
# 停止程序继续运行
return
# 3. 获取当前用户的宠物信息
user_pet_info = services.get_user_pet_info(user.id)
# 判断宠物位置只有一个,并且宠物1位置已经被占,就不能在使用宠物了
if user_pet_info['pet_position'] == 1 and len(user_pet_info['pet_1']) > 2:
emit('use_pet_prop_response', {
'errno': code.CODE_NO_SUCH_PROP,
'errmsg': message.no_such_prop,
})
# 停止程序继续运行
return
# 4. 获取当前位置的宠物的剩余时间
pet_ttl = services.get_pet_ttl(user.id, pet_position)
# 判断宠物是否死亡
if pet_ttl >=0:
emit('use_pet_prop_response', {
'errno': code.CODE_PET_NOT_DIE,
'errmsg': message.pet_not_die,
})
# 停止程序继续运行
return
# 5.获取当前用户背包信息,判断是否存在当前宠物道具
user_package_info = user_services.get_package_info_by_id(user.id)
prop_key = -1 # 默认道具不存在,所以在背包的道具列表中,下标为-1
# 获取宠物道具在背包道具列表中的索引下标
for key, prop in enumerate(user_package_info['props_list']):
if prop_id == prop['prop_id']:
prop_key = key
break
# 判断是否存在当前宠物道具
if prop_key == -1:
emit('use_pet_prop_response', {
'errno': code.CODE_NO_SUCH_PROP,
'errmsg': message.no_such_prop,
})
# 停止程序继续运行
return
# 6. MongoDB中添加用户宠物信息, 并在背包中减去已使用道具数量
services.use_pet_prop(user.id, pet_prop, pet_position)
# 7. 返回最新的用户宠物信息
self.on_user_pet_info()
# 8. 返回最新的用户背包信息
self.on_user_package()
# 响应数据
emit('use_pet_prop_response', {
'errno': code.CODE_OK,
'errmsg': message.ok,
})
- 数据库处理层代码,
orchard/services.py
,代码:
from datetime import datetime
from application.apps.users.documents import UserPackageDocument
from .documents import UsePropLogDocument
# 用户使用了宠物道具
def use_pet_prop(user_id, pet_prop, pet_position):
'''
用户使用了宠物道具
:param user_id: 用户ID
:param pet_prop: 宠物道具信息
:param pet_position: 宠物放置的位置
:return:
'''
# 1.获取用户宠物信息对象
document = UserPetDocument.objects.get(user_id=user_id)
# 更新用户宠物信息
updater = {}
updater[f'pet_{pet_position}'] = {
"start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"pet_image": pet_prop["image"],
"pet_id": pet_prop["id"],
"life_time": pet_prop["life_time"],
"hunger_num": pet_prop["hunger_num"],
"satiety_num": pet_prop["satiety_num"],
"hit_rate": pet_prop["hit_rate"]
}
document.update(**updater)
# 2.redis给宠物设置生命时间
redis_orchard.setex(f'user:{user_id}:pet:{pet_position}', config.PET_MAX_TIME, '_')
# 3.获取用户背包对象
user_package = UserPackageDocument.objects.get(user_id = user_id)
current_prop = {} # 背包道具列表中的当前使用的宠物信息
current_key = -1 # 背包道具列表索引
for key,prop in enumerate(user_package['props_list']):
# 必须确定道具类型,
if prop['type'] == 'pet':
if pet_prop['id'] == prop['prop_id']:
current_prop = prop
current_key = key
break
print('pet_prop=',pet_prop)
print('current_prop=',current_prop)
print('props_list',user_package['props_list'])
if current_prop['num'] <= 1:
# 如果只有一个道具的情况, 使用玩了以后直接删除道具项
if len(user_package['props_list']) == 1:
# 如果道具列表只剩一个道具,重置道具列表
user_package['props_list'] = []
else:
# 否则删除该道具项
user_package['props_list'].remove(current_prop)
else:
# 有多个道具,数据减1
user_package['props_list'][current_key]['num'] = int(user_package["props_list"][current_key]["num"]) - 1
# 保存更改的数据
user_package.save()
# 4. 添加道具使用记录
UsePropLogDocument.objects.create(
user_id=user_id,
prop_type=current_prop["type"],
prop_id=pet_prop["id"],
prop_name=pet_prop["name"],
prop_image=pet_prop["image"],
prop_num=1,
use_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
remark={},
)
- 添加道具使用日志的文档模型,
orchard.documents
,代码:
# 道具使用日志记录
class UsePropLogDocument(mgdb.Document):
"""道具使用日志记录"""
meta = {
'collection': 'use_prop_log',
'ordering': ['_id'], # 正序排列
'strict': False, # 是否严格语法
}
user_id = mgdb.IntField(required=True, verbose_name="用户ID")
prop_type = mgdb.StringField(required=True, verbose_name="道具类型")
prop_id = mgdb.IntField(required=True, verbose_name="道具ID")
prop_name = mgdb.StringField(required=True, verbase_name="道具名称")
prop_image = mgdb.StringField(required=True, verbase_name="道具图片地址")
prop_num = mgdb.IntField(required=True, default=1, verbose_name="道具数量")
use_time = mgdb.StringField(required=True, auto_add_now=True, verbose_name="道具使用时间")
remark = mgdb.DictField(null=True, verbose_name="道具使用其他说明")
客户端请求使用宠物道具
- 背包道具详情页
html/prop.html
,当用户点击使用道具时,判断是否使用了宠物道具,然后发送全局通知。
<!DOCTYPE html>
<html>
<head>
<title>道具详情</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar item" id="app">
<div class="box">
<p class="title">{{item.prop_name}}</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<img class="invite_code item_img" :src="`../static/images/${item.prop_image}`" alt="">
</div>
<div class="invite_tips item_remark">
<p>{{item.prop_remark}}</p><br>
<p>
<span>库存:{{item.num}}</span>
<span>道具类型:{{prop_type_tips[item.type]}}</span>
</p><br><br>
<p>
<span @click="gotouse">立即使用</span>
<span @click="gotopay">继续购买</span>
</p>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
item: {},
type: "",
prop_type_tips:{
"seed": "种子",
"food": "宠物粮",
"plant": "种植",
"pet": "宠物",
},
buy_type: ['price', 'credit'], // 金钱或积分购买
}
},
created(){
// 接受传递过来的参数
this.get_page_params();
},
methods:{
back(){
this.game.closeFrame();
},
// 接受传递过来的参数
get_page_params(){
// 接受来自商品列表页面的参数
this.item = api.pageParam.prop;
this.type = api.pageParam.prop.type;
},
gotouse(){
// 使用道具
if(this.type == 'pet'){
api.actionSheet({
title: '请选择当前宠物的放养位置?',
cancelTitle: '取消使用',
buttons: ['1号位置','2号位置']
}, (ret, err)=>{
if(ret.buttonIndex != 3){
// 发送全局广播,使用宠物道具
this.game.sendEvent('use_prop',{
prop_id: this.item.prop_id,
prop_type: this.type,
pet_position: ret.buttonIndex
})
this.back();
}
});
}
},
// 点击购买道具
gotopay(){
let self = this
let buttons = [1,2,5,10,20,50];
api.actionSheet({
title: `购买${this.item.prop_name}的数量`,
cancelTitle: '取消',
buttons: buttons,
}, (ret, err)=>{
if(ret.buttonIndex<=buttons.length){
let buy_num = buttons[ret.buttonIndex-1];
// this.game.print(buy_num,1);
api.actionSheet({
title: '请选择购买方式!',
cancelTitle: '取消购买',
buttons: ['使用金钱购买','使用果子购买']
}, (ret, err)=>{
// 金钱是1, 果子是2
if(ret.buttonIndex<=buttons.length){
// self.game.print(self.buy_type[ret.buttonIndex -1])
// 向服务端发送购买道具请求
self.pay_prop(buy_num, self.buy_type[ret.buttonIndex -1])
}
});
}
});
},
// 向服务端发送购买道具请求
pay_prop(buy_num, buy_type){
let self = this;
self.game.check_user_login(self, ()=>{
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self,{
'method': 'Orchard.pay_props',
'params':{
'prop_num': buy_num,
'prop_type': self.type,
'prop_id': self.item.prop_id,
'buy_type': buy_type,
},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 刷新token值
let token = self.game.getfs("access_token");
if(token){
// 记住密码的情况
self.game.deldata(["access_token","refresh_token"]);
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.delfs(["access_token","refresh_token"]);
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 发布全局广播,token更新
self.game.sendEvent('buy_prop_success')
self.game.sendEvent('update_token')
self.game.tips('购买道具成功~')
// 关闭页面
self.game.closeFrame()
}
}
})
})
},
}
});
}
</script>
</body>
</html>
html/orchard.html
中监听如果用户使用宠物道具,请求socket服务端使用宠物道具。
<!DOCTYPE html>
<html>
<head>
<title>种植园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info">
<div class="avatar" @click='to_user'>
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">{{user_data.nickname}}</p>
</div>
<div class="wallet" @click='user_recharge'>
<div class="balance">
<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
<p class="num">{{game.number_format(user_data.money)}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">{{game.number_format(user_data.credit)}}</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
签到有礼
</div>
<div class="menu">
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
邮件中心
</div>
</div>
</div>
<div class="footer" >
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='to_package'>背包</li>
<li class="menu-center" @click="to_shop">商店</li>
<li class="menu">消息</li>
<li class="menu">好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_data:{}, // 当前用户信息
music_play:true,
namespace: '/orchard', // websocket命名空间
socket: null, // websocket连接对象
recharge_list: [], // 允许充值的金额列表
package_init_setting: {}, // 背包初始配置信息
user_info: {}, // 用户登陆初始化化信息
user_package_info: {}, // 用户背包信息
user_pet_info: {}, // 用户宠物信息
pet_position:null, // 宠物栏位置
}
},
created(){
// socket建立连接
this.socket_connect();
// 自动加载我的果园页面
this.to_my_orchard();
// 获取用户数据
this.get_user_data()
// 监听事件变化
this.listen()
// 获取充值金额列表
this.get_recharge_list()
},
methods:{
back(){
this.game.openWin("root");
},
// 监听事件
listen(){
// 1.监听token更新的通知
this.listen_update_token();
// 2.监听是否购买道具成功的通知
this.listen_buy_prop_success();
// 3.监听是否使用道具成功的通知
// this.listen_use_prop_success();
// 4.监听解锁背包容量的通知
this.listen_unlock_package();
// 5.监听是否用其他页面需要获取用户宠物信息
this.listen_get_user_pet_info();
// 6.监听用户宠物是否死亡的通知
this.listen_user_pet_die();
// 7.监听是否使用了道具
this.listen_use_prop();
},
// 1.监听token更新的通知
listen_update_token(){
api.addEventListener({
name: 'update_token'
}, (ret, err) => {
// 更新用户数据
this.get_user_data()
});
},
// 2.监听是否购买道具成功的通知
listen_buy_prop_success(){
api.addEventListener({
name: 'buy_prop_success'
}, (ret, err)=>{
// 发送请求,更新用户背包数据
this.socket.emit('user_package');
});
},
// 3.监听解锁背包容量的通知
listen_unlock_package(){
api.addEventListener({
name: 'unlock_package'
}, (ret, err)=>{
// 发送解锁背包的请求
this.socket.emit('unlock_package');
// 获取解锁背包响应数据
this.socket.on('unlock_package_response',(response)=>{
if(response.errno === 1000){
// 更新用户背包数据
this.socket.emit('user_package')
// 用户积分改变,重新刷新token值
this.refresh_user_token();
}
})
});
},
// 5.监听是否用其他页面需要获取用户宠物信息
listen_get_user_pet_info(){
api.addEventListener({
name: 'get_user_pet_info'
}, (ret, err)=>{
// 获取用户宠物信息
this.get_user_pet_info();
});
},
// 6.监听用户宠物是否死亡的通知
listen_user_pet_die(){
api.addEventListener({
name: 'user_pet_die'
}, (ret, err)=>{
// 结束宠物死亡位置栏参数
this.pet_position = ret.value.pet_position
// 用户宠物死亡,发送请求
this.socket.emit('user_pet_die',{
pet_position:this.pet_position
})
// 响应用户宠物死亡
this.socket.on('user_pet_die_response',(response)=>{
if(response.errno === 1000){
this.game.tips("宠物"+this.pet_position+"已经死亡了")
}else {
this.game.tips(response.errmsg)
}
})
});
},
// 7.监听是否使用了道具
listen_use_prop(){
api.addEventListener({
name: 'use_prop'
}, (ret, err)=>{
// 使用宠物道具
if(ret.value.prop_type == 'pet'){
// 发送使用宠物道具请求
this.socket.emit('use_pet_prop',{
prop_id: ret.value.prop_id,
pet_position: ret.value.pet_position
});
// 响应数据
this.socket.on('use_pet_prop_response',(response)=>{
if(response.errno === 1000){
this.game.tips('宠物道具使用成功!')
}else {
this.game.tips(response.errmsg)
}
})
}
});
},
// 获取用户宠物信息
get_user_pet_info(){
// 发起请求用户宠物信息
this.socket.emit('user_pet_info');
// 获取响应
this.socket.on('user_pet_info_response', (response)=>{
if(response.errno === 1000){
// 响应成功,获取宠物信息
this.user_pet_info = response.user_pet_info
// 通知其他页面,已经获取用户宠物信息
this.game.sendEvent('get_user_pet_info_success',{
user_pet_info: this.user_pet_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 用户积分改变,重新刷新token值
refresh_user_token(){
let self = this
self.game.check_user_login(self, ()=>{
let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
self.game.post(self,{
'method': 'Users.refresh',
'params':{},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 刷新token值
let token = self.game.getfs("refresh_token");
if(token){
// 记住密码的情况
self.game.deldata(["access_token","refresh_token"]);
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.delfs(["access_token","refresh_token"]);
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 发布全局广播,token更新
self.game.sendEvent('update_token')
}
}
})
})
},
// 通过token值获取用户数据
get_user_data(){
let self = this;
// 检查token是否过期,过期从新刷新token
self.game.check_user_login(self, ()=>{
// 获取token
let token = this.game.getfs('access_token') || this.game.getdata('access_token')
// 根据token获取用户数据
this.user_data = this.game.get_user_by_token(token)
})
},
// websocket连接处理
socket_connect(){
this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("开始连接服务端");
// 获取背包初始配置信息
this.get_package_setting()
// websocket登陆处理
this.user_websocket_login()
});
},
// 获取背包初始配置信息
get_package_setting(){
this.socket.on('init_config_response', (response)=>{
// this.game.print(response,1)
this.package_init_setting = response
})
},
// websocket登陆处理
user_websocket_login(){
// 客户端发送用户登陆请求
this.socket.emit('login',{'uid': this.user_data.unique_id});
// 接收登陆响应
this.login_response();
// 接收用户背包响应
this.user_package_response();
},
// 接收登陆初始化信息
login_response(){
this.socket.on('login_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_info = response.user_info
}
})
},
// 接收用户背包信息
user_package_response(){
this.socket.on('user_package_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_package_info = response.package_info;
// 全局广播用户背包更新
this.game.sendEvent('package_update',{
user_package_info: this.user_package_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 跳转我的果园页面
to_my_orchard(){
this.game.check_user_login(this, ()=>{
this.game.openFrame("my_orchard","my_orchard.html","from_right",{
marginTop: 174, //相对父页面上外边距的距离,数字类型
marginLeft: 0, //相对父页面左外边距的距离,数字类型
marginBottom: 54, //相对父页面下外边距的距离,数字类型
marginRight: 0 //相对父页面右外边距的距离,数字类型
});
})
},
// 点击商店打开道具商城页面
to_shop(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('shop', 'shop.html', 'from_top');
})
},
// 点击头像,跳转用户中心页面
to_user(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('user', 'user.html', 'from_right');
})
},
// 获取充值金额列表
get_recharge_list(){
let self = this;
self.game.check_user_login(self,()=>{
self.game.post(self,{
'method': 'Users.recharge_list',
'params': {},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.recharge_list = data.result.recharge_list
}
}
})
})
},
// 点击钱包进行用户充值,设置充值金额
user_recharge(){
api.actionSheet({
title: '余额充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret.buttonIndex <= this.recharge_list.length ){
// 充值金额
let money = this.recharge_list[ret.buttonIndex-1];
// this.game.print(money,1);
// 发送支付宝充值请求
this.recharge_app_pay(money)
}
});
},
// 发送支付宝充值请求
recharge_app_pay(money){
// 获取支付宝支付对象
let aliPayPlus = api.require("aliPayPlus");
let self = this;
// 向服务端发送请求获取终止订单信息
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.recharge',
'params':{
'money': money,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 本次充值的订单参数
let order_string = data.result.order_string;
// 支付完成以后,支付APP返回的响应状态码
let resultCode = {
"9000": "支付成功!",
"8000": "支付正在处理中,请稍候!",
"4000": "支付失败!请联系我们的工作人员~",
"5000": "支付失败,重复的支付操作",
"6002": "网络连接出错",
"6004": "支付正在处理中,请稍后",
}
// 唤醒支付宝APP,发起支付
aliPayPlus.payOrder({
orderInfo: order_string,
sandbox: data.result.sandbox, // 将来APP上线需要修改成false
},(ret, err)=>{
if(resultCode[ret.code]){
// 提示支付结果
if(ret.code != 9000){
self.game.tips(resultCode[ret.code]);
}else {
// 支付成功,向服务端请求验证支付结果 - 参数订单号
self.check_recharge_result(data.result.order_number);
}
}
})
}
}
})
})
},
// 向服务端发送请求,校验充值是否成功
check_recharge_result(order_number){
let self = this;
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.check_recharge_result',
'params':{
'order_number':order_number,
},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 充值成功
self.game.tips('充值成功!')
// 用户数据更改过,重新刷新token值
let token = self.game.getfs("access_token");
// 删除token值
self.game.deldata(["access_token","refresh_token"]);
self.game.delfs(["access_token","refresh_token"]);
if(token){
// 记住密码的情况
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 全局广播充值成功
self.game.sendEvent('recharge_success')
// 全局广播刷新token值
self.game.sendEvent('update_token')
}
}
})
})
},
// 点击背包,跳转到背包页面,并传递背包初始配置信息
to_package(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('package', 'package.html', 'from_top',null, {
'package_init_setting': this.package_init_setting,
'user_package_info': this.user_package_info,
'user_info': this.user_info
})
})
},
}
});
}
</script>
</body>
</html>
宠物栏解锁
客户端发送请求
- 客户端在我的种植园页面中,当用户点击解锁宠物栏后,发起通知,告诉其他页面。当接收到解锁宠物栏成功的通知以后,修改宠物栏的数量。
my_orchard.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard orchard-frame" id="app">
<div class="background">
<img class="grassland2" src="../static/images/grassland2.png" alt="">
<img class="mushroom1" src="../static/images/mushroom1.png" alt="">
<img class="stake1" src="../static/images/stake1.png" alt="">
<img class="stake2" src="../static/images/stake2.png" alt="">
</div>
<div class="pet-box">
<div class="pet">
<img class="pet-item" v-if="pet_info.pet_1.has_time>0" :src="`../static/images/${pet_info.pet_1.pet_image}`" alt="">
</div>
<div class="pet turned_off" v-if="pet_info.pet_position<2" @click="unlock_pet_field">
<img class="turned_image" src="../static/images/turned_off.png" alt="">
<p>激活宠物栏</p>
</div>
<div class="pet" v-else>
<img class="pet-item" v-if="pet_info.pet_2.has_time>0" :src="`../static/images/${pet_info.pet_2.pet_image}`" alt="">
</div>
</div>
<div class="tree-list">
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree1.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
</div>
</div>
<div class="prop-list">
<div class="prop">
<img src="../static/images/prop1.png" alt="">
<span>1</span>
<p>化肥</p>
</div>
<div class="prop">
<img src="../static/images/prop2.png" alt="">
<span>0</span>
<p>修剪</p>
</div>
<div class="prop">
<img src="../static/images/prop3.png" alt="">
<span>1</span>
<p>浇水</p>
</div>
<div class="prop">
<img src="../static/images/prop4.png" alt="">
<span>1</span>
<p>宠物粮</p>
</div>
</div>
<div class="pet-hp-list">
<div class="pet-hp" v-if="pet_info.pet_1.has_time>0">
<p>宠物1 饱食度</p>
<div class="hp">
<div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
</div>
</div>
<div class="pet-hp" v-if="pet_info.pet_2.has_time>0">
<p>宠物2 饱食度</p>
<div class="hp">
<div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
pet_info:{ // 用户的信宠物息
pet_1:{},
pet_2:{},
},
}
},
computed:{
pet_1_hp(){
return ((this.pet_info.pet_1.has_time/this.pet_info.pet_1.max_time)*100).toFixed(2);
},
pet_2_hp(){
return ((this.pet_info.pet_2.has_time/this.pet_info.pet_2.max_time)*100).toFixed(2);
},
},
watch:{
"pet_info.pet_1.has_time":function(){
if(this.pet_info.pet_1.has_time<1){
this.pet_die(1);
}
},
"pet_info.pet_2.has_time":function(){
if(this.pet_info.pet_2.has_time<1){
this.pet_die(2);
}
},
},
created(){
this.listen();
},
methods:{
listen(){
this.get_pet_info();
},
get_pet_info(){
// 发起全局通知
api.sendEvent({
name: 'get_pet_info',
extra: {}
});
api.addEventListener({
name: 'get_pet_info_response'
}, (ret, err)=>{
this.pet_info = ret.value.pet_info;
this.game.print(this.pet_info);
setInterval(()=>{
if(this.pet_info.pet_1.has_time>=0){
this.pet_info.pet_1.has_time-=0.5;
}
if(this.pet_info.pet_2.has_time>=0){
this.pet_info.pet_2.has_time-=0.5;
}
},500);
});
},
pet_die(position){
api.sendEvent({
name: 'pet_die',
extra: {
position: position,
}
});
},
unlock_pet_field(){
// 激活宠物栏
api.actionSheet({
title: '确认是否解锁新的宠物栏?',
cancelTitle: '取消',
destructiveTitle: '解锁',
}, (ret, err)=>{
if(ret.buttonIndex == 1){
api.sendEvent({
name: 'unlock_pet_field',
extra: {}
});
}
});
}
}
});
}
</script>
</body>
</html>
- 种植园页面
html/orchard.html
中,监听用户解锁宠物栏的事件通知,请求服务端进行解锁。
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/main.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user_info.avatar" :src="user_info.avatar" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else-if="user_info.nickname" :username="user_info.nickname" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else :username="user_info.id" :size="62" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">{{user_info.nickname}}</p>
</div>
<div class="wallet">
<div class="balance" @click="user_recharge">
<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
<p class="num">{{user_info.money.toFixed(2).toLocaleString()}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">{{user_info.credit.toFixed(2).toLocaleString()}}</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
签到有礼
</div>
<div class="menu">
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
邮件中心
</div>
</div>
</div>
<div class="footer" >
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click="open_package">背包</li>
<li class="menu-center" @click="open_shop">商店</li>
<li class="menu">消息</li>
<li class="menu">好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_info:{},
music_play:true,
namespace: '/orchard',
socket: null,
recharge_list:[10,20,30,50,100,200,300,500,1000], // 允许充值的金额列表
package_init_setting:{}, // 配置初始信息
user_package: {}, // 用户背包信息
pet_info:{}, // 用户的宠物信息
}
},
created(){
this.listen();
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
this.user_info = this.game.get_user_by_token(token);
this.open_my_chard();
this.connect();
},
methods:{
listen(){
// 监听是否更新了token
this.listen_update_token();
// 监听是否有购买道具成功的通知
this.listen_buy_prop_success();
// 监听是否有使用道具成功的通知
this.listen_use_prop_success();
// 监听是否有其他页面需要宠物信息
this.listen_get_pet_info();
// 监听是否有宠物挂了
this.listen_pet_die();
// 监听是否有使用了道具
this.listen_use_prop();
// 监听解锁宠物栏
this.listen_unlock_pet_field();
},
listen_unlock_pet_field(){
// 监听解锁宠物栏的通知
api.addEventListener({
name: 'unlock_pet_field'
}, (ret, err)=>{
// 发送请求
this.socket.emit("unlock_pet");
// 监听响应
this.socket.on("unlock_pet_response",this.unlock_pet_field_response);
});
},
unlock_pet_field_response(data){
// 解锁宠物栏的结果
if(data.errno==1000){
// 成功
// 通知其他页面token更新了
api.sendEvent({
name: 'update_token',
extra: {}
});
this.game.tips("解锁成功!");
}else{
this.game.tips(data.errmsg);
}
},
listen_use_prop(){
// 监听是否有使用了道具
api.addEventListener({
name: 'use_prop'
}, (ret, err)=>{
this.game.print(ret.value,1);
if(ret.value.prop_type==="pet"){
// 使用了宠物道具
this.socket.emit("use_pet",{prop_id:ret.value.prop_id,position:ret.value.position});
this.socket.on("use_pet_response",(response)=>{
this.game.tips(response.errmsg);
})
}
});
},
listen_pet_die(){
api.addEventListener({
name: 'pet_die'
}, (ret, err)=>{
this.pet_die(ret.value.position);
});
},
pet_die(position){
// 宠物死亡处理
this.socket.emit("pet_die", {pet_position:position});
},
listen_get_pet_info(){
// 监听是否有其他页面需要宠物信息
api.addEventListener({
name: 'get_pet_info',
}, (ret, err)=>{
// 获取宠物信息
this.get_pet_info();
});
},
listen_update_token(){
api.addEventListener({
name: 'update_token',
}, (ret, err)=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
this.user_info = this.game.get_user_by_token(token);
});
},
listen_buy_prop_success(){
// 监听是否有购买道具成功的通知
api.addEventListener({
name: 'buy_prop_success',
}, (ret, err)=>{
this.socket.emit("user_package");
api.sendEvent({
name: 'package_update',
extra: {
user_package: this.user_package,
}
});
});
},
listen_use_prop_success(){
// 监听是否有使用道具成功的通知
},
open_shop(){
this.game.openFrame("shop","shop.html",null,null,{
type: "push", //动画类型(详见动画类型常量)
subType: "from_top", //动画子类型(详见动画子类型常量)
duration: 300 //动画过渡时间,默认300毫秒
});
},
open_my_chard(){
this.game.openFrame("my_orchard","my_orchard.html",null,{
marginTop: 174, //相对父页面上外边距的距离,数字类型
marginLeft: 0, //相对父页面左外边距的距离,数字类型
marginBottom: 54, //相对父页面下外边距的距离,数字类型
marginRight: 0 //相对父页面右外边距的距离,数字类型
},{
type: "push",
subType: "from_right",
duration: 300
});
},
connect(){
// socket连接
this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.init_config_response(); // 监听服务端返回的背包初始配置
this.login();
this.unlock_package(); // 识别是否有激活背包的通知
});
},
init_config_response(){
// 监听服务端返回的背包初始配置
this.socket.on("init_config_response",(response)=>{
this.package_init_setting = response;
});
},
login(){
this.socket.emit("login", {"uid":this.user_info.unique_id});
this.login_response();
// 监听来自服务端返回的用户异常
this.user_error_response();
this.user_package_response(); // 获取背包信息
},
user_error_response(){
// 监听来自服务端响应的用户异常
this.socket.on("user_error_response",(response)=>{
this.game.tips(response.errmsg);
});
},
get_pet_info(){
// 获取宠物信息
this.socket.emit("pet_info");
this.socket.on("pet_info_response",(response)=>{
this.pet_info = response.pet_info;
// 通知其他页面,已经获取到宠物信息
api.sendEvent({
name: 'get_pet_info_response',
extra: {
pet_info:this.pet_info,
}
});
});
},
login_response(){
this.socket.on("login_response",(response)=>{
this.game.print(response);
});
},
user_package_response(){
// 获取背包信息
this.socket.on("user_package_response",(response)=>{
this.user_package = response.package_info;
// 发起通知,背包数据更新了
api.sendEvent({
name: 'package_update',
extra: {
user_package: this.user_package,
}
});
});
},
unlock_package(){
// 激活背包
api.addEventListener({
name: 'unlock_package'
}, (ret, err)=>{
this.socket.emit("unlock_package");
this.socket.on("unlock_package_response",(response)=>{
this.game.print(response);
})
});
},
back(){
this.game.openWin("root");
},
user_recharge(){
// 用户选择充值,让用户设置充值的金额
api.actionSheet({
title: '余额充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret.buttonIndex <= this.recharge_list.length ){
// 充值金额
money = this.recharge_list[ret.buttonIndex-1];
// 调用支付宝充值
// this.game.print(money,1);
this.recharge_app_pay(money);
}
});
},
recharge_app_pay(money){
// 获取支付宝的支付信息,并发起支付。
var aliPayPlus = api.require('aliPayPlus');
// 唤醒支付宝发起支付
var self = this;
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
// 从服务端获取充值订单的参数
this.game.check_user_login(this,()=>{
this.game.post(this, {
"method": "Users.recharge",
"params": {
money:money, // 充值金额
},
"header": {
"Authorization": "jwt " + token
},
success(response) {
let data = response.data;
if (data.result && data.result.errno == 1000) {
// 本次充值的订单参数
let order_string = data.result.order_string;
// 支付结果的提示码
let resultCode = {
"9000": "支付成功!",
"8000": "支付正在处理中,请稍候!",
"4000": "支付失败!请联系我们的工作人员~",
"5000": "支付失败,重复的支付操作",
"6002": "网络连接出错",
"6004": "支付正在处理中,请稍后",
}
// 唤醒支付宝APP,发起支付
aliPayPlus.payOrder({
orderInfo: order_string,
sandbox: data.result.sandbox, // 将来APP上线需要修改成false
}, (ret, err)=>{
if(ret.code !== "9000"){
self.game.tips(resultCode[ret.code],4000);
}else{
self.check_recharge_result(data.result.order_number);
}
});
} else {
self.game.tips(data.result.errmsg);
}
}
});
});
},
check_recharge_result(order_number){
// 查询充值的结果
var self = this;
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
// 从服务端获取充值订单的参数
this.game.check_user_login(this,()=>{
this.game.post(this, {
"method": "Users.check_recharge_result",
"params": {
order_number:order_number, // 订单号
},
"header": {
"Authorization": "jwt " + token
},
success(response) {
let data = response.data;
if (data.result && data.result.errno == 1000) {
// 支付完成以后,支付APP返回的响应状态码
self.game.tips("充值成功!",4000);
// 同步token
let token = self.game.getfs("access_token");
self.game.deldata(["access_token","refresh_token"]);
self.game.delfs(["access_token","refresh_token"]);
if(token){
// 记住密码的情况
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
api.sendEvent({
name: 'pay_success',
extra: {}
});
api.sendEvent({
name: 'update_token',
extra: {}
});
}
}
});
});
},
open_package(){
// 打开背包,附带背包初始配置
this.game.openFrame("package","package.html",{
"package_init_setting":this.package_init_setting,
"user_package":this.user_package,
},null,{
type: "push", //动画类型(详见动画类型常量)
subType: "from_top", //动画子类型(详见动画子类型常量)
duration: 300 //动画过渡时间,默认300毫秒
});
}
}
});
}
</script>
</body>
</html>
服务端接口
服务端监听客户端的解锁宠物栏的事件通知,进行解锁
- 视图 解锁宠物栏
orchard.socket
,代码:
# 激活解锁宠物栏
@get_user_object_by_socket_sid
def on_unlock_pet_field(self,user):
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('unlock_pet_field_response', {
'errno': code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 2. 判断是否到达宠物栏激活上限(一共两个宠物栏)
# 获取用户宠物信息
user_pet_info = services.get_user_pet_info(user.id)
if user_pet_info['pet_position'] == 2:
emit('unlock_pet_field_response', {
'errno': code.CODE_PACKAGE_SPACE_NOT_ENOUGH,
'errmsg': message.package_space_not_enough,
})
# 停止程序继续运行
return
# 3. 判断用户是否有足够的金钱或积分激活宠物栏
res = services.check_user_unlock_pet_condition(user)
if not res:
emit('unlock_pet_field_response', {
'errno': code.CODE_NOT_ENOUGH_MONEY,
'errmsg': message.not_enough_money,
})
# 停止程序继续运行
return
# 4. 解锁宠物栏
services.unlock_pet_field(user)
# 5.刷新客户端新的用户宠物信息
self.on_user_pet_info()
# 响应
emit('unlock_pet_field_response', {
'errno': code.CODE_OK,
'errmsg': message.ok,
})
- 数据处理层,
orchard.services
,代码:
# 判断用户是否有足够的金钱或积分激活宠物栏
def check_user_unlock_pet_condition(user):
'''
判断用户是否有足够的金钱或积分激活宠物栏
:param user: 用户模型对象
:return:
'''
price_dict = config.UNLOCK_PET_FIELD_PRICE
if user.money >= price_dict['money'] and user.credit >= price_dict['credit']:
return True
return None
# 解锁宠物栏
def unlock_pet_field(user):
'''
解锁宠物栏
:param user: 用户模型对象
:return:
'''
# 1. 用户宠物信息修改宠物栏数量[mongodb]
user_pet_info = UserPetDocument.objects.get(user_id = user.id)
user_pet_info.pet_position = 2
user_pet_info.save()
# 2. 扣除用户的积分或者金额[mysql]
price_dict = config.UNLOCK_PET_FIELD_PRICE
user.money = float(user.money) - price_dict['money']
user.credit = int(user.credit) - price_dict['credit']
db.session.commit()
- 种植园配置信息中新增解锁宠物栏条件的配置信息,
setting.plant
,代码:
# 解锁宠物栏的条件[解锁价格]
UNLOCK_PET_FIELD_PRICE = {
"money": 100, # 所需金额
"credit": 1000, # 所需积分
}
宠物喂养
宠物喂养提示的图标显示
- 我的种植园页面, 添加道具样式,
html/my_orchard.html
<div class="pet-box">
<div class="pet">
<span class="popped">
<img class="pet-prop" src="../static/images/prop4.png" alt="">
</span>
<img class="pet-item" v-if="pet_info.pet_1.has_time>0" :src="`../static/images/${pet_info.pet_1.pet_image}`" alt="">
</div>
<div class="pet turned_off" v-if="pet_info.pet_position<2" @click="unlock_pet_field">
<img class="turned_image" src="../static/images/turned_off.png" alt="">
<p>激活宠物栏</p>
</div>
<div class="pet" v-else>
<span class="popped">
<img class="pet-prop" src="../static/images/prop4.png" alt="">
</span>
<img class="pet-item" v-if="pet_info.pet_2.has_time>0" :src="`../static/images/${pet_info.pet_2.pet_image}`" alt="">
</div>
</div>
- 添加css样式,
static/css/main.css
, 代码;
@keyframes prop_move {
0% { top: 5.4rem; }
50% { top: 6rem; }
100%{ top: 5.4rem; }
}
.popped {
transform: translate(-50%, -50%);
border-radius: 5rem;
height: 5rem;
5rem;
display: block;
position: absolute;
opacity: 1;
transition: box-shadow .5s ease-in-out, transform .07s ease-out, opacity .04s ease-in;
top: 6rem;
right: 0rem;
box-shadow: rgb(255, 200, 200) 0px 0px 3rem inset;
z-index: 100;
animation: prop_move 2s infinite;
}
.popped:after {
content: '';
position: absolute;
top: 18%;
left: 18%;
background-color: rgba(191, 255, 255, 0.6);
1.2rem;
height: 1.5rem;
border-radius: 50%;
transform: rotate(45deg) scale(0.8);
}
.popped img{
4rem;
height: 4rem;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
控制喂养图标的出现
1.我的种植园页面 html/my_orchard.html
,控制喂养图标出现,并且发送系统通知,代码:
<!DOCTYPE html>
<html>
<head>
<title>我的果园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard orchard-frame" id="app">
<div class="background">
<img class="grassland2" src="../static/images/grassland2.png" alt="">
<img class="mushroom1" src="../static/images/mushroom1.png" alt="">
<img class="stake1" src="../static/images/stake1.png" alt="">
<img class="stake2" src="../static/images/stake2.png" alt="">
</div>
<div class="pet-box">
<div class="pet">
<span class="popped" v-if='pet_1_hunger'>
<img class="pet-prop" src="../static/images/prop4.png" alt="">
</span>
<img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
</div>
<div class="pet turned_off" v-if='user_pet_info.pet_position < 2' @click='unlock_pet_field'>
<img class="turned_image" src="../static/images/turned_off.png" alt="">
<p>激活宠物栏</p>
</div>
<div class="pet" v-else>
<span class="popped" v-if='pet_2_hunger'>
<img class="pet-prop" src="../static/images/prop4.png" alt="">
</span>
<img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
</div>
</div>
<div class="tree-list">
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree1.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
</div>
</div>
<div class="prop-list">
<div class="prop">
<img src="../static/images/prop1.png" alt="">
<span>1</span>
<p>化肥</p>
</div>
<div class="prop">
<img src="../static/images/prop2.png" alt="">
<span>0</span>
<p>修剪</p>
</div>
<div class="prop">
<img src="../static/images/prop3.png" alt="">
<span>1</span>
<p>浇水</p>
</div>
<div class="prop">
<img src="../static/images/prop4.png" alt="">
<span>1</span>
<p>宠物粮</p>
</div>
</div>
<div class="pet-hp-list">
<div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
<p>宠物1 饱食度</p>
<div class="hp">
<div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
</div>
</div>
<div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
<p>宠物2 饱食度</p>
<div class="hp">
<div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_pet_info:{ // 用户宠物信息
pet_1:{}, // 防止调用时报错
pet_2:{},
},
pet_1_satiety_notification: false, // 宠物感到饥饿
pet_1_hunger_notification: false, // 宠物快饿死了
pet_2_satiety_notification: false,
pet_2_hunger_notification: false,
}
},
// 计算数据
computed:{
// 计算用户宠物血量百分比
pet_1_hp(){
persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
return persent
},
pet_2_hp(){
persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
return persent
},
// 计算宠物喂养图标的出现,饱食度低于一定饥饿度才可以喂养宠物
pet_1_hunger(){
if(this.user_pet_info.pet_1){
let pet_1 = this.user_pet_info.pet_1;
return pet_1.satiety_num > this.pet_1_hp;
}
},
pet_2_hunger(){
if(this.user_pet_info.pet_2){
let pet_2 = this.user_pet_info.pet_2;
return pet_2.satiety_num > this.pet_2_hp;
}
},
},
// 监听用户信息
watch:{
// 监听用户宠物剩余时间,如果死亡发送通知
'user_pet_info.pet_1.has_time':function(){
if(this.user_pet_info.pet_1.has_time < 1){
this.user_pet_die(1)
}
},
'user_pet_info.pet_2.has_time':function(){
if(this.user_pet_info.pet_2.has_time < 1){
this.user_pet_die(2)
}
},
},
created(){
// 监听事件
this.listen();
},
methods:{
// 监听事件
listen(){
// 监听获取用户宠物信息成功的通知
this.listen_get_user_pet_info_success();
},
// 监听获取用户宠物信息成功的通知
listen_get_user_pet_info_success(){
let self = this
// 发起获取用户宠物信息的全局通知
self.game.sendEvent('get_user_pet_info');
// 监听获取用户宠物信息成功的通知
api.addEventListener({
name: 'get_user_pet_info_success'
}, (ret, err)=>{
// 获取用户宠物信息
self.user_pet_info = ret.value.user_pet_info;
self.game.print(self.user_pet_info)
// 设置定时器,宠物存活时间需要递减
setInterval(()=>{
// 宠物剩余时间小于0,就不必再减了
if(self.user_pet_info.pet_1.has_time > 0){
self.user_pet_info.pet_1.has_time--;
// 根据宠物饱食度发送系统通知
if(self.pet_1_hp < self.user_pet_info.pet_1.satiety_num && !self.pet_1_satiety_notification){
// 饥饿
self.game.notification("喂养宠物通知","主人,您的1号宠物已经饿了,快来照顾吧~");
self.pet_1_satiety_notification = true;
}
if(self.pet_1_hp < self.user_pet_info.pet_1.hunger_num && !self.pet_1_hunger_notification){
// 快饿死了
self.game.notification("喂养宠物通知","主人,您的1号宠物饿死了,快来救命~");
self.pet_1_hunger_notification = true;
}
}
if(self.user_pet_info.pet_2.has_time > 0){
self.user_pet_info.pet_2.has_time--;
// 根据宠物饱食度发送系统通知
if(self.pet_2_hp < self.user_pet_info.pet_2.satiety_num && !self.pet_2_satiety_notification){
// 饥饿
self.game.notification("喂养宠物通知","主人,您的2号宠物已经饿了,快来照顾吧~");
self.pet_2_satiety_notification = true;
}
if(self.pet_2_hp < self.user_pet_info.pet_2.hunger_num && !self.pet_2_hunger_notification){
// 快饿死了
self.game.notification("喂养宠物通知","主人,您的2号宠物饿死了,快来救命~");
self.pet_2_hunger_notification = true;
}
}
},1000);
});
},
// 用户宠物死亡,发送通知
user_pet_die(pet_position){
this.game.sendEvent('user_pet_die',{
pet_position:pet_position
})
},
// 点击解锁宠物栏,发送解锁宠物栏通知
unlock_pet_field(){
api.actionSheet({
title: '确认花费一定的金钱和积分解锁宠物栏',
cancelTitle: '取消',
destructiveTitle: '解锁',
}, (ret, err)=>{
if(ret.buttonIndex == 1){
// 发送全局通知,解锁宠物栏
this.game.sendEvent('unlock_pet_field')
}
});
},
}
});
}
</script>
</body>
</html>
static/js/main.js
封装系统通知的发送
// 发起系统通知
notification(title,content){
api.notification({
notify: {
title: title,
content: content
}
});
}
- 因为消息提示的时候,有可能用户并非出于orchard种植园场景,所以我们需要在
html/index.html
主页也要进行监听识别, 点击通知,跳转种植园页面,进行宠物喂养。
<!DOCTYPE html>
<html lang="en">
<head>
<title>首页</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="bg">
<img src="../static/images/bg0.jpg">
</div>
<ul>
<li><img class="module1" src="../static/images/image1.png" @click='to_orchard'></li>
<li><img class="module2" src="../static/images/image2.png" @click='to_user'></li>
<li><img class="module3" src="../static/images/image3.png"></li>
<li><img class="module4" src="../static/images/image4.png" @click='to_login'></li>
</ul>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
music_play:true, // 默认播放背景音乐
prev:{name:"",url:"",params:{}}, // 上一页状态
current:{name:"index",url:"index.html","params":{}}, // 下一页状态
}
},
watch:{
music_play(){
if(this.music_play){
this.game.play_music("../static/mp3/bg1.mp3");
}else{
this.game.stop_music();
}
}
},
created(){
this.listen(); // 监听事件
},
methods:{
// 监听事件
listen(){
// 监听外部访问,唤醒app
this.listen_invite();
// 监听系统通知,点击处理
this.listen_noticeclicked();
},
// 监听外部访问,唤醒app
listen_invite(){
// 使用系统方法appintenr监听并使用appParam接收URLScheme的路由参数
// 收集操作保存起来,并跳转到登陆页面.
api.addEventListener({
name: 'appintent' // 系统方法
}, (ret, err)=>{
// 获取路由参数,保存到本地
let appParam = ret.appParam;
// this.game.print(appParam,1); //{"uid":xxx,"user_type":xxx};
this.game.setfs(appParam)
// 如果是其他用户邀请注册!
if(appParam.user_type == 'invite'){
// 跳转到登陆页面
this.game.openWin('login', 'login.html')
}
});
},
// 监听系统通知,点击处理
listen_noticeclicked(){
api.addEventListener({
name: 'noticeclicked'
}, (ret, err)=>{
// 跳转种植园页面
this.to_orchard();
});
},
// 点击签到跳转登陆页面
to_login(){
this.game.openWin('login','login.html')
},
// 点击跳转到个人中心页面
to_user(){
// 判断用户是否登陆
this.game.check_user_login(this,() => {
this.game.openWin('user', 'user.html')
});
},
// 点击跳转到种植园页面
to_orchard(){
this.game.check_user_login(this,() => {
this.game.openWin('orchard', 'orchard0.html')
});
},
}
})
}
</script>
</body>
</html>
点击喂养图标显示宠物粮道具喂养宠物
实现步骤
1. my_orchard-> 给宠物喂养图标绑定点击事件,发送喂养宠物的通知
2. orchard -> 页面底部出现当前用户背包中存在的所有宠物粮
3. 当用户点选了宠物粮 [哪个宠物?使用什么道具喂养的?]
3.1 发送socket事件,向服务端请求喂养宠物
3.2 扣除道具,增加宠物的饱食度
4. 同步客户端
- 我的种植园页面, 发起喂养宠物的通知
html/my_orchard.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>我的果园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard orchard-frame" id="app">
<div class="background">
<img class="grassland2" src="../static/images/grassland2.png" alt="">
<img class="mushroom1" src="../static/images/mushroom1.png" alt="">
<img class="stake1" src="../static/images/stake1.png" alt="">
<img class="stake2" src="../static/images/stake2.png" alt="">
</div>
<div class="pet-box">
<div class="pet">
<span class="popped" v-if='pet_1_hunger' @click='feed_pet(1)'>
<img class="pet-prop" src="../static/images/prop4.png" alt="">
</span>
<img class="pet-item" v-if='user_pet_info.pet_1.has_time > 0' :src="`../static/images/${user_pet_info.pet_1.pet_image}`" alt="">
</div>
<div class="pet turned_off" v-if='user_pet_info.pet_position < 2' @click='unlock_pet_field'>
<img class="turned_image" src="../static/images/turned_off.png" alt="">
<p>激活宠物栏</p>
</div>
<div class="pet" v-else>
<span class="popped" v-if='pet_2_hunger' @click='feed_pet(2)'>
<img class="pet-prop" src="../static/images/prop4.png" alt="">
</span>
<img class="pet-item" v-if='user_pet_info.pet_2.has_time > 0' :src="`../static/images/${user_pet_info.pet_2.pet_image}`" alt="">
</div>
</div>
<div class="tree-list">
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree4.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree3.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree2.png" alt="">
</div>
</div>
<div class="tree-box">
<div class="tree">
<img src="../static/images/tree1.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
<div class="tree">
<img src="../static/images/tree0.png" alt="">
</div>
</div>
</div>
<div class="prop-list">
<div class="prop">
<img src="../static/images/prop1.png" alt="">
<span>1</span>
<p>化肥</p>
</div>
<div class="prop">
<img src="../static/images/prop2.png" alt="">
<span>0</span>
<p>修剪</p>
</div>
<div class="prop">
<img src="../static/images/prop3.png" alt="">
<span>1</span>
<p>浇水</p>
</div>
<div class="prop">
<img src="../static/images/prop4.png" alt="">
<span>1</span>
<p>宠物粮</p>
</div>
</div>
<div class="pet-hp-list">
<div class="pet-hp" v-if='user_pet_info.pet_1.has_time > 0'>
<p>宠物1 饱食度</p>
<div class="hp">
<div :style="{ pet_1_hp+'%'}" class="process">{{pet_1_hp}}%</div>
</div>
</div>
<div class="pet-hp" v-if='user_pet_info.pet_2.has_time > 0'>
<p>宠物2 饱食度</p>
<div class="hp">
<div :style="{ pet_2_hp+'%'}" class="process">{{pet_2_hp}}%</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_pet_info:{ // 用户宠物信息
pet_1:{}, // 防止调用时报错
pet_2:{},
},
pet_1_satiety_notification: false, // 宠物感到饥饿
pet_1_hunger_notification: false, // 宠物快饿死了
pet_2_satiety_notification: false,
pet_2_hunger_notification: false,
}
},
// 计算数据
computed:{
// 计算用户宠物血量百分比
pet_1_hp(){
persent = ((this.user_pet_info.pet_1.has_time / this.user_pet_info.pet_1.max_time)*100).toFixed(2)
return persent
},
pet_2_hp(){
persent = ((this.user_pet_info.pet_2.has_time / this.user_pet_info.pet_2.max_time)*100).toFixed(2)
return persent
},
// 计算宠物喂养图标的出现,饱食度低于一定饥饿度才可以喂养宠物
pet_1_hunger(){
if(this.user_pet_info.pet_1){
let pet_1 = this.user_pet_info.pet_1;
return pet_1.satiety_num > this.pet_1_hp;
}
},
pet_2_hunger(){
if(this.user_pet_info.pet_2){
let pet_2 = this.user_pet_info.pet_2;
return pet_2.satiety_num > this.pet_2_hp;
}
},
},
// 监听用户信息
watch:{
// 监听用户宠物剩余时间,如果死亡发送通知
'user_pet_info.pet_1.has_time':function(){
if(this.user_pet_info.pet_1.has_time < 1){
this.user_pet_die(1)
}
},
'user_pet_info.pet_2.has_time':function(){
if(this.user_pet_info.pet_2.has_time < 1){
this.user_pet_die(2)
}
},
},
created(){
// 监听事件
this.listen();
},
methods:{
// 监听事件
listen(){
// 监听获取用户宠物信息成功的通知
this.listen_get_user_pet_info_success();
},
// 监听获取用户宠物信息成功的通知
listen_get_user_pet_info_success(){
let self = this
// 发起获取用户宠物信息的全局通知
self.game.sendEvent('get_user_pet_info');
// 监听获取用户宠物信息成功的通知
api.addEventListener({
name: 'get_user_pet_info_success'
}, (ret, err)=>{
// 获取用户宠物信息
self.user_pet_info = ret.value.user_pet_info;
self.game.print(self.user_pet_info)
// 设置定时器,宠物存活时间需要递减
setInterval(()=>{
// 宠物剩余时间小于0,就不必再减了
if(self.user_pet_info.pet_1.has_time > 0){
self.user_pet_info.pet_1.has_time--;
// 根据宠物饱食度发送系统通知
if(self.pet_1_hp < self.user_pet_info.pet_1.satiety_num && !self.pet_1_satiety_notification){
// 饥饿
self.game.notification("喂养宠物通知","主人,您的1号宠物已经饿了,快来照顾吧~");
self.pet_1_satiety_notification = true;
}
if(self.pet_1_hp < self.user_pet_info.pet_1.hunger_num && !self.pet_1_hunger_notification){
// 快饿死了
self.game.notification("喂养宠物通知","主人,您的1号宠物饿死了,快来救命~");
self.pet_1_hunger_notification = true;
}
}
if(self.user_pet_info.pet_2.has_time > 0){
self.user_pet_info.pet_2.has_time--;
// 根据宠物饱食度发送系统通知
if(self.pet_2_hp < self.user_pet_info.pet_2.satiety_num && !self.pet_2_satiety_notification){
// 饥饿
self.game.notification("喂养宠物通知","主人,您的2号宠物已经饿了,快来照顾吧~");
self.pet_2_satiety_notification = true;
}
if(self.pet_2_hp < self.user_pet_info.pet_2.hunger_num && !self.pet_2_hunger_notification){
// 快饿死了
self.game.notification("喂养宠物通知","主人,您的2号宠物饿死了,快来救命~");
self.pet_2_hunger_notification = true;
}
}
},1000);
});
},
// 用户宠物死亡,发送通知
user_pet_die(pet_position){
this.game.sendEvent('user_pet_die',{
pet_position:pet_position
})
},
// 点击解锁宠物栏,发送解锁宠物栏通知
unlock_pet_field(){
api.actionSheet({
title: '确认花费一定的金钱和积分解锁宠物栏',
cancelTitle: '取消',
destructiveTitle: '解锁',
}, (ret, err)=>{
if(ret.buttonIndex == 1){
// 发送全局通知,解锁宠物栏
this.game.sendEvent('unlock_pet_field')
}
});
},
// 点击喂养宠物图标,喂养宠物
feed_pet(pet_position){
// 发起喂养宠物的通知
this.game.sendEvent('feed_pet',{
pet_position:pet_position,
})
},
}
});
}
</script>
</body>
</html>
- 种植园页面,接收喂养宠物的通知,
html/orchard.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>种植园</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/socket.io.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info">
<div class="avatar" @click='to_user'>
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="62" :rounded="true"></v-avatar>
<v-avatar v-else :username="user_data.id" :size="62" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">{{user_data.nickname}}</p>
</div>
<div class="wallet" @click='user_recharge'>
<div class="balance">
<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
<p class="num">{{game.number_format(user_data.money)}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">{{game.number_format(user_data.credit)}}</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
签到有礼
</div>
<div class="menu">
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
邮件中心
</div>
</div>
</div>
<div class="footer" >
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='to_package'>背包</li>
<li class="menu-center" @click="to_shop">商店</li>
<li class="menu">消息</li>
<li class="menu">好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
user_data:{}, // 当前用户信息
music_play:true,
namespace: '/orchard', // websocket命名空间
socket: null, // websocket连接对象
recharge_list: [], // 允许充值的金额列表
package_init_setting: {}, // 背包初始配置信息
user_info: {}, // 用户登陆初始化化信息
user_package_info: {}, // 用户背包信息
user_pet_info: {}, // 用户宠物信息
pet_position:null, // 宠物栏位置
}
},
created(){
// socket建立连接
this.socket_connect();
// 自动加载我的果园页面
this.to_my_orchard();
// 获取用户数据
this.get_user_data()
// 监听事件变化
this.listen()
// 获取充值金额列表
this.get_recharge_list()
},
methods:{
back(){
this.game.openWin("root");
},
// 监听事件
listen(){
// 1.监听token更新的通知
this.listen_update_token();
// 2.监听是否购买道具成功的通知
this.listen_buy_prop_success();
// 3.监听是否使用道具成功的通知
// this.listen_use_prop_success();
// 4.监听解锁背包容量的通知
this.listen_unlock_package();
// 5.监听是否用其他页面需要获取用户宠物信息
this.listen_get_user_pet_info();
// 6.监听用户宠物是否死亡的通知
this.listen_user_pet_die();
// 7.监听是否使用了道具
this.listen_use_prop();
// 8.监听解锁宠物栏的通知
this.listen_unlock_pet_field();
// 9.监听喂养宠物的通知
this.listen_feed_pet();
},
// 1.监听token更新的通知
listen_update_token(){
api.addEventListener({
name: 'update_token'
}, (ret, err) => {
// 更新用户数据
this.get_user_data()
});
},
// 2.监听是否购买道具成功的通知
listen_buy_prop_success(){
api.addEventListener({
name: 'buy_prop_success'
}, (ret, err)=>{
// 发送请求,更新用户背包数据
this.socket.emit('user_package');
});
},
// 3.监听解锁背包容量的通知
listen_unlock_package(){
api.addEventListener({
name: 'unlock_package'
}, (ret, err)=>{
// 发送解锁背包的请求
this.socket.emit('unlock_package');
// 获取解锁背包响应数据
this.socket.on('unlock_package_response',(response)=>{
if(response.errno === 1000){
// 更新用户背包数据
this.socket.emit('user_package')
// 用户积分改变,重新刷新token值
this.refresh_user_token();
}
})
});
},
// 5.监听是否用其他页面需要获取用户宠物信息
listen_get_user_pet_info(){
api.addEventListener({
name: 'get_user_pet_info'
}, (ret, err)=>{
// 获取用户宠物信息
this.get_user_pet_info();
});
},
// 6.监听用户宠物是否死亡的通知
listen_user_pet_die(){
api.addEventListener({
name: 'user_pet_die'
}, (ret, err)=>{
// 结束宠物死亡位置栏参数
this.pet_position = ret.value.pet_position
// 用户宠物死亡,发送请求
this.socket.emit('user_pet_die',{
pet_position:this.pet_position
})
// 响应用户宠物死亡
this.socket.on('user_pet_die_response',(response)=>{
if(response.errno === 1000){
this.game.tips("宠物"+this.pet_position+"已经死亡了")
}else {
this.game.tips(response.errmsg)
}
})
});
},
// 7.监听是否使用了道具
listen_use_prop(){
api.addEventListener({
name: 'use_prop'
}, (ret, err)=>{
// 使用宠物道具
if(ret.value.prop_type == 'pet'){
// 发送使用宠物道具请求
this.socket.emit('use_pet_prop',{
prop_id: ret.value.prop_id,
pet_position: ret.value.pet_position
});
// 响应数据
this.socket.on('use_pet_prop_response',(response)=>{
if(response.errno === 1000){
this.game.tips('宠物道具使用成功!')
}else {
this.game.tips(response.errmsg)
}
})
}
});
},
// 8.监听解锁宠物栏的通知
listen_unlock_pet_field(){
let self = this
api.addEventListener({
name: 'unlock_pet_field'
}, (ret, err)=>{
// 发送解锁宠物栏的请求
self.socket.emit('unlock_pet_field')
// 接收响应
self.socket.on('unlock_pet_field_response',(response)=>{
if(response.errno === 1000){
self.game.tips('宠物栏解锁成功!')
// 用户金钱积分信息改变,重新刷新token值
self.refresh_user_token();
}else {
self.game.tips(response.errmsg)
}
})
});
},
// 9.监听喂养宠物的通知
listen_feed_pet(){
api.addEventListener({
name: 'feed_pet'
}, (ret, err)=>{
// 选择喂养宠物的食物
this.select_pet_food(ret.value.pet_position)
});
},
// 9.1 提供背包中所有的宠物粮让用户选择使用
select_pet_food(pet_position){
let food_list = []
let buttons = []
// 循环背包列表
this.user_package_info.props_list.forEach((food,key)=>{
// this.game.print(food,1)
if(food.type == 'pet_food'){
food_list.push(food)
buttons.push(`${food.prop_name} - 数量: ${food.num}`)
}
})
// 寻找想要使用的宠物粮
api.actionSheet({
title: '请选择使用的宠物粮道具',
cancelTitle: '取消',
buttons: buttons,
}, (ret, err)=>{
// 选择好喂养的宠物粮,发送请求与响应
if(ret.buttonIndex <= buttons.length){
// 发送请求喂养宠物
this.socket.emit('feed_pet',{
'pet_position':pet_position,
'prop_id': food_list[ret.buttonIndex-1].prop_id
})
// 响应数据
this.socket.on('feed_pet_response',(response)=>{
if(response.errno === 1000){
this.game.tips('喂养宠物成功~')
}else {
this.game.tips(response.errmsg)
}
})
}
});
},
// 获取用户宠物信息
get_user_pet_info(){
// 发起请求用户宠物信息
this.socket.emit('user_pet_info');
// 获取响应
this.socket.on('user_pet_info_response', (response)=>{
if(response.errno === 1000){
// 响应成功,获取宠物信息
this.user_pet_info = response.user_pet_info
// 通知其他页面,已经获取用户宠物信息
this.game.sendEvent('get_user_pet_info_success',{
user_pet_info: this.user_pet_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 用户积分改变,重新刷新token值
refresh_user_token(){
let self = this
self.game.check_user_login(self, ()=>{
let token = self.game.getdata('refresh_token') || self.game.getfs('refresh_token')
self.game.post(self,{
'method': 'Users.refresh',
'params':{},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 刷新token值
let token = self.game.getfs("refresh_token");
if(token){
// 记住密码的情况
self.game.deldata(["access_token","refresh_token"]);
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.delfs(["access_token","refresh_token"]);
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 发布全局广播,token更新
self.game.sendEvent('update_token')
}
}
})
})
},
// 通过token值获取用户数据
get_user_data(){
let self = this;
// 检查token是否过期,过期从新刷新token
self.game.check_user_login(self, ()=>{
// 获取token
let token = this.game.getfs('access_token') || this.game.getdata('access_token')
// 根据token获取用户数据
this.user_data = this.game.get_user_by_token(token)
})
},
// websocket连接处理
socket_connect(){
this.socket = io.connect(this.game.config.SOCKET_SERVER + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("开始连接服务端");
// 获取背包初始配置信息
this.get_package_setting()
// websocket登陆处理
this.user_websocket_login()
});
},
// 获取背包初始配置信息
get_package_setting(){
this.socket.on('init_config_response', (response)=>{
// this.game.print(response,1)
this.package_init_setting = response
})
},
// websocket登陆处理
user_websocket_login(){
// 客户端发送用户登陆请求
this.socket.emit('login',{'uid': this.user_data.unique_id});
// 接收登陆响应
this.login_response();
// 接收用户背包响应
this.user_package_response();
},
// 接收登陆初始化信息
login_response(){
this.socket.on('login_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_info = response.user_info
}
})
},
// 接收用户背包信息
user_package_response(){
this.socket.on('user_package_response',(response)=>{
// this.game.print(response,1)
if(response.errno === 1000){
this.user_package_info = response.package_info;
// 全局广播用户背包更新
this.game.sendEvent('package_update',{
user_package_info: this.user_package_info
})
}else {
this.game.tips(response.errmsg)
}
})
},
// 跳转我的果园页面
to_my_orchard(){
this.game.check_user_login(this, ()=>{
this.game.openFrame("my_orchard","my_orchard.html","from_right",{
marginTop: 174, //相对父页面上外边距的距离,数字类型
marginLeft: 0, //相对父页面左外边距的距离,数字类型
marginBottom: 54, //相对父页面下外边距的距离,数字类型
marginRight: 0 //相对父页面右外边距的距离,数字类型
});
})
},
// 点击商店打开道具商城页面
to_shop(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('shop', 'shop.html', 'from_top');
})
},
// 点击头像,跳转用户中心页面
to_user(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('user', 'user.html', 'from_right');
})
},
// 获取充值金额列表
get_recharge_list(){
let self = this;
self.game.check_user_login(self,()=>{
self.game.post(self,{
'method': 'Users.recharge_list',
'params': {},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.recharge_list = data.result.recharge_list
}
}
})
})
},
// 点击钱包进行用户充值,设置充值金额
user_recharge(){
api.actionSheet({
title: '余额充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret.buttonIndex <= this.recharge_list.length ){
// 充值金额
let money = this.recharge_list[ret.buttonIndex-1];
// this.game.print(money,1);
// 发送支付宝充值请求
this.recharge_app_pay(money)
}
});
},
// 发送支付宝充值请求
recharge_app_pay(money){
// 获取支付宝支付对象
let aliPayPlus = api.require("aliPayPlus");
let self = this;
// 向服务端发送请求获取终止订单信息
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.recharge',
'params':{
'money': money,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 本次充值的订单参数
let order_string = data.result.order_string;
// 支付完成以后,支付APP返回的响应状态码
let resultCode = {
"9000": "支付成功!",
"8000": "支付正在处理中,请稍候!",
"4000": "支付失败!请联系我们的工作人员~",
"5000": "支付失败,重复的支付操作",
"6002": "网络连接出错",
"6004": "支付正在处理中,请稍后",
}
// 唤醒支付宝APP,发起支付
aliPayPlus.payOrder({
orderInfo: order_string,
sandbox: data.result.sandbox, // 将来APP上线需要修改成false
},(ret, err)=>{
if(resultCode[ret.code]){
// 提示支付结果
if(ret.code != 9000){
self.game.tips(resultCode[ret.code]);
}else {
// 支付成功,向服务端请求验证支付结果 - 参数订单号
self.check_recharge_result(data.result.order_number);
}
}
})
}
}
})
})
},
// 向服务端发送请求,校验充值是否成功
check_recharge_result(order_number){
let self = this;
self.game.check_user_login(self, ()=>{
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
self.game.post(self,{
'method': 'Users.check_recharge_result',
'params':{
'order_number':order_number,
},
'header':{
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// 充值成功
self.game.tips('充值成功!')
// 用户数据更改过,重新刷新token值
let token = self.game.getfs("access_token");
// 删除token值
self.game.deldata(["access_token","refresh_token"]);
self.game.delfs(["access_token","refresh_token"]);
if(token){
// 记住密码的情况
self.game.setfs({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}else{
// 不记住密码的情况
self.game.setdata({
"access_token": data.result.access_token,
"refresh_token": data.result.refresh_token,
});
}
// 全局广播充值成功
self.game.sendEvent('recharge_success')
// 全局广播刷新token值
self.game.sendEvent('update_token')
}
}
})
})
},
// 点击背包,跳转到背包页面,并传递背包初始配置信息
to_package(){
this.game.check_user_login(this, ()=>{
this.game.openFrame('package', 'package.html', 'from_top',null, {
'package_init_setting': this.package_init_setting,
'user_package_info': this.user_package_info,
'user_info': this.user_info
})
})
},
}
});
}
</script>
</body>
</html>
服务端
- 喂养宠物视图 ,
orchard.socket
,代码:
# 喂养宠物
@get_user_object_by_socket_sid
def on_feed_pet(self,user,data):
'''
喂养宠物
:param user: 根据回话ID获取的用户模型对象
:param data: 客户端传回的数据,宠物位置,宠物粮道具id
:return:
'''
# print(data)
pet_position = data.get('pet_position')
prop_id = data.get('prop_id')
# 1.判断用户是否存在(检查回话状态)
if user is None:
# 响应数据
emit('feed_pet_response', {
'errno': code.CODE_USER_NOT_EXISTS,
'errmsg': message.user_not_exists,
})
# 停止程序继续运行
return
# 2.使用宠物粮道具,并判断道具是否存在,增加宠物的饱食度,减少背包道具数量
try:
services.use_pet_food_prop(user.id, pet_position, prop_id)
except services.PropNoExit:
emit('feed_pet_response', {
'errno': code.CODE_NO_SUCH_PROP,
'errmsg': message.no_such_prop,
})
# 停止程序继续运行
return
# 3. 更新用户背包和用户宠物信息
self.on_user_package()
self.on_user_pet_info()
# 响应数据
emit('feed_pet_response', {
'errno': code.CODE_OK,
'errmsg': message.ok,
})
- 数据处理层,
orchard.serivces
,代码:
class PropNoExit(Exception):
"""道具不存在"""
pass
# 使用宠物粮道具,增加用户宠物的饱食度,减少背包道具数量
def use_pet_food_prop(user_id, pet_position, prop_id):
'''
使用宠物粮道具
:param user_id: 用户ID
:param pet_position: 宠物位置
:param prop_id: 使用宠物粮道具ID
:return:
'''
# 1. 判断用户背包中是否有对应的宠物粮道具
user_package = UserPackageDocument.objects.get(user_id=user_id)
current_prop = {} # 背包道具列表中的当前使用的宠物信息
current_key = -1 # 背包道具列表索引
for key, prop in enumerate(user_package['props_list']):
# 必须确定道具类型,
if prop['type'] == 'pet_food':
if prop_id == prop['prop_id']:
current_prop = prop
current_key = key
break
if current_key == -1:
# 道具不存在
raise PropNoExit
# 减少背包中道具数量
if current_prop['num'] <= 1:
# 如果只有一个道具的情况, 使用玩了以后直接删除道具项
if len(user_package['props_list']) == 1:
# 如果道具列表只剩一个道具,重置道具列表
user_package['props_list'] = []
else:
# 否则删除该道具项
user_package['props_list'].remove(current_prop)
else:
# 有多个道具,数据减1
user_package['props_list'][current_key]['num'] = int(user_package["props_list"][current_key]["num"]) - 1
# 保存更改的数据
user_package.save()
# 2.增加宠物的饱食度
# 2.1 获取当前宠物的存活时间
pet_ttl = get_pet_ttl(user_id,pet_position)
if pet_ttl < 1:
# 宠物道具不存在
user_pet_die(user_id, pet_position)
raise PropNoExit
# 2.2 获取宠物粮信息
pet_food = get_pet_food_item(prop_id)
# 2.3 添加用户宠物的饱食度
new_ttl = int(pet_ttl) + int(pet_food['food_dot'] * config.PET_MAX_TIME / 100)
# 判断时间是否超出宠物最大存活时间
if new_ttl > config.PET_MAX_TIME:
new_ttl = config.PET_MAX_TIME
redis_orchard.expire(f'user:{user_id}:pet:{pet_position}', new_ttl)
# 3.添加道具使用记录
UsePropLogDocument.objects.create(
user_id=user_id,
prop_type=current_prop["type"],
prop_id=current_prop["prop_id"],
prop_name=current_prop["prop_name"],
prop_image=current_prop["prop_image"],
prop_num=1,
use_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
remark={},
)